diff --git a/lnwire/channel_announcement_2.go b/lnwire/channel_announcement_2.go new file mode 100644 index 000000000..074e7d084 --- /dev/null +++ b/lnwire/channel_announcement_2.go @@ -0,0 +1,234 @@ +package lnwire + +import ( + "bytes" + "io" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/tlv" +) + +// ChannelAnnouncement2 message is used to announce the existence of a taproot +// channel between two peers in the network. +type ChannelAnnouncement2 struct { + // Signature is a Schnorr signature over the TLV stream of the message. + Signature Sig + + // ChainHash denotes the target chain that this channel was opened + // within. This value should be the genesis hash of the target chain. + ChainHash tlv.RecordT[tlv.TlvType0, chainhash.Hash] + + // Features is the feature vector that encodes the features supported + // by the target node. This field can be used to signal the type of the + // channel, or modifications to the fields that would normally follow + // this vector. + Features tlv.RecordT[tlv.TlvType2, RawFeatureVector] + + // ShortChannelID is the unique description of the funding transaction, + // or where exactly it's located within the target blockchain. + ShortChannelID tlv.RecordT[tlv.TlvType4, ShortChannelID] + + // Capacity is the number of satoshis of the capacity of this channel. + // It must be less than or equal to the value of the on-chain funding + // output. + Capacity tlv.RecordT[tlv.TlvType6, uint64] + + // NodeID1 is the numerically-lesser public key ID of one of the channel + // operators. + NodeID1 tlv.RecordT[tlv.TlvType8, [33]byte] + + // NodeID2 is the numerically-greater public key ID of one of the + // channel operators. + NodeID2 tlv.RecordT[tlv.TlvType10, [33]byte] + + // BitcoinKey1 is the public key of the key used by Node1 in the + // construction of the on-chain funding transaction. This is an optional + // field and only needs to be set if the 4-of-4 MuSig construction was + // used in the creation of the message signature. + BitcoinKey1 tlv.OptionalRecordT[tlv.TlvType12, [33]byte] + + // BitcoinKey2 is the public key of the key used by Node2 in the + // construction of the on-chain funding transaction. This is an optional + // field and only needs to be set if the 4-of-4 MuSig construction was + // used in the creation of the message signature. + BitcoinKey2 tlv.OptionalRecordT[tlv.TlvType14, [33]byte] + + // MerkleRootHash is the hash used to create the optional tweak in the + // funding output. If this is not set but the bitcoin keys are, then + // the funding output is a pure 2-of-2 MuSig aggregate public key. + MerkleRootHash tlv.OptionalRecordT[tlv.TlvType16, [32]byte] + + // 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 +} + +// Decode deserializes a serialized AnnounceSignatures1 stored in the passed +// io.Reader observing the specified protocol version. +// +// This is part of the lnwire.Message interface. +func (c *ChannelAnnouncement2) 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 *ChannelAnnouncement2) DecodeTLVRecords(r io.Reader) error { + // First extract into extra opaque data. + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + chainHash = tlv.ZeroRecordT[tlv.TlvType0, [32]byte]() + btcKey1 = tlv.ZeroRecordT[tlv.TlvType12, [33]byte]() + btcKey2 = tlv.ZeroRecordT[tlv.TlvType14, [33]byte]() + merkleRootHash = tlv.ZeroRecordT[tlv.TlvType16, [32]byte]() + ) + typeMap, err := tlvRecords.ExtractRecords( + &chainHash, &c.Features, &c.ShortChannelID, &c.Capacity, + &c.NodeID1, &c.NodeID2, &btcKey1, &btcKey2, &merkleRootHash, + ) + if err != nil { + return err + } + + // By default, the chain-hash is the bitcoin mainnet genesis block hash. + c.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash + if _, ok := typeMap[c.ChainHash.TlvType()]; ok { + c.ChainHash.Val = chainHash.Val + } + + if _, ok := typeMap[c.BitcoinKey1.TlvType()]; ok { + c.BitcoinKey1 = tlv.SomeRecordT(btcKey1) + } + + if _, ok := typeMap[c.BitcoinKey2.TlvType()]; ok { + c.BitcoinKey2 = tlv.SomeRecordT(btcKey2) + } + + if _, ok := typeMap[c.MerkleRootHash.TlvType()]; ok { + c.MerkleRootHash = tlv.SomeRecordT(merkleRootHash) + } + + if len(tlvRecords) != 0 { + c.ExtraOpaqueData = tlvRecords + } + + return nil +} + +// Encode serializes the target AnnounceSignatures1 into the passed io.Writer +// observing the protocol version specified. +// +// This is part of the lnwire.Message interface. +func (c *ChannelAnnouncement2) Encode(w *bytes.Buffer, _ uint32) error { + _, err := w.Write(c.Signature.RawBytes()) + if err != nil { + return err + } + _, err = c.DataToSign() + if err != nil { + return err + } + + return WriteBytes(w, c.ExtraOpaqueData) +} + +// DataToSign encodes the data to be signed into the ExtraOpaqueData member and +// returns it. +func (c *ChannelAnnouncement2) 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 + + recordProducers = append(recordProducers, &hash) + } + + recordProducers = append(recordProducers, + &c.Features, &c.ShortChannelID, &c.Capacity, &c.NodeID1, + &c.NodeID2, + ) + + c.BitcoinKey1.WhenSome(func(key tlv.RecordT[tlv.TlvType12, [33]byte]) { + recordProducers = append(recordProducers, &key) + }) + + c.BitcoinKey2.WhenSome(func(key tlv.RecordT[tlv.TlvType14, [33]byte]) { + recordProducers = append(recordProducers, &key) + }) + + c.MerkleRootHash.WhenSome( + func(hash tlv.RecordT[tlv.TlvType16, [32]byte]) { + recordProducers = append(recordProducers, &hash) + }, + ) + + err := EncodeMessageExtraData(&c.ExtraOpaqueData, recordProducers...) + if err != nil { + return nil, err + } + + return c.ExtraOpaqueData, nil +} + +// MsgType returns the integer uniquely identifying this message type on the +// wire. +// +// This is part of the lnwire.Message interface. +func (c *ChannelAnnouncement2) MsgType() MessageType { + return MsgChannelAnnouncement2 +} + +// A compile time check to ensure ChannelAnnouncement2 implements the +// lnwire.Message interface. +var _ Message = (*ChannelAnnouncement2)(nil) + +// Node1KeyBytes returns the bytes representing the public key of node 1 in the +// channel. +// +// NOTE: This is part of the ChannelAnnouncement interface. +func (c *ChannelAnnouncement2) Node1KeyBytes() [33]byte { + return c.NodeID1.Val +} + +// Node2KeyBytes returns the bytes representing the public key of node 2 in the +// channel. +// +// NOTE: This is part of the ChannelAnnouncement interface. +func (c *ChannelAnnouncement2) Node2KeyBytes() [33]byte { + return c.NodeID2.Val +} + +// GetChainHash returns the hash of the chain which this channel's funding +// transaction is confirmed in. +// +// NOTE: This is part of the ChannelAnnouncement interface. +func (c *ChannelAnnouncement2) GetChainHash() chainhash.Hash { + return c.ChainHash.Val +} + +// SCID returns the short channel ID of the channel. +// +// NOTE: This is part of the ChannelAnnouncement interface. +func (c *ChannelAnnouncement2) SCID() ShortChannelID { + return c.ShortChannelID.Val +} + +// A compile-time check to ensure that ChannelAnnouncement2 implements the +// ChannelAnnouncement interface. +var _ ChannelAnnouncement = (*ChannelAnnouncement2)(nil) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index e4ab753f7..2e74853e4 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -19,6 +19,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn" @@ -30,17 +31,25 @@ import ( ) var ( - shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - shaHash1, _ = chainhash.NewHash(shaHash1Bytes) - outpoint1 = wire.NewOutPoint(shaHash1, 0) + shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb" + + "92427ae41e4649b934ca495991b7852b855") - testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7") - testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") - testRScalar = new(btcec.ModNScalar) - testSScalar = new(btcec.ModNScalar) - _ = testRScalar.SetByteSlice(testRBytes) - _ = testSScalar.SetByteSlice(testSBytes) - testSig = ecdsa.NewSignature(testRScalar, testSScalar) + shaHash1, _ = chainhash.NewHash(shaHash1Bytes) + outpoint1 = wire.NewOutPoint(shaHash1, 0) + + testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571" + + "319d18e949ddfa2965fb6caa1bf0314f882d7") + testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a" + + "88121167221b6700d72a0ead154c03be696a292d24ae") + testRScalar = new(btcec.ModNScalar) + testSScalar = new(btcec.ModNScalar) + _ = testRScalar.SetByteSlice(testRBytes) + _ = testSScalar.SetByteSlice(testSBytes) + testSig = ecdsa.NewSignature(testRScalar, testSScalar) + testSchnorrSigStr, _ = hex.DecodeString("04E7F9037658A92AFEB4F2" + + "5BAE5339E3DDCA81A353493827D26F16D92308E49E2A25E9220867" + + "8A2DF86970DA91B03A8AF8815A8A60498B358DAF560B347AA557") + testSchnorrSig, _ = NewSigFromSchnorrRawSignature(testSchnorrSigStr) ) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -156,17 +165,15 @@ func generateRandomBytes(n int) ([]byte, error) { return b, nil } -func randRawKey() ([33]byte, error) { +func randRawKey(t *testing.T) [33]byte { var n [33]byte priv, err := btcec.NewPrivateKey() - if err != nil { - return n, err - } + require.NoError(t, err) copy(n[:], priv.PubKey().SerializeCompressed()) - return n, nil + return n } func randDeliveryAddress(r *rand.Rand) (DeliveryAddress, error) { @@ -993,7 +1000,13 @@ func TestLightningWireProtocol(t *testing.T) { MsgChannelAnnouncement: func(v []reflect.Value, r *rand.Rand) { var err error req := ChannelAnnouncement1{ - ShortChannelID: NewShortChanIDFromInt(uint64(r.Int63())), + ShortChannelID: NewShortChanIDFromInt( + uint64(r.Int63()), + ), + NodeID1: randRawKey(t), + NodeID2: randRawKey(t), + BitcoinKey1: randRawKey(t), + BitcoinKey2: randRawKey(t), Features: randRawFeatureVector(r), ExtraOpaqueData: make([]byte, 0), } @@ -1018,26 +1031,6 @@ func TestLightningWireProtocol(t *testing.T) { return } - req.NodeID1, err = randRawKey() - if err != nil { - t.Fatalf("unable to generate key: %v", err) - return - } - req.NodeID2, err = randRawKey() - if err != nil { - t.Fatalf("unable to generate key: %v", err) - return - } - req.BitcoinKey1, err = randRawKey() - if err != nil { - t.Fatalf("unable to generate key: %v", err) - return - } - req.BitcoinKey2, err = randRawKey() - if err != nil { - t.Fatalf("unable to generate key: %v", err) - return - } if _, err := r.Read(req.ChainHash[:]); err != nil { t.Fatalf("unable to generate chain hash: %v", err) return @@ -1059,6 +1052,7 @@ func TestLightningWireProtocol(t *testing.T) { MsgNodeAnnouncement: func(v []reflect.Value, r *rand.Rand) { var err error req := NodeAnnouncement{ + NodeID: randRawKey(t), Features: randRawFeatureVector(r), Timestamp: uint32(r.Int31()), Alias: randAlias(r), @@ -1075,12 +1069,6 @@ func TestLightningWireProtocol(t *testing.T) { return } - req.NodeID, err = randRawKey() - if err != nil { - t.Fatalf("unable to generate key: %v", err) - return - } - req.Addresses, err = randAddrs(r) if err != nil { t.Fatalf("unable to generate addresses: %v", err) @@ -1114,7 +1102,9 @@ func TestLightningWireProtocol(t *testing.T) { } req := ChannelUpdate1{ - ShortChannelID: NewShortChanIDFromInt(uint64(r.Int63())), + ShortChannelID: NewShortChanIDFromInt( + uint64(r.Int63()), + ), Timestamp: uint32(r.Int31()), MessageFlags: msgFlags, ChannelFlags: ChanUpdateChanFlags(r.Int31()), @@ -1536,6 +1526,70 @@ func TestLightningWireProtocol(t *testing.T) { require.NoError(t, err) } + v[0] = reflect.ValueOf(req) + }, + MsgChannelAnnouncement2: func(v []reflect.Value, r *rand.Rand) { + req := ChannelAnnouncement2{ + Signature: testSchnorrSig, + ExtraOpaqueData: make([]byte, 0), + } + + req.ShortChannelID.Val = NewShortChanIDFromInt( + uint64(r.Int63()), + ) + req.Capacity.Val = rand.Uint64() + + req.Features.Val = *randRawFeatureVector(r) + + req.NodeID1.Val = randRawKey(t) + req.NodeID2.Val = randRawKey(t) + + // Sometimes set chain hash to bitcoin mainnet genesis + // hash. + req.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash + if r.Int31()%2 == 0 { + _, err := r.Read(req.ChainHash.Val[:]) + require.NoError(t, err) + } + + // Sometimes set the bitcoin keys. + if r.Int31()%2 == 0 { + btcKey1 := tlv.ZeroRecordT[ + tlv.TlvType12, [33]byte, + ]() + btcKey1.Val = randRawKey(t) + req.BitcoinKey1 = tlv.SomeRecordT(btcKey1) + + btcKey2 := tlv.ZeroRecordT[ + tlv.TlvType14, [33]byte, + ]() + btcKey2.Val = randRawKey(t) + req.BitcoinKey2 = tlv.SomeRecordT(btcKey2) + + // Occasionally also set the merkle root hash. + if r.Int31()%2 == 0 { + hash := tlv.ZeroRecordT[ + tlv.TlvType16, [32]byte, + ]() + + _, err := r.Read(hash.Val[:]) + require.NoError(t, err) + + req.MerkleRootHash = tlv.SomeRecordT( + hash, + ) + } + } + + numExtraBytes := r.Int31n(1000) + if numExtraBytes > 0 { + req.ExtraOpaqueData = make( + []byte, numExtraBytes, + ) + _, err := r.Read(req.ExtraOpaqueData[:]) + require.NoError(t, err) + } + v[0] = reflect.ValueOf(req) }, } @@ -1772,6 +1826,12 @@ func TestLightningWireProtocol(t *testing.T) { return mainScenario(&m) }, }, + { + msgType: MsgChannelAnnouncement2, + scenario: func(m ChannelAnnouncement2) bool { + return mainScenario(&m) + }, + }, } for _, test := range tests { var config *quick.Config diff --git a/lnwire/message.go b/lnwire/message.go index e93357277..b91db0679 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -58,6 +58,7 @@ const ( MsgQueryChannelRange = 263 MsgReplyChannelRange = 264 MsgGossipTimestampRange = 265 + MsgChannelAnnouncement2 = 267 MsgKickoffSig = 777 ) @@ -158,6 +159,8 @@ func (t MessageType) String() string { return "ClosingSig" case MsgAnnounceSignatures2: return "MsgAnnounceSignatures2" + case MsgChannelAnnouncement2: + return "ChannelAnnouncement2" default: return "" } @@ -289,6 +292,8 @@ func makeEmptyMessage(msgType MessageType) (Message, error) { msg = &ClosingSig{} case MsgAnnounceSignatures2: msg = &AnnounceSignatures2{} + case MsgChannelAnnouncement2: + msg = &ChannelAnnouncement2{} default: // If the message is not within our custom range and has not // specifically been overridden, return an unknown message.