Skip to content

Commit 09d1f36

Browse files
committed
firewall: register the ForwardingAbility endpoint
Pubkeys in the Peers field are obfuscated. Other data is averaged over large timespans usually, so that doesn't lead to privacy leaks.
1 parent 0a320b8 commit 09d1f36

2 files changed

Lines changed: 181 additions & 2 deletions

File tree

firewall/privacy_mapper.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/btcsuite/btcd/chaincfg/chainhash"
16+
"github.com/lightninglabs/faraday/frdrpc"
1617
"github.com/lightninglabs/lightning-terminal/firewalldb"
1718
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
1819
"github.com/lightninglabs/lightning-terminal/session"
@@ -535,6 +536,13 @@ func (p *PrivacyMapper) checkers(db firewalldb.PrivacyMapDB,
535536
flags session.PrivacyFlags) map[string]mid.RoundTripChecker {
536537

537538
return map[string]mid.RoundTripChecker{
539+
//nolint:ll
540+
"/frdrpc.FaradayServer/ForwardingAbility": mid.NewResponseRewriter(
541+
&frdrpc.ForwardingAbilityRequest{},
542+
&frdrpc.ForwardingAbilityResponse{},
543+
handleForwardingAbilityResponse(db, flags),
544+
mid.PassThroughErrorHandler,
545+
),
538546
"/lnrpc.Lightning/GetInfo": mid.NewResponseRewriter(
539547
&lnrpc.GetInfoRequest{}, &lnrpc.GetInfoResponse{},
540548
handleGetInfoResponse(db, flags),
@@ -585,7 +593,6 @@ func (p *PrivacyMapper) checkers(db firewalldb.PrivacyMapDB,
585593
handlePendingChannelsResponse(db, flags, p.randIntn),
586594
mid.PassThroughErrorHandler,
587595
),
588-
589596
"/lnrpc.Lightning/BatchOpenChannel": mid.NewFullRewriter(
590597
&lnrpc.BatchOpenChannelRequest{},
591598
&lnrpc.BatchOpenChannelResponse{},
@@ -600,7 +607,6 @@ func (p *PrivacyMapper) checkers(db firewalldb.PrivacyMapDB,
600607
handleChannelOpenResponse(db, flags),
601608
mid.PassThroughErrorHandler,
602609
),
603-
604610
"/lnrpc.Lightning/ConnectPeer": mid.NewRequestRewriter(
605611
&lnrpc.ConnectPeerRequest{},
606612
&lnrpc.ConnectPeerResponse{},
@@ -609,6 +615,69 @@ func (p *PrivacyMapper) checkers(db firewalldb.PrivacyMapDB,
609615
}
610616
}
611617

618+
func handleForwardingAbilityResponse(db firewalldb.PrivacyMapDB,
619+
flags session.PrivacyFlags) func(ctx context.Context,
620+
r *frdrpc.ForwardingAbilityResponse) (proto.Message, error) {
621+
622+
return func(ctx context.Context, r *frdrpc.ForwardingAbilityResponse) (
623+
proto.Message, error) {
624+
625+
// When pubkey hiding is disabled or there are no peers to
626+
// obfuscate, pass the response through untouched and avoid
627+
// opening a write transaction.
628+
if flags.Contains(session.ClearPubkeys) || len(r.Peers) == 0 {
629+
return r, nil
630+
}
631+
632+
// The response addresses peers by index from a shared peer
633+
// list, so obfuscate each peer in place to keep the entries'
634+
// packed indices valid. The list need not stay sorted because
635+
// decoding resolves peers purely by index.
636+
peers := make([][]byte, len(r.Peers))
637+
638+
err := db.Update(ctx, func(ctx context.Context,
639+
tx firewalldb.PrivacyMapTx) error {
640+
641+
for i, p := range r.Peers {
642+
// Skip empty pubkeys to avoid storing a
643+
// degenerate empty-to-empty mapping.
644+
if len(p) == 0 {
645+
peers[i] = p
646+
647+
continue
648+
}
649+
650+
// Hide the pubkey so a peer maps to the same
651+
// pseudonym across RPCs.
652+
pseudoBytes, err := firewalldb.HideBytes(
653+
ctx, tx, p,
654+
)
655+
if err != nil {
656+
return err
657+
}
658+
659+
peers[i] = pseudoBytes
660+
}
661+
662+
return nil
663+
})
664+
if err != nil {
665+
return nil, err
666+
}
667+
668+
// Peers are obfuscated in place, so the index-keyed entries and
669+
// up-but-idle bitmask stay valid and pass through unchanged.
670+
return &frdrpc.ForwardingAbilityResponse{
671+
Peers: peers,
672+
Entries: r.Entries,
673+
StartTime: r.StartTime,
674+
EndTime: r.EndTime,
675+
UpButIdleBitmask: r.UpButIdleBitmask,
676+
UptimeThreshold: r.UptimeThreshold,
677+
}, nil
678+
}
679+
}
680+
612681
func handleGetInfoResponse(db firewalldb.PrivacyMapDB,
613682
flags session.PrivacyFlags) func(ctx context.Context,
614683
r *lnrpc.GetInfoResponse) (proto.Message, error) {

firewall/privacy_mapper_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func TestPrivacyMapper(t *testing.T) {
8585
outPoint(clearTxID, 0): outPoint(obfusTxID0, obfusOut0),
8686
outPoint(clearTxID, 1): outPoint(obfusTxID1, obfusOut1),
8787
"01020304": "c8134495",
88+
"05060708": "a1b2c3d4",
8889
"secret-host.com": "sksiuekalkdoowurekdf",
8990
}
9091

@@ -151,6 +152,115 @@ func TestPrivacyMapper(t *testing.T) {
151152
msg proto.Message
152153
expectedReplacement proto.Message
153154
}{
155+
{
156+
name: "ForwardingAbility Response",
157+
uri: "/frdrpc.FaradayServer/ForwardingAbility",
158+
msgType: rpcperms.TypeResponse,
159+
msg: &frdrpc.ForwardingAbilityResponse{
160+
Peers: [][]byte{{0x01, 0x02, 0x03, 0x04}},
161+
Entries: []*frdrpc.ForwardingAbilityEntry{{
162+
PackedIdx: 0,
163+
EffectiveUptimeS: 90,
164+
ForwardedSat: 100,
165+
}},
166+
},
167+
expectedReplacement: &frdrpc.ForwardingAbilityResponse{
168+
Peers: [][]byte{{0xc8, 0x13, 0x44, 0x95}},
169+
Entries: []*frdrpc.ForwardingAbilityEntry{{
170+
PackedIdx: 0,
171+
EffectiveUptimeS: 90,
172+
ForwardedSat: 100,
173+
}},
174+
},
175+
},
176+
{
177+
// Multiple peers, including a repeated one, must map to
178+
// the same pseudonym so the index-keyed entries stay
179+
// valid.
180+
name: "ForwardingAbility Response multiple peers",
181+
uri: "/frdrpc.FaradayServer/ForwardingAbility",
182+
msgType: rpcperms.TypeResponse,
183+
msg: &frdrpc.ForwardingAbilityResponse{
184+
Peers: [][]byte{
185+
{0x01, 0x02, 0x03, 0x04},
186+
{0x05, 0x06, 0x07, 0x08},
187+
{0x01, 0x02, 0x03, 0x04},
188+
},
189+
Entries: []*frdrpc.ForwardingAbilityEntry{
190+
{
191+
PackedIdx: 0,
192+
EffectiveUptimeS: 90,
193+
ForwardedSat: 100,
194+
},
195+
{
196+
PackedIdx: 1,
197+
EffectiveUptimeS: 80,
198+
ForwardedSat: 200,
199+
},
200+
{
201+
PackedIdx: 2,
202+
EffectiveUptimeS: 70,
203+
ForwardedSat: 300,
204+
},
205+
},
206+
},
207+
expectedReplacement: &frdrpc.ForwardingAbilityResponse{
208+
Peers: [][]byte{
209+
{0xc8, 0x13, 0x44, 0x95},
210+
{0xa1, 0xb2, 0xc3, 0xd4},
211+
{0xc8, 0x13, 0x44, 0x95},
212+
},
213+
Entries: []*frdrpc.ForwardingAbilityEntry{
214+
{
215+
PackedIdx: 0,
216+
EffectiveUptimeS: 90,
217+
ForwardedSat: 100,
218+
},
219+
{
220+
PackedIdx: 1,
221+
EffectiveUptimeS: 80,
222+
ForwardedSat: 200,
223+
},
224+
{
225+
PackedIdx: 2,
226+
EffectiveUptimeS: 70,
227+
ForwardedSat: 300,
228+
},
229+
},
230+
},
231+
},
232+
{
233+
// With ClearPubkeys set the peers must pass through
234+
// without obfuscation.
235+
name: "ForwardingAbility Response clear pubkey",
236+
uri: "/frdrpc.FaradayServer/ForwardingAbility",
237+
msgType: rpcperms.TypeResponse,
238+
privacyFlags: session.PrivacyFlags{
239+
session.ClearPubkeys,
240+
},
241+
msg: &frdrpc.ForwardingAbilityResponse{
242+
Peers: [][]byte{
243+
{0x01, 0x02, 0x03, 0x04},
244+
{0x05, 0x06, 0x07, 0x08},
245+
},
246+
Entries: []*frdrpc.ForwardingAbilityEntry{{
247+
PackedIdx: 0,
248+
EffectiveUptimeS: 90,
249+
ForwardedSat: 100,
250+
}},
251+
},
252+
expectedReplacement: &frdrpc.ForwardingAbilityResponse{
253+
Peers: [][]byte{
254+
{0x01, 0x02, 0x03, 0x04},
255+
{0x05, 0x06, 0x07, 0x08},
256+
},
257+
Entries: []*frdrpc.ForwardingAbilityEntry{{
258+
PackedIdx: 0,
259+
EffectiveUptimeS: 90,
260+
ForwardedSat: 100,
261+
}},
262+
},
263+
},
154264
{
155265
name: "GetInfo Response",
156266
uri: "/lnrpc.Lightning/GetInfo",

0 commit comments

Comments
 (0)