mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
579 lines
18 KiB
Go
579 lines
18 KiB
Go
package lnwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
|
|
"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/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/shachain"
|
|
)
|
|
|
|
// MusigCommitType is an enum that denotes if this is the local or remote
|
|
// commitment.
|
|
type MusigCommitType uint8
|
|
|
|
const (
|
|
// LocalMusigCommit denotes that this a session for the local
|
|
// commitment.
|
|
LocalMusigCommit MusigCommitType = iota
|
|
|
|
// RemoteMusigCommit denotes that this is a session for the remote
|
|
// commitment.
|
|
RemoteMusigCommit
|
|
)
|
|
|
|
var (
|
|
// ErrSessionNotFinalized is returned when the SignCommit method is
|
|
// called for a local commitment, without the session being finalized
|
|
// (missing nonce).
|
|
ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
|
|
)
|
|
|
|
// 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
|
|
// requires the ability to perform abstract verification based on a public key.
|
|
type MusigPartialSig struct {
|
|
// sig is the actual musig2 partial signature.
|
|
sig *musig2.PartialSignature
|
|
|
|
// signerNonce is the nonce used by the signer to generate the partial
|
|
// signature.
|
|
signerNonce lnwire.Musig2Nonce
|
|
|
|
// combinedNonce is the combined nonce of all signers.
|
|
combinedNonce lnwire.Musig2Nonce
|
|
|
|
// signerKeys is the set of public keys of all signers.
|
|
signerKeys []*btcec.PublicKey
|
|
}
|
|
|
|
// NewMusigPartialSig creates a new musig partial signature.
|
|
func NewMusigPartialSig(sig *musig2.PartialSignature,
|
|
signerNonce, combinedNonce lnwire.Musig2Nonce,
|
|
signerKeys []*btcec.PublicKey) *MusigPartialSig {
|
|
|
|
return &MusigPartialSig{
|
|
sig: sig,
|
|
signerNonce: signerNonce,
|
|
combinedNonce: combinedNonce,
|
|
signerKeys: signerKeys,
|
|
}
|
|
}
|
|
|
|
// FromWireSig maps a wire partial sig to this internal type that we'll use to
|
|
// perform signature validation.
|
|
func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce,
|
|
) *MusigPartialSig {
|
|
|
|
p.sig = &musig2.PartialSignature{
|
|
S: &sig.Sig,
|
|
}
|
|
p.signerNonce = sig.Nonce
|
|
|
|
return p
|
|
}
|
|
|
|
// ToWireSig maps the partial signature to something that we can use to write
|
|
// out for the wire protocol.
|
|
func (p *MusigPartialSig) ToWireSig() *lnwire.PartialSigWithNonce {
|
|
return &lnwire.PartialSigWithNonce{
|
|
PartialSig: lnwire.NewPartialSig(*p.sig.S),
|
|
Nonce: p.signerNonce,
|
|
}
|
|
}
|
|
|
|
// Serialize serializes the musig2 partial signature. The serializing includes
|
|
// the signer's public nonce _and_ the partial signature. The final signature
|
|
// is always 98 bytes in length.
|
|
func (p *MusigPartialSig) Serialize() []byte {
|
|
var b bytes.Buffer
|
|
|
|
_ = p.ToWireSig().Encode(&b)
|
|
|
|
return b.Bytes()
|
|
}
|
|
|
|
// ToSchnorrShell converts the musig partial signature to a regular schnorr.
|
|
// This schnorr signature uses a zero value for the 'r' field, so we're just
|
|
// only using the last 32-bytes of the signature. This is useful when we need
|
|
// to convert an HTLC schnorr signature into something we can send using the
|
|
// existing messages.
|
|
func (p *MusigPartialSig) ToSchnorrShell() *schnorr.Signature {
|
|
var zeroVal btcec.FieldVal
|
|
return schnorr.NewSignature(&zeroVal, p.sig.S)
|
|
}
|
|
|
|
// FromSchnorrShell takes a schnorr signature and parses out the last 32 bytes
|
|
// as a normal musig2 partial signature.
|
|
func (p *MusigPartialSig) FromSchnorrShell(sig *schnorr.Signature) {
|
|
var (
|
|
partialS btcec.ModNScalar
|
|
partialSBytes [32]byte
|
|
)
|
|
copy(partialSBytes[:], sig.Serialize()[32:])
|
|
partialS.SetBytes(&partialSBytes)
|
|
|
|
p.sig = &musig2.PartialSignature{
|
|
S: &partialS,
|
|
}
|
|
}
|
|
|
|
// Verify attempts to verify the partial musig2 signature using the passed
|
|
// message and signer public key.
|
|
//
|
|
// NOTE: This implements the input.Signature interface.
|
|
func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
|
|
var m [32]byte
|
|
copy(m[:], msg)
|
|
|
|
return p.sig.Verify(
|
|
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
|
|
musig2.WithSortedKeys(), musig2.WithBip86SignTweak(),
|
|
)
|
|
}
|
|
|
|
// MusigNoncePair holds the two nonces needed to sign/verify a new commitment
|
|
// state. The signer nonce is the nonce used by the signer (remote nonce), and
|
|
// the verification nonce, the nonce used by the verifier (local nonce).
|
|
type MusigNoncePair struct {
|
|
// SigningNonce is the nonce used by the signer to sign the commitment.
|
|
SigningNonce musig2.Nonces
|
|
|
|
// VerificationNonce is the nonce used by the verifier to verify the
|
|
// commitment.
|
|
VerificationNonce musig2.Nonces
|
|
}
|
|
|
|
// String returns a string representation of the MusigNoncePair.
|
|
func (n *MusigNoncePair) String() string {
|
|
return fmt.Sprintf("NoncePair(verification_nonce=%x, "+
|
|
"signing_nonce=%x)", n.VerificationNonce.PubNonce[:],
|
|
n.SigningNonce.PubNonce[:])
|
|
}
|
|
|
|
// 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
|
|
// verifier nonce. Once a new state is signed, then the signer nonce is
|
|
// generated. Similarly, the verifier then uses the received signer nonce to
|
|
// complete the session and verify the incoming signature.
|
|
type MusigSession struct {
|
|
// session is the backing musig2 session. We'll use this to interact
|
|
// with the musig2 signer.
|
|
session *input.MuSig2SessionInfo
|
|
|
|
// combinedNonce is the combined nonce of all signers.
|
|
combinedNonce lnwire.Musig2Nonce
|
|
|
|
// nonces is the set of nonces that'll be used to generate/verify the
|
|
// next commitment.
|
|
nonces MusigNoncePair
|
|
|
|
// inputTxOut is the funding input.
|
|
inputTxOut *wire.TxOut
|
|
|
|
// signerKeys is the set of public keys of all signers.
|
|
signerKeys []*btcec.PublicKey
|
|
|
|
// remoteKey is the key desc of the remote key.
|
|
remoteKey keychain.KeyDescriptor
|
|
|
|
// localKey is the key desc of the local key.
|
|
localKey keychain.KeyDescriptor
|
|
|
|
// signer is the signer that'll be used to interact with the musig
|
|
// session.
|
|
signer input.MuSig2Signer
|
|
|
|
// commitType tracks if this is the session for the local or remote
|
|
// commitment.
|
|
commitType MusigCommitType
|
|
}
|
|
|
|
// NewPartialMusigSession creates a new musig2 session given only the
|
|
// verification nonce (local nonce), and the other information that has already
|
|
// been bound to the session.
|
|
func NewPartialMusigSession(verificationNonce musig2.Nonces,
|
|
localKey, remoteKey keychain.KeyDescriptor,
|
|
signer input.MuSig2Signer, inputTxOut *wire.TxOut,
|
|
commitType MusigCommitType) *MusigSession {
|
|
|
|
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
|
|
|
|
nonces := MusigNoncePair{
|
|
VerificationNonce: verificationNonce,
|
|
}
|
|
|
|
return &MusigSession{
|
|
nonces: nonces,
|
|
remoteKey: remoteKey,
|
|
localKey: localKey,
|
|
inputTxOut: inputTxOut,
|
|
signerKeys: signerKeys,
|
|
signer: signer,
|
|
commitType: commitType,
|
|
}
|
|
}
|
|
|
|
// FinalizeSession finalizes the session given the signer nonce. This is
|
|
// called before signing or verifying a new commitment.
|
|
func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
|
|
var (
|
|
localNonce, remoteNonce musig2.Nonces
|
|
err error
|
|
)
|
|
|
|
// First, we'll stash the freshly generated signing nonce. Depending on
|
|
// who's commitment we're handling, this'll either be our generated
|
|
// nonce, or the one we just got from the remote party.
|
|
m.nonces.SigningNonce = signingNonce
|
|
|
|
switch m.commitType {
|
|
// If we're making a session for the remote commitment, then the nonce
|
|
// we use to sign is actually will be the signing nonce for the
|
|
// session, and their nonce the verification nonce.
|
|
case RemoteMusigCommit:
|
|
localNonce = m.nonces.SigningNonce
|
|
remoteNonce = m.nonces.VerificationNonce
|
|
|
|
// Otherwise, we're generating/receiving a signature for our local
|
|
// commitment (to broadcast), so now our verification nonce is the one
|
|
// we've already generated, and we want to bind their new signing
|
|
// nonce.
|
|
case LocalMusigCommit:
|
|
localNonce = m.nonces.VerificationNonce
|
|
remoteNonce = m.nonces.SigningNonce
|
|
}
|
|
|
|
tweakDesc := input.MuSig2Tweaks{
|
|
TaprootBIP0086Tweak: true,
|
|
}
|
|
m.session, err = m.signer.MuSig2CreateSession(
|
|
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
|
|
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
|
|
musig2.WithPreGeneratedNonce(&localNonce),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// We'll need the raw combined nonces later to be able to verify
|
|
// partial signatures, and also combine partial signatures, so we'll
|
|
// generate it now ourselves.
|
|
aggNonce, err := musig2.AggregateNonces([][musig2.PubNonceSize]byte{
|
|
m.nonces.SigningNonce.PubNonce,
|
|
m.nonces.VerificationNonce.PubNonce,
|
|
})
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
m.combinedNonce = aggNonce
|
|
|
|
return nil
|
|
}
|
|
|
|
// taprootKeyspendSighash generates the sighash for a taproot key spend. As
|
|
// this is a musig2 channel output, the keyspend is the only path we can take.
|
|
func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte,
|
|
value int64) ([]byte, error) {
|
|
|
|
prevOutputFetcher := txscript.NewCannedPrevOutputFetcher(
|
|
pkScript, value,
|
|
)
|
|
|
|
sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
|
|
|
|
return txscript.CalcTaprootSignatureHash(
|
|
sigHashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher,
|
|
)
|
|
}
|
|
|
|
// SignCommit signs the passed commitment w/ the current signing (relative
|
|
// remote) nonce. Given nonces should only ever be used once, once the method
|
|
// returns a new nonce is returned, w/ the existing nonce blanked out.
|
|
func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
|
|
switch {
|
|
// If we already have a session, then we don't need to finalize as this
|
|
// was done up front (symmetric nonce case, like for co-op close).
|
|
case m.session == nil && m.commitType == RemoteMusigCommit:
|
|
// Before we can sign a new commitment, we'll need to generate
|
|
// a fresh nonce that'll be sent along side our signature. With
|
|
// the nonce in hand, we can finalize the session.
|
|
txHash := tx.TxHash()
|
|
signingNonce, err := musig2.GenNonces(
|
|
musig2.WithPublicKey(m.localKey.PubKey),
|
|
musig2.WithNonceAuxInput(txHash[:]),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := m.FinalizeSession(*signingNonce); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Otherwise, we're trying to make a new commitment transaction without
|
|
// an active session, so we'll error out.
|
|
case m.session == nil:
|
|
return nil, ErrSessionNotFinalized
|
|
}
|
|
|
|
// Next we can sign, we'll need to generate the sighash for their
|
|
// commitment transaction.
|
|
sigHash, err := taprootKeyspendSighash(
|
|
tx, m.inputTxOut.PkScript, m.inputTxOut.Value,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Now that we have our session created, we'll use it to generate the
|
|
// initial partial signature over our sighash.
|
|
var sigHashMsg [32]byte
|
|
copy(sigHashMsg[:], sigHash)
|
|
|
|
walletLog.Infof("Generating new musig2 sig for session=%x, nonces=%s",
|
|
m.session.SessionID[:], m.nonces.String())
|
|
|
|
sig, err := m.signer.MuSig2Sign(
|
|
m.session.SessionID, sigHashMsg, false,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewMusigPartialSig(
|
|
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
|
|
), nil
|
|
}
|
|
|
|
// Refresh is called once we receive a new verification nonce from the remote
|
|
// party after sending a signature. This nonce will be coupled within the
|
|
// revoke-and-ack message of the remote party.
|
|
func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
|
|
) (*MusigSession, error) {
|
|
|
|
return NewPartialMusigSession(
|
|
*verificationNonce, m.localKey, m.remoteKey, m.signer,
|
|
m.inputTxOut, m.commitType,
|
|
), nil
|
|
}
|
|
|
|
// VerificationNonce returns the current verification nonce for the session.
|
|
func (m *MusigSession) VerificationNonce() *musig2.Nonces {
|
|
return &m.nonces.VerificationNonce
|
|
}
|
|
|
|
// musigSessionOpts is a set of options that can be used to modify calls to the
|
|
// musig session.
|
|
type musigSessionOpts struct {
|
|
// customRand is an optional custom random source that can be used to
|
|
// generate nonces via a counter scheme.
|
|
customRand io.Reader
|
|
}
|
|
|
|
// defaultMusigSessionOpts returns the default set of options for the musig
|
|
// session.
|
|
func defaultMusigSessionOpts() *musigSessionOpts {
|
|
return &musigSessionOpts{}
|
|
}
|
|
|
|
// MusigSessionOpt is a functional option that can be used to modify calls to
|
|
// the musig session.
|
|
type MusigSessionOpt func(*musigSessionOpts)
|
|
|
|
// WithLocalCounterNonce is used to generate local nonces based on the shachain
|
|
// producer and the current height. This allows us to not have to write secret
|
|
// nonce state to disk. Instead, we can use this to derive the nonce we need to
|
|
// sign and broadcast our own commitment transaction.
|
|
func WithLocalCounterNonce(targetHeight uint64,
|
|
shaGen shachain.Producer) MusigSessionOpt {
|
|
|
|
return func(opt *musigSessionOpts) {
|
|
nextPreimage, _ := shaGen.AtIndex(targetHeight)
|
|
|
|
opt.customRand = bytes.NewBuffer(nextPreimage[:])
|
|
}
|
|
}
|
|
|
|
// invalidPartialSigError is used to return additional debug information to a
|
|
// caller that encounters an invalid partial sig.
|
|
type invalidPartialSigError struct {
|
|
partialSig []byte
|
|
sigHash []byte
|
|
signingNonce [musig2.PubNonceSize]byte
|
|
verificationNonce [musig2.PubNonceSize]byte
|
|
}
|
|
|
|
// Error returns the error string for the partial sig error.
|
|
func (i invalidPartialSigError) Error() string {
|
|
return fmt.Sprintf("invalid partial sig: partial_sig=%x, "+
|
|
"sig_hash=%x, signing_nonce=%x, verification_nonce=%x",
|
|
i.partialSig, i.sigHash, i.signingNonce[:],
|
|
i.verificationNonce[:])
|
|
}
|
|
|
|
// VerifyCommitSig attempts to verify the passed partial signature against the
|
|
// passed commitment transaction. A keyspend sighash is assumed to generate the
|
|
// signed message. As we never re-use nonces, a new verification nonce (our
|
|
// relative local nonce) returned to transmit to the remote party, which allows
|
|
// them to generate another signature.
|
|
func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
|
|
sig *lnwire.PartialSigWithNonce,
|
|
musigOpts ...MusigSessionOpt) (*musig2.Nonces, error) {
|
|
|
|
opts := defaultMusigSessionOpts()
|
|
for _, optFunc := range musigOpts {
|
|
optFunc(opts)
|
|
}
|
|
|
|
if sig == nil {
|
|
return nil, fmt.Errorf("sig not provided")
|
|
}
|
|
|
|
// Before we can verify the signature, we'll need to finalize the
|
|
// session by binding the remote party's provided signing nonce.
|
|
if err := m.FinalizeSession(musig2.Nonces{
|
|
PubNonce: sig.Nonce,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 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.
|
|
partialSig := NewMusigPartialSig(
|
|
&musig2.PartialSignature{S: &sig.Sig},
|
|
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
|
|
)
|
|
|
|
// With the partial sig loaded with the proper context, we'll now
|
|
// generate the sighash that the remote party should have signed.
|
|
sigHash, err := taprootKeyspendSighash(
|
|
commitTx, m.inputTxOut.PkScript, m.inputTxOut.Value,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
walletLog.Infof("Verifying new musig2 sig for session=%x, nonce=%s",
|
|
m.session.SessionID[:], m.nonces.String())
|
|
|
|
if partialSig == nil {
|
|
return nil, fmt.Errorf("partial sig not set")
|
|
}
|
|
|
|
if !partialSig.Verify(sigHash, m.remoteKey.PubKey) {
|
|
return nil, &invalidPartialSigError{
|
|
partialSig: partialSig.Serialize(),
|
|
sigHash: sigHash,
|
|
verificationNonce: m.nonces.VerificationNonce.PubNonce,
|
|
signingNonce: m.nonces.SigningNonce.PubNonce,
|
|
}
|
|
}
|
|
|
|
nonceOpts := []musig2.NonceGenOption{
|
|
musig2.WithPublicKey(m.localKey.PubKey),
|
|
}
|
|
if opts.customRand != nil {
|
|
nonceOpts = append(
|
|
nonceOpts, musig2.WithCustomRand(opts.customRand),
|
|
)
|
|
}
|
|
|
|
// At this point, we know that their signature is valid, so we'll
|
|
// generate another verification nonce for them, so they can generate a
|
|
// new state transition.
|
|
nextVerificationNonce, err := musig2.GenNonces(nonceOpts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to gen new nonce: %w", err)
|
|
}
|
|
|
|
return nextVerificationNonce, nil
|
|
}
|
|
|
|
// CombineSigs combines the passed partial signatures into a valid schnorr
|
|
// signature.
|
|
func (m *MusigSession) CombineSigs(sigs ...*musig2.PartialSignature,
|
|
) (*schnorr.Signature, error) {
|
|
|
|
sig, _, err := m.signer.MuSig2CombineSig(
|
|
m.session.SessionID, sigs,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sig, nil
|
|
}
|
|
|
|
// MusigSessionCfg is used to create a new musig2 pair session. It contains the
|
|
// keys for both parties, as well as their initial verification nonces.
|
|
type MusigSessionCfg struct {
|
|
// LocalKey is a key desc for the local key.
|
|
LocalKey keychain.KeyDescriptor
|
|
|
|
// RemoteKey is a key desc for the remote key.
|
|
RemoteKey keychain.KeyDescriptor
|
|
|
|
// LocalNonce is the local party's initial verification nonce.
|
|
LocalNonce musig2.Nonces
|
|
|
|
// RemoteNonce is the remote party's initial verification nonce.
|
|
RemoteNonce musig2.Nonces
|
|
|
|
// Signer is the signer that will be used to generate the session.
|
|
Signer input.MuSig2Signer
|
|
|
|
// InputTxOut is the output that we're signing for. This will be the
|
|
// funding input.
|
|
InputTxOut *wire.TxOut
|
|
}
|
|
|
|
// MusigPairSession houses the two musig2 sessions needed to do funding and
|
|
// drive forward the state machine. The local session is used to verify
|
|
// incoming commitment states. The remote session is used to propose new
|
|
// commitment states to the remote party.
|
|
type MusigPairSession struct {
|
|
// LocalSession is the local party's musig2 session.
|
|
LocalSession *MusigSession
|
|
|
|
// RemoteSession is the remote party's musig2 session.
|
|
RemoteSession *MusigSession
|
|
|
|
// signer is the signer that will be used to drive the session.
|
|
signer input.MuSig2Signer
|
|
}
|
|
|
|
// NewMusigPairSession creates a new musig2 pair session.
|
|
func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
|
|
// Given the config passed in, we'll now create our two sessions: one
|
|
// for the local commit, and one for the remote commit.
|
|
//
|
|
// Both sessions will be created using only the verification nonce for
|
|
// the local+remote party.
|
|
localSession := NewPartialMusigSession(
|
|
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey,
|
|
cfg.Signer, cfg.InputTxOut, LocalMusigCommit,
|
|
)
|
|
remoteSession := NewPartialMusigSession(
|
|
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey,
|
|
cfg.Signer, cfg.InputTxOut, RemoteMusigCommit,
|
|
)
|
|
|
|
return &MusigPairSession{
|
|
LocalSession: localSession,
|
|
RemoteSession: remoteSession,
|
|
signer: cfg.Signer,
|
|
}
|
|
}
|