mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-14 02:15:29 +02:00
input+btcwallet: add MuSig2 signing operations
With this commit we add the high-level MuSig2 signing methods to the btcwallet which will later be exposed through an RPC interface.
This commit is contained in:
228
input/musig2.go
Normal file
228
input/musig2.go
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
package input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||||
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MuSig2PartialSigSize is the size of a MuSig2 partial signature.
|
||||||
|
// Because a partial signature is just the s value, this corresponds to
|
||||||
|
// the length of a scalar.
|
||||||
|
MuSig2PartialSigSize = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// MuSig2SessionID is a type for a session ID that is just a hash of the MuSig2
|
||||||
|
// combined key and the local public nonces.
|
||||||
|
type MuSig2SessionID [sha256.Size]byte
|
||||||
|
|
||||||
|
// MuSig2Signer is an interface that declares all methods that a MuSig2
|
||||||
|
// compatible signer needs to implement.
|
||||||
|
type MuSig2Signer interface {
|
||||||
|
// MuSig2CreateSession creates a new MuSig2 signing session using the
|
||||||
|
// local key identified by the key locator. The complete list of all
|
||||||
|
// public keys of all signing parties must be provided, including the
|
||||||
|
// public key of the local signing key. If nonces of other parties are
|
||||||
|
// already known, they can be submitted as well to reduce the number of
|
||||||
|
// method calls necessary later on.
|
||||||
|
MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey,
|
||||||
|
*MuSig2Tweaks, [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo,
|
||||||
|
error)
|
||||||
|
|
||||||
|
// MuSig2RegisterNonces registers one or more public nonces of other
|
||||||
|
// signing participants for a session identified by its ID. This method
|
||||||
|
// returns true once we have all nonces for all other signing
|
||||||
|
// participants.
|
||||||
|
MuSig2RegisterNonces(MuSig2SessionID,
|
||||||
|
[][musig2.PubNonceSize]byte) (bool, error)
|
||||||
|
|
||||||
|
// MuSig2Sign creates a partial signature using the local signing key
|
||||||
|
// that was specified when the session was created. This can only be
|
||||||
|
// called when all public nonces of all participants are known and have
|
||||||
|
// been registered with the session. If this node isn't responsible for
|
||||||
|
// combining all the partial signatures, then the cleanup parameter
|
||||||
|
// should be set, indicating that the session can be removed from memory
|
||||||
|
// once the signature was produced.
|
||||||
|
MuSig2Sign(MuSig2SessionID, [sha256.Size]byte,
|
||||||
|
bool) (*musig2.PartialSignature, error)
|
||||||
|
|
||||||
|
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||||
|
// local one, if it already exists. Once a partial signature of all
|
||||||
|
// participants is registered, the final signature will be combined and
|
||||||
|
// returned.
|
||||||
|
MuSig2CombineSig(MuSig2SessionID,
|
||||||
|
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuSig2SessionInfo is a struct for keeping track of a signing session
|
||||||
|
// information in memory.
|
||||||
|
type MuSig2SessionInfo struct {
|
||||||
|
// SessionID is the wallet's internal unique ID of this session. The ID
|
||||||
|
// is the hash over the combined public key and the local public nonces.
|
||||||
|
SessionID [32]byte
|
||||||
|
|
||||||
|
// PublicNonce contains the public nonce of the local signer session.
|
||||||
|
PublicNonce [musig2.PubNonceSize]byte
|
||||||
|
|
||||||
|
// CombinedKey is the combined public key with all tweaks applied to it.
|
||||||
|
CombinedKey *btcec.PublicKey
|
||||||
|
|
||||||
|
// TaprootTweak indicates whether a taproot tweak (BIP-0086 or script
|
||||||
|
// path) was used. The TaprootInternalKey will only be set if this is
|
||||||
|
// set to true.
|
||||||
|
TaprootTweak bool
|
||||||
|
|
||||||
|
// TaprootInternalKey is the raw combined public key without any tweaks
|
||||||
|
// applied to it. This is only set if TaprootTweak is true.
|
||||||
|
TaprootInternalKey *btcec.PublicKey
|
||||||
|
|
||||||
|
// HaveAllNonces indicates whether this session already has all nonces
|
||||||
|
// of all other signing participants registered.
|
||||||
|
HaveAllNonces bool
|
||||||
|
|
||||||
|
// HaveAllSigs indicates whether this session already has all partial
|
||||||
|
// signatures of all other signing participants registered.
|
||||||
|
HaveAllSigs bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuSig2Tweaks is a struct that contains all tweaks that can be applied to a
|
||||||
|
// MuSig2 combined public key.
|
||||||
|
type MuSig2Tweaks struct {
|
||||||
|
// GenericTweaks is a list of normal tweaks to apply to the combined
|
||||||
|
// public key (and to the private key when signing).
|
||||||
|
GenericTweaks []musig2.KeyTweakDesc
|
||||||
|
|
||||||
|
// TaprootBIP0086Tweak indicates that the final key should use the
|
||||||
|
// taproot tweak as defined in BIP 341, with the BIP 86 modification:
|
||||||
|
// outputKey = internalKey + h_tapTweak(internalKey)*G.
|
||||||
|
// In this case, the aggregated key before the tweak will be used as the
|
||||||
|
// internal key. If this is set to true then TaprootTweak will be
|
||||||
|
// ignored.
|
||||||
|
TaprootBIP0086Tweak bool
|
||||||
|
|
||||||
|
// TaprootTweak specifies that the final key should use the taproot
|
||||||
|
// tweak as defined in BIP 341:
|
||||||
|
// outputKey = internalKey + h_tapTweak(internalKey || scriptRoot).
|
||||||
|
// In this case, the aggregated key before the tweak will be used as the
|
||||||
|
// internal key. Will be ignored if TaprootBIP0086Tweak is set to true.
|
||||||
|
TaprootTweak []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasTaprootTweak returns true if either a taproot BIP0086 tweak or a taproot
|
||||||
|
// script root tweak is set.
|
||||||
|
func (t *MuSig2Tweaks) HasTaprootTweak() bool {
|
||||||
|
return t.TaprootBIP0086Tweak || len(t.TaprootTweak) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToContextOptions converts the tweak descriptor to context options.
|
||||||
|
func (t *MuSig2Tweaks) ToContextOptions() []musig2.ContextOption {
|
||||||
|
var tweakOpts []musig2.ContextOption
|
||||||
|
if len(t.GenericTweaks) > 0 {
|
||||||
|
tweakOpts = append(tweakOpts, musig2.WithTweakedContext(
|
||||||
|
t.GenericTweaks...,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The BIP0086 tweak and the taproot script tweak are mutually
|
||||||
|
// exclusive.
|
||||||
|
if t.TaprootBIP0086Tweak {
|
||||||
|
tweakOpts = append(tweakOpts, musig2.WithBip86TweakCtx())
|
||||||
|
} else if len(t.TaprootTweak) > 0 {
|
||||||
|
tweakOpts = append(tweakOpts, musig2.WithTaprootTweakCtx(
|
||||||
|
t.TaprootTweak,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return tweakOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuSig2CombineKeys combines the given set of public keys into a single
|
||||||
|
// combined MuSig2 combined public key, applying the given tweaks.
|
||||||
|
func MuSig2CombineKeys(allSignerPubKeys []*btcec.PublicKey,
|
||||||
|
tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
|
||||||
|
|
||||||
|
// Convert the tweak options into the appropriate MuSig2 API functional
|
||||||
|
// options.
|
||||||
|
var keyAggOpts []musig2.KeyAggOption
|
||||||
|
switch {
|
||||||
|
case tweaks.TaprootBIP0086Tweak:
|
||||||
|
keyAggOpts = append(keyAggOpts, musig2.WithBIP86KeyTweak())
|
||||||
|
case len(tweaks.TaprootTweak) > 0:
|
||||||
|
keyAggOpts = append(keyAggOpts, musig2.WithTaprootKeyTweak(
|
||||||
|
tweaks.TaprootTweak,
|
||||||
|
))
|
||||||
|
case len(tweaks.GenericTweaks) > 0:
|
||||||
|
keyAggOpts = append(keyAggOpts, musig2.WithKeyTweaks(
|
||||||
|
tweaks.GenericTweaks...,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then we'll use this information to compute the aggregated public key.
|
||||||
|
combinedKey, _, _, err := musig2.AggregateKeys(
|
||||||
|
allSignerPubKeys, true, keyAggOpts...,
|
||||||
|
)
|
||||||
|
return combinedKey, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMuSig2SessionID returns the unique ID of a MuSig2 session by using the
|
||||||
|
// combined key and the local public nonces and hashing that data.
|
||||||
|
func NewMuSig2SessionID(combinedKey *btcec.PublicKey,
|
||||||
|
publicNonces [musig2.PubNonceSize]byte) MuSig2SessionID {
|
||||||
|
|
||||||
|
// We hash the data to save some bytes in memory.
|
||||||
|
hash := sha256.New()
|
||||||
|
_, _ = hash.Write(combinedKey.SerializeCompressed())
|
||||||
|
_, _ = hash.Write(publicNonces[:])
|
||||||
|
|
||||||
|
id := MuSig2SessionID{}
|
||||||
|
copy(id[:], hash.Sum(nil))
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializePartialSignature encodes the partial signature to a fixed size byte
|
||||||
|
// array.
|
||||||
|
func SerializePartialSignature(
|
||||||
|
sig *musig2.PartialSignature) ([MuSig2PartialSigSize]byte, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
buf bytes.Buffer
|
||||||
|
result [MuSig2PartialSigSize]byte
|
||||||
|
)
|
||||||
|
if err := sig.Encode(&buf); err != nil {
|
||||||
|
return result, fmt.Errorf("error encoding partial signature: "+
|
||||||
|
"%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.Len() != MuSig2PartialSigSize {
|
||||||
|
return result, fmt.Errorf("invalid partial signature length, "+
|
||||||
|
"got %d wanted %d", buf.Len(), MuSig2PartialSigSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(result[:], buf.Bytes())
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeserializePartialSignature decodes a partial signature from a byte slice.
|
||||||
|
func DeserializePartialSignature(scalarBytes []byte) (*musig2.PartialSignature,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
if len(scalarBytes) != MuSig2PartialSigSize {
|
||||||
|
return nil, fmt.Errorf("invalid partial signature length, got "+
|
||||||
|
"%d wanted %d", len(scalarBytes), MuSig2PartialSigSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := &musig2.PartialSignature{}
|
||||||
|
if err := sig.Decode(bytes.NewReader(scalarBytes)); err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding partial signature: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig, nil
|
||||||
|
}
|
@@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/btcsuite/btcwallet/walletdb"
|
"github.com/btcsuite/btcwallet/walletdb"
|
||||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||||
"github.com/lightningnetwork/lnd/blockcache"
|
"github.com/lightningnetwork/lnd/blockcache"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
@@ -98,6 +99,9 @@ type BtcWallet struct {
|
|||||||
chainKeyScope waddrmgr.KeyScope
|
chainKeyScope waddrmgr.KeyScope
|
||||||
|
|
||||||
blockCache *blockcache.BlockCache
|
blockCache *blockcache.BlockCache
|
||||||
|
|
||||||
|
musig2Sessions map[input.MuSig2SessionID]*muSig2State
|
||||||
|
musig2SessionsMtx sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile time check to ensure that BtcWallet implements the
|
// A compile time check to ensure that BtcWallet implements the
|
||||||
@@ -160,13 +164,14 @@ func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &BtcWallet{
|
return &BtcWallet{
|
||||||
cfg: &cfg,
|
cfg: &cfg,
|
||||||
wallet: wallet,
|
wallet: wallet,
|
||||||
db: wallet.Database(),
|
db: wallet.Database(),
|
||||||
chain: cfg.ChainSource,
|
chain: cfg.ChainSource,
|
||||||
netParams: cfg.NetParams,
|
netParams: cfg.NetParams,
|
||||||
chainKeyScope: chainKeyScope,
|
chainKeyScope: chainKeyScope,
|
||||||
blockCache: blockCache,
|
blockCache: blockCache,
|
||||||
|
musig2Sessions: make(map[input.MuSig2SessionID]*muSig2State),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
package btcwallet
|
package btcwallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||||
"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/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
@@ -451,6 +453,257 @@ func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// muSig2State is a struct that holds on to the internal signing session state
|
||||||
|
// of a MuSig2 session.
|
||||||
|
type muSig2State struct {
|
||||||
|
// MuSig2SessionInfo is the associated meta information of the signing
|
||||||
|
// session.
|
||||||
|
input.MuSig2SessionInfo
|
||||||
|
|
||||||
|
// context is the signing context responsible for keeping track of the
|
||||||
|
// public keys involved in the signing process.
|
||||||
|
context *musig2.Context
|
||||||
|
|
||||||
|
// session is the signing session responsible for keeping track of the
|
||||||
|
// nonces and partial signatures involved in the signing process.
|
||||||
|
session *musig2.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuSig2CreateSession creates a new MuSig2 signing session using the local
|
||||||
|
// key identified by the key locator. The complete list of all public keys of
|
||||||
|
// all signing parties must be provided, including the public key of the local
|
||||||
|
// signing key. If nonces of other parties are already known, they can be
|
||||||
|
// submitted as well to reduce the number of method calls necessary later on.
|
||||||
|
func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator,
|
||||||
|
allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks,
|
||||||
|
otherSignerNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
// We need to derive the private key for signing. In the remote signing
|
||||||
|
// setup, this whole RPC call will be forwarded to the signing
|
||||||
|
// instance, which requires it to be stateful.
|
||||||
|
privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
|
||||||
|
KeyLocator: keyLoc,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error deriving private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The context keeps track of all signing keys and our local key.
|
||||||
|
allOpts := append(
|
||||||
|
[]musig2.ContextOption{
|
||||||
|
musig2.WithKnownSigners(allSignerPubKeys),
|
||||||
|
},
|
||||||
|
tweaks.ToContextOptions()...,
|
||||||
|
)
|
||||||
|
musigContext, err := musig2.NewContext(privKey, true, allOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating MuSig2 signing "+
|
||||||
|
"context: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The session keeps track of the own and other nonces.
|
||||||
|
musigSession, err := musigContext.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating MuSig2 signing "+
|
||||||
|
"session: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all nonces we might've learned so far.
|
||||||
|
haveAllNonces := false
|
||||||
|
for _, otherSignerNonce := range otherSignerNonces {
|
||||||
|
haveAllNonces, err = musigSession.RegisterPubNonce(
|
||||||
|
otherSignerNonce,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error registering other "+
|
||||||
|
"signer public nonce: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the new session.
|
||||||
|
combinedKey, err := musigContext.CombinedKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting combined key: %v", err)
|
||||||
|
}
|
||||||
|
session := &muSig2State{
|
||||||
|
MuSig2SessionInfo: input.MuSig2SessionInfo{
|
||||||
|
SessionID: input.NewMuSig2SessionID(
|
||||||
|
combinedKey, musigSession.PublicNonce(),
|
||||||
|
),
|
||||||
|
PublicNonce: musigSession.PublicNonce(),
|
||||||
|
CombinedKey: combinedKey,
|
||||||
|
TaprootTweak: tweaks.HasTaprootTweak(),
|
||||||
|
HaveAllNonces: haveAllNonces,
|
||||||
|
},
|
||||||
|
context: musigContext,
|
||||||
|
session: musigSession,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The internal key is only calculated if we are using a taproot tweak
|
||||||
|
// and need to know it for a potential script spend.
|
||||||
|
if tweaks.HasTaprootTweak() {
|
||||||
|
internalKey, err := musigContext.TaprootInternalKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting internal key: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
session.TaprootInternalKey = internalKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we generate new nonces for every session, there is no way that
|
||||||
|
// a session with the same ID already exists. So even if we call the API
|
||||||
|
// twice with the same signers, we still get a new ID.
|
||||||
|
b.musig2SessionsMtx.Lock()
|
||||||
|
b.musig2Sessions[session.SessionID] = session
|
||||||
|
b.musig2SessionsMtx.Unlock()
|
||||||
|
|
||||||
|
return &session.MuSig2SessionInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuSig2RegisterNonces registers one or more public nonces of other signing
|
||||||
|
// participants for a session identified by its ID. This method returns true
|
||||||
|
// once we have all nonces for all other signing participants.
|
||||||
|
func (b *BtcWallet) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
|
||||||
|
otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) {
|
||||||
|
|
||||||
|
// We hold the lock during the whole operation, we don't want any
|
||||||
|
// interference with calls that might come through in parallel for the
|
||||||
|
// same session.
|
||||||
|
b.musig2SessionsMtx.Lock()
|
||||||
|
defer b.musig2SessionsMtx.Unlock()
|
||||||
|
|
||||||
|
session, ok := b.musig2Sessions[sessionID]
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("session with ID %x not found",
|
||||||
|
sessionID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we don't exceed the number of expected nonces as that would
|
||||||
|
// indicate something is wrong with the signing setup.
|
||||||
|
if session.HaveAllNonces {
|
||||||
|
return true, fmt.Errorf("already have all nonces")
|
||||||
|
}
|
||||||
|
|
||||||
|
numSigners := len(session.context.SigningKeys())
|
||||||
|
remainingNonces := numSigners - session.session.NumRegisteredNonces()
|
||||||
|
if len(otherSignerNonces) > remainingNonces {
|
||||||
|
return false, fmt.Errorf("only %d other nonces remaining but "+
|
||||||
|
"trying to register %d more", remainingNonces,
|
||||||
|
len(otherSignerNonces))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all nonces we've learned so far.
|
||||||
|
var err error
|
||||||
|
for _, otherSignerNonce := range otherSignerNonces {
|
||||||
|
session.HaveAllNonces, err = session.session.RegisterPubNonce(
|
||||||
|
otherSignerNonce,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("error registering other "+
|
||||||
|
"signer public nonce: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.HaveAllNonces, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuSig2Sign creates a partial signature using the local signing key
|
||||||
|
// that was specified when the session was created. This can only be
|
||||||
|
// called when all public nonces of all participants are known and have
|
||||||
|
// been registered with the session. If this node isn't responsible for
|
||||||
|
// combining all the partial signatures, then the cleanup parameter
|
||||||
|
// should be set, indicating that the session can be removed from memory
|
||||||
|
// once the signature was produced.
|
||||||
|
func (b *BtcWallet) MuSig2Sign(sessionID input.MuSig2SessionID,
|
||||||
|
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
|
||||||
|
|
||||||
|
// We hold the lock during the whole operation, we don't want any
|
||||||
|
// interference with calls that might come through in parallel for the
|
||||||
|
// same session.
|
||||||
|
b.musig2SessionsMtx.Lock()
|
||||||
|
defer b.musig2SessionsMtx.Unlock()
|
||||||
|
|
||||||
|
session, ok := b.musig2Sessions[sessionID]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("session with ID %x not found",
|
||||||
|
sessionID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can only sign once we have all other signer's nonces.
|
||||||
|
if !session.HaveAllNonces {
|
||||||
|
return nil, fmt.Errorf("only have %d of %d required nonces",
|
||||||
|
session.session.NumRegisteredNonces(),
|
||||||
|
len(session.context.SigningKeys()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our own partial signature with the local signing key.
|
||||||
|
partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error signing with local key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up our local state if requested.
|
||||||
|
if cleanUp {
|
||||||
|
delete(b.musig2Sessions, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return partialSig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||||
|
// local one, if it already exists. Once a partial signature of all
|
||||||
|
// participants is registered, the final signature will be combined and
|
||||||
|
// returned.
|
||||||
|
func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID,
|
||||||
|
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
// We hold the lock during the whole operation, we don't want any
|
||||||
|
// interference with calls that might come through in parallel for the
|
||||||
|
// same session.
|
||||||
|
b.musig2SessionsMtx.Lock()
|
||||||
|
defer b.musig2SessionsMtx.Unlock()
|
||||||
|
|
||||||
|
session, ok := b.musig2Sessions[sessionID]
|
||||||
|
if !ok {
|
||||||
|
return nil, false, fmt.Errorf("session with ID %x not found",
|
||||||
|
sessionID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we don't exceed the number of expected partial signatures
|
||||||
|
// as that would indicate something is wrong with the signing setup.
|
||||||
|
if session.HaveAllSigs {
|
||||||
|
return nil, true, fmt.Errorf("already have all partial" +
|
||||||
|
"signatures")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all sigs we got so far.
|
||||||
|
var (
|
||||||
|
finalSig *schnorr.Signature
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, otherPartialSig := range partialSigs {
|
||||||
|
session.HaveAllSigs, err = session.session.CombineSig(
|
||||||
|
otherPartialSig,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("error combining "+
|
||||||
|
"partial signature: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have all partial signatures, we should be able to get the
|
||||||
|
// complete signature now. We also remove this session from memory since
|
||||||
|
// there is nothing more left to do.
|
||||||
|
if session.HaveAllSigs {
|
||||||
|
finalSig = session.session.FinalSig()
|
||||||
|
delete(b.musig2Sessions, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalSig, session.HaveAllSigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// A compile time check to ensure that BtcWallet implements the Signer
|
// A compile time check to ensure that BtcWallet implements the Signer
|
||||||
// interface.
|
// interface.
|
||||||
var _ input.Signer = (*BtcWallet)(nil)
|
var _ input.Signer = (*BtcWallet)(nil)
|
||||||
|
Reference in New Issue
Block a user