lnwire: update AnnounceSigs2 to use pure TLV
What changed, and why it matters
This commit refactors how one Lightning network message type (AnnounceSignatures2) is encoded and decoded, switching it from a fixed-field format to a more flexible Type-Length-Value (TLV) format. The change is primarily a code modernization and protocol-alignment refactor. There is no direct evidence in the commit or supplied references that this fixes an active security vulnerability.
Treat as a normal code review item. Verify that the new TLV encoding preserves all previously signed bytes exactly and that signature validation still covers the full signed TLV range. No urgent security action is indicated by the supplied materials.
Security signals we found
Refactor of wire protocol encoding/decoding for a P2P message
Preservation of unknown fields in signed TLV ranges for signature validation
No explicit security bug, CVE, or vulnerability described in commit message or diff
No bounds-check, memory-safety, or cryptographic bug visible in the diff
Evidence from the diff
The patch converts AnnounceSignatures2 from a legacy fixed-field wire format to a ‘pure TLV’ representation. Fields ChannelID, ShortChannelID, and PartialSignature are now wrapped in tlv.RecordT types, and Encode/Decode use the generic TLV stream helpers. A constructor NewAnnSigs2 is added, and the message now implements the PureTLVMessage interface. The test file demonstrates round-trip encoding/decoding including unknown fields in the signed TLV ranges, which are preserved for signature validation. The change is structural and forward-compatibility oriented.
Changed components
lnwire/announcement_signatures_2.golnwire/announcement_signatures_2_test.golnwire/test_message.goInspect captured patch +166 / −37
diff --git a/lnwire/announcement_signatures_2.go b/lnwire/announcement_signatures_2.go
index 6e893da..04e4c0a 100644
--- a/lnwire/announcement_signatures_2.go
+++ b/lnwire/announcement_signatures_2.go
@@ -3,6 +3,8 @@ package lnwire
import (
"bytes"
"io"
+
+ "github.com/lightningnetwork/lnd/tlv"
)
// AnnounceSignatures2 is a direct message between two endpoints of a
@@ -14,27 +16,40 @@ type AnnounceSignatures2 struct {
// Channel id is better for users and debugging and short channel id is
// used for quick test on existence of the particular utxo inside the
// blockchain, because it contains information about block.
- ChannelID ChannelID
+ ChannelID tlv.RecordT[tlv.TlvType0, ChannelID]
// ShortChannelID is the unique description of the funding transaction.
// It is constructed with the most significant 3 bytes as the block
// height, the next 3 bytes indicating the transaction index within the
// block, and the least significant two bytes indicating the output
// index which pays to the channel.
- ShortChannelID ShortChannelID
+ ShortChannelID tlv.RecordT[tlv.TlvType2, ShortChannelID]
// PartialSignature is the combination of the partial Schnorr signature
// created for the node's bitcoin key with the partial signature created
// for the node's node ID key.
- PartialSignature PartialSig
-
- // ExtraOpaqueData is the set of data that was appended to this
- // message, some of which we may not actually know how to iterate or
- // parse. By holding onto this data, we ensure that we're able to
- // properly validate the set of signatures that cover these new fields,
- // and ensure we're able to make upgrades to the network in a forwards
- // compatible manner.
- ExtraOpaqueData ExtraOpaqueData
+ PartialSignature tlv.RecordT[tlv.TlvType4, PartialSig]
+
+ // 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
+}
+
+// NewAnnSigs2 is a constructor for AnnounceSignatures2.
+func NewAnnSigs2(chanID ChannelID, scid ShortChannelID,
+ partialSig PartialSig) *AnnounceSignatures2 {
+
+ return &AnnounceSignatures2{
+ ChannelID: tlv.NewRecordT[tlv.TlvType0, ChannelID](chanID),
+ ShortChannelID: tlv.NewRecordT[tlv.TlvType2, ShortChannelID](
+ scid,
+ ),
+ PartialSignature: tlv.NewRecordT[tlv.TlvType4, PartialSig](
+ partialSig,
+ ),
+ ExtraSignedFields: make(ExtraSignedFields),
+ }
}
// A compile time check to ensure AnnounceSignatures2 implements the
@@ -45,37 +60,38 @@ var _ Message = (*AnnounceSignatures2)(nil)
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*AnnounceSignatures2)(nil)
+// A compile time check to ensure ChannelAnnouncement2 implements the
+// lnwire.PureTLVMessage interface.
+var _ PureTLVMessage = (*AnnounceSignatures2)(nil)
+
// Decode deserializes a serialized AnnounceSignatures2 stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures2) Decode(r io.Reader, _ uint32) error {
- return ReadElements(r,
- &a.ChannelID,
- &a.ShortChannelID,
- &a.PartialSignature,
- &a.ExtraOpaqueData,
- )
-}
-
-// Encode serializes the target AnnounceSignatures2 into the passed io.Writer
-// observing the protocol version specified.
-//
-// This is part of the lnwire.Message interface.
-func (a *AnnounceSignatures2) Encode(w *bytes.Buffer, _ uint32) error {
- if err := WriteChannelID(w, a.ChannelID); err != nil {
+ stream, err := tlv.NewStream(ProduceRecordsSorted(
+ &a.ChannelID, &a.ShortChannelID, &a.PartialSignature,
+ )...)
+ if err != nil {
return err
}
- if err := WriteShortChannelID(w, a.ShortChannelID); err != nil {
+ typeMap, err := stream.DecodeWithParsedTypesP2P(r)
+ if err != nil {
return err
}
- if err := WriteElement(w, a.PartialSignature); err != nil {
- return err
- }
+ a.ExtraSignedFields = ExtraSignedFieldsFromTypeMap(typeMap)
+
+ return nil
+}
- return WriteBytes(w, a.ExtraOpaqueData)
+// Encode serializes the target AnnounceSignatures2 into the passed io.Writer
+// observing the protocol version specified.
+//
+// This is part of the lnwire.Message interface.
+func (a *AnnounceSignatures2) Encode(w *bytes.Buffer, _ uint32) error {
+ return EncodePureTLVMessage(a, w)
}
// MsgType returns the integer uniquely identifying this message type on the
@@ -93,16 +109,34 @@ func (a *AnnounceSignatures2) SerializedSize() (uint32, error) {
return MessageSerializedSize(a)
}
+// 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.
+//
+// NOTE: this is part of the PureTLVMessage interface.
+func (a *AnnounceSignatures2) AllRecords() []tlv.Record {
+ recordProducers := []tlv.RecordProducer{
+ &a.ChannelID, &a.ShortChannelID,
+ &a.PartialSignature,
+ }
+
+ recordProducers = append(recordProducers, RecordsAsProducers(
+ tlv.MapToRecords(a.ExtraSignedFields),
+ )...)
+
+ return ProduceRecordsSorted(recordProducers...)
+}
+
// SCID returns the ShortChannelID of the channel.
//
// NOTE: this is part of the AnnounceSignatures interface.
func (a *AnnounceSignatures2) SCID() ShortChannelID {
- return a.ShortChannelID
+ return a.ShortChannelID.Val
}
// ChanID returns the ChannelID identifying the channel.
//
// NOTE: this is part of the AnnounceSignatures interface.
func (a *AnnounceSignatures2) ChanID() ChannelID {
- return a.ChannelID
+ return a.ChannelID.Val
}
diff --git a/lnwire/announcement_signatures_2_test.go b/lnwire/announcement_signatures_2_test.go
new file mode 100644
index 0000000..6b945ed
--- /dev/null
+++ b/lnwire/announcement_signatures_2_test.go
@@ -0,0 +1,78 @@
+package lnwire
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/lightningnetwork/lnd/tlv"
+ "github.com/stretchr/testify/require"
+)
+
+// TestAnnSigs2EncodeDecode tests the encoding and decoding of the
+// AnnounceSignatures2 message using hardcoded byte slices.
+func TestAnnSigs2EncodeDecode(t *testing.T) {
+ t.Parallel()
+
+ // We'll create a raw byte stream that represents a valid
+ // AnnounceSignatures2 message with various known and unknown fields in
+ // the signed TLV ranges.
+ var rawBytes []byte
+
+ // ChannelID.
+ rawBytes = append(rawBytes, []byte{
+ 0x00, // type
+ 0x20, // length
+ }...)
+ rawBytes = append(rawBytes, make([]byte, 32)...) // value
+
+ // ShortChannelID.
+ rawBytes = append(rawBytes, []byte{
+ 0x02, // type
+ 0x08, // length
+ 0, 0, 1, 0, 0, 2, 0, 3, // value
+ }...)
+
+ // PartialSignature.
+ rawBytes = append(rawBytes, []byte{
+ 0x04, // type
+ 0x20, // length
+ }...)
+ rawBytes = append(rawBytes, make([]byte, 32)...) // value
+
+ // Extra field in the first signed range.
+ rawBytes = append(rawBytes, []byte{
+ 0x30, // type
+ 0x02, // length
+ 0xab, 0xcd, // value
+ }...)
+
+ w := new(bytes.Buffer)
+ var buf [8]byte
+ err := tlv.WriteVarInt(w, pureTLVSignedSecondRangeStart+1, &buf)
+ require.NoError(t, err)
+
+ // Extra field in the second signed range.
+ rawBytes = append(rawBytes, w.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 := &AnnounceSignatures2{}
+ r := bytes.NewReader(rawBytes)
+ err = msg.Decode(r, 0)
+ require.NoError(t, err)
+
+ // At this point, we expect 2 extra signed fields.
+ require.Len(t, msg.ExtraSignedFields, 2)
+
+ // 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 77432fd..8f946f1 100644
--- a/lnwire/test_message.go
+++ b/lnwire/test_message.go
@@ -129,12 +129,29 @@ var _ TestMessage = (*AnnounceSignatures2)(nil)
//
// This is part of the TestMessage interface.
func (a *AnnounceSignatures2) RandTestMessage(t *rapid.T) Message {
- return &AnnounceSignatures2{
- ChannelID: RandChannelID(t),
- ShortChannelID: RandShortChannelID(t),
- PartialSignature: *RandPartialSig(t),
- ExtraOpaqueData: RandExtraOpaqueData(t, nil),
+ var (
+ chanID = RandChannelID(t)
+ scid = RandShortChannelID(t)
+ pSig = RandPartialSig(t)
+ )
+
+ msg := &AnnounceSignatures2{
+ ChannelID: tlv.NewRecordT[tlv.TlvType0, ChannelID](
+ chanID,
+ ),
+ ShortChannelID: tlv.NewRecordT[tlv.TlvType2](scid),
+ PartialSignature: tlv.NewRecordT[tlv.TlvType4, PartialSig](
+ *pSig,
+ ),
+ ExtraSignedFields: make(map[uint64][]byte),
+ }
+
+ randRecs, _ := RandSignedRangeRecords(t)
+ if len(randRecs) > 0 {
+ msg.ExtraSignedFields = ExtraSignedFields(randRecs)
}
+
+ return msg
}
// A compile time check to ensure ChannelAnnouncement1 implements the
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.