Skip to content

Commit 612e93e

Browse files
committed
lnwallet+peer: add tapscript root awareness to musig2 sessions
With this commit, the channel is now aware of if it's a musig2 channel, that also has a tapscript root. We'll need to always pass in the tapscript root each time we: make the funding output, sign a new state, and also verify a new state.
1 parent b8cb4e6 commit 612e93e

File tree

4 files changed

+95
-29
lines changed

4 files changed

+95
-29
lines changed

lnwallet/chancloser/chancloser_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/btcsuite/btcd/txscript"
1515
"github.com/btcsuite/btcd/wire"
1616
"github.com/lightningnetwork/lnd/channeldb"
17+
"github.com/lightningnetwork/lnd/fn"
1718
"github.com/lightningnetwork/lnd/input"
1819
"github.com/lightningnetwork/lnd/keychain"
1920
"github.com/lightningnetwork/lnd/lnutils"
@@ -185,6 +186,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
185186
R: new(btcec.PublicKey),
186187
},
187188
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
189+
fn.None[chainhash.Hash](),
188190
), nil, 0, nil
189191
}
190192

lnwallet/channel.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/lightningnetwork/lnd/chainntnfs"
2727
"github.com/lightningnetwork/lnd/channeldb"
2828
"github.com/lightningnetwork/lnd/channeldb/models"
29+
"github.com/lightningnetwork/lnd/fn"
2930
"github.com/lightningnetwork/lnd/input"
3031
"github.com/lightningnetwork/lnd/keychain"
3132
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -1468,8 +1469,13 @@ func (lc *LightningChannel) createSignDesc() error {
14681469
remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
14691470

14701471
if chanState.ChanType.IsTaproot() {
1472+
fundingOpts := fn.MapOptionZ(
1473+
chanState.TapscriptRoot, TapscriptRootToOpt,
1474+
)
1475+
14711476
fundingPkScript, _, err = input.GenTaprootFundingScript(
14721477
localKey, remoteKey, int64(lc.channelState.Capacity),
1478+
fundingOpts...,
14731479
)
14741480
if err != nil {
14751481
return err
@@ -6487,11 +6493,15 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
64876493
"verification nonce: %w", err)
64886494
}
64896495

6496+
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
6497+
lc.channelState.TapscriptRoot,
6498+
)
6499+
64906500
// Now that we have the local nonce, we'll re-create the musig
64916501
// session we had for this height.
64926502
musigSession := NewPartialMusigSession(
64936503
*localNonce, ourKey, theirKey, lc.Signer,
6494-
&lc.fundingOutput, LocalMusigCommit,
6504+
&lc.fundingOutput, LocalMusigCommit, tapscriptTweak,
64956505
)
64966506

64976507
var remoteSig lnwire.PartialSigWithNonce
@@ -9019,12 +9029,13 @@ func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces,
90199029
// TODO(roasbeef): propagate rename of signing and verification nonces
90209030

