Skip to content

Commit 8df03de

Browse files
committed
routing: swap out final hop blinded route pub keys
If multiple blinded paths are provided, they will each have a different pub key for the destination node. This makes using our existing pathfinding logic tricky since it depends on having a single destination node (characterised by a single pub key). We want to re-use this logic. So what we do is swap out the pub keys of the destinaion hop with a pseudo target pub key. This will then be used during pathfinding. Later on once a path is found, we will swap the real destination keys back in so that onion creation can be done.
1 parent 4a22ec8 commit 8df03de

File tree

6 files changed

+99
-42
lines changed

6 files changed

+99
-42
lines changed

routing/blinding.go

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/btcsuite/btcd/btcec/v2"
88
sphinx "github.com/lightningnetwork/lightning-onion"
99
"github.com/lightningnetwork/lnd/channeldb/models"
10+
"github.com/lightningnetwork/lnd/fn"
1011
"github.com/lightningnetwork/lnd/lnwire"
1112
"github.com/lightningnetwork/lnd/routing/route"
1213
)
@@ -86,16 +87,35 @@ func NewBlindedPaymentPathSet(paths []*BlindedPayment) (*BlindedPaymentPathSet,
8687
}
8788
}
8889

89-
// NOTE: for now, we just take a single path. By the end of this PR
90-
// series, all paths will be kept.
91-
path := paths[0]
90+
// Derive an ephemeral target priv key that will be injected into each
91+
// blinded path final hop.
92+
targetPriv, err := btcec.NewPrivateKey()
93+
if err != nil {
94+
return nil, err
95+
}
96+
targetPub := targetPriv.PubKey()
97+
98+
// If any provided blinded path only has a single hop (ie, the
99+
// destination node is also the introduction node), then we discard all
100+
// other paths since we know the real pub key of the destination node.
101+
// For a single hop path, there is also no need for the pseudo target
102+
// pub key replacement, so our target pub key in this case just remains
103+
// the real introduction node ID.
104+
var pathSet = paths
105+
for _, path := range paths {
106+
if len(path.BlindedPath.BlindedHops) != 1 {
107+
continue
108+
}
92109

93-
finalHop := path.BlindedPath.
94-
BlindedHops[len(path.BlindedPath.BlindedHops)-1]
110+
pathSet = []*BlindedPayment{path}
111+
targetPub = path.BlindedPath.IntroductionPoint
112+
113+
break
114+
}
95115

96116
return &BlindedPaymentPathSet{
97-
paths: paths,
98-
targetPubKey: finalHop.BlindedNodePub,
117+
paths: pathSet,
118+
targetPubKey: targetPub,
99119
features: features,
100120
}, nil
101121
}
@@ -144,7 +164,7 @@ func (s *BlindedPaymentPathSet) ToRouteHints() (RouteHints, error) {
144164
hints := make(RouteHints)
145165

146166
for _, path := range s.paths {
147-
pathHints, err := path.toRouteHints()
167+
pathHints, err := path.toRouteHints(fn.Some(s.targetPubKey))
148168
if err != nil {
149169
return nil, err
150170
}
@@ -223,8 +243,11 @@ func (b *BlindedPayment) Validate() error {
223243
// effectively the final_cltv_delta for the receiving introduction node). In
224244
// the case of multiple blinded hops, CLTV delta is fully accounted for in the
225245
// hints (both for intermediate hops and the final_cltv_delta for the receiving
226-
// node).
227-
func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
246+
// node). The pseudoTarget, if provided, will be used to override the pub key
247+
// of the destination node in the path.
248+
func (b *BlindedPayment) toRouteHints(
249+
pseudoTarget fn.Option[*btcec.PublicKey]) (RouteHints, error) {
250+
228251
// If we just have a single hop in our blinded route, it just contains
229252
// an introduction node (this is a valid path according to the spec).
230253
// Since we have the un-blinded node ID for the introduction node, we
@@ -272,12 +295,12 @@ func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
272295
ToNodeFeatures: features,
273296
}
274297

275-
edge, err := NewBlindedEdge(edgePolicy, b, 0)
298+
lastEdge, err := NewBlindedEdge(edgePolicy, b, 0)
276299
if err != nil {
277300
return nil, err
278301
}
279302

280-
hints[fromNode] = []AdditionalEdge{edge}
303+
hints[fromNode] = []AdditionalEdge{lastEdge}
281304

282305
// Start at an offset of 1 because the first node in our blinded hops
283306
// is the introduction node and terminate at the second-last node
@@ -304,13 +327,24 @@ func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
304327
ToNodeFeatures: features,
305328
}
306329

307-
edge, err := NewBlindedEdge(edgePolicy, b, i)
330+
lastEdge, err = NewBlindedEdge(edgePolicy, b, i)
308331
if err != nil {
309332
return nil, err
310333
}
311334

312-
hints[fromNode] = []AdditionalEdge{edge}
335+
hints[fromNode] = []AdditionalEdge{lastEdge}
313336
}
314337

338+
pseudoTarget.WhenSome(func(key *btcec.PublicKey) {
339+
// For the very last hop on the path, switch out the ToNodePub
340+
// for the pseudo target pub key.
341+
lastEdge.policy.ToNodePubKey = func() route.Vertex {
342+
return route.NewVertex(key)
343+
}
344+
345+
// Then override the final hint with this updated edge.
346+
hints[fromNode] = []AdditionalEdge{lastEdge}
347+
})
348+
315349
return hints, nil
316350
}

