So that we have one place that converts from our `models` struct to the `lnwire.ChannelAnnouncement` struct. The commit also refactors netann.CreateChanAnnouncement to only take a ChannelEdgeInfo and get the proof from there instead of needing the proof to be passed in separately. Add ToChannelAnnouncement() method to ChannelEdgeInfo that converts the model struct to a lnwire.ChannelAnnouncement1 message. This: - Centralizes the conversion logic in one place instead of scattered across multiple call sites - Validates that AuthProof is present (can't create announcement without proof) - Currently only supports v1 channels, returning error for v2 Refactor netann.CreateChanAnnouncement to use this helper and remove the separate chanProof parameter since proof is now accessed from within ChannelEdgeInfo. This improves encapsulation and reduces parameter count.
344 lines
9.7 KiB
Go
344 lines
9.7 KiB
Go
package netann
|
|
|
|
import (
|
|
"errors"
|
|
"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/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/lightningnetwork/lnd/graph/db/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
|
|
// announcements given the necessary channel related database items. This
|
|
// function is used to transform out database structs into the corresponding wire
|
|
// structs for announcing new channels to other peers, or simply syncing up a
|
|
// peer's initial routing table upon connect.
|
|
func CreateChanAnnouncement(chanInfo *models.ChannelEdgeInfo,
|
|
e1, e2 *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1,
|
|
*lnwire.ChannelUpdate1, *lnwire.ChannelUpdate1, error) {
|
|
|
|
// First, using the parameters of the channel, along with the channel
|
|
// authentication proof, we'll create re-create the original
|
|
// authenticated channel announcement.
|
|
chanAnn, err := chanInfo.ToChannelAnnouncement()
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
// We'll unconditionally queue the channel's existence chanProof as it
|
|
// will need to be processed before either of the channel update
|
|
// networkMsgs.
|
|
|
|
// Since it's up to a node's policy as to whether they advertise the
|
|
// edge in a direction, we don't create an advertisement if the edge is
|
|
// nil.
|
|
var edge1Ann, edge2Ann *lnwire.ChannelUpdate1
|
|
if e1 != nil {
|
|
edge1Ann, err = ChannelUpdateFromEdge(chanInfo, e1)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
if e2 != nil {
|
|
edge2Ann, err = ChannelUpdateFromEdge(chanInfo, e2)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
|
|
return chanAnn, edge1Ann, edge2Ann, nil
|
|
}
|
|
|
|
// FetchPkScript defines a function that can be used to fetch the output script
|
|
// for the transaction with the given SCID.
|
|
type FetchPkScript func(lnwire.ShortChannelID) (txscript.ScriptClass,
|
|
btcutil.Address, error)
|
|
|
|
// ValidateChannelAnn validates the channel announcement.
|
|
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)
|
|
}
|
|
}
|
|
|
|
// validateChannelAnn1 validates the channel announcement message and checks
|
|
// that node signatures covers the announcement message, and that the bitcoin
|
|
// signatures covers the node keys.
|
|
func validateChannelAnn1(a *lnwire.ChannelAnnouncement1) error {
|
|
// First, we'll compute the digest (h) which is to be signed by each of
|
|
// the keys included within the node announcement message. This hash
|
|
// digest includes all the keys, so the (up to 4 signatures) will
|
|
// attest to the validity of each of the keys.
|
|
data, err := a.DataToSign()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dataHash := chainhash.DoubleHashB(data)
|
|
|
|
// First we'll verify that the passed bitcoin key signature is indeed a
|
|
// signature over the computed hash digest.
|
|
bitcoinSig1, err := a.BitcoinSig1.ToSignature()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bitcoinKey1, err := btcec.ParsePubKey(a.BitcoinKey1[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !bitcoinSig1.Verify(dataHash, bitcoinKey1) {
|
|
return errors.New("can't verify first bitcoin signature")
|
|
}
|
|
|
|
// If that checks out, then we'll verify that the second bitcoin
|
|
// signature is a valid signature of the bitcoin public key over hash
|
|
// digest as well.
|
|
bitcoinSig2, err := a.BitcoinSig2.ToSignature()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bitcoinKey2, err := btcec.ParsePubKey(a.BitcoinKey2[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !bitcoinSig2.Verify(dataHash, bitcoinKey2) {
|
|
return errors.New("can't verify second bitcoin signature")
|
|
}
|
|
|
|
// Both node signatures attached should indeed be a valid signature
|
|
// over the selected digest of the channel announcement signature.
|
|
nodeSig1, err := a.NodeSig1.ToSignature()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodeKey1, err := btcec.ParsePubKey(a.NodeID1[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !nodeSig1.Verify(dataHash, nodeKey1) {
|
|
return errors.New("can't verify data in first node signature")
|
|
}
|
|
|
|
nodeSig2, err := a.NodeSig2.ToSignature()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodeKey2, err := btcec.ParsePubKey(a.NodeID2[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !nodeSig2.Verify(dataHash, nodeKey2) {
|
|
return errors.New("can't verify data in second node signature")
|
|
}
|
|
|
|
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 {
|
|
|
|
// Next, we fetch the funding transaction's PK script. We need this so
|
|
// that we know what type of channel we will be validating: P2WSH or
|
|
// P2TR.
|
|
scriptClass, scriptAddr, err := fetchPkScript(a.ShortChannelID.Val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var keys []*btcec.PublicKey
|
|
|
|
switch scriptClass {
|
|
case txscript.WitnessV0ScriptHashTy:
|
|
keys, err = chanAnn2P2WSHMuSig2Keys(a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case txscript.WitnessV1TaprootTy:
|
|
keys, err = chanAnn2P2TRMuSig2Keys(a, scriptAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("invalid on-chain pk script type for "+
|
|
"channel_announcement_2: %s", scriptClass)
|
|
}
|
|
|
|
// Do a MuSig2 aggregation of the keys to obtain the aggregate key that
|
|
// the signature will be validated against.
|
|
aggKey, _, _, err := musig2.AggregateKeys(keys, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get the message that the signature should have signed.
|
|
dataHash, err := ChanAnn2DigestToSign(a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Obtain the signature.
|
|
sig, err := a.Signature.Val.ToSignature()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check that the signature is valid for the aggregate key given the
|
|
// message digest.
|
|
if !sig.Verify(dataHash.CloneBytes(), aggKey.FinalKey) {
|
|
return fmt.Errorf("invalid sig")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// chanAnn2P2WSHMuSig2Keys returns the set of keys that should be used to
|
|
// construct the aggregate key that the signature in an
|
|
// lnwire.ChannelAnnouncement2 message should be verified against in the case
|
|
// where the channel being announced is a P2WSH channel.
|
|
func chanAnn2P2WSHMuSig2Keys(a *lnwire.ChannelAnnouncement2) (
|
|
[]*btcec.PublicKey, error) {
|
|
|
|
nodeKey1, err := btcec.ParsePubKey(a.NodeID1.Val[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodeKey2, err := btcec.ParsePubKey(a.NodeID2.Val[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
btcKeyMissingErrString := "bitcoin key %d missing for announcement " +
|
|
"of a P2WSH channel"
|
|
|
|
btcKey1Bytes, err := a.BitcoinKey1.UnwrapOrErr(
|
|
fmt.Errorf(btcKeyMissingErrString, 1),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
btcKey1, err := btcec.ParsePubKey(btcKey1Bytes.Val[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
btcKey2Bytes, err := a.BitcoinKey2.UnwrapOrErr(
|
|
fmt.Errorf(btcKeyMissingErrString, 2),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
btcKey2, err := btcec.ParsePubKey(btcKey2Bytes.Val[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []*btcec.PublicKey{
|
|
nodeKey1, nodeKey2, btcKey1, btcKey2,
|
|
}, nil
|
|
}
|
|
|
|
// chanAnn2P2TRMuSig2Keys returns the set of keys that should be used to
|
|
// construct the aggregate key that the signature in an
|
|
// lnwire.ChannelAnnouncement2 message should be verified against in the case
|
|
// where the channel being announced is a P2TR channel.
|
|
func chanAnn2P2TRMuSig2Keys(a *lnwire.ChannelAnnouncement2,
|
|
scriptAddr btcutil.Address) ([]*btcec.PublicKey, error) {
|
|
|
|
nodeKey1, err := btcec.ParsePubKey(a.NodeID1.Val[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodeKey2, err := btcec.ParsePubKey(a.NodeID2.Val[:])
|
|
if err != nil {
|
|
return nil, 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 nil, err
|
|
}
|
|
|
|
bitcoinKey2, err := btcec.ParsePubKey(btcKey2.Val[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keys = append(keys, bitcoinKey1, bitcoinKey2)
|
|
} else {
|
|
// If bitcoin keys are not provided, then the on-chain output
|
|
// key is considered the 3rd key in the 3-of-3 MuSig2 signature.
|
|
outputKey, err := schnorr.ParsePubKey(
|
|
scriptAddr.ScriptAddress(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keys = append(keys, outputKey)
|
|
}
|
|
|
|
return keys, nil
|
|
}
|
|
|
|
// ChanAnn2DigestToSign computes the digest of the message to be signed.
|
|
func ChanAnn2DigestToSign(a *lnwire.ChannelAnnouncement2) (*chainhash.Hash,
|
|
error) {
|
|
|
|
data, err := lnwire.SerialiseFieldsToSign(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return MsgHash(chanAnn2MsgName, chanAnn2SigFieldName, data), nil
|
|
}
|