lnd/netann/channel_announcement_test.go

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/channeldb/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
}