From 62a97f86ddd8abba4886721559286f9766ca1fd5 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 10 Jul 2024 09:59:47 +0200 Subject: [PATCH] record: add NextNodeID type to BlindedRouteData Add the NextNodeID TLV (tlv type 4) to the BlindedRouteData TLV stream. This will be used during the dummy hop payload construction. --- record/blinded_data.go | 40 +++++++++++++++++++++++++++++++++++-- record/blinded_data_test.go | 32 +++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/record/blinded_data.go b/record/blinded_data.go index 7b7081b3b..2c3204d20 100644 --- a/record/blinded_data.go +++ b/record/blinded_data.go @@ -22,6 +22,11 @@ type BlindedRouteData struct { // ShortChannelID is the channel ID of the next hop. ShortChannelID tlv.OptionalRecordT[tlv.TlvType2, lnwire.ShortChannelID] + // NextNodeID is the node ID of the next node on the path. In the + // context of blinded path payments, this is used to indicate the + // presence of dummy hops that need to be peeled from the onion. + NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey] + // PathID is a secret set of bytes that the blinded path creator will // set so that they can check the value on decryption to ensure that the // path they created was used for the intended purpose. @@ -98,6 +103,26 @@ func NewFinalHopBlindedRouteData(constraints *PaymentConstraints, return &data } +// NewDummyHopRouteData creates the data that's provided for any hop preceding +// a dummy hop. The presence of such a payload indicates to the reader that +// they are the intended recipient and should peel the remainder of the onion. +func NewDummyHopRouteData(ourPubKey *btcec.PublicKey, + relayInfo PaymentRelayInfo, + constraints PaymentConstraints) *BlindedRouteData { + + return &BlindedRouteData{ + NextNodeID: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType4](ourPubKey), + ), + RelayInfo: tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType10](relayInfo), + ), + Constraints: tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType12](constraints), + ), + } +} + // DecodeBlindedRouteData decodes the data provided within a blinded route. func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) { var ( @@ -105,6 +130,7 @@ func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) { padding = d.Padding.Zero() scid = d.ShortChannelID.Zero() + nextNodeID = d.NextNodeID.Zero() pathID = d.PathID.Zero() blindingOverride = d.NextBlindingOverride.Zero() relayInfo = d.RelayInfo.Zero() @@ -118,8 +144,8 @@ func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) { } typeMap, err := tlvRecords.ExtractRecords( - &padding, &scid, &pathID, &blindingOverride, &relayInfo, - &constraints, &features, + &padding, &scid, &nextNodeID, &pathID, &blindingOverride, + &relayInfo, &constraints, &features, ) if err != nil { return nil, err @@ -134,6 +160,10 @@ func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) { d.ShortChannelID = tlv.SomeRecordT(scid) } + if val, ok := typeMap[d.NextNodeID.TlvType()]; ok && val == nil { + d.NextNodeID = tlv.SomeRecordT(nextNodeID) + } + if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil { d.PathID = tlv.SomeRecordT(pathID) } @@ -175,6 +205,12 @@ func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) { recordProducers = append(recordProducers, &scid) }) + data.NextNodeID.WhenSome(func(f tlv.RecordT[tlv.TlvType4, + *btcec.PublicKey]) { + + recordProducers = append(recordProducers, &f) + }) + data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) { recordProducers = append(recordProducers, &pathID) }) diff --git a/record/blinded_data_test.go b/record/blinded_data_test.go index 88ab9cddd..6de97e497 100644 --- a/record/blinded_data_test.go +++ b/record/blinded_data_test.go @@ -171,6 +171,38 @@ func TestBlindedDataFinalHopEncoding(t *testing.T) { } } +// TestDummyHopBlindedDataEncoding tests the encoding and decoding of a blinded +// data blob intended for hops preceding a dummy hop in a blinded path. These +// hops provide the reader with a signal that the next hop may be a dummy hop. +func TestDummyHopBlindedDataEncoding(t *testing.T) { + t.Parallel() + + priv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + info := PaymentRelayInfo{ + FeeRate: 2, + CltvExpiryDelta: 3, + BaseFee: 30, + } + + constraints := PaymentConstraints{ + MaxCltvExpiry: 4, + HtlcMinimumMsat: 100, + } + + routeData := NewDummyHopRouteData(priv.PubKey(), info, constraints) + + encoded, err := EncodeBlindedRouteData(routeData) + require.NoError(t, err) + + b := bytes.NewBuffer(encoded) + decodedData, err := DecodeBlindedRouteData(b) + require.NoError(t, err) + + require.Equal(t, routeData, decodedData) +} + // TestBlindedRouteDataPadding tests the PadBy method of BlindedRouteData. func TestBlindedRouteDataPadding(t *testing.T) { newBlindedRouteData := func() *BlindedRouteData {