From b6b764da715ce5c7f6ae3768e832be685ee2c9a3 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 11 Oct 2024 16:30:53 +0200 Subject: [PATCH] lnwire+netann: update ChannelUpdate2 such that all fields are now TLV including the signature. --- lnwire/channel_update_2.go | 89 ++++++++++-------------- lnwire/channel_update_2_test.go | 119 ++++++++++++++++++++++++++++++++ lnwire/test_message.go | 6 +- netann/channel_update.go | 4 +- 4 files changed, 159 insertions(+), 59 deletions(-) create mode 100644 lnwire/channel_update_2_test.go diff --git a/lnwire/channel_update_2.go b/lnwire/channel_update_2.go index 343af6b1e..b832bc58d 100644 --- a/lnwire/channel_update_2.go +++ b/lnwire/channel_update_2.go @@ -22,10 +22,6 @@ const ( // HTLCs and other parameters. This message is also used to redeclare initially // set channel parameters. type ChannelUpdate2 struct { - // Signature is used to validate the announced data and prove the - // ownership of node id. - Signature Sig - // ChainHash denotes the target chain that this channel was opened // within. This value should be the genesis hash of the target chain. // Along with the short channel ID, this uniquely identifies the @@ -74,10 +70,22 @@ type ChannelUpdate2 struct { // millionth of a satoshi. FeeProportionalMillionths tlv.RecordT[tlv.TlvType18, uint32] - // ExtraOpaqueData 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. - ExtraOpaqueData ExtraOpaqueData + // Signature is used to validate the announced data and prove the + // ownership of node id. + Signature tlv.RecordT[tlv.TlvType160, Sig] + + // Any extra fields in the signed range that we do not yet know about, + // but we need to keep them for signature validation and to produce a + // valid message. + ExtraSignedFields +} + +// Encode serializes the target ChannelUpdate2 into the passed io.Writer +// observing the protocol version specified. +// +// This is part of the lnwire.Message interface. +func (c *ChannelUpdate2) Encode(w *bytes.Buffer, _ uint32) error { + return EncodePureTLVMessage(c, w) } // Decode deserializes a serialized ChannelUpdate2 stored in the passed @@ -85,17 +93,6 @@ type ChannelUpdate2 struct { // // This is part of the lnwire.Message interface. func (c *ChannelUpdate2) Decode(r io.Reader, _ uint32) error { - err := ReadElement(r, &c.Signature) - if err != nil { - return err - } - c.Signature.ForceSchnorr() - - return c.DecodeTLVRecords(r) -} - -// DecodeTLVRecords decodes only the TLV section of the message. -func (c *ChannelUpdate2) DecodeTLVRecords(r io.Reader) error { // First extract into extra opaque data. var tlvRecords ExtraOpaqueData if err := ReadElements(r, &tlvRecords); err != nil { @@ -111,10 +108,12 @@ func (c *ChannelUpdate2) DecodeTLVRecords(r io.Reader) error { &secondPeer, &c.CLTVExpiryDelta, &c.HTLCMinimumMsat, &c.HTLCMaximumMsat, &c.FeeBaseMsat, &c.FeeProportionalMillionths, + &c.Signature, ) if err != nil { return err } + c.Signature.Val.ForceSchnorr() // By default, the chain-hash is the bitcoin mainnet genesis block hash. c.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash @@ -150,38 +149,21 @@ func (c *ChannelUpdate2) DecodeTLVRecords(r io.Reader) error { c.FeeProportionalMillionths.Val = defaultFeeProportionalMillionths //nolint:ll } - if len(tlvRecords) != 0 { - c.ExtraOpaqueData = tlvRecords - } + c.ExtraSignedFields = ExtraSignedFieldsFromTypeMap(typeMap) - return c.ExtraOpaqueData.ValidateTLV() + return nil } -// Encode serializes the target ChannelUpdate2 into the passed io.Writer -// observing the protocol version specified. +// AllRecords returns all the TLV records for the message. This will include all +// the records we know about along with any that we don't know about but that +// fall in the signed TLV range. // -// This is part of the lnwire.Message interface. -func (c *ChannelUpdate2) Encode(w *bytes.Buffer, _ uint32) error { - _, err := w.Write(c.Signature.RawBytes()) - if err != nil { - return err - } +// NOTE: this is part of the PureTLVMessage interface. +func (c *ChannelUpdate2) AllRecords() []tlv.Record { + var recordProducers []tlv.RecordProducer - _, err = c.DataToSign() - if err != nil { - return err - } - - return WriteBytes(w, c.ExtraOpaqueData) -} - -// DataToSign is used to retrieve part of the announcement message which should -// be signed. For the ChannelUpdate2 message, this includes the serialised TLV -// records. -func (c *ChannelUpdate2) DataToSign() ([]byte, error) { // The chain-hash record is only included if it is _not_ equal to the // bitcoin mainnet genisis block hash. - var recordProducers []tlv.RecordProducer if !c.ChainHash.Val.IsEqual(chaincfg.MainNetParams.GenesisHash) { hash := tlv.ZeroRecordT[tlv.TlvType0, [32]byte]() hash.Val = c.ChainHash.Val @@ -190,7 +172,7 @@ func (c *ChannelUpdate2) DataToSign() ([]byte, error) { } recordProducers = append(recordProducers, - &c.ShortChannelID, &c.BlockHeight, + &c.ShortChannelID, &c.BlockHeight, &c.Signature, ) // Only include the disable flags if any bit is set. @@ -225,12 +207,11 @@ func (c *ChannelUpdate2) DataToSign() ([]byte, error) { ) } - err := EncodeMessageExtraData(&c.ExtraOpaqueData, recordProducers...) - if err != nil { - return nil, err - } + recordProducers = append(recordProducers, RecordsAsProducers( + tlv.MapToRecords(c.ExtraSignedFields), + )...) - return c.ExtraOpaqueData, nil + return ProduceRecordsSorted(recordProducers...) } // MsgType returns the integer uniquely identifying this message type on the @@ -248,14 +229,14 @@ func (c *ChannelUpdate2) SerializedSize() (uint32, error) { return MessageSerializedSize(c) } -func (c *ChannelUpdate2) ExtraData() ExtraOpaqueData { - return c.ExtraOpaqueData -} - // A compile time check to ensure ChannelUpdate2 implements the // lnwire.Message interface. var _ Message = (*ChannelUpdate2)(nil) +// A compile time check to ensure ChannelUpdate2 implements the +// lnwire.PureTLVMessage interface. +var _ PureTLVMessage = (*ChannelUpdate2)(nil) + // SCID returns the ShortChannelID of the channel that the update applies to. // // NOTE: this is part of the ChannelUpdate interface. diff --git a/lnwire/channel_update_2_test.go b/lnwire/channel_update_2_test.go new file mode 100644 index 000000000..4e771d89f --- /dev/null +++ b/lnwire/channel_update_2_test.go @@ -0,0 +1,119 @@ +package lnwire + +import ( + "bytes" + "testing" + + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// TestChanUpdate2EncodeDecode tests the encoding and decoding of the +// ChannelUpdate2 message using hardcoded byte slices. +func TestChanUpdate2EncodeDecode(t *testing.T) { + t.Parallel() + + // We'll create a raw byte stream that represents a valid ChannelUpdate2 + // message. This includes the signature and a TLV stream with both known + // and unknown records. + rawBytes := []byte{ + // ChainHash record (optional, not mainnet). + 0x0, // type. + 0x20, // length. + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + + // ShortChannelID record. + 0x2, // type. + 0x8, // length. + 0x0, 0x0, 0x1, 0x0, 0x0, 0x2, 0x0, 0x3, // value. + + // BlockHeight record. + 0x4, // type. + 0x4, // length. + 0x0, 0x0, 0x1, 0x0, // value. + + // DisabledFlags record. + 0x6, // type. + 0x1, // length. + 0x1, // value. + + // SecondPeer record. + 0x8, // type. + 0x0, // length. + + // Unknown odd-type TLV record. + 0x9, // type. + 0x2, // length. + 0xab, 0xcd, // value. + + // CLTVExpiryDelta record. + 0xa, // type. + 0x2, // length. + 0x0, 0x10, // value. + + // HTLCMinimumMsat record. + 0xc, // type. + 0x5, // length. + 0xfe, 0x0, 0xf, 0x42, 0x40, // value (BigSize: 1_000_000). + + // HTLCMaximumMsat record. + 0xe, // type. + 0x5, // length. + 0xfe, 0x0, 0xf, 0x42, 0x40, // value (BigSize: 1_000_000). + + // FeeBaseMsat record. + 0x10, // type. + 0x4, // length. + 0x0, 0x0, 0x1, 0x0, // value. + + // FeeProportionalMillionths record. + 0x12, // type. + 0x4, // length. + 0x0, 0x0, 0x1, 0x0, // value. + + // Extra Opaque Data - Unknown Record. + 0x14, // type. + 0x2, // length. + 0x79, 0x79, // value. + + // Signature. + 0xa0, // type. + 0x40, // length. + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, + 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, + 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, // value + } + + secondSignedRangeType := new(bytes.Buffer) + var buf [8]byte + err := tlv.WriteVarInt( + secondSignedRangeType, pureTLVSignedSecondRangeStart+1, &buf, + ) + require.NoError(t, err) + rawBytes = append(rawBytes, secondSignedRangeType.Bytes()...) // type. + rawBytes = append(rawBytes, []byte{ + 0x02, // length. + 0x79, 0x79, // value. + }...) + + // Now, create a new empty message and decode the raw bytes into it. + msg := &ChannelUpdate2{} + r := bytes.NewReader(rawBytes) + err = msg.Decode(r, 0) + require.NoError(t, err) + + // Next, encode the message back into a new byte buffer. + var b bytes.Buffer + err = msg.Encode(&b, 0) + require.NoError(t, err) + + // The re-encoded bytes should be exactly the same as the original raw + // bytes. + require.Equal(t, rawBytes, b.Bytes()) +} diff --git a/lnwire/test_message.go b/lnwire/test_message.go index 8e2e46ba6..77432fd89 100644 --- a/lnwire/test_message.go +++ b/lnwire/test_message.go @@ -518,7 +518,6 @@ func (c *ChannelUpdate2) RandTestMessage(t *rapid.T) Message { //nolint:ll msg := &ChannelUpdate2{ - Signature: RandSignature(t), ChainHash: tlv.NewPrimitiveRecord[tlv.TlvType0, chainhash.Hash]( chainHashObj, ), @@ -546,10 +545,11 @@ func (c *ChannelUpdate2) RandTestMessage(t *rapid.T) Message { FeeProportionalMillionths: tlv.NewPrimitiveRecord[tlv.TlvType18, uint32]( feeProportionalMillionths, ), - ExtraOpaqueData: RandExtraOpaqueData(t, nil), + ExtraSignedFields: make(map[uint64][]byte), } - msg.Signature.ForceSchnorr() + msg.Signature.Val = RandSignature(t) + msg.Signature.Val.ForceSchnorr() if rapid.Bool().Draw(t, "isSecondPeer") { msg.SecondPeer = tlv.SomeRecordT( diff --git a/netann/channel_update.go b/netann/channel_update.go index d453b5306..f26210313 100644 --- a/netann/channel_update.go +++ b/netann/channel_update.go @@ -242,7 +242,7 @@ func verifyChannelUpdate2Signature(c *lnwire.ChannelUpdate2, return fmt.Errorf("unable to reconstruct message data: %w", err) } - nodeSig, err := c.Signature.ToSignature() + nodeSig, err := c.Signature.Val.ToSignature() if err != nil { return err } @@ -330,7 +330,7 @@ func ChanUpdate2DigestTag() []byte { // chanUpdate2DigestToSign computes the digest of the ChannelUpdate2 message to // be signed. func chanUpdate2DigestToSign(c *lnwire.ChannelUpdate2) ([]byte, error) { - data, err := c.DataToSign() + data, err := lnwire.SerialiseFieldsToSign(c) if err != nil { return nil, err }