Skip to content

Commit 6f0ce3b

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 22293d5 commit 6f0ce3b

File tree

5 files changed

+310
-56
lines changed

5 files changed

+310
-56
lines changed

discovery/gossiper.go

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111
"github.com/btcsuite/btcd/btcec/v2"
1212
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
1313
"github.com/btcsuite/btcd/btcutil"
14+
"github.com/btcsuite/btcd/chaincfg"
1415
"github.com/btcsuite/btcd/chaincfg/chainhash"
16+
"github.com/btcsuite/btcd/txscript"
1517
"github.com/btcsuite/btcd/wire"
1618
"github.com/davecgh/go-spew/spew"
1719
"github.com/lightninglabs/neutrino/cache"
@@ -166,14 +168,9 @@ type PinnedSyncers map[route.Vertex]struct{}
166168
// Config defines the configuration for the service. ALL elements within the
167169
// configuration MUST be non-nil for the service to carry out its duties.
168170
type Config struct {
169-
// ChainHash is a hash that indicates which resident chain of the
170-
// AuthenticatedGossiper. Any announcements that don't match this
171-
// chain hash will be ignored.
172-
//
173-
// TODO(roasbeef): eventually make into map so can de-multiplex
174-
// incoming announcements
175-
// * also need to do same for Notifier
176-
ChainHash chainhash.Hash
171+
// ChainParams holds the chain parameters for the active network this
172+
// node is participating on.
173+
ChainParams *chaincfg.Params
177174

178175
// Graph is the subsystem which is responsible for managing the
179176
// topology of lightning network. After incoming channel, node, channel
@@ -359,6 +356,12 @@ type Config struct {
359356
// updates for a channel and returns true if the channel should be
360357
// considered a zombie based on these timestamps.
361358
IsStillZombieChannel func(time.Time, time.Time) bool
359+
360+
// chainHash is a hash that indicates which resident chain of the
361+
// AuthenticatedGossiper. Any announcements that don't match this
362+
// chain hash will be ignored. This is an internal config value obtained
363+
// from ChainParams.
364+
chainHash *chainhash.Hash
362365
}
363366

364367
// processedNetworkMsg is a wrapper around networkMsg and a boolean. It is
@@ -518,6 +521,8 @@ type AuthenticatedGossiper struct {
518521
// New creates a new AuthenticatedGossiper instance, initialized with the
519522
// passed configuration parameters.
520523
func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper {
524+
cfg.chainHash = cfg.ChainParams.GenesisHash
525+
521526
gossiper := &AuthenticatedGossiper{
522527
selfKey: selfKeyDesc.PubKey,
523528
selfKeyLoc: selfKeyDesc.KeyLocator,
@@ -538,7 +543,7 @@ func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper
538543
}
539544

540545
gossiper.syncMgr = newSyncManager(&SyncManagerCfg{
541-
ChainHash: cfg.ChainHash,
546+
ChainHash: *cfg.chainHash,
542547
ChanSeries: cfg.ChanSeries,
543548
RotateTicker: cfg.RotateTicker,
544549
HistoricalSyncTicker: cfg.HistoricalSyncTicker,
@@ -1946,9 +1951,28 @@ func (d *AuthenticatedGossiper) processRejectedEdge(
19461951

19471952
// fetchPKScript fetches the output script for the given SCID.
19481953
func (d *AuthenticatedGossiper) fetchPKScript(chanID *lnwire.ShortChannelID) (
1949-
[]byte, error) {
1954+
txscript.ScriptClass, btcutil.Address, error) {
1955+
1956+
pkScript, err := lnwallet.FetchPKScriptWithQuit(
1957+
d.cfg.ChainIO, chanID, d.quit,
1958+
)
1959+
if err != nil {
1960+
return txscript.WitnessUnknownTy, nil, err
1961+
}
1962+
1963+
scriptClass, addrs, _, err := txscript.ExtractPkScriptAddrs(
1964+
pkScript, d.cfg.ChainParams,
1965+
)
1966+
if err != nil {
1967+
return txscript.WitnessUnknownTy, nil, err
1968+
}
1969+
1970+
if len(addrs) != 1 {
1971+
return txscript.WitnessUnknownTy, nil, fmt.Errorf("expected "+
1972+
"1 address, got: %d", len(addrs))
1973+
}
19501974

1951-
return lnwallet.FetchPKScriptWithQuit(d.cfg.ChainIO, chanID, d.quit)
1975+
return scriptClass, addrs[0], nil
19521976
}
19531977

19541978
// addNode processes the given node announcement, and adds it to our channel
@@ -2448,10 +2472,10 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
24482472

24492473
// We'll ignore any channel announcements that target any chain other
24502474
// than the set of chains we know of.
2451-
if !bytes.Equal(ann.ChainHash[:], d.cfg.ChainHash[:]) {
2475+
if !bytes.Equal(ann.ChainHash[:], d.cfg.chainHash[:]) {
24522476
err := fmt.Errorf("ignoring ChannelAnnouncement1 from chain=%v"+
24532477
", gossiper on chain=%v", ann.ChainHash,
2454-
d.cfg.ChainHash)
2478+
d.cfg.chainHash)
24552479
log.Errorf(err.Error())
24562480

24572481
key := newRejectCacheKey(
@@ -2837,9 +2861,9 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
28372861

28382862
// We'll ignore any channel updates that target any chain other than
28392863
// the set of chains we know of.
2840-
if !bytes.Equal(upd.ChainHash[:], d.cfg.ChainHash[:]) {
2864+
if !bytes.Equal(upd.ChainHash[:], d.cfg.chainHash[:]) {
28412865
err := fmt.Errorf("ignoring ChannelUpdate from chain=%v, "+
2842-
"gossiper on chain=%v", upd.ChainHash, d.cfg.ChainHash)
2866+
"gossiper on chain=%v", upd.ChainHash, d.cfg.chainHash)
28432867
log.Errorf(err.Error())
28442868

28452869
key := newRejectCacheKey(

discovery/gossiper_test.go

Lines changed: 4 additions & 1 deletion
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"
@@ -766,7 +767,8 @@ func createTestCtx(t *testing.T, startHeight uint32, isChanPeer bool) (
766767
}
767768

768769
gossiper := New(Config{
769-
Notifier: notifier,
770+
ChainParams: &chaincfg.MainNetParams,
771+
Notifier: notifier,
770772
Broadcast: func(senders map[route.Vertex]struct{},
771773
msgs ...lnwire.Message) error {
772774

@@ -1480,6 +1482,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) {
14801482

14811483
//nolint:lll
14821484
gossiper := New(Config{
1485+
ChainParams: &chaincfg.MainNetParams,
14831486
Notifier: ctx.gossiper.cfg.Notifier,
14841487
Broadcast: ctx.gossiper.cfg.Broadcast,
14851488
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/channeldb/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)