@@ -46,14 +46,16 @@ var _ mid.RequestInterceptor = (*PrivacyMapper)(nil)
46
46
// PrivacyMapper is a RequestInterceptor that maps any pseudo names in certain
47
47
// requests to their real values and vice versa for responses.
48
48
type PrivacyMapper struct {
49
- newDB firewalldb.NewPrivacyMapDB
49
+ newDB firewalldb.NewPrivacyMapDB
50
+ randIntn func (int ) (int , error )
50
51
}
51
52
52
- // NewPrivacyMapper returns a new instance of PrivacyMapper.
53
- func NewPrivacyMapper (newDB firewalldb.NewPrivacyMapDB ) * PrivacyMapper {
54
- return & PrivacyMapper {
55
- newDB : newDB ,
56
- }
53
+ // NewPrivacyMapper returns a new instance of PrivacyMapper. The randIntn
54
+ // function is used to draw randomness for request field obfuscation.
55
+ func NewPrivacyMapper (newDB firewalldb.NewPrivacyMapDB ,
56
+ randIntn func (int ) (int , error )) * PrivacyMapper {
57
+
58
+ return & PrivacyMapper {newDB : newDB , randIntn : randIntn }
57
59
}
58
60
59
61
// Name returns the name of the interceptor.
@@ -224,7 +226,7 @@ func (p *PrivacyMapper) checkers(
224
226
"/lnrpc.Lightning/ForwardingHistory" : mid .NewResponseRewriter (
225
227
& lnrpc.ForwardingHistoryRequest {},
226
228
& lnrpc.ForwardingHistoryResponse {},
227
- handleFwdHistoryResponse (db ),
229
+ handleFwdHistoryResponse (db , p . randIntn ),
228
230
mid .PassThroughErrorHandler ,
229
231
),
230
232
"/lnrpc.Lightning/FeeReport" : mid .NewResponseRewriter (
@@ -236,7 +238,8 @@ func (p *PrivacyMapper) checkers(
236
238
& lnrpc.ListChannelsRequest {},
237
239
& lnrpc.ListChannelsResponse {},
238
240
handleListChannelsRequest (db ),
239
- handleListChannelsResponse (db ),
241
+ handleListChannelsResponse (db , p .randIntn ),
242
+
240
243
mid .PassThroughErrorHandler ,
241
244
),
242
245
"/lnrpc.Lightning/UpdateChannelPolicy" : mid .NewFullRewriter (
@@ -282,15 +285,16 @@ func handleGetInfoRequest(db firewalldb.PrivacyMapDB) func(ctx context.Context,
282
285
}
283
286
}
284
287
285
- func handleFwdHistoryResponse (db firewalldb.PrivacyMapDB ) func (
286
- ctx context. Context , r * lnrpc. ForwardingHistoryResponse ) (proto. Message ,
287
- error ) {
288
+ func handleFwdHistoryResponse (db firewalldb.PrivacyMapDB ,
289
+ randIntn func ( int ) ( int , error )) func ( ctx context. Context ,
290
+ r * lnrpc. ForwardingHistoryResponse ) (proto. Message , error ) {
288
291
289
- return func (ctx context.Context , r * lnrpc.ForwardingHistoryResponse ) (
292
+ return func (_ context.Context , r * lnrpc.ForwardingHistoryResponse ) (
290
293
proto.Message , error ) {
291
294
292
295
err := db .Update (func (tx firewalldb.PrivacyMapTx ) error {
293
296
for _ , fe := range r .ForwardingEvents {
297
+ // Deterministically hide channel ids.
294
298
chanIn , err := firewalldb .HideUint64 (
295
299
tx , fe .ChanIdIn ,
296
300
)
@@ -306,6 +310,44 @@ func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB) func(
306
310
return err
307
311
}
308
312
fe .ChanIdOut = chanOut
313
+
314
+ // We randomize the outgoing amount for privacy.
315
+ hiddenAmtOutMsat , err := hideAmount (
316
+ randIntn , amountVariation ,
317
+ fe .AmtOutMsat ,
318
+ )
319
+ if err != nil {
320
+ return err
321
+ }
322
+ fe .AmtOutMsat = hiddenAmtOutMsat
323
+
324
+ // We randomize fees for privacy.
325
+ hiddenFeeMsat , err := hideAmount (
326
+ randIntn , amountVariation , fe .FeeMsat ,
327
+ )
328
+ if err != nil {
329
+ return err
330
+ }
331
+ fe .FeeMsat = hiddenFeeMsat
332
+
333
+ // Populate other fields in a consistent manner.
334
+ fe .AmtInMsat = fe .AmtOutMsat + fe .FeeMsat
335
+ fe .AmtOut = fe .AmtOutMsat / 1000
336
+ fe .AmtIn = fe .AmtInMsat / 1000
337
+ fe .Fee = fe .FeeMsat / 1000
338
+
339
+ // We randomize the forwarding timestamp.
340
+ timestamp := time .Unix (0 , int64 (fe .TimestampNs ))
341
+ hiddenTimestamp , err := hideTimestamp (
342
+ randIntn , timeVariation , timestamp ,
343
+ )
344
+ if err != nil {
345
+ return err
346
+ }
347
+ fe .TimestampNs = uint64 (
348
+ hiddenTimestamp .UnixNano (),
349
+ )
350
+ fe .Timestamp = uint64 (hiddenTimestamp .Unix ())
309
351
}
310
352
return nil
311
353
})
@@ -382,36 +424,121 @@ func handleListChannelsRequest(db firewalldb.PrivacyMapDB) func(
382
424
}
383
425
}
384
426
385
- func handleListChannelsResponse (db firewalldb.PrivacyMapDB ) func (
386
- ctx context. Context , r * lnrpc. ListChannelsResponse ) (proto. Message ,
387
- error ) {
427
+ func handleListChannelsResponse (db firewalldb.PrivacyMapDB ,
428
+ randIntn func ( int ) ( int , error )) func ( ctx context. Context ,
429
+ r * lnrpc. ListChannelsResponse ) (proto. Message , error ) {
388
430
389
- return func (ctx context.Context , r * lnrpc.ListChannelsResponse ) (
431
+ return func (_ context.Context , r * lnrpc.ListChannelsResponse ) (
390
432
proto.Message , error ) {
391
433
434
+ hideAmount := func (a int64 ) (int64 , error ) {
435
+ hiddenAmount , err := hideAmount (
436
+ randIntn , amountVariation , uint64 (a ),
437
+ )
438
+ if err != nil {
439
+ return 0 , err
440
+ }
441
+
442
+ return int64 (hiddenAmount ), nil
443
+ }
444
+
392
445
err := db .Update (func (tx firewalldb.PrivacyMapTx ) error {
393
446
for i , c := range r .Channels {
447
+ ch := r .Channels [i ]
448
+
449
+ // Deterministically hide the peer pubkey,
450
+ // the channel point, and the channel id.
394
451
pk , err := firewalldb .HideString (
395
452
tx , c .RemotePubkey ,
396
453
)
397
454
if err != nil {
398
455
return err
399
456
}
400
- r . Channels [ i ] .RemotePubkey = pk
457
+ ch .RemotePubkey = pk
401
458
402
459
cp , err := firewalldb .HideChanPointStr (
403
460
tx , c .ChannelPoint ,
404
461
)
405
462
if err != nil {
406
463
return err
407
464
}
408
- r . Channels [ i ] .ChannelPoint = cp
465
+ ch .ChannelPoint = cp
409
466
410
467
cid , err := firewalldb .HideUint64 (tx , c .ChanId )
411
468
if err != nil {
412
469
return err
413
470
}
414
- r .Channels [i ].ChanId = cid
471
+ ch .ChanId = cid
472
+
473
+ // We hide the initiator.
474
+ initiator , err := hideBool (randIntn )
475
+ if err != nil {
476
+ return err
477
+ }
478
+ ch .Initiator = initiator
479
+
480
+ // Consider the capacity to be public
481
+ // information. We don't care about reserves, as
482
+ // having some funds as a balance is the normal
483
+ // state over the lifetime of a channel. The
484
+ // balance would be zero only for the initial
485
+ // state as a non-funder.
486
+
487
+ // We randomize local/remote balances.
488
+ localBalance , err := hideAmount (c .LocalBalance )
489
+ if err != nil {
490
+ return err
491
+ }
492
+
493
+ // We may have a too large value for the local
494
+ // balance, restrict it to the capacity.
495
+ if localBalance > c .Capacity {
496
+ localBalance = c .Capacity
497
+ }
498
+ if ch .Initiator {
499
+ localBalance -= ch .CommitFee
500
+ }
501
+ ch .LocalBalance = localBalance
502
+
503
+ // We adapt the remote balance accordingly.
504
+ remoteBalance := c .Capacity - localBalance -
505
+ c .CommitFee
506
+ if ! ch .Initiator {
507
+ remoteBalance -= ch .CommitFee
508
+ }
509
+ ch .RemoteBalance = remoteBalance
510
+
511
+ // We hide the total sats sent and received.
512
+ hiddenSatsReceived , err := hideAmount (
513
+ c .TotalSatoshisReceived ,
514
+ )
515
+ if err != nil {
516
+ return err
517
+ }
518
+ ch .TotalSatoshisReceived = hiddenSatsReceived
519
+
520
+ hiddenSatsSent , err := hideAmount (
521
+ c .TotalSatoshisSent ,
522
+ )
523
+ if err != nil {
524
+ return err
525
+ }
526
+ ch .TotalSatoshisSent = hiddenSatsSent
527
+
528
+ // We only keep track of the number of unsettled
529
+ // HTLCs.
530
+ ch .PendingHtlcs = make (
531
+ []* lnrpc.HTLC , len (ch .PendingHtlcs ),
532
+ )
533
+
534
+ // We hide the unsettled balance.
535
+ unsettled , err := hideAmount (
536
+ c .UnsettledBalance ,
537
+ )
538
+ if err != nil {
539
+ return err
540
+ }
541
+ ch .UnsettledBalance = unsettled
415
542
}
416
543
417
544
return nil
0 commit comments