Skip to content

Commit 64aed86

Browse files
Roasbeefguggero
authored andcommitted
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 4cb351b commit 64aed86

File tree

4 files changed

+101
-38
lines changed

4 files changed

+101
-38
lines changed

lnwallet/chancloser/chancloser_test.go

Lines changed: 5 additions & 2 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"
@@ -175,8 +176,9 @@ func (m *mockChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress {
175176
}
176177

177178
func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
178-
localScript, remoteScript []byte, _ ...lnwallet.ChanCloseOpt,
179-
) (input.Signature, *chainhash.Hash, btcutil.Amount, error) {
179+
localScript, remoteScript []byte,
180+
_ ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash,
181+
btcutil.Amount, error) {
180182

181183
if m.chanType.IsTaproot() {
182184
return lnwallet.NewMusigPartialSig(
@@ -185,6 +187,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
185187
R: new(btcec.PublicKey),
186188
},
187189
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
190+
fn.None[chainhash.Hash](),
188191
), nil, 0, nil
189192
}
190193

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"
@@ -1475,8 +1476,13 @@ func (lc *LightningChannel) createSignDesc() error {
14751476
remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
14761477

14771478
if chanState.ChanType.IsTaproot() {
1479+
fundingOpts := fn.MapOptionZ(
1480+
chanState.TapscriptRoot, TapscriptRootToOpt,
1481+
)
1482+
14781483
fundingPkScript, _, err = input.GenTaprootFundingScript(
14791484
localKey, remoteKey, int64(lc.channelState.Capacity),
1485+
fundingOpts...,
14801486
)
14811487
if err != nil {
14821488
return err
@@ -6501,11 +6507,15 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
65016507
"verification nonce: %w", err)
65026508
}
65036509

6510+
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
6511+
lc.channelState.TapscriptRoot,
6512+
)
6513+
65046514
// Now that we have the local nonce, we'll re-create the musig
65056515
// session we had for this height.
65066516
musigSession := NewPartialMusigSession(
65076517
*localNonce, ourKey, theirKey, lc.Signer,
6508-
&lc.fundingOutput, LocalMusigCommit,
6518+
&lc.fundingOutput, LocalMusigCommit, tapscriptTweak,
65096519
)
65106520

65116521
var remoteSig lnwire.PartialSigWithNonce
@@ -9048,12 +9058,13 @@ func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces,
90489058
// TODO(roasbeef): propagate rename of signing and verification nonces
90499059

