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.
This commit is contained in:
Elle Mouton 2024-07-10 11:39:11 +02:00
parent b0d3e4dc0d
commit 65aef6a69c
No known key found for this signature in database
GPG key ID: D7D916376026F177
2 changed files with 72 additions and 7 deletions

View file

@ -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.

View file

@ -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(