// 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
}