90219031
sessionCfg := &MusigSessionCfg{
9022-
LocalKey: localChanCfg.MultiSigKey,
9023-
RemoteKey: remoteChanCfg.MultiSigKey,
9024-
LocalNonce: *localNonce,
9025-
RemoteNonce: *remoteNonce,
9026-
Signer: lc.Signer,
9027-
InputTxOut: &lc.fundingOutput,
9032+
LocalKey: localChanCfg.MultiSigKey,
9033+
RemoteKey: remoteChanCfg.MultiSigKey,
9034+
LocalNonce: *localNonce,
9035+
RemoteNonce: *remoteNonce,
9036+
Signer: lc.Signer,
9037+
InputTxOut: &lc.fundingOutput,
9038+
TapscriptTweak: lc.channelState.TapscriptRoot,
90289039
}
90299040
lc.musigSessions = NewMusigPairSession(
90309041
sessionCfg,

lnwallet/musig_session.go

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ 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/chaincfg/chainhash"
1112
"github.com/btcsuite/btcd/txscript"
1213
"github.com/btcsuite/btcd/wire"
14+
"github.com/lightningnetwork/lnd/fn"
1315
"github.com/lightningnetwork/lnd/input"
1416
"github.com/lightningnetwork/lnd/keychain"
1517
"github.com/lightningnetwork/lnd/lnwire"
@@ -37,6 +39,12 @@ var (
3739
ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
3840
)
3941

42+
// tapscriptRootToSignOpt is a function that takes a tapscript root and returns
43+
// a musig2 sign opt that'll apply the tweak when signing+verifying.
44+
func tapscriptRootToSignOpt(root chainhash.Hash) musig2.SignOption {
45+
return musig2.WithTaprootSignTweak(root[:])
46+
}
47+
4048
// MusigPartialSig is a wrapper around the base musig2.PartialSignature type
4149
// that also includes information about the set of nonces used, and also the
4250
// signer. This allows us to implement the input.Signature interface, as that
@@ -54,18 +62,24 @@ type MusigPartialSig struct {
5462

5563
// signerKeys is the set of public keys of all signers.
5664
signerKeys []*btcec.PublicKey
65+
66+
// tapscriptRoot is an optional tweak, that if specified, will be used
67+
// instead of the normal BIP 86 tweak when validating the signature.
68+
tapscriptTweak fn.Option[chainhash.Hash]
5769
}
5870

5971
// NewMusigPartialSig creates a new musig partial signature.
6072
func NewMusigPartialSig(sig *musig2.PartialSignature,
6173
signerNonce, combinedNonce lnwire.Musig2Nonce,
62-
signerKeys []*btcec.PublicKey) *MusigPartialSig {
74+
signerKeys []*btcec.PublicKey, tapscriptTweak fn.Option[chainhash.Hash],
75+
) *MusigPartialSig {
6376

6477
return &MusigPartialSig{
65-
sig: sig,
66-
signerNonce: signerNonce,
67-
combinedNonce: combinedNonce,
68-
signerKeys: signerKeys,
78+
sig: sig,
79+
signerNonce: signerNonce,
80+
combinedNonce: combinedNonce,
81+
signerKeys: signerKeys,
82+
tapscriptTweak: tapscriptTweak,
6983
}
7084
}
7185

@@ -135,9 +149,15 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
135149
var m [32]byte
136150
copy(m[:], msg)
137151

152+
// If we have a tapscript tweak, then we'll use that as a tweak
153+
// otherwise, we'll fall back to the normal BIP 86 sign tweak.
154+
signOpts := fn.MapOption(tapscriptRootToSignOpt)(
155+
p.tapscriptTweak,
156+
).UnwrapOr(musig2.WithBip86SignTweak())
157+
138158
return p.sig.Verify(
139159
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
140-
musig2.WithSortedKeys(), musig2.WithBip86SignTweak(),
160+
musig2.WithSortedKeys(), signOpts,
141161
)
142162
}
143163

@@ -160,6 +180,14 @@ func (n *MusigNoncePair) String() string {
160180
n.SigningNonce.PubNonce[:])
161181
}
162182

183+
// TapscriptRootToTweak is a function that takes a musig2 taproot tweak and
184+
// returns the root hash of the tapscript tree.
185+
func musig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash {
186+
var root chainhash.Hash
187+
copy(root[:], tweak.TaprootTweak)
188+
return root
189+
}
190+
163191
// MusigSession abstracts over the details of a logical musig session. A single
164192
// session is used for each commitment transactions. The sessions use a JIT
165193
// nonce style, wherein part of the session can be created using only the
@@ -197,6 +225,11 @@ type MusigSession struct {
197225
// commitType tracks if this is the session for the local or remote
198226
// commitment.
199227
commitType MusigCommitType
228+
229+
// tapscriptTweak is an optional tweak, that if specified, will be used
230+
// instead of the normal BIP 86 tweak when creating the musig2
231+
// aggregate key and session.
232+
tapscriptTweak fn.Option[input.MuSig2Tweaks]
200233
}
201234

