diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 6047a1e67..8be0005dc 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -651,8 +651,7 @@ func TestChannelStateTransition(t *testing.T) { { LogIndex: 2, UpdateMsg: &lnwire.UpdateAddHTLC{ - ChanID: lnwire.ChannelID{1, 2, 3}, - ExtraData: make([]byte, 0), + ChanID: lnwire.ChannelID{1, 2, 3}, }, }, } @@ -710,25 +709,22 @@ func TestChannelStateTransition(t *testing.T) { wireSig, wireSig, }, - ExtraData: make([]byte, 0), }, LogUpdates: []LogUpdate{ { LogIndex: 1, UpdateMsg: &lnwire.UpdateAddHTLC{ - ID: 1, - Amount: lnwire.NewMSatFromSatoshis(100), - Expiry: 25, - ExtraData: make([]byte, 0), + ID: 1, + Amount: lnwire.NewMSatFromSatoshis(100), + Expiry: 25, }, }, { LogIndex: 2, UpdateMsg: &lnwire.UpdateAddHTLC{ - ID: 2, - Amount: lnwire.NewMSatFromSatoshis(200), - Expiry: 50, - ExtraData: make([]byte, 0), + ID: 2, + Amount: lnwire.NewMSatFromSatoshis(200), + Expiry: 50, }, }, }, diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 53c93aab2..7494433e6 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -3037,7 +3037,6 @@ func TestChanSyncOweCommitment(t *testing.T) { Amount: htlcAmt, Expiry: uint32(10), OnionBlob: fakeOnionBlob, - ExtraData: make([]byte, 0), } htlcIndex, err := bobChannel.AddHTLC(h, nil) @@ -3082,7 +3081,6 @@ func TestChanSyncOweCommitment(t *testing.T) { Amount: htlcAmt, Expiry: uint32(10), OnionBlob: fakeOnionBlob, - ExtraData: make([]byte, 0), } aliceHtlcIndex, err := aliceChannel.AddHTLC(aliceHtlc, nil) require.NoError(t, err, "unable to add alice's htlc") diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 3a1d02c18..e4c5c6baf 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -1337,6 +1337,42 @@ func TestLightningWireProtocol(t *testing.T) { v[0] = reflect.ValueOf(req) }, + MsgUpdateAddHTLC: func(v []reflect.Value, r *rand.Rand) { + req := &UpdateAddHTLC{ + ID: r.Uint64(), + Amount: MilliSatoshi(r.Uint64()), + Expiry: r.Uint32(), + } + + _, err := r.Read(req.ChanID[:]) + require.NoError(t, err) + + _, err = r.Read(req.PaymentHash[:]) + require.NoError(t, err) + + _, err = r.Read(req.OnionBlob[:]) + require.NoError(t, err) + + // Generate a blinding point 50% of the time, since not + // all update adds will use route blinding. + if r.Int31()%2 == 0 { + pubkey, err := randPubKey() + if err != nil { + t.Fatalf("unable to generate key: %v", + err) + + return + } + + req.BlindingPoint = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType0]( + pubkey, + ), + ) + } + + v[0] = reflect.ValueOf(*req) + }, } // With the above types defined, we'll now generate a slice of diff --git a/lnwire/update_add_htlc.go b/lnwire/update_add_htlc.go index c9ba8d269..8a40710e8 100644 --- a/lnwire/update_add_htlc.go +++ b/lnwire/update_add_htlc.go @@ -3,6 +3,9 @@ package lnwire import ( "bytes" "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/tlv" ) // OnionPacketSize is the size of the serialized Sphinx onion packet included @@ -11,6 +14,17 @@ import ( // of per-hop data, and a 32-byte HMAC over the entire packet. const OnionPacketSize = 1366 +type ( + // BlindingPointTlvType is the type for ephemeral pubkeys used in + // route blinding. + BlindingPointTlvType = tlv.TlvType0 + + // BlindingPointRecord holds an optional blinding point on update add + // htlc. + //nolint:lll + BlindingPointRecord = tlv.OptionalRecordT[BlindingPointTlvType, *btcec.PublicKey] +) + // UpdateAddHTLC is the message sent by Alice to Bob when she wishes to add an // HTLC to his remote commitment transaction. In addition to information // detailing the value, the ID, expiry, and the onion blob is also included @@ -54,6 +68,10 @@ type UpdateAddHTLC struct { // used in the subsequent UpdateAddHTLC message. OnionBlob [OnionPacketSize]byte + // BlindingPoint is the ephemeral pubkey used to optionally blind the + // next hop for this htlc. + BlindingPoint BlindingPointRecord + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -74,7 +92,7 @@ var _ Message = (*UpdateAddHTLC)(nil) // // This is part of the lnwire.Message interface. func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, + if err := ReadElements(r, &c.ChanID, &c.ID, &c.Amount, @@ -82,7 +100,27 @@ func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error { &c.Expiry, c.OnionBlob[:], &c.ExtraData, - ) + ); err != nil { + return err + } + + blindingRecord := c.BlindingPoint.Zero() + tlvMap, err := c.ExtraData.ExtractRecords(&blindingRecord) + if err != nil { + return err + } + + if val, ok := tlvMap[c.BlindingPoint.TlvType()]; ok && val == nil { + c.BlindingPoint = tlv.SomeRecordT(blindingRecord) + } + + // Set extra data to nil if we didn't parse anything out of it so that + // we can use assert.Equal in tests. + if len(tlvMap) == 0 { + c.ExtraData = nil + } + + return nil } // Encode serializes the target UpdateAddHTLC into the passed io.Writer @@ -114,6 +152,20 @@ func (c *UpdateAddHTLC) Encode(w *bytes.Buffer, pver uint32) error { return err } + // Only include blinding point in extra data if present. + var records []tlv.RecordProducer + + c.BlindingPoint.WhenSome(func(b tlv.RecordT[BlindingPointTlvType, + *btcec.PublicKey]) { + + records = append(records, &b) + }) + + err := EncodeMessageExtraData(&c.ExtraData, records...) + if err != nil { + return err + } + return WriteBytes(w, c.ExtraData) } diff --git a/peer/brontide.go b/peer/brontide.go index 07fb5731e..989db3a8d 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -45,6 +45,7 @@ import ( "github.com/lightningnetwork/lnd/queue" "github.com/lightningnetwork/lnd/subscribe" "github.com/lightningnetwork/lnd/ticker" + "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/watchtower/wtclient" ) @@ -1985,8 +1986,19 @@ func messageSummary(msg lnwire.Message) string { msg.FeeSatoshis) case *lnwire.UpdateAddHTLC: - return fmt.Sprintf("chan_id=%v, id=%v, amt=%v, expiry=%v, hash=%x", - msg.ChanID, msg.ID, msg.Amount, msg.Expiry, msg.PaymentHash[:]) + var blindingPoint []byte + msg.BlindingPoint.WhenSome( + func(b tlv.RecordT[lnwire.BlindingPointTlvType, + *btcec.PublicKey]) { + + blindingPoint = b.Val.SerializeCompressed() + }, + ) + + return fmt.Sprintf("chan_id=%v, id=%v, amt=%v, expiry=%v, "+ + "hash=%x, blinding_point=%x", msg.ChanID, msg.ID, + msg.Amount, msg.Expiry, msg.PaymentHash[:], + blindingPoint) case *lnwire.UpdateFailHTLC: return fmt.Sprintf("chan_id=%v, id=%v, reason=%x", msg.ChanID,