routing/blinding_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/btcsuite/btcd/btcec/v2"
88
sphinx "github.com/lightningnetwork/lightning-onion"
99
"github.com/lightningnetwork/lnd/channeldb/models"
10+
"github.com/lightningnetwork/lnd/fn"
1011
"github.com/lightningnetwork/lnd/lnwire"
1112
"github.com/lightningnetwork/lnd/routing/route"
1213
"github.com/stretchr/testify/require"
@@ -128,7 +129,7 @@ func TestBlindedPaymentToHints(t *testing.T) {
128129
HtlcMaximum: htlcMax,
129130
Features: features,
130131
}
131-
hints, err := blindedPayment.toRouteHints()
132+
hints, err := blindedPayment.toRouteHints(fn.None[*btcec.PublicKey]())
132133
require.NoError(t, err)
133134
require.Nil(t, hints)
134135

@@ -183,7 +184,7 @@ func TestBlindedPaymentToHints(t *testing.T) {
183184
},
184185
}
185186

186-
actual, err := blindedPayment.toRouteHints()
187+
actual, err := blindedPayment.toRouteHints(fn.None[*btcec.PublicKey]())
187188
require.NoError(t, err)
188189

189190
require.Equal(t, len(expected), len(actual))

routing/pathfind.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,24 @@ func newRoute(sourceVertex route.Vertex,
153153
// sender of the payment.
154154
nextIncomingAmount lnwire.MilliSatoshi
155155

156-
blindedPath *sphinx.BlindedPath
156+
blindedPayment *BlindedPayment
157157
)
158158

