From 65aef6a69cae2fb30d16b200f7256e570307f1ae Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 10 Jul 2024 11:39:11 +0200 Subject: [PATCH] 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. --- htlcswitch/hop/iterator.go | 72 +++++++++++++++++++++++++++++---- htlcswitch/hop/iterator_test.go | 7 ++++ 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/htlcswitch/hop/iterator.go b/htlcswitch/hop/iterator.go index bb3fb12d7..83b5e3f52 100644 --- a/htlcswitch/hop/iterator.go +++ b/htlcswitch/hop/iterator.go @@ -284,13 +284,6 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator, return nil, routeRole, err } - nextSCID, err := routeData.ShortChannelID.UnwrapOrErr( - fmt.Errorf("next SCID not set for non-final blinded hop"), - ) - if err != nil { - return nil, routeRole, err - } - fwdAmt, err := calculateForwardingAmount( r.blindingKit.IncomingAmount, relayInfo.Val.BaseFee, relayInfo.Val.FeeRate, @@ -315,6 +308,22 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator, return nil, routeRole, err } + // If the payload signals that the following hop is a dummy hop, then + // we will iteratively peel the dummy hop until we reach the final + // payload. + if checkForDummyHop(routeData, r.router.OnionPublicKey()) { + return peelBlindedPathDummyHop( + r, uint32(relayInfo.Val.CltvExpiryDelta), fwdAmt, + routeRole, nextEph, + ) + } + + nextSCID, err := routeData.ShortChannelID.UnwrapOrErr( + fmt.Errorf("next SCID not set for non-final blinded hop"), + ) + if err != nil { + return nil, routeRole, err + } payload.FwdInfo = ForwardingInfo{ NextHop: nextSCID.Val, AmountToForward: fwdAmt, @@ -332,6 +341,55 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator, return payload, routeRole, nil } +// checkForDummyHop returns whether the given BlindedRouteData packet indicates +// the presence of a dummy hop. +func checkForDummyHop(routeData *record.BlindedRouteData, + routerPubKey *btcec.PublicKey) bool { + + var isDummy bool + routeData.NextNodeID.WhenSome( + func(r tlv.RecordT[tlv.TlvType4, *btcec.PublicKey]) { + isDummy = r.Val.IsEqual(routerPubKey) + }, + ) + + return isDummy +} + +// peelBlindedPathDummyHop packages the next onion packet and then constructs +// a new hop iterator using our router and then proceeds to process the next +// packet. This can only be done for blinded route dummy hops since we expect +// to be the final hop on the path. +func peelBlindedPathDummyHop(r *sphinxHopIterator, cltvExpiryDelta uint32, + fwdAmt lnwire.MilliSatoshi, routeRole RouteRole, + nextEph tlv.RecordT[tlv.TlvType8, *btcec.PublicKey]) (*Payload, + RouteRole, error) { + + onionPkt := r.processedPacket.NextPacket + sphinxPacket, err := r.router.ReconstructOnionPacket( + onionPkt, r.rHash, sphinx.WithBlindingPoint(nextEph.Val), + ) + if err != nil { + return nil, routeRole, err + } + + iterator := makeSphinxHopIterator( + r.router, onionPkt, sphinxPacket, BlindingKit{ + Processor: r.router, + UpdateAddBlinding: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( //nolint:lll + nextEph.Val, + ), + ), + IncomingAmount: fwdAmt, + IncomingCltv: r.blindingKit.IncomingCltv - + cltvExpiryDelta, + }, r.rHash, + ) + + return extractTLVPayload(iterator) +} + // decryptAndValidateBlindedRouteData decrypts the encrypted payload from the // payment recipient using a blinding key. The incoming HTLC amount and CLTV // values are then verified against the policy values from the recipient. diff --git a/htlcswitch/hop/iterator_test.go b/htlcswitch/hop/iterator_test.go index 74be6b190..fff9ae17d 100644 --- a/htlcswitch/hop/iterator_test.go +++ b/htlcswitch/hop/iterator_test.go @@ -206,6 +206,9 @@ func TestParseAndValidateRecipientData(t *testing.T) { // Mocked error. errDecryptFailed := errors.New("could not decrypt") + nodeKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + tests := []struct { name string data []byte @@ -284,6 +287,10 @@ func TestParseAndValidateRecipientData(t *testing.T) { } iterator := &sphinxHopIterator{ blindingKit: kit, + router: sphinx.NewRouter( + &sphinx.PrivKeyECDH{PrivKey: nodeKey}, + sphinx.NewMemoryReplayLog(), + ), } _, _, err = parseAndValidateRecipientData(