Skip to content

Commit 1b5c49c

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 6de9b86 commit 1b5c49c

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
@@ -12,7 +12,9 @@ import (
1212
"github.com/btcsuite/btcd/btcec/v2"
1313
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
1414
"github.com/btcsuite/btcd/btcutil"
15+
"github.com/btcsuite/btcd/chaincfg"
1516
"github.com/btcsuite/btcd/chaincfg/chainhash"
17+
"github.com/btcsuite/btcd/txscript"
1618
"github.com/btcsuite/btcd/wire"
1719
"github.com/davecgh/go-spew/spew"
1820
"github.com/lightninglabs/neutrino/cache"
@@ -192,14 +194,9 @@ type PinnedSyncers map[route.Vertex]struct{}
192194
// Config defines the configuration for the service. ALL elements within the
193195
// configuration MUST be non-nil for the service to carry out its duties.
194196
type Config struct {
195-
// ChainHash is a hash that indicates which resident chain of the
196-
// AuthenticatedGossiper. Any announcements that don't match this
197-
// chain hash will be ignored.
198-
//
199-
// TODO(roasbeef): eventually make into map so can de-multiplex
200-
// incoming announcements
201-
// * also need to do same for Notifier
202-
ChainHash chainhash.Hash
197+
// ChainParams holds the chain parameters for the active network this
198+
// node is participating on.
199+
ChainParams *chaincfg.Params
203200

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

576573
gossiper.syncMgr = newSyncManager(&SyncManagerCfg{
577-
ChainHash: cfg.ChainHash,
574+
ChainHash: *cfg.ChainParams.GenesisHash,
578575
ChanSeries: cfg.ChanSeries,
579576
RotateTicker: cfg.RotateTicker,
580577
HistoricalSyncTicker: cfg.HistoricalSyncTicker,
@@ -1993,9 +1990,28 @@ func (d *AuthenticatedGossiper) processRejectedEdge(
19931990

19941991
// fetchPKScript fetches the output script for the given SCID.
19951992
func (d *AuthenticatedGossiper) fetchPKScript(chanID lnwire.ShortChannelID) (
1996-
[]byte, error) {
1993+
txscript.ScriptClass, btcutil.Address, error) {
19971994

1998-
return lnwallet.FetchPKScriptWithQuit(d.cfg.ChainIO, chanID, d.quit)
1995+
pkScript, err := lnwallet.FetchPKScriptWithQuit(
1996+
d.cfg.ChainIO, chanID, d.quit,
1997+
)
1998+
if err != nil {
1999+
return txscript.WitnessUnknownTy, nil, err
2000+
}
2001+
2002+
scriptClass, addrs, _, err := txscript.ExtractPkScriptAddrs(
2003+
pkScript, d.cfg.ChainParams,
2004+
)
2005+
if err != nil {
2006+
return txscript.WitnessUnknownTy, nil, err
2007+
}
2008+
2009+
if len(addrs) != 1 {
2010+
return txscript.WitnessUnknownTy, nil, fmt.Errorf("expected "+
2011+
"1 address, got: %d", len(addrs))
2012+
}
2013+
2014+
return scriptClass, addrs[0], nil
19992015
}
20002016

20012017
// addNode processes the given node announcement, and adds it to our channel
@@ -2482,16 +2498,16 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
24822498
ops ...batch.SchedulerOption) ([]networkMsg, bool) {
24832499

24842500
scid := ann.ShortChannelID
2501+
chainHash := d.cfg.ChainParams.GenesisHash
24852502

24862503
log.Debugf("Processing ChannelAnnouncement1: peer=%v, short_chan_id=%v",
24872504
nMsg.peer, scid.ToUint64())
24882505

24892506
// We'll ignore any channel announcements that target any chain other
24902507
// than the set of chains we know of.
2491-
if !bytes.Equal(ann.ChainHash[:], d.cfg.ChainHash[:]) {
2508+
if !bytes.Equal(ann.ChainHash[:], chainHash[:]) {
24922509
err := fmt.Errorf("ignoring ChannelAnnouncement1 from chain=%v"+
2493-
", gossiper on chain=%v", ann.ChainHash,
2494-
d.cfg.ChainHash)
2510+
", gossiper on chain=%v", ann.ChainHash, chainHash)
24952511
log.Errorf(err.Error())
24962512

24972513
key := newRejectCacheKey(
@@ -2942,11 +2958,13 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
29422958
log.Debugf("Processing ChannelUpdate: peer=%v, short_chan_id=%v, ",
29432959
nMsg.peer, upd.ShortChannelID.ToUint64())
29442960

2961+
chainHash := d.cfg.ChainParams.GenesisHash
2962+
29452963
// We'll ignore any channel updates that target any chain other than
29462964
// the set of chains we know of.
2947-
if !bytes.Equal(upd.ChainHash[:], d.cfg.ChainHash[:]) {
2965+
if !bytes.Equal(upd.ChainHash[:], chainHash[:]) {
29482966
err := fmt.Errorf("ignoring ChannelUpdate from chain=%v, "+
2949-
"gossiper on chain=%v", upd.ChainHash, d.cfg.ChainHash)
2967+
"gossiper on chain=%v", upd.ChainHash, chainHash)
29502968
log.Errorf(err.Error())
29512969

29522970
key := newRejectCacheKey(

discovery/gossiper_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/btcsuite/btcd/btcec/v2"
1717
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
1818
"github.com/btcsuite/btcd/btcutil"
19+
"github.com/btcsuite/btcd/chaincfg"
1920
"github.com/btcsuite/btcd/chaincfg/chainhash"
2021
"github.com/btcsuite/btcd/wire"
2122
"github.com/davecgh/go-spew/spew"
@@ -616,6 +617,7 @@ func createUpdateAnnouncement(blockHeight uint32,
616617

617618
htlcMinMsat := lnwire.MilliSatoshi(100)
618619
a := &lnwire.ChannelUpdate1{
620+
ChainHash: *chaincfg.MainNetParams.GenesisHash,
619621
ShortChannelID: lnwire.ShortChannelID{
620622
BlockHeight: blockHeight,
621623
},
@@ -768,6 +770,7 @@ func (ctx *testCtx) createAnnouncementWithoutProof(blockHeight uint32,
768770
}
769771

770772
a := &lnwire.ChannelAnnouncement1{
773+
ChainHash: *chaincfg.MainNetParams.GenesisHash,
771774
ShortChannelID: lnwire.ShortChannelID{
772775
BlockHeight: blockHeight,
773776
TxIndex: 0,
@@ -934,8 +937,9 @@ func createTestCtx(t *testing.T, startHeight uint32, isChanPeer bool) (
934937
}
935938

936939
gossiper := New(Config{
937-
ChainIO: chain,
938-
Notifier: notifier,
940+
ChainIO: chain,
941+
ChainParams: &chaincfg.MainNetParams,
942+
Notifier: notifier,
939943
Broadcast: func(senders map[route.Vertex]struct{},
940944
msgs ...lnwire.Message) error {
941945

@@ -1653,6 +1657,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) {
16531657

16541658
//nolint:ll
16551659
gossiper := New(Config{
1660+
ChainParams: &chaincfg.MainNetParams,
16561661
Notifier: ctx.gossiper.cfg.Notifier,
16571662
Broadcast: ctx.gossiper.cfg.Broadcast,
16581663
NotifyWhenOnline: ctx.gossiper.reliableSender.cfg.NotifyWhenOnline,

netann/channel_announcement.go

Lines changed: 115 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"github.com/btcsuite/btcd/btcec/v2"
99
"github.com/btcsuite/btcd/btcec/v2/schnorr"
1010
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
11+
"github.com/btcsuite/btcd/btcutil"
1112
"github.com/btcsuite/btcd/chaincfg/chainhash"
13+
"github.com/btcsuite/btcd/txscript"
1214
"github.com/lightningnetwork/lnd/graph/db/models"
1315
"github.com/lightningnetwork/lnd/lnwire"
1416
"github.com/lightningnetwork/lnd/tlv"
@@ -108,7 +110,8 @@ func CreateChanAnnouncement(chanProof *models.ChannelAuthProof,
108110

109111
// FetchPkScript defines a function that can be used to fetch the output script
110112
// for the transaction with the given SCID.
111-
type FetchPkScript func(lnwire.ShortChannelID) ([]byte, error)
113+
type FetchPkScript func(lnwire.ShortChannelID) (txscript.ScriptClass,
114+
btcutil.Address, error)
112115

113116
// ValidateChannelAnn validates the channel announcement.
114117
func ValidateChannelAnn(a lnwire.ChannelAnnouncement,
@@ -202,24 +205,124 @@ func validateChannelAnn1(a *lnwire.ChannelAnnouncement1) error {
202205
func validateChannelAnn2(a *lnwire.ChannelAnnouncement2,
203206
fetchPkScript FetchPkScript) error {
204207

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

247+
// Obtain the signature.
210248
sig, err := a.Signature.Val.ToSignature()
211249
if err != nil {
212250
return err
213251
}
214252

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

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

225328
keys := []*btcec.PublicKey{
@@ -240,42 +343,29 @@ func validateChannelAnn2(a *lnwire.ChannelAnnouncement2,
240343

241344
bitcoinKey1, err := btcec.ParsePubKey(btcKey1.Val[:])
242345
if err != nil {
243-
return err
346+
return nil, err
244347
}
245348

246349
bitcoinKey2, err := btcec.ParsePubKey(btcKey2.Val[:])
247350
if err != nil {
248-
return err
351+
return nil, err
249352
}
250353

251354
keys = append(keys, bitcoinKey1, bitcoinKey2)
252355
} else {
253-
// If bitcoin keys are not provided, then we need to get the
254-
// on-chain output key since this will be the 3rd key in the
255-
// 3-of-3 MuSig2 signature.
256-
pkScript, err := fetchPkScript(a.ShortChannelID.Val)
257-
if err != nil {
258-
return err
259-
}
260-
261-
outputKey, err := schnorr.ParsePubKey(pkScript[2:])
356+
// If bitcoin keys are not provided, then the on-chain output
357+
// key is considered the 3rd key in the 3-of-3 MuSig2 signature.
358+
outputKey, err := schnorr.ParsePubKey(
359+
scriptAddr.ScriptAddress(),
360+
)
262361
if err != nil {
263-
return err
362+
return nil, err
264363
}
265364

266365
keys = append(keys, outputKey)
267366
}
268367

269-
aggKey, _, _, err := musig2.AggregateKeys(keys, true)
270-
if err != nil {
271-
return err
272-
}
273-
274-
if !sig.Verify(dataHash.CloneBytes(), aggKey.FinalKey) {
275-
return fmt.Errorf("invalid sig")
276-
}
277-
278-
return nil
368+
return keys, nil
279369
}
280370

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

0 commit comments

Comments
 (0)