From 90595cd2719b1dc6fac1ec1f0395d2418f4569d5 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 29 Sep 2023 10:33:24 +0200 Subject: [PATCH] lnwire: add ChannelAnnouncement2 message And ensure that it implements the ChannelAnnouncement interface. --- lnwire/channel_announcement_2.go | 344 ++++++++++++++++++++++++++++ lnwire/channel_announcement_test.go | 301 ++++++++++++++++++++++++ lnwire/lnwire_test.go | 146 ++++++++---- lnwire/message.go | 5 + 4 files changed, 753 insertions(+), 43 deletions(-) create mode 100644 lnwire/channel_announcement_2.go create mode 100644 lnwire/channel_announcement_test.go diff --git a/lnwire/channel_announcement_2.go b/lnwire/channel_announcement_2.go new file mode 100644 index 000000000..51171510e --- /dev/null +++ b/lnwire/channel_announcement_2.go @@ -0,0 +1,344 @@ +package lnwire + +import ( + "bytes" + "fmt" + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // chanAnn2MsgName is a string representing the name of the + // ChannelAnnouncement2 message. This string will be used during the + // construction of the tagged hash message to be signed when producing + // the signature for the ChannelAnnouncement2 message. + chanAnn2MsgName = "channel_announcement_2" + + // chanAnn2SigFieldName is the name of the signature field of the + // ChannelAnnouncement2 message. This string will be used during the + // construction of the tagged hash message to be signed when producing + // the signature for the ChannelAnnouncement2 message. + chanAnn2SigFieldName = "signature" +) + +// 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) +} + +// DigestToSign computes the digest of the message to be signed. +func (c *ChannelAnnouncement2) DigestToSign() (*chainhash.Hash, error) { + data, err := c.DataToSign() + if err != nil { + return nil, err + } + + return MsgHash(chanAnn2MsgName, chanAnn2SigFieldName, data), nil +} + +// 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 +} + +// Validate checks that the announcement signature is valid. +// +// NOTE: This is part of the ChannelAnnouncement interface. +func (c *ChannelAnnouncement2) Validate( + fetchPkScript func(id *ShortChannelID) ([]byte, error)) error { + + dataHash, err := c.DigestToSign() + if err != nil { + return err + } + + sig, err := c.Signature.ToSignature() + if err != nil { + return err + } + + nodeKey1, err := btcec.ParsePubKey(c.NodeID1.Val[:]) + if err != nil { + return err + } + + nodeKey2, err := btcec.ParsePubKey(c.NodeID2.Val[:]) + if err != nil { + return err + } + + keys := []*btcec.PublicKey{ + nodeKey1, nodeKey2, + } + + // If the bitcoin keys are provided in the announcement, then it is + // assumed that the signature of the announcement is a 4-of-4 MuSig2 + // over the bitcoin keys and node ID keys. + if c.BitcoinKey1.IsSome() && c.BitcoinKey2.IsSome() { + var ( + btcKey1 tlv.RecordT[tlv.TlvType12, [33]byte] + btcKey2 tlv.RecordT[tlv.TlvType14, [33]byte] + ) + + btcKey1 = c.BitcoinKey1.UnwrapOr(btcKey1) + btcKey2 = c.BitcoinKey2.UnwrapOr(btcKey2) + + bitcoinKey1, err := btcec.ParsePubKey(btcKey1.Val[:]) + if err != nil { + return err + } + + bitcoinKey2, err := btcec.ParsePubKey(btcKey2.Val[:]) + if err != nil { + return err + } + + keys = append(keys, bitcoinKey1, bitcoinKey2) + } else { + // If bitcoin keys are not provided, then we need to get the + // on-chain output key since this will be the 3rd key in the + // 3-of-3 MuSig2 signature. + pkScript, err := fetchPkScript(&c.ShortChannelID.Val) + if err != nil { + return err + } + + outputKey, err := schnorr.ParsePubKey(pkScript[2:]) + if err != nil { + return err + } + + keys = append(keys, outputKey) + } + + aggKey, _, _, err := musig2.AggregateKeys(keys, true) + if err != nil { + return err + } + + if !sig.Verify(dataHash.CloneBytes(), aggKey.FinalKey) { + return fmt.Errorf("invalid sig") + } + + return nil +} + +// A compile-time check to ensure that ChannelAnnouncement2 implements the +// ChannelAnnouncement interface. +var _ ChannelAnnouncement = (*ChannelAnnouncement2)(nil) diff --git a/lnwire/channel_announcement_test.go b/lnwire/channel_announcement_test.go new file mode 100644 index 000000000..87f7da021 --- /dev/null +++ b/lnwire/channel_announcement_test.go @@ -0,0 +1,301 @@ +package lnwire + +import ( + "bytes" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// TestChanAnnounce2Validation checks that the various forms of the +// channel_announcement_2 message are validated correctly. +func TestChanAnnounce2Validation(t *testing.T) { + t.Parallel() + + t.Run( + "test 4-of-4 MuSig2 channel announcement", + test4of4MuSig2ChanAnnouncement, + ) + + t.Run( + "test 3-of-3 MuSig2 channel announcement", + test3of3MuSig2ChanAnnouncement, + ) +} + +// test4of4MuSig2ChanAnnouncement covers the case where both bitcoin keys are +// present in the channel announcement. In this case, the signature should be +// a 4-of-4 MuSig2. +func test4of4MuSig2ChanAnnouncement(t *testing.T) { + t.Parallel() + + // Generate the keys for node 1 and node2. + node1, node2 := genChanAnnKeys(t) + + // Build the unsigned channel announcement. + ann := buildUnsignedChanAnnouncement(node1, node2, true) + + // Serialise the bytes that need to be signed. + msg, err := ann.DigestToSign() + require.NoError(t, err) + + var msgBytes [32]byte + copy(msgBytes[:], msg.CloneBytes()) + + // Generate the 4 nonces required for producing the signature. + var ( + node1NodeNonce = genNonceForPubKey(t, node1.nodePub) + node1BtcNonce = genNonceForPubKey(t, node1.btcPub) + node2NodeNonce = genNonceForPubKey(t, node2.nodePub) + node2BtcNonce = genNonceForPubKey(t, node2.btcPub) + ) + + nonceAgg, err := musig2.AggregateNonces([][66]byte{ + node1NodeNonce.PubNonce, + node1BtcNonce.PubNonce, + node2NodeNonce.PubNonce, + node2BtcNonce.PubNonce, + }) + require.NoError(t, err) + + pubKeys := []*btcec.PublicKey{ + node1.nodePub, node2.nodePub, node1.btcPub, node2.btcPub, + } + + // Let Node1 sign the announcement message with its node key. + psA1, err := musig2.Sign( + node1NodeNonce.SecNonce, node1.nodePriv, nonceAgg, pubKeys, + msgBytes, musig2.WithSortedKeys(), + ) + require.NoError(t, err) + + // Let Node1 sign the announcement message with its bitcoin key. + psA2, err := musig2.Sign( + node1BtcNonce.SecNonce, node1.btcPriv, nonceAgg, pubKeys, + msgBytes, musig2.WithSortedKeys(), + ) + require.NoError(t, err) + + // Let Node2 sign the announcement message with its node key. + psB1, err := musig2.Sign( + node2NodeNonce.SecNonce, node2.nodePriv, nonceAgg, pubKeys, + msgBytes, musig2.WithSortedKeys(), + ) + require.NoError(t, err) + + // Let Node2 sign the announcement message with its bitcoin key. + psB2, err := musig2.Sign( + node2BtcNonce.SecNonce, node2.btcPriv, nonceAgg, pubKeys, + msgBytes, musig2.WithSortedKeys(), + ) + require.NoError(t, err) + + // Finally, combine the partial signatures from Node1 and Node2 and add + // the signature to the announcement message. + s := musig2.CombineSigs(psA1.R, []*musig2.PartialSignature{ + psA1, psA2, psB1, psB2, + }) + + sig, err := NewSigFromSignature(s) + require.NoError(t, err) + + ann.Signature = sig + + // Validate the announcement. + require.NoError(t, ann.Validate(nil)) +} + +// test3of3MuSig2ChanAnnouncement covers the case where no bitcoin keys are +// present in the channel announcement. In this case, the signature should be +// a 3-of-3 MuSig2 where the keys making up the pub key are: node1 ID, node2 ID +// and the output key found on-chain in the funding transaction. As the +// verifier, we don't care about the construction of the output key. We only +// care that the channel peers were able to sign for the output key. In reality, +// this key will likely be constructed from at least 1 key from each peer and +// the partial signature for it will be constructed via nested MuSig2 but for +// the sake of the test, we will just have it be backed by a single key. +func test3of3MuSig2ChanAnnouncement(t *testing.T) { + // Generate the keys for node 1 and node 2. + node1, node2 := genChanAnnKeys(t) + + // Build the unsigned channel announcement. + ann := buildUnsignedChanAnnouncement(node1, node2, false) + + // Serialise the bytes that need to be signed. + msg, err := ann.DigestToSign() + require.NoError(t, err) + + var msgBytes [32]byte + copy(msgBytes[:], msg.CloneBytes()) + + // Create a random 3rd key to be used for the output key. + outputKeyPriv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + outputKey := outputKeyPriv.PubKey() + + // Ensure that the output key has an even Y by negating the private key + // if required. + if outputKey.SerializeCompressed()[0] == + input.PubKeyFormatCompressedOdd { + + outputKeyPriv.Key.Negate() + outputKey = outputKeyPriv.PubKey() + } + + // Generate the nonces required for producing the partial signatures. + var ( + node1NodeNonce = genNonceForPubKey(t, node1.nodePub) + node2NodeNonce = genNonceForPubKey(t, node2.nodePub) + outputKeyNonce = genNonceForPubKey(t, outputKey) + ) + + nonceAgg, err := musig2.AggregateNonces([][66]byte{ + node1NodeNonce.PubNonce, + node2NodeNonce.PubNonce, + outputKeyNonce.PubNonce, + }) + require.NoError(t, err) + + pkScript, err := input.PayToTaprootScript(outputKey) + require.NoError(t, err) + + // We'll pass in a mock tx fetcher that will return the funding output + // containing this key. This is needed since the output key can not be + // determined from the channel announcement itself. + fetchTx := func(chanID *ShortChannelID) ([]byte, error) { + return pkScript, nil + } + + pubKeys := []*btcec.PublicKey{node1.nodePub, node2.nodePub, outputKey} + + // Let Node1 sign the announcement message with its node key. + psA, err := musig2.Sign( + node1NodeNonce.SecNonce, node1.nodePriv, nonceAgg, pubKeys, + msgBytes, musig2.WithSortedKeys(), + ) + require.NoError(t, err) + + // Let Node2 sign the announcement message with its node key. + psB, err := musig2.Sign( + node2NodeNonce.SecNonce, node2.nodePriv, nonceAgg, pubKeys, + msgBytes, musig2.WithSortedKeys(), + ) + require.NoError(t, err) + + // Create a partial sig for the output key. + psO, err := musig2.Sign( + outputKeyNonce.SecNonce, outputKeyPriv, nonceAgg, pubKeys, + msgBytes, musig2.WithSortedKeys(), + ) + require.NoError(t, err) + + // Finally, combine the partial signatures from Node1 and Node2 and add + // the signature to the announcement message. + s := musig2.CombineSigs(psA.R, []*musig2.PartialSignature{ + psA, psB, psO, + }) + + sig, err := NewSigFromSignature(s) + require.NoError(t, err) + + ann.Signature = sig + + // Validate the announcement. + require.NoError(t, ann.Validate(fetchTx)) +} + +func genNonceForPubKey(t *testing.T, pub *btcec.PublicKey) *musig2.Nonces { + nonce, err := musig2.GenNonces(musig2.WithPublicKey(pub)) + require.NoError(t, err) + + return nonce +} + +type keyRing struct { + nodePriv *btcec.PrivateKey + nodePub *btcec.PublicKey + btcPriv *btcec.PrivateKey + btcPub *btcec.PublicKey +} + +func genChanAnnKeys(t *testing.T) (*keyRing, *keyRing) { + // Let Alice and Bob derive the various keys they need. + aliceNodePrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + aliceNodeID := aliceNodePrivKey.PubKey() + + aliceBtcPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + bobNodePrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + bobNodeID := bobNodePrivKey.PubKey() + + bobBtcPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + alice := &keyRing{ + nodePriv: aliceNodePrivKey, + nodePub: aliceNodePrivKey.PubKey(), + btcPriv: aliceBtcPrivKey, + btcPub: aliceBtcPrivKey.PubKey(), + } + + bob := &keyRing{ + nodePriv: bobNodePrivKey, + nodePub: bobNodePrivKey.PubKey(), + btcPriv: bobBtcPrivKey, + btcPub: bobBtcPrivKey.PubKey(), + } + + if bytes.Compare( + aliceNodeID.SerializeCompressed(), + bobNodeID.SerializeCompressed(), + ) != -1 { + + return bob, alice + } + + return alice, bob +} + +func buildUnsignedChanAnnouncement(node1, node2 *keyRing, + withBtcKeys bool) *ChannelAnnouncement2 { + + var ann ChannelAnnouncement2 + ann.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash + features := NewRawFeatureVector() + ann.Features.Val = *features + ann.ShortChannelID.Val = ShortChannelID{ + BlockHeight: 1000, + TxIndex: 100, + TxPosition: 0, + } + ann.Capacity.Val = 100000 + + copy(ann.NodeID1.Val[:], node1.nodePub.SerializeCompressed()) + copy(ann.NodeID2.Val[:], node2.nodePub.SerializeCompressed()) + + if !withBtcKeys { + return &ann + } + + btcKey1Bytes := tlv.ZeroRecordT[tlv.TlvType12, [33]byte]() + btcKey2Bytes := tlv.ZeroRecordT[tlv.TlvType14, [33]byte]() + + copy(btcKey1Bytes.Val[:], node1.btcPub.SerializeCompressed()) + copy(btcKey2Bytes.Val[:], node2.btcPub.SerializeCompressed()) + + ann.BitcoinKey1 = tlv.SomeRecordT(btcKey1Bytes) + ann.BitcoinKey2 = tlv.SomeRecordT(btcKey2Bytes) + + return &ann +} diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index e2dd6a259..2f09becc0 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -18,6 +18,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" @@ -29,17 +30,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" @@ -134,17 +143,15 @@ func randPubKey() (*btcec.PublicKey, error) { return priv.PubKey(), 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) { @@ -940,7 +947,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), } @@ -965,26 +978,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 @@ -1006,6 +999,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), @@ -1022,12 +1016,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) @@ -1061,7 +1049,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()), @@ -1458,6 +1448,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) }, } @@ -1694,6 +1748,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.