lnd/netann/channel_announcement_test.go
Elle Mouton 9f54ec90aa
multi+refactor: move models package to graph/db
All the structs defined in the `channeldb/models` package are graph
related. So once we move all the graph CRUD code to the graph package,
it makes sense to have the schema structs there too. So this just moves
the `models` package over to `graph/db/models`.
2024-11-28 13:34:33 +02:00

360 lines
10 KiB
Go

package netann
import (
"bytes"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"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/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateChanAnnouncement(t *testing.T) {
t.Parallel()
key := [33]byte{0x1}
var sig lnwire.Sig
features := lnwire.NewRawFeatureVector(lnwire.AnchorsRequired)
var featuresBuf bytes.Buffer
if err := features.Encode(&featuresBuf); err != nil {
t.Fatalf("unable to encode features: %v", err)
}
expChanAnn := &lnwire.ChannelAnnouncement1{
ChainHash: chainhash.Hash{0x1},
ShortChannelID: lnwire.ShortChannelID{BlockHeight: 1},
NodeID1: key,
NodeID2: key,
NodeSig1: sig,
NodeSig2: sig,
BitcoinKey1: key,
BitcoinKey2: key,
BitcoinSig1: sig,
BitcoinSig2: sig,
Features: features,
ExtraOpaqueData: []byte{0x1},
}
chanProof := &models.ChannelAuthProof{
NodeSig1Bytes: expChanAnn.NodeSig1.ToSignatureBytes(),
NodeSig2Bytes: expChanAnn.NodeSig2.ToSignatureBytes(),
BitcoinSig1Bytes: expChanAnn.BitcoinSig1.ToSignatureBytes(),
BitcoinSig2Bytes: expChanAnn.BitcoinSig2.ToSignatureBytes(),
}
chanInfo := &models.ChannelEdgeInfo{
ChainHash: expChanAnn.ChainHash,
ChannelID: expChanAnn.ShortChannelID.ToUint64(),
ChannelPoint: wire.OutPoint{Index: 1},
Capacity: btcutil.SatoshiPerBitcoin,
NodeKey1Bytes: key,
NodeKey2Bytes: key,
BitcoinKey1Bytes: key,
BitcoinKey2Bytes: key,
Features: featuresBuf.Bytes(),
ExtraOpaqueData: expChanAnn.ExtraOpaqueData,
}
chanAnn, _, _, err := CreateChanAnnouncement(
chanProof, chanInfo, nil, nil,
)
require.NoError(t, err, "unable to create channel announcement")
assert.Equal(t, chanAnn, expChanAnn)
}
// 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 := ChanAnn2DigestToSign(ann)
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 := lnwire.NewSigFromSignature(s)
require.NoError(t, err)
ann.Signature = sig
// Validate the announcement.
require.NoError(t, ValidateChannelAnn(ann, 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 := ChanAnn2DigestToSign(ann)
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 *lnwire.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 := lnwire.NewSigFromSignature(s)
require.NoError(t, err)
ann.Signature = sig
// Validate the announcement.
require.NoError(t, ValidateChannelAnn(ann, 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) *lnwire.ChannelAnnouncement2 {
var ann lnwire.ChannelAnnouncement2
ann.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash
features := lnwire.NewRawFeatureVector()
ann.Features.Val = *features
ann.ShortChannelID.Val = lnwire.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
}