netann: update ChanAnn2 validation to work for P2WSH channels
What changed, and why it matters
This commit updates how LND validates a new style of Lightning channel announcement (ChannelAnnouncement2) so it correctly handles both older P2WSH-style channels and newer P2TR (Taproot) channels. Before this change, the validation logic appears to have assumed P2TR channels, which could have caused LND to reject valid announcements for older P2WSH channels or, in the worst case, accept invalid ones. The change also switches the gossiper from using a raw chain hash to carrying full chain parameters, which is needed to interpret on-chain scripts correctly.
Review the new P2WSH validation branch for correct MuSig2 key ordering and sorted-key semantics, ensure `fetchPKScript` cannot be induced to return a misleading script class via a reorg or stale UTXO lookup, and confirm that all callers of the changed `Config.ChainHash` field have been migrated to `ChainParams`.
Security signals we found
Validation bypass or denial-of-service risk if ChannelAnnouncement2 for P2WSH channels was incorrectly rejected or accepted
Change in cryptographic key aggregation path based on on-chain script type
Introduction of chain parameter dependency for script parsing in gossip validation
Potential for malformed or non-standard scripts to trigger new error paths in gossip processing
Evidence from the diff
The patch refactors ValidateChannelAnn for lnwire.ChannelAnnouncement2. It now calls fetchPkScript to determine whether the funding output is WitnessV0ScriptHashTy (P2WSH) or WitnessV1TaprootTy (P2TR) and validates the MuSig2 aggregate signature against the appropriate key set. For P2WSH, it requires both bitcoin keys and builds a 4-of-4 aggregate of node1, node2, bitcoin1, bitcoin2. For P2TR, it keeps the existing 4-of-4 or 3-of-3 logic using the on-chain output key. The gossiper’s Config is changed from ChainHash chainhash.Hash to ChainParams *chaincfg.Params, and fetchPKScript now returns a txscript.ScriptClass and btcutil.Address instead of raw bytes. Tests are added/updated for both P2WSH and P2TR cases.
Changed components
discovery/gossiper.gonetann/channel_announcement.goserver.godiscovery/gossiper_test.gonetann/channel_announcement_test.goInspect captured patch +308 / −58
diff --git a/discovery/gossiper.go b/discovery/gossiper.go
index 894fccf..22f493e 100644
--- a/discovery/gossiper.go
+++ b/discovery/gossiper.go
@@ -13,7 +13,9 @@ 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/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightninglabs/neutrino/cache"
@@ -193,14 +195,9 @@ type PinnedSyncers map[route.Vertex]struct{}
// Config defines the configuration for the service. ALL elements within the
// configuration MUST be non-nil for the service to carry out its duties.
type Config struct {
- // ChainHash is a hash that indicates which resident chain of the
- // AuthenticatedGossiper. Any announcements that don't match this
- // chain hash will be ignored.
- //
- // TODO(roasbeef): eventually make into map so can de-multiplex
- // incoming announcements
- // * also need to do same for Notifier
- ChainHash chainhash.Hash
+ // ChainParams holds the chain parameters for the active network this
+ // node is participating on.
+ ChainParams *chaincfg.Params
// Graph is the subsystem which is responsible for managing the
// topology of lightning network. After incoming channel, node, channel
@@ -596,7 +593,7 @@ func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper
gossiper.vb = NewValidationBarrier(1000, gossiper.quit)
gossiper.syncMgr = newSyncManager(&SyncManagerCfg{
- ChainHash: cfg.ChainHash,
+ ChainHash: *cfg.ChainParams.GenesisHash,
ChanSeries: cfg.ChanSeries,
RotateTicker: cfg.RotateTicker,
HistoricalSyncTicker: cfg.HistoricalSyncTicker,
@@ -2034,9 +2031,28 @@ func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context,
// fetchPKScript fetches the output script for the given SCID.
func (d *AuthenticatedGossiper) fetchPKScript(chanID lnwire.ShortChannelID) (
- []byte, error) {
+ txscript.ScriptClass, btcutil.Address, error) {
- return lnwallet.FetchPKScriptWithQuit(d.cfg.ChainIO, chanID, d.quit)
+ pkScript, err := lnwallet.FetchPKScriptWithQuit(
+ d.cfg.ChainIO, chanID, d.quit,
+ )
+ if err != nil {
+ return txscript.WitnessUnknownTy, nil, err
+ }
+
+ scriptClass, addrs, _, err := txscript.ExtractPkScriptAddrs(
+ pkScript, d.cfg.ChainParams,
+ )
+ if err != nil {
+ return txscript.WitnessUnknownTy, nil, err
+ }
+
+ if len(addrs) != 1 {
+ return txscript.WitnessUnknownTy, nil, fmt.Errorf("expected "+
+ "1 address, got: %d", len(addrs))
+ }
+
+ return scriptClass, addrs[0], nil
}
// addNode processes the given node announcement, and adds it to our channel
@@ -2530,16 +2546,16 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context,
ops ...batch.SchedulerOption) ([]networkMsg, bool) {
scid := ann.ShortChannelID
+ chainHash := d.cfg.ChainParams.GenesisHash
log.Debugf("Processing ChannelAnnouncement1: peer=%v, short_chan_id=%v",
nMsg.peer, scid.ToUint64())
// We'll ignore any channel announcements that target any chain other
// than the set of chains we know of.
- if !bytes.Equal(ann.ChainHash[:], d.cfg.ChainHash[:]) {
+ if !bytes.Equal(ann.ChainHash[:], chainHash[:]) {
err := fmt.Errorf("ignoring ChannelAnnouncement1 from chain=%v"+
- ", gossiper on chain=%v", ann.ChainHash,
- d.cfg.ChainHash)
+ ", gossiper on chain=%v", ann.ChainHash, chainHash)
log.Errorf(err.Error())
key := newRejectCacheKey(
@@ -2960,11 +2976,13 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context,
log.Debugf("Processing ChannelUpdate: peer=%v, short_chan_id=%v, ",
nMsg.peer, upd.ShortChannelID.ToUint64())
+ chainHash := d.cfg.ChainParams.GenesisHash
+
// We'll ignore any channel updates that target any chain other than
// the set of chains we know of.
- if !bytes.Equal(upd.ChainHash[:], d.cfg.ChainHash[:]) {
+ if !bytes.Equal(upd.ChainHash[:], chainHash[:]) {
err := fmt.Errorf("ignoring ChannelUpdate from chain=%v, "+
- "gossiper on chain=%v", upd.ChainHash, d.cfg.ChainHash)
+ "gossiper on chain=%v", upd.ChainHash, chainHash)
log.Errorf(err.Error())
key := newRejectCacheKey(
diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go
index 58c975b..5d99494 100644
--- a/discovery/gossiper_test.go
+++ b/discovery/gossiper_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/davecgh/go-spew/spew"
@@ -620,6 +621,7 @@ func createUpdateAnnouncement(blockHeight uint32,
htlcMinMsat := lnwire.MilliSatoshi(100)
a := &lnwire.ChannelUpdate1{
+ ChainHash: *chaincfg.MainNetParams.GenesisHash,
ShortChannelID: lnwire.ShortChannelID{
BlockHeight: blockHeight,
},
@@ -772,6 +774,7 @@ func (ctx *testCtx) createAnnouncementWithoutProof(blockHeight uint32,
}
a := &lnwire.ChannelAnnouncement1{
+ ChainHash: *chaincfg.MainNetParams.GenesisHash,
ShortChannelID: lnwire.ShortChannelID{
BlockHeight: blockHeight,
TxIndex: 0,
@@ -938,8 +941,9 @@ func createTestCtx(t *testing.T, startHeight uint32, isChanPeer bool) (
}
gossiper := New(Config{
- ChainIO: chain,
- Notifier: notifier,
+ ChainIO: chain,
+ ChainParams: &chaincfg.MainNetParams,
+ Notifier: notifier,
Broadcast: func(senders map[route.Vertex]struct{},
msgs ...lnwire.Message) error {
@@ -1669,6 +1673,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) {
//nolint:ll
gossiper := New(Config{
+ ChainParams: &chaincfg.MainNetParams,
Notifier: tCtx.gossiper.cfg.Notifier,
Broadcast: tCtx.gossiper.cfg.Broadcast,
NotifyWhenOnline: tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline,
diff --git a/netann/channel_announcement.go b/netann/channel_announcement.go
index d5c2005..9bb21c4 100644
--- a/netann/channel_announcement.go
+++ b/netann/channel_announcement.go
@@ -7,7 +7,9 @@ import (
"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"
@@ -104,7 +106,8 @@ func CreateChanAnnouncement(chanProof *models.ChannelAuthProof,
// 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) ([]byte, error)
+type FetchPkScript func(lnwire.ShortChannelID) (txscript.ScriptClass,
+ btcutil.Address, error)
// ValidateChannelAnn validates the channel announcement.
func ValidateChannelAnn(a lnwire.ChannelAnnouncement,
@@ -198,24 +201,124 @@ func validateChannelAnn1(a *lnwire.ChannelAnnouncement1) error {
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 err
+ return nil, err
}
nodeKey2, err := btcec.ParsePubKey(a.NodeID2.Val[:])
if err != nil {
- return err
+ 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{
@@ -236,42 +339,29 @@ func validateChannelAnn2(a *lnwire.ChannelAnnouncement2,
bitcoinKey1, err := btcec.ParsePubKey(btcKey1.Val[:])
if err != nil {
- return err
+ return nil, err
}
bitcoinKey2, err := btcec.ParsePubKey(btcKey2.Val[:])
if err != nil {
- return err
+ return nil, 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 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 err
+ return nil, 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
+ return keys, nil
}
// ChanAnn2DigestToSign computes the digest of the message to be signed.
diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go
index 390a716..38949e0 100644
--- a/netann/channel_announcement_test.go
+++ b/netann/channel_announcement_test.go
@@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
+ "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
@@ -73,20 +74,131 @@ func TestChanAnnounce2Validation(t *testing.T) {
t.Parallel()
t.Run(
- "test 4-of-4 MuSig2 channel announcement",
- test4of4MuSig2ChanAnnouncement,
+ "test 4-of-4 MuSig2 P2TR channel announcement",
+ test4of4MuSig2P2TRChanAnnouncement,
)
t.Run(
- "test 3-of-3 MuSig2 channel announcement",
+ "test 3-of-3 MuSig2 P2TR channel announcement",
test3of3MuSig2ChanAnnouncement,
)
+
+ t.Run(
+ "test 4-of-4 MuSig2 P2WSH channel announcement",
+ test4of4MuSig2P2WSHChanAnnouncement,
+ )
}
-// 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) {
+// test4of4MuSig2P2TRChanAnnouncement covers the case where the funding
+// transaction PK script is a P2WSH. In this case, the signature should be valid
+// for the MuSig2 4-of-4 aggregation of the node keys and the bitcoin keys.
+func test4of4MuSig2P2WSHChanAnnouncement(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.Val = sig
+
+ // Create an accurate representation of what the on-chain pk script will
+ // look like. For this case, it is only important that we get the
+ // correct script class.
+ multiSigScript, err := input.GenMultiSigScript(
+ node1.btcPub.SerializeCompressed(),
+ node2.btcPub.SerializeCompressed(),
+ )
+ require.NoError(t, err)
+
+ scriptHash, err := input.WitnessScriptHash(multiSigScript)
+ require.NoError(t, err)
+ pkAddr, err := btcutil.NewAddressScriptHash(
+ scriptHash, &chaincfg.MainNetParams,
+ )
+ require.NoError(t, err)
+
+ // Create a mock tx fetcher that returns the expected script class and
+ // pk address.
+ fetchTx := func(lnwire.ShortChannelID) (txscript.ScriptClass,
+ btcutil.Address, error) {
+
+ return txscript.WitnessV0ScriptHashTy, pkAddr, nil
+ }
+
+ // Validate the announcement.
+ require.NoError(t, ValidateChannelAnn(ann, fetchTx))
+}
+
+// test4of4MuSig2P2TRChanAnnouncement covers the case where both bitcoin keys
+// are present in the channel announcement 2 and the funding transaction PK
+// script is a P2TR. In this case, the signature should be a 4-of-4 MuSig2.
+func test4of4MuSig2P2TRChanAnnouncement(t *testing.T) {
t.Parallel()
// Generate the keys for node 1 and node2.
@@ -161,8 +273,30 @@ func test4of4MuSig2ChanAnnouncement(t *testing.T) {
ann.Signature.Val = sig
+ // Create an accurate representation of what the on-chain pk script will
+ // look like. For this case, it is only important that we get the
+ // correct script class.
+ combinedKey, _, _, err := musig2.AggregateKeys(
+ []*btcec.PublicKey{node1.btcPub, node2.btcPub}, true,
+ )
+ require.NoError(t, err)
+
+ pkAddr, err := btcutil.NewAddressTaproot(
+ combinedKey.FinalKey.SerializeCompressed()[1:],
+ &chaincfg.MainNetParams,
+ )
+ require.NoError(t, err)
+
+ // Create a mock tx fetcher that returns the expected script class and
+ // pk address.
+ fetchTx := func(lnwire.ShortChannelID) (txscript.ScriptClass,
+ btcutil.Address, error) {
+
+ return txscript.WitnessV1TaprootTy, pkAddr, nil
+ }
+
// Validate the announcement.
- require.NoError(t, ValidateChannelAnn(ann, nil))
+ require.NoError(t, ValidateChannelAnn(ann, fetchTx))
}
// test3of3MuSig2ChanAnnouncement covers the case where no bitcoin keys are
@@ -217,14 +351,17 @@ func test3of3MuSig2ChanAnnouncement(t *testing.T) {
})
require.NoError(t, err)
- pkScript, err := input.PayToTaprootScript(outputKey)
+ pkAddr, err := btcutil.NewAddressTaproot(
+ outputKey.SerializeCompressed()[1:], &chaincfg.MainNetParams,
+ )
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(_ lnwire.ShortChannelID) ([]byte, error) {
- return pkScript, nil
+ // Create a mock tx fetcher that returns the expected script class
+ // and pk address.
+ fetchTx := func(lnwire.ShortChannelID) (txscript.ScriptClass,
+ btcutil.Address, error) {
+
+ return txscript.WitnessV1TaprootTy, pkAddr, nil
}
pubKeys := []*btcec.PublicKey{node1.nodePub, node2.nodePub, outputKey}
diff --git a/server.go b/server.go
index 8770f7a..fb61b68 100644
--- a/server.go
+++ b/server.go
@@ -1063,7 +1063,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
Graph: s.graphBuilder,
ChainIO: s.cc.ChainIO,
Notifier: s.cc.ChainNotifier,
- ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
+ ChainParams: s.cfg.ActiveNetParams.Params,
Broadcast: s.BroadcastMessage,
ChanSeries: chanSeries,
NotifyWhenOnline: s.NotifyWhenOnline,
Why this scored 46/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.