mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
671 lines
21 KiB
Go
671 lines
21 KiB
Go
// Copyright (c) 2013-2022 The btcsuite developers
|
|
|
|
package musig2v040
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
)
|
|
|
|
var (
|
|
// ErrSignersNotSpecified is returned when a caller attempts to create
|
|
// a context without specifying either the total number of signers, or
|
|
// the complete set of singers.
|
|
ErrSignersNotSpecified = fmt.Errorf("total number of signers or all " +
|
|
"signers must be known")
|
|
|
|
// 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")
|
|
|
|
// ErrNotEnoughSigners is returned when a caller attempts to create a
|
|
// session from a context, but before all the required signers are
|
|
// known.
|
|
ErrNotEnoughSigners = fmt.Errorf("not enough signers")
|
|
|
|
// ErrAlredyHaveAllNonces is returned when a caller attempts to
|
|
// register a signer, once we already have the total set of known
|
|
// signers.
|
|
ErrAlreadyHaveAllSigners = fmt.Errorf("all signers registered")
|
|
|
|
// 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")
|
|
|
|
// ErrTaprootInternalKeyUnavailable is returned when a user attempts to
|
|
// obtain the
|
|
ErrTaprootInternalKeyUnavailable = fmt.Errorf("taproot tweak not used")
|
|
|
|
// ErrNotEnoughSigners is returned if a caller attempts to obtain an
|
|
// early nonce when it wasn't specified
|
|
ErrNoEarlyNonce = fmt.Errorf("no early nonce available")
|
|
)
|
|
|
|
// 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
|
|
|
|
// combinedKey is the aggregated public key.
|
|
combinedKey *AggregateKey
|
|
|
|
// 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
|
|
|
|
// opts is the set of options for the context.
|
|
opts *contextOptions
|
|
|
|
// shouldSort keeps track of if the public keys should be sorted before
|
|
// any operations.
|
|
shouldSort bool
|
|
|
|
// sessionNonce will be populated if the earlyNonce option is true.
|
|
// After the first session is created, this nonce will be blanked out.
|
|
sessionNonce *Nonces
|
|
}
|
|
|
|
// 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 {
|
|
// tweaks is the set of optinoal tweaks to apply to the combined public
|
|
// key.
|
|
tweaks []KeyTweakDesc
|
|
|
|
// 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
|
|
|
|
// bip86Tweak if true, then the weak will just be
|
|
// h_tapTweak(internalKey) as there is no true script root.
|
|
bip86Tweak bool
|
|
|
|
// keySet is the complete set of signers for this context.
|
|
keySet []*btcec.PublicKey
|
|
|
|
// numSigners is the total number of signers that will eventually be a
|
|
// part of the context.
|
|
numSigners int
|
|
|
|
// earlyNonce determines if a nonce should be generated during context
|
|
// creation, to be automatically passed to the created session.
|
|
earlyNonce bool
|
|
}
|
|
|
|
// 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.
|
|
func WithTweakedContext(tweaks ...KeyTweakDesc) ContextOption {
|
|
return func(o *contextOptions) {
|
|
o.tweaks = tweaks
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// WithBip86TweakCtx specifies that within this context, 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
|
|
// aggreaged key before the tweak will be used as the internal key.
|
|
func WithBip86TweakCtx() ContextOption {
|
|
return func(o *contextOptions) {
|
|
o.bip86Tweak = true
|
|
}
|
|
}
|
|
|
|
// WithKnownSigners is an optional parameter that should be used if a session
|
|
// can be created as soon as all the singers are known.
|
|
func WithKnownSigners(signers []*btcec.PublicKey) ContextOption {
|
|
return func(o *contextOptions) {
|
|
o.keySet = signers
|
|
o.numSigners = len(signers)
|
|
}
|
|
}
|
|
|
|
// WithNumSigners is a functional option used to specify that a context should
|
|
// be created without knowing all the signers. Instead the total number of
|
|
// signers is specified to ensure that a session can only be created once all
|
|
// the signers are known.
|
|
//
|
|
// NOTE: Either WithKnownSigners or WithNumSigners MUST be specified.
|
|
func WithNumSigners(n int) ContextOption {
|
|
return func(o *contextOptions) {
|
|
o.numSigners = n
|
|
}
|
|
}
|
|
|
|
// WithEarlyNonceGen allow a caller to specify that a nonce should be generated
|
|
// early, before the session is created. This should be used in protocols that
|
|
// require some partial nonce exchange before all the signers are known.
|
|
//
|
|
// NOTE: This option must only be specified with the WithNumSigners option.
|
|
func WithEarlyNonceGen() ContextOption {
|
|
return func(o *contextOptions) {
|
|
o.earlyNonce = true
|
|
}
|
|
}
|
|
|
|
// 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, shouldSort bool,
|
|
ctxOpts ...ContextOption) (*Context, error) {
|
|
|
|
// First, parse the set of optional context options.
|
|
opts := defaultContextOptions()
|
|
for _, option := range ctxOpts {
|
|
option(opts)
|
|
}
|
|
|
|
pubKey, err := schnorr.ParsePubKey(
|
|
schnorr.SerializePubKey(signingKey.PubKey()),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctx := &Context{
|
|
signingKey: signingKey,
|
|
pubKey: pubKey,
|
|
opts: opts,
|
|
shouldSort: shouldSort,
|
|
}
|
|
|
|
switch {
|
|
|
|
// We know all the signers, so we can compute the aggregated key, along
|
|
// with all the other intermediate state we need to do signing and
|
|
// verification.
|
|
case opts.keySet != nil:
|
|
if err := ctx.combineSignerKeys(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The total signers are known, so we add ourselves, and skip key
|
|
// aggregation.
|
|
case opts.numSigners != 0:
|
|
// Otherwise, we'll add ourselves as the only known signer, and
|
|
// await further calls to RegisterSigner before a session can
|
|
// be created.
|
|
opts.keySet = make([]*btcec.PublicKey, 0, opts.numSigners)
|
|
opts.keySet = append(opts.keySet, pubKey)
|
|
|
|
// If early nonce generation is specified, then we'll generate
|
|
// the nonce now to pass in to the session once all the callers
|
|
// are known.
|
|
if opts.earlyNonce {
|
|
ctx.sessionNonce, err = GenNonces()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
default:
|
|
return nil, ErrSignersNotSpecified
|
|
}
|
|
|
|
return ctx, nil
|
|
}
|
|
|
|
// combineSignerKeys is used to compute the aggregated signer key once all the
|
|
// signers are known.
|
|
func (c *Context) combineSignerKeys() error {
|
|
// As a sanity check, make sure the signing key is actually
|
|
// amongst the sit of signers.
|
|
var keyFound bool
|
|
for _, key := range c.opts.keySet {
|
|
if key.IsEqual(c.pubKey) {
|
|
keyFound = true
|
|
break
|
|
}
|
|
}
|
|
if !keyFound {
|
|
return 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.
|
|
c.keysHash = keyHashFingerprint(c.opts.keySet, c.shouldSort)
|
|
c.uniqueKeyIndex = secondUniqueKeyIndex(
|
|
c.opts.keySet, c.shouldSort,
|
|
)
|
|
|
|
keyAggOpts := []KeyAggOption{
|
|
WithKeysHash(c.keysHash),
|
|
WithUniqueKeyIndex(c.uniqueKeyIndex),
|
|
}
|
|
switch {
|
|
case c.opts.bip86Tweak:
|
|
keyAggOpts = append(
|
|
keyAggOpts, WithBIP86KeyTweak(),
|
|
)
|
|
case c.opts.taprootTweak != nil:
|
|
keyAggOpts = append(
|
|
keyAggOpts, WithTaprootKeyTweak(c.opts.taprootTweak),
|
|
)
|
|
case len(c.opts.tweaks) != 0:
|
|
keyAggOpts = append(keyAggOpts, WithKeyTweaks(c.opts.tweaks...))
|
|
}
|
|
|
|
// Next, we'll use this information to compute the aggregated
|
|
// public key that'll be used for signing in practice.
|
|
var err error
|
|
c.combinedKey, _, _, err = AggregateKeys(
|
|
c.opts.keySet, c.shouldSort, keyAggOpts...,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EarlySessionNonce returns the early session nonce, if available.
|
|
func (c *Context) EarlySessionNonce() (*Nonces, error) {
|
|
if c.sessionNonce == nil {
|
|
return nil, ErrNoEarlyNonce
|
|
}
|
|
|
|
return c.sessionNonce, nil
|
|
}
|
|
|
|
// RegisterSigner allows a caller to register a signer after the context has
|
|
// been created. This will be used in scenarios where the total number of
|
|
// signers is known, but nonce exchange needs to happen before all the signers
|
|
// are known.
|
|
//
|
|
// A bool is returned which indicates if all the signers have been registered.
|
|
//
|
|
// NOTE: If the set of keys are not to be sorted during signing, then the
|
|
// ordering each key is registered with MUST match the desired ordering.
|
|
func (c *Context) RegisterSigner(pub *btcec.PublicKey) (bool, error) {
|
|
haveAllSigners := len(c.opts.keySet) == c.opts.numSigners
|
|
if haveAllSigners {
|
|
return false, ErrAlreadyHaveAllSigners
|
|
}
|
|
|
|
c.opts.keySet = append(c.opts.keySet, pub)
|
|
|
|
// If we have the expected number of signers at this point, then we can
|
|
// generate the aggregated key and other necessary information.
|
|
haveAllSigners = len(c.opts.keySet) == c.opts.numSigners
|
|
if haveAllSigners {
|
|
if err := c.combineSignerKeys(); err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
return haveAllSigners, nil
|
|
}
|
|
|
|
// NumRegisteredSigners returns the total number of registered signers.
|
|
func (c *Context) NumRegisteredSigners() int {
|
|
return len(c.opts.keySet)
|
|
}
|
|
|
|
// CombinedKey returns the combined public key that will be used to generate
|
|
// multi-signatures against.
|
|
func (c *Context) CombinedKey() (*btcec.PublicKey, error) {
|
|
// If the caller hasn't registered all the signers at this point, then
|
|
// the combined key won't be available.
|
|
if c.combinedKey == nil {
|
|
return nil, ErrNotEnoughSigners
|
|
}
|
|
|
|
return c.combinedKey.FinalKey, nil
|
|
}
|
|
|
|
// 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.opts.keySet))
|
|
copy(keys, c.opts.keySet)
|
|
|
|
return keys
|
|
}
|
|
|
|
// 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 specified, then this
|
|
// method will return an error.
|
|
func (c *Context) TaprootInternalKey() (*btcec.PublicKey, error) {
|
|
// If the caller hasn't registered all the signers at this point, then
|
|
// the combined key won't be available.
|
|
if c.combinedKey == nil {
|
|
return nil, ErrNotEnoughSigners
|
|
}
|
|
|
|
if c.opts.taprootTweak == nil && !c.opts.bip86Tweak {
|
|
return nil, ErrTaprootInternalKeyUnavailable
|
|
}
|
|
|
|
return c.combinedKey.PreTweakedKey, nil
|
|
}
|
|
|
|
// SessionOption is a functional option argument that allows callers to modify
|
|
// the musig2 signing is done within a session.
|
|
type SessionOption func(*sessionOptions)
|
|
|
|
// sessionOptions houses the set of functional options that can be used to
|
|
// modify the musig2 signing protocol.
|
|
type sessionOptions struct {
|
|
externalNonce *Nonces
|
|
}
|
|
|
|
// defaultSessionOptions returns the default session options.
|
|
func defaultSessionOptions() *sessionOptions {
|
|
return &sessionOptions{}
|
|
}
|
|
|
|
// WithPreGeneratedNonce allows a caller to start a session using a nonce
|
|
// they've generated themselves. This may be useful in protocols where all the
|
|
// signer keys may not be known before nonce exchange needs to occur.
|
|
func WithPreGeneratedNonce(nonce *Nonces) SessionOption {
|
|
return func(o *sessionOptions) {
|
|
o.externalNonce = nonce
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
opts *sessionOptions
|
|
|
|
ctx *Context
|
|
|
|
localNonces *Nonces
|
|
|
|
pubNonces [][PubNonceSize]byte
|
|
|
|
combinedNonce *[PubNonceSize]byte
|
|
|
|
msg [32]byte
|
|
|
|
ourSig *PartialSignature
|
|
sigs []*PartialSignature
|
|
|
|
finalSig *schnorr.Signature
|
|
}
|
|
|
|
// NewSession creates a new musig2 signing session.
|
|
func (c *Context) NewSession(options ...SessionOption) (*Session, error) {
|
|
opts := defaultSessionOptions()
|
|
for _, opt := range options {
|
|
opt(opts)
|
|
}
|
|
|
|
// At this point we verify that we know of all the signers, as
|
|
// otherwise we can't proceed with the session. This check is intended
|
|
// to catch misuse of the API wherein a caller forgets to register the
|
|
// remaining signers if they're doing nonce generation ahead of time.
|
|
if len(c.opts.keySet) != c.opts.numSigners {
|
|
return nil, ErrNotEnoughSigners
|
|
}
|
|
|
|
// If an early nonce was specified, then we'll automatically add the
|
|
// corresponding session option for the caller.
|
|
var localNonces *Nonces
|
|
if c.sessionNonce != nil {
|
|
// Apply the early nonce to the session, and also blank out the
|
|
// session nonce on the context to ensure it isn't ever re-used
|
|
// for another session.
|
|
localNonces = c.sessionNonce
|
|
c.sessionNonce = nil
|
|
} else if opts.externalNonce != nil {
|
|
// Otherwise if there's a custom nonce passed in via the
|
|
// session options, then use that instead.
|
|
localNonces = opts.externalNonce
|
|
}
|
|
|
|
// Now that we know we have enough signers, we'll either use the caller
|
|
// specified nonce, or generate a fresh set.
|
|
var err error
|
|
if localNonces == nil {
|
|
// At this point we need to generate a fresh nonce. We'll pass
|
|
// in some auxiliary information to strengthen the nonce
|
|
// generated.
|
|
localNonces, err = GenNonces(
|
|
WithNonceSecretKeyAux(c.signingKey),
|
|
WithNonceCombinedKeyAux(c.combinedKey.FinalKey),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
s := &Session{
|
|
opts: opts,
|
|
ctx: c,
|
|
localNonces: localNonces,
|
|
pubNonces: make([][PubNonceSize]byte, 0, c.opts.numSigners),
|
|
sigs: make([]*PartialSignature, 0, c.opts.numSigners),
|
|
}
|
|
|
|
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) == s.ctx.opts.numSigners
|
|
if haveAllNonces {
|
|
return false, ErrAlredyHaveAllNonces
|
|
}
|
|
|
|
// 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) == s.ctx.opts.numSigners
|
|
|
|
// 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) {
|
|
|
|
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
|
|
// function was called too early.
|
|
case s.combinedNonce == nil:
|
|
return nil, ErrCombinedNonceUnavailable
|
|
}
|
|
|
|
switch {
|
|
case s.ctx.opts.bip86Tweak:
|
|
signOpts = append(
|
|
signOpts, WithBip86SignTweak(),
|
|
)
|
|
case s.ctx.opts.taprootTweak != nil:
|
|
signOpts = append(
|
|
signOpts, WithTaprootSignTweak(s.ctx.opts.taprootTweak),
|
|
)
|
|
case len(s.ctx.opts.tweaks) != 0:
|
|
signOpts = append(signOpts, WithTweaks(s.ctx.opts.tweaks...))
|
|
}
|
|
|
|
partialSig, err := Sign(
|
|
s.localNonces.SecNonce, s.ctx.signingKey, *s.combinedNonce,
|
|
s.ctx.opts.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.msg = msg
|
|
|
|
s.ourSig = partialSig
|
|
s.sigs = append(s.sigs, partialSig)
|
|
|
|
return partialSig, nil
|
|
}
|
|
|
|
// CombineSig buffers a partial signature received from a signing party. The
|
|
// 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.opts.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.opts.keySet)
|
|
|
|
// If we have all the signatures, then we can combine them all into the
|
|
// final signature.
|
|
if haveAllSigs {
|
|
var combineOpts []CombineOption
|
|
switch {
|
|
case s.ctx.opts.bip86Tweak:
|
|
combineOpts = append(
|
|
combineOpts, WithBip86TweakedCombine(
|
|
s.msg, s.ctx.opts.keySet,
|
|
s.ctx.shouldSort,
|
|
),
|
|
)
|
|
case s.ctx.opts.taprootTweak != nil:
|
|
combineOpts = append(
|
|
combineOpts, WithTaprootTweakedCombine(
|
|
s.msg, s.ctx.opts.keySet,
|
|
s.ctx.opts.taprootTweak, s.ctx.shouldSort,
|
|
),
|
|
)
|
|
case len(s.ctx.opts.tweaks) != 0:
|
|
combineOpts = append(
|
|
combineOpts, WithTweakedCombine(
|
|
s.msg, s.ctx.opts.keySet,
|
|
s.ctx.opts.tweaks, s.ctx.shouldSort,
|
|
),
|
|
)
|
|
}
|
|
|
|
finalSig := CombineSigs(s.ourSig.R, s.sigs, combineOpts...)
|
|
|
|
// We'll also verify the signature at this point to ensure it's
|
|
// valid.
|
|
//
|
|
// TODO(roasbef): allow skipping?
|
|
if !finalSig.Verify(s.msg[:], s.ctx.combinedKey.FinalKey) {
|
|
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
|
|
}
|