Skip to content

Commit d68d1fb

Browse files
committed
netann: update ChanAnn2 validation to work for P2WSH channels
This commit expands the ChannelAnnouncement2 validation for the case where it is announcing a P2WSH channel.
1 parent 06bf0c2 commit d68d1fb

File tree

5 files changed

+308
-58
lines changed

5 files changed

+308
-58
lines changed

discovery/gossiper.go

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import (
1313
"github.com/btcsuite/btcd/btcec/v2"
1414
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
1515
"github.com/btcsuite/btcd/btcutil"
16+
"github.com/btcsuite/btcd/chaincfg"
1617
"github.com/btcsuite/btcd/chaincfg/chainhash"
18+
"github.com/btcsuite/btcd/txscript"
1719
"github.com/btcsuite/btcd/wire"
1820
"github.com/davecgh/go-spew/spew"
1921
"github.com/lightninglabs/neutrino/cache"
@@ -193,14 +195,9 @@ type PinnedSyncers map[route.Vertex]struct{}
193195
// Config defines the configuration for the service. ALL elements within the
194196
// configuration MUST be non-nil for the service to carry out its duties.
195197
type Config struct {
196-
// ChainHash is a hash that indicates which resident chain of the
197-
// AuthenticatedGossiper. Any announcements that don't match this
198-
// chain hash will be ignored.
199-
//
200-
// TODO(roasbeef): eventually make into map so can de-multiplex
201-
// incoming announcements
202-
// * also need to do same for Notifier
203-
ChainHash chainhash.Hash
198+
// ChainParams holds the chain parameters for the active network this
199+
// node is participating on.
200+
ChainParams *chaincfg.Params
204201

205202
// Graph is the subsystem which is responsible for managing the
206203
// topology of lightning network. After incoming channel, node, channel
@@ -596,7 +593,7 @@ func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper
596593
gossiper.vb = NewValidationBarrier(1000, gossiper.quit)
597594

598595
gossiper.syncMgr = newSyncManager(&SyncManagerCfg{
599-
ChainHash: cfg.ChainHash,
596+
ChainHash: *cfg.ChainParams.GenesisHash,
600597
ChanSeries: cfg.ChanSeries,
601598
RotateTicker: cfg.RotateTicker,
602599
HistoricalSyncTicker: cfg.HistoricalSyncTicker,
@@ -2034,9 +2031,28 @@ func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context,
20342031

20352032
// fetchPKScript fetches the output script for the given SCID.
20362033
func (d *AuthenticatedGossiper) fetchPKScript(chanID lnwire.ShortChannelID) (
2037-
[]byte, error) {
2034+
txscript.ScriptClass, btcutil.Address, error) {
20382035

2039-
return lnwallet.FetchPKScriptWithQuit(d.cfg.ChainIO, chanID, d.quit)
2036+
pkScript, err := lnwallet.FetchPKScriptWithQuit(
2037+
d.cfg.ChainIO, chanID, d.quit,
2038+
)
2039+
if err != nil {
2040+
return txscript.WitnessUnknownTy, nil, err
2041+
}
2042+
2043+
scriptClass, addrs, _, err := txscript.ExtractPkScriptAddrs(
2044+
pkScript, d.cfg.ChainParams,
2045+
)
2046+
if err != nil {
2047+
return txscript.WitnessUnknownTy, nil, err
2048+
}
2049+
2050+
if len(addrs) != 1 {
2051+
return txscript.WitnessUnknownTy, nil, fmt.Errorf("expected "+
2052+
"1 address, got: %d", len(addrs))
2053+
}
2054+
2055+
return scriptClass, addrs[0], nil
20402056
}
20412057

20422058
// addNode processes the given node announcement, and adds it to our channel
@@ -2530,16 +2546,16 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context,
25302546
ops ...batch.SchedulerOption) ([]networkMsg, bool) {
25312547

25322548
scid := ann.ShortChannelID
2549+
chainHash := d.cfg.ChainParams.GenesisHash
25332550

25342551
log.Debugf("Processing ChannelAnnouncement1: peer=%v, short_chan_id=%v",
25352552
nMsg.peer, scid.ToUint64())
25362553

25372554
// We'll ignore any channel announcements that target any chain other
25382555
// than the set of chains we know of.
2539-
if !bytes.Equal(ann.ChainHash[:], d.cfg.ChainHash[:]) {
2556+
if !bytes.Equal(ann.ChainHash[:], chainHash[:]) {
25402557
err := fmt.Errorf("ignoring ChannelAnnouncement1 from chain=%v"+
2541-
", gossiper on chain=%v", ann.ChainHash,
2542-
d.cfg.ChainHash)
2558+
", gossiper on chain=%v", ann.ChainHash, chainHash)
25432559
log.Errorf(err.Error())
25442560

25452561
key := newRejectCacheKey(
@@ -2960,11 +2976,13 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context,
29602976
log.Debugf("Processing ChannelUpdate: peer=%v, short_chan_id=%v, ",
29612977
nMsg.peer, upd.ShortChannelID.ToUint64())
29622978

2979+
chainHash := d.cfg.ChainParams.GenesisHash
2980+
29632981
// We'll ignore any channel updates that target any chain other than
29642982
// the set of chains we know of.
2965-
if !bytes.Equal(upd.ChainHash[:], d.cfg.ChainHash[:]) {
2983+
if !bytes.Equal(upd.ChainHash[:], chainHash[:]) {
29662984
err := fmt.Errorf("ignoring ChannelUpdate from chain=%v, "+
2967-
"gossiper on chain=%v", upd.ChainHash, d.cfg.ChainHash)
2985+
"gossiper on chain=%v", upd.ChainHash, chainHash)
29682986
log.Errorf(err.Error())
29692987

29702988
key := newRejectCacheKey(

discovery/gossiper_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/btcsuite/btcd/btcec/v2"
1919
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
2020
"github.com/btcsuite/btcd/btcutil"
21+
"github.com/btcsuite/btcd/chaincfg"
2122
"github.com/btcsuite/btcd/chaincfg/chainhash"
2223
"github.com/btcsuite/btcd/wire"
2324
"github.com/davecgh/go-spew/spew"
@@ -620,6 +621,7 @@ func createUpdateAnnouncement(blockHeight uint32,
620621

621622
htlcMinMsat := lnwire.MilliSatoshi(100)
622623
a := &lnwire.ChannelUpdate1{
624+
ChainHash: *chaincfg.MainNetParams.GenesisHash,
623625
ShortChannelID: lnwire.ShortChannelID{
624626
BlockHeight: blockHeight,
625627
},
@@ -772,6 +774,7 @@ func (ctx *testCtx) createAnnouncementWithoutProof(blockHeight uint32,
772774
}
773775

774776
a := &lnwire.ChannelAnnouncement1{
777+
ChainHash: *chaincfg.MainNetParams.GenesisHash,
775778
ShortChannelID: lnwire.ShortChannelID{
776779
BlockHeight: blockHeight,
777780
TxIndex: 0,
@@ -938,8 +941,9 @@ func createTestCtx(t *testing.T, startHeight uint32, isChanPeer bool) (
938941
}
939942

940943
gossiper := New(Config{
941-
ChainIO: chain,
942-
Notifier: notifier,
944+
ChainIO: chain,
945+
ChainParams: &chaincfg.MainNetParams,
946+
Notifier: notifier,
943947
Broadcast: func(senders map[route.Vertex]struct{},
944948
msgs ...lnwire.Message) error {
945949

@@ -1669,6 +1673,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) {
16691673

16701674
//nolint:ll
16711675
gossiper := New(Config{
1676+
ChainParams: &chaincfg.MainNetParams,
16721677
Notifier: tCtx.gossiper.cfg.Notifier,
16731678
Broadcast: tCtx.gossiper.cfg.Broadcast,
16741679
NotifyWhenOnline: tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline,

netann/channel_announcement.go

Lines changed: 115 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"github.com/btcsuite/btcd/btcec/v2"
88
"github.com/btcsuite/btcd/btcec/v2/schnorr"
99
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
10+
"github.com/btcsuite/btcd/btcutil"
1011
"github.com/btcsuite/btcd/chaincfg/chainhash"
12+
"github.com/btcsuite/btcd/txscript"
1113
"github.com/lightningnetwork/lnd/graph/db/models"
1214
"github.com/lightningnetwork/lnd/lnwire"
1315
"github.com/lightningnetwork/lnd/tlv"
@@ -104,7 +106,8 @@ func CreateChanAnnouncement(chanProof *models.ChannelAuthProof,
104106

105107
// FetchPkScript defines a function that can be used to fetch the output script
106108
// for the transaction with the given SCID.
107-
type FetchPkScript func(lnwire.ShortChannelID) ([]byte, error)
109+
type FetchPkScript func(lnwire.ShortChannelID) (txscript.ScriptClass,
110+
btcutil.Address, error)
108111

109112
// ValidateChannelAnn validates the channel announcement.
110113
func ValidateChannelAnn(a lnwire.ChannelAnnouncement,
@@ -198,24 +201,124 @@ func validateChannelAnn1(a *lnwire.ChannelAnnouncement1) error {
198201
func validateChannelAnn2(a *lnwire.ChannelAnnouncement2,
199202
fetchPkScript FetchPkScript) error {
200203

204+
// Next, we fetch the funding transaction's PK script. We need this so
205+
// that we know what type of channel we will be validating: P2WSH or
206+
// P2TR.
207+
scriptClass, scriptAddr, err := fetchPkScript(a.ShortChannelID.Val)
208+
if err != nil {
209+
return err
210+
}
211+
212+
var keys []*btcec.PublicKey
213+
214+
switch scriptClass {
215+
case txscript.WitnessV0ScriptHashTy:
216+
keys, err = chanAnn2P2WSHMuSig2Keys(a)
217+
if err != nil {
218+
return err
219+
}
220+
case txscript.WitnessV1TaprootTy:
221+
keys, err = chanAnn2P2TRMuSig2Keys(a, scriptAddr)
222+
if err != nil {
223+
return err
224+
}
225+
default:
226+
return fmt.Errorf("invalid on-chain pk script type for "+
227+
"channel_announcement_2: %s", scriptClass)
228+
}
229+
230+
// Do a MuSig2 aggregation of the keys to obtain the aggregate key that
231+
// the signature will be validated against.
232+
aggKey, _, _, err := musig2.AggregateKeys(keys, true)
233+
if err != nil {
234+
return err
235+
}
236+
237+
// Get the message that the signature should have signed.
201238
dataHash, err := ChanAnn2DigestToSign(a)
202239
if err != nil {
203240
return err
204241
}
205242

243+
// Obtain the signature.
206244
sig, err := a.Signature.Val.ToSignature()
207245
if err != nil {
208246
return err
209247
}
210248

249+
// Check that the signature is valid for the aggregate key given the
250+
// message digest.
251+
if !sig.Verify(dataHash.CloneBytes(), aggKey.FinalKey) {
252+
return fmt.Errorf("invalid sig")
253+
}
254+
255+
return nil
256+
}
257+
258+
// chanAnn2P2WSHMuSig2Keys returns the set of keys that should be used to
259+
// construct the aggregate key that the signature in an
260+
// lnwire.ChannelAnnouncement2 message should be verified against in the case
261+
// where the channel being announced is a P2WSH channel.
262+
func chanAnn2P2WSHMuSig2Keys(a *lnwire.ChannelAnnouncement2) (
263+
[]*btcec.PublicKey, error) {
264+
211265
nodeKey1, err := btcec.ParsePubKey(a.NodeID1.Val[:])
212266
if err != nil {
213-
return err
267+
return nil, err
214268
}
215269

216270
nodeKey2, err := btcec.ParsePubKey(a.NodeID2.Val[:])
217271
if err != nil {
218-
return err
272+
return nil, err
273+
}
274+
275+
btcKeyMissingErrString := "bitcoin key %d missing for announcement " +
276+
"of a P2WSH channel"
277+
278+
btcKey1Bytes, err := a.BitcoinKey1.UnwrapOrErr(
279+
fmt.Errorf(btcKeyMissingErrString, 1),
280+
)
281+
if err != nil {
282+
return nil, err
283+
}
284+
285+
btcKey1, err := btcec.ParsePubKey(btcKey1Bytes.Val[:])
286+
if err != nil {
287+
return nil, err
288+
}
289+
290+
btcKey2Bytes, err := a.BitcoinKey2.UnwrapOrErr(
291+
fmt.Errorf(btcKeyMissingErrString, 2),
292+
)
293+
if err != nil {
294+
return nil, err
295+
}
296+
297+
btcKey2, err := btcec.ParsePubKey(btcKey2Bytes.Val[:])
298+
if err != nil {
299+
return nil, err
300+
}
301+
302+
return []*btcec.PublicKey{
303+
nodeKey1, nodeKey2, btcKey1, btcKey2,
304+
}, nil
305+
}
306+
307+
// chanAnn2P2TRMuSig2Keys returns the set of keys that should be used to
308+
// construct the aggregate key that the signature in an
309+
// lnwire.ChannelAnnouncement2 message should be verified against in the case
310+
// where the channel being announced is a P2TR channel.
311+
func chanAnn2P2TRMuSig2Keys(a *lnwire.ChannelAnnouncement2,
312+
scriptAddr btcutil.Address) ([]*btcec.PublicKey, error) {
313+
314+
nodeKey1, err := btcec.ParsePubKey(a.NodeID1.Val[:])
315+
if err != nil {
316+
return nil, err
317+
}
318+
319+
nodeKey2, err := btcec.ParsePubKey(a.NodeID2.Val[:])
320+
if err != nil {
321+
return nil, err
219322
}
220323

221324
keys := []*btcec.PublicKey{
@@ -236,42 +339,29 @@ func validateChannelAnn2(a *lnwire.ChannelAnnouncement2,
236339

237340
bitcoinKey1, err := btcec.ParsePubKey(btcKey1.Val[:])
238341
if err != nil {
239-
return err
342+
return nil, err
240343
}
241344

242345
bitcoinKey2, err := btcec.ParsePubKey(btcKey2.Val[:])
243346
if err != nil {
244-
return err
347+
return nil, err
245348
}
246349

247350
keys = append(keys, bitcoinKey1, bitcoinKey2)
248351
} else {
249-
// If bitcoin keys are not provided, then we need to get the
250-
// on-chain output key since this will be the 3rd key in the
251-
// 3-of-3 MuSig2 signature.
252-
pkScript, err := fetchPkScript(a.ShortChannelID.Val)
253-
if err != nil {
254-
return err
255-
}
256-
257-
outputKey, err := schnorr.ParsePubKey(pkScript[2:])
352+
// If bitcoin keys are not provided, then the on-chain output
353+
// key is considered the 3rd key in the 3-of-3 MuSig2 signature.
354+
outputKey, err := schnorr.ParsePubKey(
355+
scriptAddr.ScriptAddress(),
356+
)
258357
if err != nil {
259-
return err
358+
return nil, err
260359
}
261360

262361
keys = append(keys, outputKey)
263362
}
264363

265-
aggKey, _, _, err := musig2.AggregateKeys(keys, true)
266-
if err != nil {
267-
return err
268-
}
269-
270-
if !sig.Verify(dataHash.CloneBytes(), aggKey.FinalKey) {
271-
return fmt.Errorf("invalid sig")
272-
}
273-
274-
return nil
364+
return keys, nil
275365
}
276366

277367
// ChanAnn2DigestToSign computes the digest of the message to be signed.

0 commit comments

Comments
 (0)