mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
netann: validation and verification funcs for ChannelAnnouncement2
This commit is contained in:
parent
5fc1da3abe
commit
21c9ef8904
@ -6,9 +6,26 @@ import (
|
||||
"fmt"
|
||||
|
||||
"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/chainhash"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"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"
|
||||
)
|
||||
|
||||
// CreateChanAnnouncement is a helper function which creates all channel
|
||||
@ -94,10 +111,14 @@ func CreateChanAnnouncement(chanProof *models.ChannelAuthProof,
|
||||
type FetchPkScript func(*lnwire.ShortChannelID) ([]byte, error)
|
||||
|
||||
// ValidateChannelAnn validates the channel announcement.
|
||||
func ValidateChannelAnn(a lnwire.ChannelAnnouncement, _ FetchPkScript) error {
|
||||
func ValidateChannelAnn(a lnwire.ChannelAnnouncement,
|
||||
fetchPkScript FetchPkScript) error {
|
||||
|
||||
switch ann := a.(type) {
|
||||
case *lnwire.ChannelAnnouncement1:
|
||||
return validateChannelAnn1(ann)
|
||||
case *lnwire.ChannelAnnouncement2:
|
||||
return validateChannelAnn2(ann, fetchPkScript)
|
||||
default:
|
||||
return fmt.Errorf("unhandled implementation of "+
|
||||
"lnwire.ChannelAnnouncement: %T", a)
|
||||
@ -175,3 +196,96 @@ func validateChannelAnn1(a *lnwire.ChannelAnnouncement1) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateChannelAnn2 validates the channel announcement message and checks
|
||||
// that message signature covers the announcement message.
|
||||
func validateChannelAnn2(a *lnwire.ChannelAnnouncement2,
|
||||
fetchPkScript FetchPkScript) error {
|
||||
|
||||
dataHash, err := ChanAnn2DigestToSign(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sig, err := a.Signature.ToSignature()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeKey1, err := btcec.ParsePubKey(a.NodeID1.Val[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeKey2, err := btcec.ParsePubKey(a.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 a.BitcoinKey1.IsSome() && a.BitcoinKey2.IsSome() {
|
||||
var (
|
||||
btcKey1 tlv.RecordT[tlv.TlvType12, [33]byte]
|
||||
btcKey2 tlv.RecordT[tlv.TlvType14, [33]byte]
|
||||
)
|
||||
|
||||
btcKey1 = a.BitcoinKey1.UnwrapOr(btcKey1)
|
||||
btcKey2 = a.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(&a.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
|
||||
}
|
||||
|
||||
// ChanAnn2DigestToSign computes the digest of the message to be signed.
|
||||
func ChanAnn2DigestToSign(a *lnwire.ChannelAnnouncement2) (*chainhash.Hash,
|
||||
error) {
|
||||
|
||||
data, err := a.DataToSign()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MsgHash(chanAnn2MsgName, chanAnn2SigFieldName, data), nil
|
||||
}
|
||||
|
@ -4,11 +4,16 @@ 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"
|
||||
)
|
||||
@ -64,3 +69,291 @@ func TestCreateChanAnnouncement(t *testing.T) {
|
||||
|
||||
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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user