Skip to content

Commit 65aef6a

Browse files
committed
htlcswitch: handle blinded path dummy hops
If a blinded path payload contains a signal that the following hop on the path is a dummy hop, then we iteratively peel the dummy hops until the final payload is reached.
1 parent b0d3e4d commit 65aef6a

File tree

2 files changed

+72
-7
lines changed

2 files changed

+72
-7
lines changed

htlcswitch/hop/iterator.go

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,6 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
284284
return nil, routeRole, err
285285
}
286286

287-
nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
288-
fmt.Errorf("next SCID not set for non-final blinded hop"),
289-
)
290-
if err != nil {
291-
return nil, routeRole, err
292-
}
293-
294287
fwdAmt, err := calculateForwardingAmount(
295288
r.blindingKit.IncomingAmount, relayInfo.Val.BaseFee,
296289
relayInfo.Val.FeeRate,
@@ -315,6 +308,22 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
315308
return nil, routeRole, err
316309
}
317310

311+
// If the payload signals that the following hop is a dummy hop, then
312+
// we will iteratively peel the dummy hop until we reach the final
313+
// payload.
314+
if checkForDummyHop(routeData, r.router.OnionPublicKey()) {
315+
return peelBlindedPathDummyHop(
316+
r, uint32(relayInfo.Val.CltvExpiryDelta), fwdAmt,
317+
routeRole, nextEph,
318+
)
319+
}
320+
321+
nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
322+
fmt.Errorf("next SCID not set for non-final blinded hop"),
323+
)
324+
if err != nil {
325+
return nil, routeRole, err
326+
}
318327
payload.FwdInfo = ForwardingInfo{
319328
NextHop: nextSCID.Val,
320329
AmountToForward: fwdAmt,
@@ -332,6 +341,55 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
332341
return payload, routeRole, nil
333342
}
334343

344+
// checkForDummyHop returns whether the given BlindedRouteData packet indicates
345+
// the presence of a dummy hop.
346+
func checkForDummyHop(routeData *record.BlindedRouteData,
347+
routerPubKey *btcec.PublicKey) bool {
348+
349+
var isDummy bool
350+
routeData.NextNodeID.WhenSome(
351+
func(r tlv.RecordT[tlv.TlvType4, *btcec.PublicKey]) {
352+
isDummy = r.Val.IsEqual(routerPubKey)
353+
},
354+
)
355+
356+
return isDummy
357+
}
358+
359+
// peelBlindedPathDummyHop packages the next onion packet and then constructs
360+
// a new hop iterator using our router and then proceeds to process the next
361+
// packet. This can only be done for blinded route dummy hops since we expect
362+
// to be the final hop on the path.
363+
func peelBlindedPathDummyHop(r *sphinxHopIterator, cltvExpiryDelta uint32,
364+
fwdAmt lnwire.MilliSatoshi, routeRole RouteRole,
365+
nextEph tlv.RecordT[tlv.TlvType8, *btcec.PublicKey]) (*Payload,
366+
RouteRole, error) {
367+
368+
onionPkt := r.processedPacket.NextPacket
369+
sphinxPacket, err := r.router.ReconstructOnionPacket(
370+
onionPkt, r.rHash, sphinx.WithBlindingPoint(nextEph.Val),
371+
)
372+
if err != nil {
373+
return nil, routeRole, err
374+
}
375+
376+
iterator := makeSphinxHopIterator(
377+
r.router, onionPkt, sphinxPacket, BlindingKit{
378+
Processor: r.router,
379+
UpdateAddBlinding: tlv.SomeRecordT(
380+
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( //nolint:lll
381+
nextEph.Val,
382+
),
383+
),
384+
IncomingAmount: fwdAmt,
385+
IncomingCltv: r.blindingKit.IncomingCltv -
386+
cltvExpiryDelta,
387+
}, r.rHash,
388+
)
389+
390+
return extractTLVPayload(iterator)
391+
}
392+
335393
// decryptAndValidateBlindedRouteData decrypts the encrypted payload from the
336394
// payment recipient using a blinding key. The incoming HTLC amount and CLTV
337395
// values are then verified against the policy values from the recipient.

htlcswitch/hop/iterator_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ func TestParseAndValidateRecipientData(t *testing.T) {
206206
// Mocked error.
207207
errDecryptFailed := errors.New("could not decrypt")
208208

209+
nodeKey, err := btcec.NewPrivateKey()
210+
require.NoError(t, err)
211+
209212
tests := []struct {
210213
name string
211214
data []byte
@@ -284,6 +287,10 @@ func TestParseAndValidateRecipientData(t *testing.T) {
284287
}
285288
iterator := &sphinxHopIterator{
286289
blindingKit: kit,
290+
router: sphinx.NewRouter(
291+
&sphinx.PrivKeyECDH{PrivKey: nodeKey},
292+
sphinx.NewMemoryReplayLog(),
293+
),
287294
}
288295

289296
_, _, err = parseAndValidateRecipientData(

0 commit comments

Comments
 (0)