Skip to content
Closed
2 changes: 2 additions & 0 deletions lnwallet/chancloser/chancloser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnutils"
Expand Down Expand Up @@ -185,6 +186,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
R: new(btcec.PublicKey),
},
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
fn.None[chainhash.Hash](),
), nil, 0, nil
}

Expand Down
25 changes: 18 additions & 7 deletions lnwallet/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
Expand Down Expand Up @@ -1468,8 +1469,13 @@ func (lc *LightningChannel) createSignDesc() error {
remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey

if chanState.ChanType.IsTaproot() {
fundingOpts := fn.MapOptionZ(
chanState.TapscriptRoot, TapscriptRootToOpt,
)

fundingPkScript, _, err = input.GenTaprootFundingScript(
localKey, remoteKey, int64(lc.channelState.Capacity),
fundingOpts...,
)
if err != nil {
return err
Expand Down Expand Up @@ -6487,11 +6493,15 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
"verification nonce: %w", err)
}

tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
lc.channelState.TapscriptRoot,
)

// Now that we have the local nonce, we'll re-create the musig
// session we had for this height.
musigSession := NewPartialMusigSession(
*localNonce, ourKey, theirKey, lc.Signer,
&lc.fundingOutput, LocalMusigCommit,
&lc.fundingOutput, LocalMusigCommit, tapscriptTweak,
)

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

sessionCfg := &MusigSessionCfg{
LocalKey: localChanCfg.MultiSigKey,
RemoteKey: remoteChanCfg.MultiSigKey,
LocalNonce: *localNonce,
RemoteNonce: *remoteNonce,
Signer: lc.Signer,
InputTxOut: &lc.fundingOutput,
LocalKey: localChanCfg.MultiSigKey,
RemoteKey: remoteChanCfg.MultiSigKey,
LocalNonce: *localNonce,
RemoteNonce: *remoteNonce,
Signer: lc.Signer,
InputTxOut: &lc.fundingOutput,
TapscriptTweak: lc.channelState.TapscriptRoot,
}
lc.musigSessions = NewMusigPairSession(
sessionCfg,
Expand Down
89 changes: 68 additions & 21 deletions lnwallet/musig_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
Expand Down Expand Up @@ -37,6 +39,12 @@ var (
ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
)

// tapscriptRootToSignOpt is a function that takes a tapscript root and returns
// a musig2 sign opt that'll apply the tweak when signing+verifying.
func tapscriptRootToSignOpt(root chainhash.Hash) musig2.SignOption {
return musig2.WithTaprootSignTweak(root[:])
}

// MusigPartialSig is a wrapper around the base musig2.PartialSignature type
// that also includes information about the set of nonces used, and also the
// signer. This allows us to implement the input.Signature interface, as that
Expand All @@ -54,18 +62,24 @@ type MusigPartialSig struct {

// signerKeys is the set of public keys of all signers.
signerKeys []*btcec.PublicKey

// tapscriptRoot is an optional tweak, that if specified, will be used
// instead of the normal BIP 86 tweak when validating the signature.
tapscriptTweak fn.Option[chainhash.Hash]
}

// NewMusigPartialSig creates a new musig partial signature.
func NewMusigPartialSig(sig *musig2.PartialSignature,
signerNonce, combinedNonce lnwire.Musig2Nonce,
signerKeys []*btcec.PublicKey) *MusigPartialSig {
signerKeys []*btcec.PublicKey, tapscriptTweak fn.Option[chainhash.Hash],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: break before tapscriptTweak instead?

) *MusigPartialSig {

return &MusigPartialSig{
sig: sig,
signerNonce: signerNonce,
combinedNonce: combinedNonce,
signerKeys: signerKeys,
sig: sig,
signerNonce: signerNonce,
combinedNonce: combinedNonce,
signerKeys: signerKeys,
tapscriptTweak: tapscriptTweak,
}
}

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

// If we have a tapscript tweak, then we'll use that as a tweak
// otherwise, we'll fall back to the normal BIP 86 sign tweak.
signOpts := fn.MapOption(tapscriptRootToSignOpt)(
p.tapscriptTweak,
).UnwrapOr(musig2.WithBip86SignTweak())

return p.sig.Verify(
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
musig2.WithSortedKeys(), musig2.WithBip86SignTweak(),
musig2.WithSortedKeys(), signOpts,
)
}

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

