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.
This commit is contained in:
Olaoluwa Osuntokun 2024-03-13 10:54:49 -04:00 committed by Oliver Gugger
parent c8b7987a39
commit 82ba5bf0bf
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
4 changed files with 104 additions and 39 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
@ -178,8 +179,9 @@ func (m *mockChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress {
} }
func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount, func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
localScript, remoteScript []byte, _ ...lnwallet.ChanCloseOpt, localScript, remoteScript []byte,
) (input.Signature, *chainhash.Hash, btcutil.Amount, error) { _ ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash,
btcutil.Amount, error) {
if m.chanType.IsTaproot() { if m.chanType.IsTaproot() {
return lnwallet.NewMusigPartialSig( return lnwallet.NewMusigPartialSig(
@ -188,6 +190,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
R: new(btcec.PublicKey), R: new(btcec.PublicKey),
}, },
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil, lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
fn.None[chainhash.Hash](),
), nil, 0, nil ), nil, 0, nil
} }

View File

@ -1013,7 +1013,7 @@ func (lc *LightningChannel) createSignDesc() error {
if chanState.ChanType.IsTaproot() { if chanState.ChanType.IsTaproot() {
fundingPkScript, _, err = input.GenTaprootFundingScript( fundingPkScript, _, err = input.GenTaprootFundingScript(
localKey, remoteKey, int64(lc.channelState.Capacity), localKey, remoteKey, int64(lc.channelState.Capacity),
fn.None[chainhash.Hash](), chanState.TapscriptRoot,
) )
if err != nil { if err != nil {
return err return err
@ -6057,11 +6057,15 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
"verification nonce: %w", err) "verification nonce: %w", err)
} }
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
lc.channelState.TapscriptRoot,
)
// Now that we have the local nonce, we'll re-create the musig // Now that we have the local nonce, we'll re-create the musig
// session we had for this height. // session we had for this height.
musigSession := NewPartialMusigSession( musigSession := NewPartialMusigSession(
*localNonce, ourKey, theirKey, lc.Signer, *localNonce, ourKey, theirKey, lc.Signer,
&lc.fundingOutput, LocalMusigCommit, &lc.fundingOutput, LocalMusigCommit, tapscriptTweak,
) )
var remoteSig lnwire.PartialSigWithNonce var remoteSig lnwire.PartialSigWithNonce
@ -8672,12 +8676,13 @@ func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces,
// TODO(roasbeef): propagate rename of signing and verification nonces // TODO(roasbeef): propagate rename of signing and verification nonces
sessionCfg := &MusigSessionCfg{ sessionCfg := &MusigSessionCfg{
LocalKey: localChanCfg.MultiSigKey, LocalKey: localChanCfg.MultiSigKey,
RemoteKey: remoteChanCfg.MultiSigKey, RemoteKey: remoteChanCfg.MultiSigKey,
LocalNonce: *localNonce, LocalNonce: *localNonce,
RemoteNonce: *remoteNonce, RemoteNonce: *remoteNonce,
Signer: lc.Signer, Signer: lc.Signer,
InputTxOut: &lc.fundingOutput, InputTxOut: &lc.fundingOutput,
TapscriptTweak: lc.channelState.TapscriptRoot,
} }
lc.musigSessions = NewMusigPairSession( lc.musigSessions = NewMusigPairSession(
sessionCfg, sessionCfg,

View File

@ -8,8 +8,10 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@ -37,6 +39,20 @@ var (
ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized") 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[:])
}
// TapscriptRootToTweak is a helper function that converts a tapscript root
// into a tweak that can be used with the MuSig2 API.
func TapscriptRootToTweak(root chainhash.Hash) input.MuSig2Tweaks {
return input.MuSig2Tweaks{
TaprootTweak: root[:],
}
}
// MusigPartialSig is a wrapper around the base musig2.PartialSignature type // MusigPartialSig is a wrapper around the base musig2.PartialSignature type
// that also includes information about the set of nonces used, and also the // 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 // signer. This allows us to implement the input.Signature interface, as that
@ -54,25 +70,30 @@ type MusigPartialSig struct {
// signerKeys is the set of public keys of all signers. // signerKeys is the set of public keys of all signers.
signerKeys []*btcec.PublicKey signerKeys []*btcec.PublicKey
// tapscriptTweak 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. // NewMusigPartialSig creates a new MuSig2 partial signature.
func NewMusigPartialSig(sig *musig2.PartialSignature, func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce,
signerNonce, combinedNonce lnwire.Musig2Nonce, combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey,
signerKeys []*btcec.PublicKey) *MusigPartialSig { tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig {
return &MusigPartialSig{ return &MusigPartialSig{
sig: sig, sig: sig,
signerNonce: signerNonce, signerNonce: signerNonce,
combinedNonce: combinedNonce, combinedNonce: combinedNonce,
signerKeys: signerKeys, signerKeys: signerKeys,
tapscriptTweak: tapscriptTweak,
} }
} }
// FromWireSig maps a wire partial sig to this internal type that we'll use to // FromWireSig maps a wire partial sig to this internal type that we'll use to
// perform signature validation. // perform signature validation.
func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce, func (p *MusigPartialSig) FromWireSig(
) *MusigPartialSig { sig *lnwire.PartialSigWithNonce) *MusigPartialSig {
p.sig = &musig2.PartialSignature{ p.sig = &musig2.PartialSignature{
S: &sig.Sig, S: &sig.Sig,
@ -135,9 +156,15 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
var m [32]byte var m [32]byte
copy(m[:], msg) 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( return p.sig.Verify(
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m, p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
musig2.WithSortedKeys(), musig2.WithBip86SignTweak(), musig2.WithSortedKeys(), signOpts,
) )
} }
@ -160,6 +187,14 @@ func (n *MusigNoncePair) String() string {
n.SigningNonce.PubNonce[:]) 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 // MusigSession abstracts over the details of a logical musig session. A single
// session is used for each commitment transactions. The sessions use a JIT // 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 // nonce style, wherein part of the session can be created using only the
@ -197,15 +232,20 @@ type MusigSession struct {
// commitType tracks if this is the session for the local or remote // commitType tracks if this is the session for the local or remote
// commitment. // commitment.
commitType MusigCommitType 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 // NewPartialMusigSession creates a new musig2 session given only the
// verification nonce (local nonce), and the other information that has already // verification nonce (local nonce), and the other information that has already
// been bound to the session. // been bound to the session.
func NewPartialMusigSession(verificationNonce musig2.Nonces, func NewPartialMusigSession(verificationNonce musig2.Nonces,
localKey, remoteKey keychain.KeyDescriptor, localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer,
signer input.MuSig2Signer, inputTxOut *wire.TxOut, inputTxOut *wire.TxOut, commitType MusigCommitType,
commitType MusigCommitType) *MusigSession { tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
@ -214,13 +254,14 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces,
} }
return &MusigSession{ return &MusigSession{
nonces: nonces, nonces: nonces,
remoteKey: remoteKey, remoteKey: remoteKey,
localKey: localKey, localKey: localKey,
inputTxOut: inputTxOut, inputTxOut: inputTxOut,
signerKeys: signerKeys, signerKeys: signerKeys,
signer: signer, signer: signer,
commitType: commitType, commitType: commitType,
tapscriptTweak: tapscriptTweak,
} }
} }
@ -254,9 +295,9 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
remoteNonce = m.nonces.SigningNonce remoteNonce = m.nonces.SigningNonce
} }
tweakDesc := input.MuSig2Tweaks{ tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
TaprootBIP0086Tweak: true, TaprootBIP0086Tweak: true,
} })
m.session, err = m.signer.MuSig2CreateSession( m.session, err = m.signer.MuSig2CreateSession(
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys, input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce}, &tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
@ -351,8 +392,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
return nil, err return nil, err
} }
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
return NewMusigPartialSig( return NewMusigPartialSig(
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys, sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
tapscriptRoot,
), nil ), nil
} }
@ -364,7 +408,7 @@ func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
return NewPartialMusigSession( return NewPartialMusigSession(
*verificationNonce, m.localKey, m.remoteKey, m.signer, *verificationNonce, m.localKey, m.remoteKey, m.signer,
m.inputTxOut, m.commitType, m.inputTxOut, m.commitType, m.tapscriptTweak,
), nil ), nil
} }
@ -451,9 +495,11 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
// When we verify a commitment signature, we always assume that we're // When we verify a commitment signature, we always assume that we're
// verifying a signature on our local commitment. Therefore, we'll use: // verifying a signature on our local commitment. Therefore, we'll use:
// their remote nonce, and also public key. // their remote nonce, and also public key.
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
partialSig := NewMusigPartialSig( partialSig := NewMusigPartialSig(
&musig2.PartialSignature{S: &sig.Sig}, &musig2.PartialSignature{S: &sig.Sig},
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys, m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
tapscriptRoot,
) )
// With the partial sig loaded with the proper context, we'll now // With the partial sig loaded with the proper context, we'll now
@ -537,6 +583,10 @@ type MusigSessionCfg struct {
// InputTxOut is the output that we're signing for. This will be the // InputTxOut is the output that we're signing for. This will be the
// funding input. // funding input.
InputTxOut *wire.TxOut 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 // MusigPairSession houses the two musig2 sessions needed to do funding and
@ -561,13 +611,14 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
// //
// Both sessions will be created using only the verification nonce for // Both sessions will be created using only the verification nonce for
// the local+remote party. // the local+remote party.
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak)
localSession := NewPartialMusigSession( localSession := NewPartialMusigSession(
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
cfg.Signer, cfg.InputTxOut, LocalMusigCommit, cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
) )
remoteSession := NewPartialMusigSession( remoteSession := NewPartialMusigSession(
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
cfg.Signer, cfg.InputTxOut, RemoteMusigCommit, cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
) )
return &MusigPairSession{ return &MusigPairSession{

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwallet/chancloser"
@ -43,10 +44,15 @@ func (m *MusigChanCloser) ProposalClosingOpts() (
} }
localKey, remoteKey := m.channel.MultiSigKeys() localKey, remoteKey := m.channel.MultiSigKeys()
tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)(
m.channel.State().TapscriptRoot,
)
m.musigSession = lnwallet.NewPartialMusigSession( m.musigSession = lnwallet.NewPartialMusigSession(
*m.remoteNonce, localKey, remoteKey, *m.remoteNonce, localKey, remoteKey,
m.channel.Signer, m.channel.FundingTxOut(), m.channel.Signer, m.channel.FundingTxOut(),
lnwallet.RemoteMusigCommit, lnwallet.RemoteMusigCommit, tapscriptTweak,
) )
err := m.musigSession.FinalizeSession(*m.localNonce) err := m.musigSession.FinalizeSession(*m.localNonce)