diff --git a/lnwire/fuzz_test.go b/lnwire/fuzz_test.go index c3cd8198e..a143ce6d9 100644 --- a/lnwire/fuzz_test.go +++ b/lnwire/fuzz_test.go @@ -244,6 +244,30 @@ func FuzzNodeAnnouncement(f *testing.F) { }) } +func FuzzNodeAnnouncement2(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + // We can't use require.Equal for Features, since we consider + // the empty map and nil to be equivalent. + assertEq := func(t *testing.T, x, y any) { + require.IsType(t, &NodeAnnouncement2{}, x) + first, _ := x.(*NodeAnnouncement2) + require.IsType(t, &NodeAnnouncement2{}, y) + second, _ := y.(*NodeAnnouncement2) + + require.True( + t, + first.Features.Val.Equals(&second.Features.Val), + ) + first.Features.Val = *NewRawFeatureVector() + second.Features.Val = *NewRawFeatureVector() + + require.Equal(t, first, second) + } + + wireMsgHarnessCustom(t, data, MsgNodeAnnouncement2, assertEq) + }) +} + func FuzzOpenChannel(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { // We can't use require.Equal for UpfrontShutdownScript, since diff --git a/lnwire/message.go b/lnwire/message.go index 9584bfc44..c64b09b12 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -64,6 +64,7 @@ const ( MsgReplyChannelRange = 264 MsgGossipTimestampRange = 265 MsgChannelAnnouncement2 = 267 + MsgNodeAnnouncement2 = 269 MsgChannelUpdate2 = 271 MsgKickoffSig = 777 @@ -193,6 +194,8 @@ func (t MessageType) String() string { return "MsgAnnounceSignatures2" case MsgChannelAnnouncement2: return "ChannelAnnouncement2" + case MsgNodeAnnouncement2: + return "NodeAnnouncement2" case MsgChannelUpdate2: return "ChannelUpdate2" default: @@ -355,6 +358,8 @@ func makeEmptyMessage(msgType MessageType) (Message, error) { msg = &AnnounceSignatures2{} case MsgChannelAnnouncement2: msg = &ChannelAnnouncement2{} + case MsgNodeAnnouncement2: + msg = &NodeAnnouncement2{} case MsgChannelUpdate2: msg = &ChannelUpdate2{} default: diff --git a/lnwire/node_announcement_2.go b/lnwire/node_announcement_2.go new file mode 100644 index 000000000..23fcc509f --- /dev/null +++ b/lnwire/node_announcement_2.go @@ -0,0 +1,581 @@ +package lnwire + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "image/color" + "io" + "net" + "unicode/utf8" + + "github.com/lightningnetwork/lnd/tlv" + "github.com/lightningnetwork/lnd/tor" +) + +// NodeAnnouncement2 message is used to announce the presence of a Lightning +// node and also to signal that the node is accepting incoming connections. +// Each NodeAnnouncement2 authenticating the advertised information within the +// announcement via a signature using the advertised node pubkey. +type NodeAnnouncement2 struct { + // Features is the feature vector that encodes the features supported + // by the target node. + Features tlv.RecordT[tlv.TlvType0, RawFeatureVector] + + // Color is an optional field used to customize a node's appearance in + // maps and graphs. + Color tlv.OptionalRecordT[tlv.TlvType1, Color] + + // BlockHeight allows ordering in the case of multiple announcements. We + // should ignore the message if block height is not greater than the + // last-received. The block height must always be greater or equal to + // the block height that the channel funding transaction was confirmed + // in. + BlockHeight tlv.RecordT[tlv.TlvType2, uint32] + + // Alias is used to customize their node's appearance in maps and + // graphs. + Alias tlv.OptionalRecordT[tlv.TlvType3, NodeAlias2] + + // NodeID is the public key of the node creating the announcement. + NodeID tlv.RecordT[tlv.TlvType4, [33]byte] + + // IPV4Addrs is an optional list of ipv4 addresses that the node is + // reachable at. + IPV4Addrs tlv.OptionalRecordT[tlv.TlvType5, IPV4Addrs] + + // IPV6Addrs is an optional list of ipv6 addresses that the node is + // reachable at. + IPV6Addrs tlv.OptionalRecordT[tlv.TlvType7, IPV6Addrs] + + // TorV3Addrs is an optional list of tor v3 addresses that the node is + // reachable at. + TorV3Addrs tlv.OptionalRecordT[tlv.TlvType9, TorV3Addrs] + + // DNSHostName is an optional DNS hostname that the node is reachable + // at. + DNSHostName tlv.OptionalRecordT[tlv.TlvType11, DNSAddress] + + // 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 +} + +// SerializedSize returns the serialized size of the message in bytes. +// +// This is part of the lnwire.SizeableMessage interface. +func (n *NodeAnnouncement2) SerializedSize() (uint32, error) { + return MessageSerializedSize(n) +} + +// 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 (n *NodeAnnouncement2) AllRecords() []tlv.Record { + recordProducers := []tlv.RecordProducer{ + &n.Features, + &n.BlockHeight, + &n.NodeID, + &n.Signature, + } + + n.Color.WhenSome(func(r tlv.RecordT[tlv.TlvType1, Color]) { + recordProducers = append(recordProducers, &r) + }) + + n.Alias.WhenSome(func(a tlv.RecordT[tlv.TlvType3, NodeAlias2]) { + recordProducers = append(recordProducers, &a) + }) + + n.IPV4Addrs.WhenSome(func(r tlv.RecordT[tlv.TlvType5, IPV4Addrs]) { + recordProducers = append(recordProducers, &r) + }) + + n.IPV6Addrs.WhenSome(func(r tlv.RecordT[tlv.TlvType7, IPV6Addrs]) { + recordProducers = append(recordProducers, &r) + }) + + n.TorV3Addrs.WhenSome(func(r tlv.RecordT[tlv.TlvType9, TorV3Addrs]) { + recordProducers = append(recordProducers, &r) + }) + + n.DNSHostName.WhenSome(func(r tlv.RecordT[tlv.TlvType11, DNSAddress]) { + recordProducers = append(recordProducers, &r) + }) + + recordProducers = append(recordProducers, RecordsAsProducers( + tlv.MapToRecords(n.ExtraSignedFields), + )...) + + return ProduceRecordsSorted(recordProducers...) +} + +// Decode deserializes a serialized NodeAnnouncement2 stored in the passed +// io.Reader observing the specified protocol version. +// +// This is part of the lnwire.Message interface. +func (n *NodeAnnouncement2) Decode(r io.Reader, _ uint32) error { + var ( + color = tlv.ZeroRecordT[tlv.TlvType1, Color]() + alias = tlv.ZeroRecordT[tlv.TlvType3, NodeAlias2]() + ipv4 = tlv.ZeroRecordT[tlv.TlvType5, IPV4Addrs]() + ipv6 = tlv.ZeroRecordT[tlv.TlvType7, IPV6Addrs]() + torV3 = tlv.ZeroRecordT[tlv.TlvType9, TorV3Addrs]() + dns = tlv.ZeroRecordT[tlv.TlvType11, DNSAddress]() + ) + + stream, err := tlv.NewStream(ProduceRecordsSorted( + &n.Features, + &n.BlockHeight, + &n.NodeID, + &n.Signature, + &alias, + &color, + &ipv4, + &ipv6, + &torV3, + &dns, + )...) + if err != nil { + return err + } + n.Signature.Val.ForceSchnorr() + + typeMap, err := stream.DecodeWithParsedTypesP2P(r) + if err != nil { + return err + } + + if _, ok := typeMap[n.Alias.TlvType()]; ok { + n.Alias = tlv.SomeRecordT(alias) + } + + if _, ok := typeMap[n.Color.TlvType()]; ok { + n.Color = tlv.SomeRecordT(color) + } + + if _, ok := typeMap[n.IPV4Addrs.TlvType()]; ok { + n.IPV4Addrs = tlv.SomeRecordT(ipv4) + } + + if _, ok := typeMap[n.IPV6Addrs.TlvType()]; ok { + n.IPV6Addrs = tlv.SomeRecordT(ipv6) + } + + if _, ok := typeMap[n.TorV3Addrs.TlvType()]; ok { + n.TorV3Addrs = tlv.SomeRecordT(torV3) + } + + if _, ok := typeMap[n.DNSHostName.TlvType()]; ok { + n.DNSHostName = tlv.SomeRecordT(dns) + } + + n.ExtraSignedFields = ExtraSignedFieldsFromTypeMap(typeMap) + + return nil +} + +// Encode serializes the target NodeAnnouncement2 into the passed io.Writer +// observing the protocol version specified. +// +// This is part of the lnwire.Message interface. +func (n *NodeAnnouncement2) Encode(w *bytes.Buffer, _ uint32) error { + return EncodePureTLVMessage(n, w) +} + +// MsgType returns the integer uniquely identifying this message type on the +// wire. +// +// This is part of the lnwire.Message interface. +func (n *NodeAnnouncement2) MsgType() MessageType { + return MsgNodeAnnouncement2 +} + +// A compile-time check to ensure NodeAnnouncement2 implements the Message +// interface. +var _ Message = (*NodeAnnouncement2)(nil) + +// A compile-time check to ensure NodeAnnouncement2 implements the +// PureTLVMessage interface. +var _ PureTLVMessage = (*NodeAnnouncement2)(nil) + +// A compile time check to ensure ChannelAnnouncement2 implements the +// lnwire.SizeableMessage interface. +var _ SizeableMessage = (*NodeAnnouncement2)(nil) + +// NodeAlias2 represents a UTF-8 encoded node alias with a maximum length of 32 +// bytes. +type NodeAlias2 []byte + +// Record returns a TLV record for encoding/decoding NodeAlias2. +func (n *NodeAlias2) Record() tlv.Record { + sizeFunc := func() uint64 { + return uint64(len(*n)) + } + + return tlv.MakeDynamicRecord( + 0, n, sizeFunc, nodeAlias2Encoder, nodeAlias2Decoder, + ) +} + +// ValidateNodeAlias2 validates that the node alias is valid UTF-8 and within +// the 32-byte limit. +func ValidateNodeAlias2(alias []byte) error { + if len(alias) == 0 { + return errors.New("node alias cannot be empty") + } + + if len(alias) > 32 { + return fmt.Errorf("node alias exceeds 32 bytes: length %d", + len(alias)) + } + + if !utf8.Valid(alias) { + return errors.New("node alias contains invalid UTF-8") + } + + return nil +} + +// nodeAlias2Encoder encodes a NodeAlias2 as raw UTF-8 bytes. +func nodeAlias2Encoder(w io.Writer, val any, _ *[8]byte) error { + if v, ok := val.(*NodeAlias2); ok { + if err := ValidateNodeAlias2(*v); err != nil { + return err + } + + _, err := w.Write(*v) + + return err + } + + return tlv.NewTypeForEncodingErr(val, "NodeAlias2") +} + +// nodeAlias2Decoder decodes raw bytes into a NodeAlias2. +func nodeAlias2Decoder(r io.Reader, val any, _ *[8]byte, l uint64) error { + if v, ok := val.(*NodeAlias2); ok { + if l > 32 { + return fmt.Errorf("node alias exceeds 32 bytes: %d", l) + } + + aliasBytes := make([]byte, l) + if _, err := io.ReadFull(r, aliasBytes); err != nil { + return err + } + + if err := ValidateNodeAlias2(aliasBytes); err != nil { + return err + } + + *v = aliasBytes + + return nil + } + + return tlv.NewTypeForDecodingErr(val, "NodeAlias2", l, 0) +} + +// Color is a custom type to represent a color.RGBA that can be encoded/decoded +// as a TLV record. +type Color color.RGBA + +// Record returns a TLV record for encoding/decoding Color. +func (c *Color) Record() tlv.Record { + return tlv.MakeStaticRecord(0, c, 3, rgbEncoder, rgbDecoder) +} + +// rgbEncoder encodes a Color as RGB bytes. +func rgbEncoder(w io.Writer, val any, _ *[8]byte) error { + if v, ok := val.(*Color); ok { + buf := bytes.NewBuffer(nil) + err := WriteColorRGBA(buf, color.RGBA(*v)) + if err != nil { + return err + } + _, err = w.Write(buf.Bytes()) + + return err + } + + return tlv.NewTypeForEncodingErr(val, "Color") +} + +// rgbDecoder decodes RGB bytes into a Color. +func rgbDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { + if v, ok := val.(*Color); ok { + return ReadElements(r, &v.R, &v.G, &v.B) + } + + return tlv.NewTypeForDecodingErr(val, "Color", l, 3) +} + +// ipv4AddrEncodedSize is the number of bytes required to encode a single ipv4 +// address. Four bytes are used to encode the IP address and two bytes for the +// port number. +const ipv4AddrEncodedSize = 4 + 2 + +// IPV4Addrs is a list of ipv4 addresses that can be encoded as a TLV record. +type IPV4Addrs []*net.TCPAddr + +// Record returns a Record that can be used to encode/decode a IPV4Addrs +// to/from a TLV stream. +func (a *IPV4Addrs) Record() tlv.Record { + return tlv.MakeDynamicRecord( + 0, a, a.encodedSize, ipv4AddrsEncoder, ipv4AddrsDecoder, + ) +} + +// encodedSize returns the number of bytes required to encode an IPV4Addrs +// variable. +func (a *IPV4Addrs) encodedSize() uint64 { + return uint64(len(*a) * ipv4AddrEncodedSize) +} + +// ipv4AddrsEncoder encodes IPv4 addresses as TLV bytes. +func ipv4AddrsEncoder(w io.Writer, val interface{}, _ *[8]byte) error { + if v, ok := val.(*IPV4Addrs); ok { + for _, ip := range *v { + _, err := w.Write(ip.IP.To4()) + if err != nil { + return err + } + var port [2]byte + binary.BigEndian.PutUint16(port[:], uint16(ip.Port)) + _, err = w.Write(port[:]) + if err != nil { + return err + } + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.IPV4Addrs") +} + +// ipv4AddrsDecoder decodes TLV bytes into IPv4 addresses. +func ipv4AddrsDecoder(r io.Reader, val interface{}, _ *[8]byte, + l uint64) error { + + if v, ok := val.(*IPV4Addrs); ok { + if l%(ipv4AddrEncodedSize) != 0 { + return fmt.Errorf("invalid ipv4 list encoding") + } + var ( + numAddrs = int(l / ipv4AddrEncodedSize) + addrs = make([]*net.TCPAddr, 0, numAddrs) + ip [4]byte + port [2]byte + ) + for len(addrs) < numAddrs { + _, err := r.Read(ip[:]) + if err != nil { + return err + } + _, err = r.Read(port[:]) + if err != nil { + return err + } + addrs = append(addrs, &net.TCPAddr{ + IP: ip[:], + Port: int(binary.BigEndian.Uint16(port[:])), + }) + } + *v = addrs + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.IPV4Addrs") +} + +// IPV6Addrs is a list of ipv6 addresses that can be encoded as a TLV record. +type IPV6Addrs []*net.TCPAddr + +// Record returns a Record that can be used to encode/decode a IPV4Addrs +// to/from a TLV stream. +func (a *IPV6Addrs) Record() tlv.Record { + return tlv.MakeDynamicRecord( + 0, a, a.encodedSize, ipv6AddrsEncoder, ipv6AddrsDecoder, + ) +} + +// ipv6AddrEncodedSize is the number of bytes required to encode a single ipv6 +// address. Sixteen bytes are used to encode the IP address and two bytes for +// the port number. +const ipv6AddrEncodedSize = 16 + 2 + +// encodedSize returns the number of bytes required to encode an IPV6Addrs +// variable. +func (a *IPV6Addrs) encodedSize() uint64 { + return uint64(len(*a) * ipv6AddrEncodedSize) +} + +// ipv6AddrsEncoder encodes IPv6 addresses as TLV bytes. +func ipv6AddrsEncoder(w io.Writer, val interface{}, _ *[8]byte) error { + if v, ok := val.(*IPV6Addrs); ok { + for _, ip := range *v { + _, err := w.Write(ip.IP.To16()) + if err != nil { + return err + } + var port [2]byte + binary.BigEndian.PutUint16(port[:], uint16(ip.Port)) + _, err = w.Write(port[:]) + if err != nil { + return err + } + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.IPV6Addrs") +} + +// ipv6AddrsDecoder decodes TLV bytes into IPv6 addresses. +func ipv6AddrsDecoder(r io.Reader, val interface{}, _ *[8]byte, + l uint64) error { + + if v, ok := val.(*IPV6Addrs); ok { + if l%(ipv6AddrEncodedSize) != 0 { + return fmt.Errorf("invalid ipv6 list encoding") + } + var ( + numAddrs = int(l / ipv6AddrEncodedSize) + addrs = make([]*net.TCPAddr, 0, numAddrs) + ip [16]byte + port [2]byte + ) + for len(addrs) < numAddrs { + _, err := r.Read(ip[:]) + if err != nil { + return err + } + _, err = r.Read(port[:]) + if err != nil { + return err + } + addrs = append(addrs, &net.TCPAddr{ + IP: ip[:], + Port: int(binary.BigEndian.Uint16(port[:])), + }) + } + *v = addrs + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.IPV6Addrs") +} + +// TorV3Addrs is a list of tor v3 addresses that can be encoded as a TLV record. +type TorV3Addrs []*tor.OnionAddr + +// torV3AddrEncodedSize is the number of bytes required to encode a single tor +// v3 address. +const torV3AddrEncodedSize = tor.V3DecodedLen + 2 + +// encodedSize returns the number of bytes required to encode an TorV3Addrs +// variable. +func (a *TorV3Addrs) encodedSize() uint64 { + return uint64(len(*a) * torV3AddrEncodedSize) +} + +// Record returns a Record that can be used to encode/decode a IPV4Addrs +// to/from a TLV stream. +func (a *TorV3Addrs) Record() tlv.Record { + return tlv.MakeDynamicRecord( + 0, a, a.encodedSize, torV3AddrsEncoder, torV3AddrsDecoder, + ) +} + +// torV3AddrsEncoder encodes Tor v3 addresses as TLV bytes. +func torV3AddrsEncoder(w io.Writer, val interface{}, _ *[8]byte) error { + if v, ok := val.(*TorV3Addrs); ok { + for _, addr := range *v { + encodedHostLen := tor.V3Len - tor.OnionSuffixLen + host, err := tor.Base32Encoding.DecodeString( + addr.OnionService[:encodedHostLen], + ) + if err != nil { + return err + } + + if len(host) != tor.V3DecodedLen { + return fmt.Errorf("expected a tor v3 host "+ + "length of %d, got: %d", + tor.V3DecodedLen, len(host)) + } + + if _, err = w.Write(host); err != nil { + return err + } + + var port [2]byte + binary.BigEndian.PutUint16(port[:], uint16(addr.Port)) + if _, err = w.Write(port[:]); err != nil { + return err + } + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.TorV3Addrs") +} + +// torV3AddrsDecoder decodes TLV bytes into Tor v3 addresses. +func torV3AddrsDecoder(r io.Reader, val interface{}, _ *[8]byte, + l uint64) error { + + if v, ok := val.(*TorV3Addrs); ok { + if l%torV3AddrEncodedSize != 0 { + return fmt.Errorf("invalid tor v3 list encoding") + } + var ( + numAddrs = int(l / torV3AddrEncodedSize) + addrs = make([]*tor.OnionAddr, 0, numAddrs) + ip [tor.V3DecodedLen]byte + p [2]byte + ) + for len(addrs) < numAddrs { + _, err := r.Read(ip[:]) + if err != nil { + return err + } + _, err = r.Read(p[:]) + if err != nil { + return err + } + onionService := tor.Base32Encoding.EncodeToString(ip[:]) + onionService += tor.OnionSuffix + + if len(onionService) != tor.V3Len { + return fmt.Errorf("expected a tor v3 onion "+ + "service length of %d, got: %d", + tor.V3Len, len(onionService)) + } + + port := int(binary.BigEndian.Uint16(p[:])) + addrs = append(addrs, &tor.OnionAddr{ + OnionService: onionService, + Port: port, + }) + } + *v = addrs + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.TorV3Addrs") +} diff --git a/lnwire/node_announcement_2_test.go b/lnwire/node_announcement_2_test.go new file mode 100644 index 000000000..0aea66147 --- /dev/null +++ b/lnwire/node_announcement_2_test.go @@ -0,0 +1,119 @@ +package lnwire + +import ( + "bytes" + "testing" + + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// TestNodeAnn2EncodeDecode tests the encoding and decoding of the +// NodeAnnouncement2 message using hardcoded byte slices. +func TestNodeAnn2EncodeDecode(t *testing.T) { + t.Parallel() + + // We'll create a raw byte stream that represents a valid + // NodeAnnouncement2 message with various known and unknown fields in + // the signed TLV ranges along with the signature in the unsigned range. + rawBytes := []byte{ + // Features record. + 0x00, // type. + 0x02, // length. + 0x1, 0x2, // value. + + // Color record (optional). + 0x01, // type. + 0x03, // length. + 0xff, 0x00, 0x80, // value: RGB(255, 0, 128). + + // BlockHeight record. + 0x02, // type. + 0x04, // length. + 0x00, 0x01, 0x86, 0xa0, // value: 100000. + + // Alias record (optional). + 0x03, // type. + 0x20, // length (32). + 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x54, + 0x65, 0x73, 0x74, 0x20, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x21, 0x00, // value. + + // NodeID record. + 0x04, // type. + 0x21, // length. + 0x02, 0x28, 0xf2, 0xaf, 0xa, 0xbe, 0x32, 0x24, 0x3, 0x48, 0xf, + 0xb3, 0xee, 0x17, 0x2f, 0x7f, 0x16, 0x1, 0xe6, 0x7d, 0x1d, 0xa6, + 0xca, 0xd4, 0xb, 0x54, 0xc4, 0x46, 0x8d, 0x48, 0x23, 0x6c, 0x39, + + // IPV4Addrs record (optional). + 0x05, // type. + 0x0c, // length (12 bytes). + 0x0a, 0x00, 0x00, 0x01, 0x23, 0x28, // 10.0.0.1:9000 + 0x0a, 0x00, 0x00, 0x01, 0x1f, 0x90, // 10.0.0.1:8080 + + // IPV6Addrs record (optional). + 0x07, // type. + 0x12, // length. + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, // IPv6 address + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // 2001:db8::1 + 0x1f, 0x40, // port 8000 + + // TorV3Addrs record (optional). + 0x09, // type. + 0x25, // length (37 bytes = 1 address). + // value: 35-byte tor v3 address + 2-byte port. + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, + 0x23, 0x28, // port 9000. + + // DNSHostName record (optional). + 0x0b, // type (11). + 0x17, // length (23). + 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, // value. + 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x23, 0x28, // port 9000. + + // 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 := &NodeAnnouncement2{} + 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 ff7db2db6..4e62bee98 100644 --- a/lnwire/test_message.go +++ b/lnwire/test_message.go @@ -5,12 +5,14 @@ import ( "fmt" "image/color" "math" + "net" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/tlv" + "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/require" "pgregory.net/rapid" ) @@ -1231,6 +1233,10 @@ func (ks *KickoffSig) RandTestMessage(t *rapid.T) Message { // lnwire.TestMessage interface. var _ TestMessage = (*NodeAnnouncement1)(nil) +// A compile time check to ensure NodeAnnouncement2 implements the +// lnwire.TestMessage interface. +var _ TestMessage = (*NodeAnnouncement2)(nil) + // RandTestMessage populates the message with random data suitable for testing. // It uses the rapid testing framework to generate random values. // @@ -1262,6 +1268,131 @@ func (a *NodeAnnouncement1) RandTestMessage(t *rapid.T) Message { } } +// RandTestMessage populates the message with random data suitable for testing. +// It uses the rapid testing framework to generate random values. +// +// This is part of the TestMessage interface. +func (n *NodeAnnouncement2) RandTestMessage(t *rapid.T) Message { + features := RandFeatureVector(t) + blockHeight := uint32(rapid.IntRange(0, 1000000).Draw(t, "blockHeight")) + + // Generate random compressed public key for node ID + pubKey := RandPubKey(t) + var nodeID [33]byte + copy(nodeID[:], pubKey.SerializeCompressed()) + + msg := &NodeAnnouncement2{ + Features: tlv.NewRecordT[tlv.TlvType0](*features), + BlockHeight: tlv.NewPrimitiveRecord[tlv.TlvType2]( + blockHeight, + ), + NodeID: tlv.NewPrimitiveRecord[tlv.TlvType4](nodeID), + ExtraSignedFields: make(map[uint64][]byte), + } + + msg.Signature.Val = RandSignature(t) + msg.Signature.Val.ForceSchnorr() + + // Test optional fields one by one + if rapid.Bool().Draw(t, "includeColor") { + rgbColor := Color{ + R: uint8(rapid.IntRange(0, 255).Draw(t, "rgbR")), + G: uint8(rapid.IntRange(0, 255).Draw(t, "rgbG")), + B: uint8(rapid.IntRange(0, 255).Draw(t, "rgbB")), + } + colorRecord := tlv.ZeroRecordT[tlv.TlvType1, Color]() + colorRecord.Val = rgbColor + msg.Color = tlv.SomeRecordT(colorRecord) + } + + if rapid.Bool().Draw(t, "includeAlias") { + aliasBytes := RandNodeAlias2(t) + aliasRecord := tlv.ZeroRecordT[tlv.TlvType3, NodeAlias2]() + aliasRecord.Val = aliasBytes + msg.Alias = tlv.SomeRecordT(aliasRecord) + } + + if rapid.Bool().Draw(t, "includeIPV4Addrs") { + ipv4Addrs := make(IPV4Addrs, 1) + ip := make(net.IP, 4) + ip[0] = uint8(rapid.IntRange(1, 223).Draw(t, "ip4_0")) + ip[1] = uint8(rapid.IntRange(0, 255).Draw(t, "ip4_1")) + ip[2] = uint8(rapid.IntRange(0, 255).Draw(t, "ip4_2")) + ip[3] = uint8(rapid.IntRange(1, 254).Draw(t, "ip4_3")) + + ipv4Addrs[0] = &net.TCPAddr{ + IP: ip, + Port: rapid.IntRange(1, 65535).Draw(t, "port4"), + } + + ipv4Record := tlv.ZeroRecordT[tlv.TlvType5, IPV4Addrs]() + ipv4Record.Val = ipv4Addrs + msg.IPV4Addrs = tlv.SomeRecordT(ipv4Record) + } + + if rapid.Bool().Draw(t, "includeIPV6Addrs") { + ipv6Addrs := make(IPV6Addrs, 1) + ip := make(net.IP, 16) + // Generate random IPv6 address. + for j := 0; j < 16; j++ { + ip[j] = uint8(rapid.IntRange(0, 255).Draw( + t, fmt.Sprintf("ip6_%d", j)), + ) + } + + ipv6Addrs[0] = &net.TCPAddr{ + IP: ip, + Port: rapid.IntRange(1, 65535).Draw(t, "port6"), + } + + ipv6Record := tlv.ZeroRecordT[tlv.TlvType7, IPV6Addrs]() + ipv6Record.Val = ipv6Addrs + msg.IPV6Addrs = tlv.SomeRecordT(ipv6Record) + } + + if rapid.Bool().Draw(t, "includeTorV3Addrs") { + torV3Addrs := make(TorV3Addrs, 1) + onionBytes := rapid.SliceOfN(rapid.Byte(), 35, 35).Draw( + t, "onion", + ) + onionService := tor.Base32Encoding.EncodeToString(onionBytes) + + tor.OnionSuffix + + torV3Addrs[0] = &tor.OnionAddr{ + OnionService: onionService, + Port: rapid.IntRange(1, 65535).Draw( + t, "torPort", + ), + } + + torV3Record := tlv.ZeroRecordT[tlv.TlvType9, TorV3Addrs]() + torV3Record.Val = torV3Addrs + msg.TorV3Addrs = tlv.SomeRecordT(torV3Record) + } + + if rapid.Bool().Draw(t, "includeDNSHostName") { + // Generate a valid DNS hostname. + hostname := genValidHostname(t) + port := rapid.Uint16Range(1, 65535).Draw(t, "dnsPort") + + dnsAddr := DNSAddress{ + Hostname: hostname, + Port: port, + } + + dnsRecord := tlv.ZeroRecordT[tlv.TlvType11, DNSAddress]() + dnsRecord.Val = dnsAddr + msg.DNSHostName = tlv.SomeRecordT(dnsRecord) + } + + randRecs, _ := RandSignedRangeRecords(t) + if len(randRecs) > 0 { + msg.ExtraSignedFields = ExtraSignedFields(randRecs) + } + + return msg +} + // A compile time check to ensure OpenChannel implements the TestMessage // interface. var _ TestMessage = (*OpenChannel)(nil) diff --git a/lnwire/test_utils.go b/lnwire/test_utils.go index 4c88687d1..ed8623034 100644 --- a/lnwire/test_utils.go +++ b/lnwire/test_utils.go @@ -152,6 +152,19 @@ func RandNodeAlias(t *rapid.T) NodeAlias { return alias } +// RandNodeAlias2 generates a random node alias that is compatible with +// NodeAlias2 validation (non-empty, max 32 bytes, valid UTF-8). +func RandNodeAlias2(t *rapid.T) NodeAlias2 { + // Ensure length is at least 1 to satisfy NodeAlias2 validation + aliasLength := rapid.IntRange(1, 32).Draw(t, "aliasLength") + + aliasBytes := rapid.SliceOfN( + rapid.SampledFrom([]rune(charset)), aliasLength, aliasLength, + ).Draw(t, "alias") + + return []byte(string(aliasBytes)) +} + // RandNetAddrs generates random network addresses. func RandNetAddrs(t *rapid.T) []net.Addr { numAddresses := rapid.IntRange(0, 5).Draw(t, "numAddresses")