// TapscriptRootToTweak is a function that takes a musig2 taproot tweak and
// returns the root hash of the tapscript tree.
func musig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash {
var root chainhash.Hash
copy(root[:], tweak.TaprootTweak)
return root
}

// MusigSession abstracts over the details of a logical musig session. A single
// session is used for each commitment transactions. The sessions use a JIT
// nonce style, wherein part of the session can be created using only the
Expand Down Expand Up @@ -197,6 +225,11 @@ type MusigSession struct {
// commitType tracks if this is the session for the local or remote
// commitment.
commitType MusigCommitType

// tapscriptTweak is an optional tweak, that if specified, will be used
// instead of the normal BIP 86 tweak when creating the musig2
// aggregate key and session.
tapscriptTweak fn.Option[input.MuSig2Tweaks]
}

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

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

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

return &MusigSession{
nonces: nonces,
remoteKey: remoteKey,
localKey: localKey,
inputTxOut: inputTxOut,
signerKeys: signerKeys,
signer: signer,
commitType: commitType,
nonces: nonces,
remoteKey: remoteKey,
localKey: localKey,
inputTxOut: inputTxOut,
signerKeys: signerKeys,
signer: signer,
commitType: commitType,
tapscriptTweak: tapscriptTweak,
}
}

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

tweakDesc := input.MuSig2Tweaks{
tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
TaprootBIP0086Tweak: true,
}
})
m.session, err = m.signer.MuSig2CreateSession(
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
Expand Down Expand Up @@ -351,8 +386,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
return nil, err
}

tapscriptRoot := fn.MapOption(musig2TweakToRoot)(m.tapscriptTweak)

return NewMusigPartialSig(
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
tapscriptRoot,
), nil
}

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

return NewPartialMusigSession(
*verificationNonce, m.localKey, m.remoteKey, m.signer,
m.inputTxOut, m.commitType,
m.inputTxOut, m.commitType, m.tapscriptTweak,
), nil
}

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

// With the partial sig loaded with the proper context, we'll now
Expand Down Expand Up @@ -537,6 +577,10 @@ type MusigSessionCfg struct {
// InputTxOut is the output that we're signing for. This will be the
// funding input.
InputTxOut *wire.TxOut

// TapscriptRoot is an optional tweak that can be used to modify the
// musig2 public key used in the session.
TapscriptTweak fn.Option[chainhash.Hash]
}

// MusigPairSession houses the two musig2 sessions needed to do funding and
Expand All @@ -561,13 +605,16 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
//
// Both sessions will be created using only the verification nonce for
// the local+remote party.
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
cfg.TapscriptTweak,
)
localSession := NewPartialMusigSession(
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey,
cfg.Signer, cfg.InputTxOut, LocalMusigCommit,
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
)
remoteSession := NewPartialMusigSession(
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey,
cfg.Signer, cfg.InputTxOut, RemoteMusigCommit,
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
)

return &MusigPairSession{
Expand Down
8 changes: 7 additions & 1 deletion peer/musig_chan_closer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
Expand Down Expand Up @@ -43,10 +44,15 @@ func (m *MusigChanCloser) ProposalClosingOpts() (
}

localKey, remoteKey := m.channel.MultiSigKeys()

tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)(
m.channel.State().TapscriptRoot,
)

m.musigSession = lnwallet.NewPartialMusigSession(
*m.remoteNonce, localKey, remoteKey,
m.channel.Signer, m.channel.FundingTxOut(),
lnwallet.RemoteMusigCommit,
lnwallet.RemoteMusigCommit, tapscriptTweak,
)

err := m.musigSession.FinalizeSession(*m.localNonce)
Expand Down