202235
// NewPartialMusigSession creates a new musig2 session given only the
@@ -205,7 +238,8 @@ type MusigSession struct {
205238
func NewPartialMusigSession(verificationNonce musig2.Nonces,
206239
localKey, remoteKey keychain.KeyDescriptor,
207240
signer input.MuSig2Signer, inputTxOut *wire.TxOut,
208-
commitType MusigCommitType) *MusigSession {
241+
commitType MusigCommitType,
242+
tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
209243

210244
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
211245

@@ -214,13 +248,14 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces,
214248
}
215249

216250
return &MusigSession{
217-
nonces: nonces,
218-
remoteKey: remoteKey,
219-
localKey: localKey,
220-
inputTxOut: inputTxOut,
221-
signerKeys: signerKeys,
222-
signer: signer,
223-
commitType: commitType,
251+
nonces: nonces,
252+
remoteKey: remoteKey,
253+
localKey: localKey,
254+
inputTxOut: inputTxOut,
255+
signerKeys: signerKeys,
256+
signer: signer,
257+
commitType: commitType,
258+
tapscriptTweak: tapscriptTweak,
224259
}
225260
}
226261

@@ -254,9 +289,9 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
254289
remoteNonce = m.nonces.SigningNonce
255290
}
256291

257-
tweakDesc := input.MuSig2Tweaks{
292+
tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
258293
TaprootBIP0086Tweak: true,
259-
}
294+
})
260295
m.session, err = m.signer.MuSig2CreateSession(
261296
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
262297
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
@@ -351,8 +386,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
351386
return nil, err
352387
}
353388

389+
tapscriptRoot := fn.MapOption(musig2TweakToRoot)(m.tapscriptTweak)
390+
354391
return NewMusigPartialSig(
355392
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
393+
tapscriptRoot,
356394
), nil
357395
}
358396

@@ -364,7 +402,7 @@ func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
364402

365403
return NewPartialMusigSession(
366404
*verificationNonce, m.localKey, m.remoteKey, m.signer,
367-
m.inputTxOut, m.commitType,
405+
m.inputTxOut, m.commitType, m.tapscriptTweak,
368406
), nil
369407
}
370408

@@ -451,9 +489,11 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
451489
// When we verify a commitment signature, we always assume that we're
452490
// verifying a signature on our local commitment. Therefore, we'll use:
453491
// their remote nonce, and also public key.
492+
tapscriptRoot := fn.MapOption(musig2TweakToRoot)(m.tapscriptTweak)
454493
partialSig := NewMusigPartialSig(
455494
&musig2.PartialSignature{S: &sig.Sig},
456495
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
496+
tapscriptRoot,
457497
)
458498

459499
// With the partial sig loaded with the proper context, we'll now
@@ -537,6 +577,10 @@ type MusigSessionCfg struct {
537577
// InputTxOut is the output that we're signing for. This will be the
538578
// funding input.
539579
InputTxOut *wire.TxOut
580+
581+
// TapscriptRoot is an optional tweak that can be used to modify the
582+
// musig2 public key used in the session.
583+
TapscriptTweak fn.Option[chainhash.Hash]
540584
}
541585

542586
// MusigPairSession houses the two musig2 sessions needed to do funding and
@@ -561,13 +605,16 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
561605
//
562606
// Both sessions will be created using only the verification nonce for
563607
// the local+remote party.
608+
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
609+
cfg.TapscriptTweak,
610+
)
564611
localSession := NewPartialMusigSession(
565-
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey,
566-
cfg.Signer, cfg.InputTxOut, LocalMusigCommit,
612+
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
613+
cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
567614
)
568615
remoteSession := NewPartialMusigSession(
569-
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey,
570-
cfg.Signer, cfg.InputTxOut, RemoteMusigCommit,
616+
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
617+
cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
571618
)
572619

573620
return &MusigPairSession{

peer/musig_chan_closer.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
7+
"github.com/lightningnetwork/lnd/fn"
78
"github.com/lightningnetwork/lnd/input"
89
"github.com/lightningnetwork/lnd/lnwallet"
910
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
@@ -43,10 +44,15 @@ func (m *MusigChanCloser) ProposalClosingOpts() (
4344
}
4445

4546
localKey, remoteKey := m.channel.MultiSigKeys()
47+
48+
tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)(
49+
m.channel.State().TapscriptRoot,
50+
)
51+
4652
m.musigSession = lnwallet.NewPartialMusigSession(
4753
*m.remoteNonce, localKey, remoteKey,
4854
m.channel.Signer, m.channel.FundingTxOut(),
49-
lnwallet.RemoteMusigCommit,
55+
lnwallet.RemoteMusigCommit, tapscriptTweak,
5056
)
5157

5258
err := m.musigSession.FinalizeSession(*m.localNonce)

0 commit comments

Comments
 (0)