2022-03-11 19:00:33 -08:00
|
|
|
// Copyright (c) 2013-2022 The btcsuite developers
|
|
|
|
|
|
|
|
package musig2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrSignerNotInKeySet is returned when a the private key for a signer
|
|
|
|
// isn't included in the set of signing public keys.
|
|
|
|
ErrSignerNotInKeySet = fmt.Errorf("signing key is not found in key" +
|
|
|
|
" set")
|
|
|
|
|
|
|
|
// ErrAlredyHaveAllNonces is called when RegisterPubNonce is called too
|
|
|
|
// many times for a given signing session.
|
|
|
|
ErrAlredyHaveAllNonces = fmt.Errorf("already have all nonces")
|
|
|
|
|
|
|
|
// ErrAlredyHaveAllSigs is called when CombineSig is called too many
|
|
|
|
// times for a given signing session.
|
|
|
|
ErrAlredyHaveAllSigs = fmt.Errorf("already have all sigs")
|
|
|
|
|
|
|
|
// ErrSigningContextReuse is returned if a user attempts to sign using
|
|
|
|
// the same signing context more than once.
|
|
|
|
ErrSigningContextReuse = fmt.Errorf("nonce already used")
|
|
|
|
|
|
|
|
// ErrFinalSigInvalid is returned when the combined signature turns out
|
|
|
|
// to be invalid.
|
|
|
|
ErrFinalSigInvalid = fmt.Errorf("final signature is invalid")
|
|
|
|
|
|
|
|
// ErrCombinedNonceUnavailable is returned when a caller attempts to
|
|
|
|
// sign a partial signature, without first having collected all the
|
|
|
|
// required combined nonces.
|
|
|
|
ErrCombinedNonceUnavailable = fmt.Errorf("missing combined nonce")
|
2022-03-31 19:27:34 -05:00
|
|
|
|
|
|
|
// ErrTaprootInternalKeyUnavailable is returned when a user attempts to
|
|
|
|
// obtain the
|
|
|
|
ErrTaprootInternalKeyUnavailable = fmt.Errorf("taproot tweak not used")
|
2022-03-11 19:00:33 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
// Context is a managed signing context for musig2. It takes care of things
|
|
|
|
// like securely generating secret nonces, aggregating keys and nonces, etc.
|
|
|
|
type Context struct {
|
|
|
|
// signingKey is the key we'll use for signing.
|
|
|
|
signingKey *btcec.PrivateKey
|
|
|
|
|
|
|
|
// pubKey is our even-y coordinate public key.
|
|
|
|
pubKey *btcec.PublicKey
|
|
|
|
|
|
|
|
// keySet is the set of all signers.
|
|
|
|
keySet []*btcec.PublicKey
|
|
|
|
|
|
|
|
// combinedKey is the aggregated public key.
|
2022-03-31 19:27:34 -05:00
|
|
|
combinedKey *AggregateKey
|
2022-03-11 19:00:33 -08:00
|
|
|
|
|
|
|
// uniqueKeyIndex is the index of the second unique key in the keySet.
|
|
|
|
// This is used to speed up signing and verification computations.
|
|
|
|
uniqueKeyIndex int
|
|
|
|
|
|
|
|
// keysHash is the hash of all the keys as defined in musig2.
|
|
|
|
keysHash []byte
|
|
|
|
|
2022-03-31 19:27:34 -05:00
|
|
|
// opts is the set of options for the context.
|
|
|
|
opts *contextOptions
|
2022-03-30 22:52:39 -05:00
|
|
|
|
2022-03-11 19:00:33 -08:00
|
|
|
// shouldSort keeps track of if the public keys should be sorted before
|
|
|
|
// any operations.
|
|
|
|
shouldSort bool
|
|
|
|
}
|
|
|
|
|
2022-03-30 22:52:39 -05:00
|
|
|
// ContextOption is a functional option argument that allows callers to modify
|
|
|
|
// the musig2 signing is done within a context.
|
|
|
|
type ContextOption func(*contextOptions)
|
|
|
|
|
|
|
|
// contextOptions houses the set of functional options that can be used to
|
|
|
|
// musig2 signing protocol.
|
|
|
|
type contextOptions struct {
|
2022-03-31 19:27:34 -05:00
|
|
|
// tweaks is the set of optinoal tweaks to apply to the combined public
|
|
|
|
// key.
|
2022-03-30 22:52:39 -05:00
|
|
|
tweaks []KeyTweakDesc
|
2022-03-31 19:27:34 -05:00
|
|
|
|
|
|
|
// taprootTweak specifies the taproot tweak. If specified, then we'll
|
|
|
|
// use this as the script root for the BIP 341 taproot (x-only) tweak.
|
|
|
|
// Normally we'd just apply the raw 32 byte tweak, but for taproot, we
|
|
|
|
// first need to compute the aggregated key before tweaking, and then
|
|
|
|
// use it as the internal key. This is required as the taproot tweak
|
|
|
|
// also commits to the public key, which in this case is the aggregated
|
|
|
|
// key before the tweak.
|
|
|
|
taprootTweak []byte
|
2022-03-30 22:52:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// defaultContextOptions returns the default context options.
|
|
|
|
func defaultContextOptions() *contextOptions {
|
|
|
|
return &contextOptions{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithTweakedContext specifies that within the context, the aggregated public
|
|
|
|
// key should be tweaked with the specified tweaks.
|
2022-03-31 19:27:34 -05:00
|
|
|
func WithTweakedContext(tweaks ...KeyTweakDesc) ContextOption {
|
2022-03-30 22:52:39 -05:00
|
|
|
return func(o *contextOptions) {
|
|
|
|
o.tweaks = tweaks
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-31 19:27:34 -05:00
|
|
|
// WithTaprootTweakCtx specifies that within this context, the final key should
|
|
|
|
// use the taproot tweak as defined in BIP 341: outputKey = internalKey +
|
|
|
|
// h_tapTweak(internalKey || scriptRoot). In this case, the aggreaged key
|
|
|
|
// before the tweak will be used as the internal key.
|
|
|
|
func WithTaprootTweakCtx(scriptRoot []byte) ContextOption {
|
|
|
|
return func(o *contextOptions) {
|
|
|
|
o.taprootTweak = scriptRoot
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-11 19:00:33 -08:00
|
|
|
// NewContext creates a new signing context with the passed singing key and set
|
|
|
|
// of public keys for each of the other signers.
|
|
|
|
//
|
|
|
|
// NOTE: This struct should be used over the raw Sign API whenever possible.
|
|
|
|
func NewContext(signingKey *btcec.PrivateKey,
|
2022-03-30 22:52:39 -05:00
|
|
|
signers []*btcec.PublicKey, shouldSort bool,
|
|
|
|
ctxOpts ...ContextOption) (*Context, error) {
|
|
|
|
|
|
|
|
// First, parse the set of optional context options.
|
|
|
|
opts := defaultContextOptions()
|
|
|
|
for _, option := range ctxOpts {
|
|
|
|
option(opts)
|
|
|
|
}
|
2022-03-11 19:00:33 -08:00
|
|
|
|
|
|
|
// As a sanity check, make sure the signing key is actually amongst the sit
|
|
|
|
// of signers.
|
|
|
|
//
|
|
|
|
// TODO(roasbeef): instead have pass all the _other_ signers?
|
|
|
|
pubKey, err := schnorr.ParsePubKey(
|
|
|
|
schnorr.SerializePubKey(signingKey.PubKey()),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var keyFound bool
|
|
|
|
for _, key := range signers {
|
|
|
|
if key.IsEqual(pubKey) {
|
|
|
|
keyFound = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !keyFound {
|
|
|
|
return nil, ErrSignerNotInKeySet
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we know that we're actually a signer, we'll generate the
|
|
|
|
// key hash finger print and second unique key index so we can speed up
|
|
|
|
// signing later.
|
|
|
|
keysHash := keyHashFingerprint(signers, shouldSort)
|
|
|
|
uniqueKeyIndex := secondUniqueKeyIndex(signers, shouldSort)
|
|
|
|
|
2022-03-31 19:27:34 -05:00
|
|
|
keyAggOpts := []KeyAggOption{
|
|
|
|
WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
|
|
|
|
}
|
|
|
|
if opts.taprootTweak != nil {
|
|
|
|
keyAggOpts = append(
|
|
|
|
keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
|
|
|
|
)
|
|
|
|
} else if len(opts.tweaks) != 0 {
|
|
|
|
keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
|
|
|
|
}
|
|
|
|
|
2022-03-11 19:00:33 -08:00
|
|
|
// Next, we'll use this information to compute the aggregated public
|
|
|
|
// key that'll be used for signing in practice.
|
2022-03-30 22:52:39 -05:00
|
|
|
combinedKey, _, _, err := AggregateKeys(
|
2022-03-31 19:27:34 -05:00
|
|
|
signers, shouldSort, keyAggOpts...,
|
2022-03-11 19:00:33 -08:00
|
|
|
)
|
2022-03-30 22:52:39 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-11 19:00:33 -08:00
|
|
|
|
|
|
|
return &Context{
|
|
|
|
signingKey: signingKey,
|
|
|
|
pubKey: pubKey,
|
|
|
|
keySet: signers,
|
|
|
|
combinedKey: combinedKey,
|
|
|
|
uniqueKeyIndex: uniqueKeyIndex,
|
|
|
|
keysHash: keysHash,
|
2022-03-31 19:27:34 -05:00
|
|
|
opts: opts,
|
2022-03-11 19:00:33 -08:00
|
|
|
shouldSort: shouldSort,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CombinedKey returns the combined public key that will be used to generate
|
|
|
|
// multi-signatures against.
|
2022-03-31 19:27:34 -05:00
|
|
|
func (c *Context) CombinedKey() *btcec.PublicKey {
|
|
|
|
return c.combinedKey.FinalKey
|
2022-03-11 19:00:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// PubKey returns the public key of the signer of this session.
|
|
|
|
func (c *Context) PubKey() btcec.PublicKey {
|
|
|
|
return *c.pubKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// SigningKeys returns the set of keys used for signing.
|
|
|
|
func (c *Context) SigningKeys() []*btcec.PublicKey {
|
|
|
|
keys := make([]*btcec.PublicKey, len(c.keySet))
|
|
|
|
copy(keys, c.keySet)
|
|
|
|
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
2022-03-31 19:27:34 -05:00
|
|
|
// TaprootInternalKey returns the internal taproot key, which is the aggregated
|
|
|
|
// key _before_ the tweak is applied. If a taproot tweak was specified, then
|
|
|
|
// CombinedKey() will return the fully tweaked output key, with this method
|
|
|
|
// returning the internal key. If a taproot tweak wasn't speciifed, then this
|
|
|
|
// method will return an error.
|
|
|
|
func (c *Context) TaprootInternalKey() (*btcec.PublicKey, error) {
|
|
|
|
if c.opts.taprootTweak == nil {
|
|
|
|
return nil, ErrTaprootInternalKeyUnavailable
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.combinedKey.PreTweakedKey, nil
|
|
|
|
}
|
|
|
|
|
2022-03-11 19:00:33 -08:00
|
|
|
// Session represents a musig2 signing session. A new instance should be
|
|
|
|
// created each time a multi-signature is needed. The session struct handles
|
|
|
|
// nonces management, incremental partial sig vitrifaction, as well as final
|
|
|
|
// signature combination. Errors are returned when unsafe behavior such as
|
|
|
|
// nonce re-use is attempted.
|
|
|
|
//
|
|
|
|
// NOTE: This struct should be used over the raw Sign API whenever possible.
|
|
|
|
type Session struct {
|
|
|
|
ctx *Context
|
|
|
|
|
|
|
|
localNonces *Nonces
|
|
|
|
|
|
|
|
pubNonces [][PubNonceSize]byte
|
|
|
|
|
|
|
|
combinedNonce *[PubNonceSize]byte
|
|
|
|
|
|
|
|
msg [32]byte
|
|
|
|
|
|
|
|
ourSig *PartialSignature
|
|
|
|
sigs []*PartialSignature
|
|
|
|
|
|
|
|
finalSig *schnorr.Signature
|
|
|
|
}
|
|
|
|
|
2022-03-30 22:52:39 -05:00
|
|
|
// TODO(roasbeef): optional arg to allow parsing in pre-generated nonces
|
|
|
|
|
2022-03-11 19:00:33 -08:00
|
|
|
// NewSession creates a new musig2 signing session.
|
|
|
|
func (c *Context) NewSession() (*Session, error) {
|
|
|
|
localNonces, err := GenNonces()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s := &Session{
|
|
|
|
ctx: c,
|
|
|
|
localNonces: localNonces,
|
|
|
|
pubNonces: make([][PubNonceSize]byte, 0, len(c.keySet)),
|
|
|
|
sigs: make([]*PartialSignature, 0, len(c.keySet)),
|
|
|
|
}
|
|
|
|
|
|
|
|
s.pubNonces = append(s.pubNonces, localNonces.PubNonce)
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PublicNonce returns the public nonce for a signer. This should be sent to
|
|
|
|
// other parties before signing begins, so they can compute the aggregated
|
|
|
|
// public nonce.
|
|
|
|
func (s *Session) PublicNonce() [PubNonceSize]byte {
|
|
|
|
return s.localNonces.PubNonce
|
|
|
|
}
|
|
|
|
|
|
|
|
// NumRegisteredNonces returns the total number of nonces that have been
|
|
|
|
// regsitered so far.
|
|
|
|
func (s *Session) NumRegisteredNonces() int {
|
|
|
|
return len(s.pubNonces)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterPubNonce should be called for each public nonce from the set of
|
|
|
|
// signers. This method returns true once all the public nonces have been
|
|
|
|
// accounted for.
|
|
|
|
func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) {
|
|
|
|
// If we already have all the nonces, then this method was called too many
|
|
|
|
// times.
|
|
|
|
haveAllNonces := len(s.pubNonces) == len(s.ctx.keySet)
|
|
|
|
if haveAllNonces {
|
2022-03-31 19:27:34 -05:00
|
|
|
return false, ErrAlredyHaveAllNonces
|
2022-03-11 19:00:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add this nonce and check again if we already have tall the nonces we
|
|
|
|
// need.
|
|
|
|
s.pubNonces = append(s.pubNonces, nonce)
|
|
|
|
haveAllNonces = len(s.pubNonces) == len(s.ctx.keySet)
|
|
|
|
|
|
|
|
// If we have all the nonces, then we can go ahead and combine them
|
|
|
|
// now.
|
|
|
|
if haveAllNonces {
|
|
|
|
combinedNonce, err := AggregateNonces(s.pubNonces)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.combinedNonce = &combinedNonce
|
|
|
|
}
|
|
|
|
|
|
|
|
return haveAllNonces, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sign generates a partial signature for the target message, using the target
|
|
|
|
// context. If this method is called more than once per context, then an error
|
|
|
|
// is returned, as that means a nonce was re-used.
|
|
|
|
func (s *Session) Sign(msg [32]byte,
|
|
|
|
signOpts ...SignOption) (*PartialSignature, error) {
|
|
|
|
|
|
|
|
s.msg = msg
|
|
|
|
|
|
|
|
switch {
|
|
|
|
// If no local nonce is present, then this means we already signed, so
|
|
|
|
// we'll return an error to prevent nonce re-use.
|
|
|
|
case s.localNonces == nil:
|
|
|
|
return nil, ErrSigningContextReuse
|
|
|
|
|
|
|
|
// We also need to make sure we have the combined nonce, otherwise this
|
|
|
|
// funciton was called too early.
|
|
|
|
case s.combinedNonce == nil:
|
|
|
|
return nil, ErrCombinedNonceUnavailable
|
|
|
|
}
|
|
|
|
|
2022-03-31 19:27:34 -05:00
|
|
|
if s.ctx.opts.taprootTweak != nil {
|
|
|
|
signOpts = append(
|
|
|
|
signOpts, WithTaprootSignTweak(s.ctx.opts.taprootTweak),
|
|
|
|
)
|
|
|
|
} else if len(s.ctx.opts.tweaks) != 0 {
|
|
|
|
signOpts = append(signOpts, WithTweaks(s.ctx.opts.tweaks...))
|
2022-03-30 22:52:39 -05:00
|
|
|
}
|
|
|
|
|
2022-03-11 19:00:33 -08:00
|
|
|
partialSig, err := Sign(
|
|
|
|
s.localNonces.SecNonce, s.ctx.signingKey, *s.combinedNonce,
|
|
|
|
s.ctx.keySet, msg, signOpts...,
|
|
|
|
)
|
|
|
|
|
|
|
|
// Now that we've generated our signature, we'll make sure to blank out
|
|
|
|
// our signing nonce.
|
|
|
|
s.localNonces = nil
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.ourSig = partialSig
|
|
|
|
s.sigs = append(s.sigs, partialSig)
|
|
|
|
|
|
|
|
return partialSig, nil
|
|
|
|
}
|
|
|
|
|
2022-03-31 19:27:34 -05:00
|
|
|
// CombineSig buffers a partial signature received from a signing party. The
|
2022-03-11 19:00:33 -08:00
|
|
|
// method returns true once all the signatures are available, and can be
|
|
|
|
// combined into the final signature.
|
|
|
|
func (s *Session) CombineSig(sig *PartialSignature) (bool, error) {
|
|
|
|
// First check if we already have all the signatures we need. We
|
|
|
|
// already accumulated our own signature when we generated the sig.
|
|
|
|
haveAllSigs := len(s.sigs) == len(s.ctx.keySet)
|
|
|
|
if haveAllSigs {
|
|
|
|
return false, ErrAlredyHaveAllSigs
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(roasbeef): incremental check for invalid sig, or just detect at
|
|
|
|
// the very end?
|
|
|
|
|
|
|
|
// Accumulate this sig, and check again if we have all the sigs we
|
|
|
|
// need.
|
|
|
|
s.sigs = append(s.sigs, sig)
|
|
|
|
haveAllSigs = len(s.sigs) == len(s.ctx.keySet)
|
|
|
|
|
|
|
|
// If we have all the signatures, then we can combine them all into the
|
|
|
|
// final signature.
|
|
|
|
if haveAllSigs {
|
2022-03-30 22:52:39 -05:00
|
|
|
var combineOpts []CombineOption
|
2022-03-31 19:27:34 -05:00
|
|
|
if s.ctx.opts.taprootTweak != nil {
|
|
|
|
combineOpts = append(
|
|
|
|
combineOpts, WithTaprootTweakedCombine(
|
|
|
|
s.msg, s.ctx.keySet, s.ctx.opts.taprootTweak,
|
|
|
|
s.ctx.shouldSort,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
} else if len(s.ctx.opts.tweaks) != 0 {
|
2022-03-30 22:52:39 -05:00
|
|
|
combineOpts = append(
|
|
|
|
combineOpts, WithTweakedCombine(
|
2022-03-31 19:27:34 -05:00
|
|
|
s.msg, s.ctx.keySet, s.ctx.opts.tweaks,
|
2022-03-30 22:52:39 -05:00
|
|
|
s.ctx.shouldSort,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
finalSig := CombineSigs(s.ourSig.R, s.sigs, combineOpts...)
|
2022-03-11 19:00:33 -08:00
|
|
|
|
|
|
|
// We'll also verify the signature at this point to ensure it's
|
|
|
|
// valid.
|
|
|
|
//
|
|
|
|
// TODO(roasbef): allow skipping?
|
2022-03-31 19:27:34 -05:00
|
|
|
if !finalSig.Verify(s.msg[:], s.ctx.combinedKey.FinalKey) {
|
2022-03-11 19:00:33 -08:00
|
|
|
return false, ErrFinalSigInvalid
|
|
|
|
}
|
|
|
|
|
|
|
|
s.finalSig = finalSig
|
|
|
|
}
|
|
|
|
|
|
|
|
return haveAllSigs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FinalSig returns the final combined multi-signature, if present.
|
|
|
|
func (s *Session) FinalSig() *schnorr.Signature {
|
|
|
|
return s.finalSig
|
|
|
|
}
|