90509060
sessionCfg := &MusigSessionCfg{
9051-
LocalKey: localChanCfg.MultiSigKey,
9052-
RemoteKey: remoteChanCfg.MultiSigKey,
9053-
LocalNonce: *localNonce,
9054-
RemoteNonce: *remoteNonce,
9055-
Signer: lc.Signer,
9056-
InputTxOut: &lc.fundingOutput,
9061+
LocalKey: localChanCfg.MultiSigKey,
9062+
RemoteKey: remoteChanCfg.MultiSigKey,
9063+
LocalNonce: *localNonce,
9064+
RemoteNonce: *remoteNonce,
9065+
Signer: lc.Signer,
9066+
InputTxOut: &lc.fundingOutput,
9067+
TapscriptTweak: lc.channelState.TapscriptRoot,
90579068
}
90589069
lc.musigSessions = NewMusigPairSession(
90599070
sessionCfg,

lnwallet/musig_session.go

Lines changed: 71 additions & 28 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,25 +62,30 @@ 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

59-
// NewMusigPartialSig creates a new musig partial signature.
60-
func NewMusigPartialSig(sig *musig2.PartialSignature,
61-
signerNonce, combinedNonce lnwire.Musig2Nonce,
62-
signerKeys []*btcec.PublicKey) *MusigPartialSig {
71+
// NewMusigPartialSig creates a new MuSig2 partial signature.
72+
func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce,
73+
combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey,
74+
tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig {
6375

6476
return &MusigPartialSig{
65-
sig: sig,
66-
signerNonce: signerNonce,
67-
combinedNonce: combinedNonce,
68-
signerKeys: signerKeys,
77+
sig: sig,
78+
signerNonce: signerNonce,
79+
combinedNonce: combinedNonce,
80+
signerKeys: signerKeys,
81+
tapscriptTweak: tapscriptTweak,
6982
}
7083
}
7184

7285
// FromWireSig maps a wire partial sig to this internal type that we'll use to
7386
// perform signature validation.
74-
func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce,
75-
) *MusigPartialSig {
87+
func (p *MusigPartialSig) FromWireSig(
88+
sig *lnwire.PartialSigWithNonce) *MusigPartialSig {
7689

7790
p.sig = &musig2.PartialSignature{
7891
S: &sig.Sig,
@@ -135,9 +148,15 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
135148
var m [32]byte
136149
copy(m[:], msg)
137150

151+
// If we have a tapscript tweak, then we'll use that as a tweak
152+
// otherwise, we'll fall back to the normal BIP 86 sign tweak.
153+
signOpts := fn.MapOption(tapscriptRootToSignOpt)(
154+
p.tapscriptTweak,
155+
).UnwrapOr(musig2.WithBip86SignTweak())
156+
138157
return p.sig.Verify(
139158
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
140-
musig2.WithSortedKeys(), musig2.WithBip86SignTweak(),
159+
musig2.WithSortedKeys(), signOpts,
141160
)
142161
}
143162

@@ -160,6 +179,14 @@ func (n *MusigNoncePair) String() string {
160179
n.SigningNonce.PubNonce[:])
161180
}
162181

182+
// TapscriptRootToTweak is a function that takes a MuSig2 taproot tweak and
183+
// returns the root hash of the tapscript tree.
184+
func muSig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash {
185+
var root chainhash.Hash
186+
copy(root[:], tweak.TaprootTweak)
187+
return root
188+
}
189+
163190
// MusigSession abstracts over the details of a logical musig session. A single
164191
// session is used for each commitment transactions. The sessions use a JIT
165192
// nonce style, wherein part of the session can be created using only the
@@ -197,15 +224,20 @@ type MusigSession struct {
197224
// commitType tracks if this is the session for the local or remote
198225
// commitment.
199226
commitType MusigCommitType
227+
228+
// tapscriptTweak is an optional tweak, that if specified, will be used
229+
// instead of the normal BIP 86 tweak when creating the MuSig2
230+
// aggregate key and session.
231+
tapscriptTweak fn.Option[input.MuSig2Tweaks]
200232
}
201233

202234
// NewPartialMusigSession creates a new musig2 session given only the
203235
// verification nonce (local nonce), and the other information that has already
204236
// been bound to the session.
205237
func NewPartialMusigSession(verificationNonce musig2.Nonces,
206-
localKey, remoteKey keychain.KeyDescriptor,
207-
signer input.MuSig2Signer, inputTxOut *wire.TxOut,
208-
commitType MusigCommitType) *MusigSession {
238+
localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer,
239+
inputTxOut *wire.TxOut, commitType MusigCommitType,
240+
tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
209241

210242
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
211243

@@ -214,13 +246,14 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces,
214246
}
215247

216248
return &MusigSession{
217-
nonces: nonces,
218-
remoteKey: remoteKey,
219-
localKey: localKey,
220-
inputTxOut: inputTxOut,
221-
signerKeys: signerKeys,
222-
signer: signer,
223-
commitType: commitType,
249+
nonces: nonces,
250+
remoteKey: remoteKey,
251+
localKey: localKey,
252+
inputTxOut: inputTxOut,
253+
signerKeys: signerKeys,
254+
signer: signer,
255+
commitType: commitType,
256+
tapscriptTweak: tapscriptTweak,
224257
}
225258
}
226259

@@ -254,9 +287,9 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
254287
remoteNonce = m.nonces.SigningNonce
255288
}
256289

257-
tweakDesc := input.MuSig2Tweaks{
290+
tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
258291
TaprootBIP0086Tweak: true,
259-
}
292+
})
260293
m.session, err = m.signer.MuSig2CreateSession(
261294
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
262295
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
@@ -351,8 +384,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
351384
return nil, err
352385
}
353386

387+
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
388+
354389
return NewMusigPartialSig(
355390
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
391+
tapscriptRoot,
356392
), nil
357393
}
358394

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

365401
return NewPartialMusigSession(
366402
*verificationNonce, m.localKey, m.remoteKey, m.signer,
367-
m.inputTxOut, m.commitType,
403+
m.inputTxOut, m.commitType, m.tapscriptTweak,
368404
), nil
369405
}
370406

@@ -451,9 +487,11 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
451487
// When we verify a commitment signature, we always assume that we're
452488
// verifying a signature on our local commitment. Therefore, we'll use:
453489
// their remote nonce, and also public key.
490+
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
454491
partialSig := NewMusigPartialSig(
455492
&musig2.PartialSignature{S: &sig.Sig},
456493
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
494+
tapscriptRoot,
457495
)
458496

459497
// With the partial sig loaded with the proper context, we'll now
@@ -537,6 +575,10 @@ type MusigSessionCfg struct {
537575
// InputTxOut is the output that we're signing for. This will be the
538576
// funding input.
539577
InputTxOut *wire.TxOut
578+
579+
// TapscriptRoot is an optional tweak that can be used to modify the
580+
// MuSig2 public key used in the session.
581+
TapscriptTweak fn.Option[chainhash.Hash]
540582
}
541583

542584
// MusigPairSession houses the two musig2 sessions needed to do funding and
@@ -561,13 +603,14 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
561603
//
562604
// Both sessions will be created using only the verification nonce for
563605
// the local+remote party.
606+
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak)
564607
localSession := NewPartialMusigSession(
565-
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey,
566-
cfg.Signer, cfg.InputTxOut, LocalMusigCommit,
608+
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
609+
cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
567610
)
568611
remoteSession := NewPartialMusigSession(
569-
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey,
570-
cfg.Signer, cfg.InputTxOut, RemoteMusigCommit,
612+
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
613+
cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
571614
)
572615

573616
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)