159-
if blindedPathSet != nil {
160-
blindedPath = blindedPathSet.GetPath().BlindedPath
161-
}
162-
163159
pathLength := len(pathEdges)
164160
for i := pathLength - 1; i >= 0; i-- {
165161
// Now we'll start to calculate the items within the per-hop
166162
// payload for the hop this edge is leading to.
167163
edge := pathEdges[i].policy
168164

165+
// If this is an edge from a blinded path and the
166+
// blindedPayment variable has not been set yet, then set it now
167+
// by extracting the corresponding blinded payment from the
168+
// edge.
169+
isBlindedEdge := pathEdges[i].blindedPayment != nil
170+
if isBlindedEdge && blindedPayment == nil {
171+
blindedPayment = pathEdges[i].blindedPayment
172+
}
173+
169174
// We'll calculate the amounts, timelocks, and fees for each hop
170175
// in the route. The base case is the final hop which includes
171176
// their amount and timelocks. These values will accumulate
@@ -212,8 +217,9 @@ func newRoute(sourceVertex route.Vertex,
212217
// node's CLTV delta. The exception is for the case
213218
// where the final hop is the blinded path introduction
214219
// node.
215-
if blindedPath == nil ||
216-
len(blindedPath.BlindedHops) == 1 {
220+
if blindedPathSet == nil ||
221+
len(blindedPathSet.GetPath().BlindedPath.
222+
BlindedHops) == 1 {
217223

218224
// As this is the last hop, we'll use the
219225
// specified final CLTV delta value instead of
@@ -245,7 +251,7 @@ func newRoute(sourceVertex route.Vertex,
245251

246252
metadata = finalHop.metadata
247253

248-
if blindedPath != nil {
254+
if blindedPathSet != nil {
249255
totalAmtMsatBlinded = finalHop.totalAmt
250256
}
251257
} else {
@@ -305,11 +311,25 @@ func newRoute(sourceVertex route.Vertex,
305311
// If we are creating a route to a blinded path, we need to add some
306312
// additional data to the route that is required for blinded forwarding.
307313
// We do another pass on our edges to append this data.
308-
if blindedPath != nil {
314+
if blindedPathSet != nil {
315+
// If the passed in BlindedPaymentPathSet is non-nil but no
316+
// edge had a BlindedPayment attached, it means that the path
317+
// chosen was an introduction-node-only path. So in this case,
318+
// we can assume the relevant payment is the only one in the
319+
// payment set.
320+
if blindedPayment == nil {
321+
blindedPayment = blindedPathSet.GetPath()
322+
}
323+
309324
var (
310325
inBlindedRoute bool
311326
dataIndex = 0
312327

328+
blindedPath = blindedPayment.BlindedPath
329+
numHops = len(blindedPath.BlindedHops)
330+
realFinal = blindedPath.BlindedHops[numHops-1].
331+
BlindedNodePub
332+
313333
introVertex = route.NewVertex(
314334
blindedPath.IntroductionPoint,
315335
)
@@ -337,6 +357,11 @@ func newRoute(sourceVertex route.Vertex,
337357
if i != len(hops)-1 {
338358
hop.AmtToForward = 0
339359
hop.OutgoingTimeLock = 0
360+
} else {
361+
// For the final hop, we swap out the pub key
362+
// bytes to the original destination node pub
363+
// key for that payment path.
364+
hop.PubKeyBytes = route.NewVertex(realFinal)
340365
}
341366

342367
dataIndex++

routing/pathfind_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
sphinx "github.com/lightningnetwork/lightning-onion"
2424
"github.com/lightningnetwork/lnd/channeldb"
2525
"github.com/lightningnetwork/lnd/channeldb/models"
26+
"github.com/lightningnetwork/lnd/fn"
2627
"github.com/lightningnetwork/lnd/htlcswitch"
2728
switchhop "github.com/lightningnetwork/lnd/htlcswitch/hop"
2829
"github.com/lightningnetwork/lnd/kvdb"
@@ -3286,7 +3287,9 @@ func TestBlindedRouteConstruction(t *testing.T) {
32863287
// that make up the graph we'll give to route construction. The hints
32873288
// map is keyed by source node, so we can retrieve our blinded edges
32883289
// accordingly.
3289-
blindedEdges, err := blindedPayment.toRouteHints()
3290+
blindedEdges, err := blindedPayment.toRouteHints(
3291+
fn.None[*btcec.PublicKey](),
3292+
)
32903293
require.NoError(t, err)
32913294

32923295
carolDaveEdge := blindedEdges[carolVertex][0]

routing/router.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -573,20 +573,7 @@ func getTargetNode(target *route.Vertex,
573573
return route.Vertex{}, ErrTargetAndBlinded
574574

575575
case blinded:
576-
blindedPayment := blindedPathSet.GetPath()
577-
578-
// If we're dealing with an edge-case blinded path that just
579-
// has an introduction node (first hop expected to be the intro
580-
// hop), then we return the unblinded introduction node as our
581-
// target.
582-
hops := blindedPayment.BlindedPath.BlindedHops
583-
if len(hops) == 1 {
584-
return route.NewVertex(
585-
blindedPayment.BlindedPath.IntroductionPoint,
586-
), nil
587-
}
588-
589-
return route.NewVertex(hops[len(hops)-1].BlindedNodePub), nil
576+
return route.NewVertex(blindedPathSet.TargetPubKey()), nil
590577

591578
case targetSet:
592579
return *target, nil

routing/router_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2231,14 +2231,21 @@ func TestNewRouteRequest(t *testing.T) {
22312231
t.Run(testCase.name, func(t *testing.T) {
22322232
t.Parallel()
22332233

2234-
var blindedPathInfo *BlindedPaymentPathSet
2234+
var (
2235+
blindedPathInfo *BlindedPaymentPathSet
2236+
expectedTarget = testCase.expectedTarget
2237+
)
22352238
if testCase.blindedPayment != nil {
22362239
blindedPathInfo, err = NewBlindedPaymentPathSet(
22372240
[]*BlindedPayment{
22382241
testCase.blindedPayment,
22392242
},
22402243
)
22412244
require.NoError(t, err)
2245+
2246+
expectedTarget = route.NewVertex(
2247+
blindedPathInfo.TargetPubKey(),
2248+
)
22422249
}
22432250

22442251
req, err := NewRouteRequest(
@@ -2253,7 +2260,7 @@ func TestNewRouteRequest(t *testing.T) {
22532260
return
22542261
}
22552262

2256-
require.Equal(t, req.Target, testCase.expectedTarget)
2263+
require.Equal(t, req.Target, expectedTarget)
22572264
require.Equal(
22582265
t, req.FinalExpiry, testCase.expectedCltv,
22592266
)

0 commit comments

Comments
 (0)