2023-01-27 16:13:10 +01:00
|
|
|
// Copyright 2013-2022 The btcsuite developers
|
|
|
|
|
2023-01-27 16:13:11 +01:00
|
|
|
package musig2v040
|
2023-01-27 16:13:10 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
|
|
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// NonceBlindTag is that tag used to construct the value b, which
|
|
|
|
// blinds the second public nonce of each party.
|
|
|
|
NonceBlindTag = []byte("MuSig/noncecoef")
|
|
|
|
|
|
|
|
// ChallengeHashTag is the tag used to construct the challenge hash
|
|
|
|
ChallengeHashTag = []byte("BIP0340/challenge")
|
|
|
|
|
|
|
|
// ErrNoncePointAtInfinity is returned if during signing, the fully
|
|
|
|
// combined public nonce is the point at infinity.
|
|
|
|
ErrNoncePointAtInfinity = fmt.Errorf("signing nonce is the infinity " +
|
|
|
|
"point")
|
|
|
|
|
|
|
|
// ErrPrivKeyZero is returned when the private key for signing is
|
|
|
|
// actually zero.
|
|
|
|
ErrPrivKeyZero = fmt.Errorf("priv key is zero")
|
|
|
|
|
|
|
|
// ErrPartialSigInvalid is returned when a partial is found to be
|
|
|
|
// invalid.
|
|
|
|
ErrPartialSigInvalid = fmt.Errorf("partial signature is invalid")
|
|
|
|
|
|
|
|
// ErrSecretNonceZero is returned when a secret nonce is passed in a
|
|
|
|
// zero.
|
|
|
|
ErrSecretNonceZero = fmt.Errorf("secret nonce is blank")
|
|
|
|
)
|
|
|
|
|
|
|
|
// infinityPoint is the jacobian representation of the point at infinity.
|
|
|
|
var infinityPoint btcec.JacobianPoint
|
|
|
|
|
|
|
|
// PartialSignature reprints a partial (s-only) musig2 multi-signature. This
|
|
|
|
// isn't a valid schnorr signature by itself, as it needs to be aggregated
|
|
|
|
// along with the other partial signatures to be completed.
|
|
|
|
type PartialSignature struct {
|
|
|
|
S *btcec.ModNScalar
|
|
|
|
|
|
|
|
R *btcec.PublicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewPartialSignature returns a new instances of the partial sig struct.
|
|
|
|
func NewPartialSignature(s *btcec.ModNScalar,
|
|
|
|
r *btcec.PublicKey) PartialSignature {
|
|
|
|
|
|
|
|
return PartialSignature{
|
|
|
|
S: s,
|
|
|
|
R: r,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode writes a serialized version of the partial signature to the passed
|
|
|
|
// io.Writer
|
|
|
|
func (p *PartialSignature) Encode(w io.Writer) error {
|
|
|
|
var sBytes [32]byte
|
|
|
|
p.S.PutBytes(&sBytes)
|
|
|
|
|
|
|
|
if _, err := w.Write(sBytes[:]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode attempts to parse a serialized PartialSignature stored in the passed
|
|
|
|
// io reader.
|
|
|
|
func (p *PartialSignature) Decode(r io.Reader) error {
|
|
|
|
p.S = new(btcec.ModNScalar)
|
|
|
|
|
|
|
|
var sBytes [32]byte
|
|
|
|
if _, err := io.ReadFull(r, sBytes[:]); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
overflows := p.S.SetBytes(&sBytes)
|
|
|
|
if overflows == 1 {
|
|
|
|
return ErrPartialSigInvalid
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignOption is a functional option argument that allows callers to modify the
|
|
|
|
// way we generate musig2 schnorr signatures.
|
|
|
|
type SignOption func(*signOptions)
|
|
|
|
|
|
|
|
// signOptions houses the set of functional options that can be used to modify
|
|
|
|
// the method used to generate the musig2 partial signature.
|
|
|
|
type signOptions struct {
|
|
|
|
// fastSign determines if we'll skip the check at the end of the
|
|
|
|
// routine where we attempt to verify the produced signature.
|
|
|
|
fastSign bool
|
|
|
|
|
|
|
|
// sortKeys determines if the set of keys should be sorted before doing
|
|
|
|
// key aggregation.
|
|
|
|
sortKeys bool
|
|
|
|
|
|
|
|
// tweaks specifies a series of tweaks to be applied to the aggregated
|
|
|
|
// public key, which also partially carries over into the signing
|
|
|
|
// process.
|
|
|
|
tweaks []KeyTweakDesc
|
|
|
|
|
|
|
|
// taprootTweak specifies a taproot specific tweak. of the tweaks
|
|
|
|
// specified above. 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 specifies that the taproot tweak should be done in a BIP
|
|
|
|
// 86 style, where we don't expect an actual tweak and instead just
|
|
|
|
// commit to the public key itself.
|
|
|
|
bip86Tweak bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultSignOptions returns the default set of signing operations.
|
|
|
|
func defaultSignOptions() *signOptions {
|
|
|
|
return &signOptions{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithFastSign forces signing to skip the extra verification step at the end.
|
|
|
|
// Performance sensitive applications may opt to use this option to speed up
|
|
|
|
// the signing operation.
|
|
|
|
func WithFastSign() SignOption {
|
|
|
|
return func(o *signOptions) {
|
|
|
|
o.fastSign = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithSortedKeys determines if the set of signing public keys are to be sorted
|
|
|
|
// or not before doing key aggregation.
|
|
|
|
func WithSortedKeys() SignOption {
|
|
|
|
return func(o *signOptions) {
|
|
|
|
o.sortKeys = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithTweaks determines if the aggregated public key used should apply a
|
|
|
|
// series of tweaks before key aggregation.
|
|
|
|
func WithTweaks(tweaks ...KeyTweakDesc) SignOption {
|
|
|
|
return func(o *signOptions) {
|
|
|
|
o.tweaks = tweaks
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithTaprootSignTweak allows a caller to specify a tweak that should be used
|
|
|
|
// in a bip 340 manner when signing. This differs from WithTweaks as the tweak
|
|
|
|
// will be assumed to always be x-only and the intermediate aggregate key
|
|
|
|
// before tweaking will be used to generate part of the tweak (as the taproot
|
|
|
|
// tweak also commits to the internal key).
|
|
|
|
//
|
|
|
|
// This option should be used in the taproot context to create a valid
|
|
|
|
// signature for the keypath spend for taproot, when the output key is actually
|
|
|
|
// committing to a script path, or some other data.
|
|
|
|
func WithTaprootSignTweak(scriptRoot []byte) SignOption {
|
|
|
|
return func(o *signOptions) {
|
|
|
|
o.taprootTweak = scriptRoot
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithBip86SignTweak allows a caller to specify a tweak that should be used in
|
|
|
|
// a bip 340 manner when signing, factoring in BIP 86 as well. This differs
|
|
|
|
// from WithTaprootSignTweak as no true script root will be committed to,
|
|
|
|
// instead we just commit to the internal key.
|
|
|
|
//
|
|
|
|
// This option should be used in the taproot context to create a valid
|
|
|
|
// signature for the keypath spend for taproot, when the output key was
|
|
|
|
// generated using BIP 86.
|
|
|
|
func WithBip86SignTweak() SignOption {
|
|
|
|
return func(o *signOptions) {
|
|
|
|
o.bip86Tweak = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sign generates a musig2 partial signature given the passed key set, secret
|
|
|
|
// nonce, public nonce, and private keys. This method returns an error if the
|
|
|
|
// generated nonces are either too large, or end up mapping to the point at
|
|
|
|
// infinity.
|
|
|
|
func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
|
|
|
|
combinedNonce [PubNonceSize]byte, pubKeys []*btcec.PublicKey,
|
|
|
|
msg [32]byte, signOpts ...SignOption) (*PartialSignature, error) {
|
|
|
|
|
|
|
|
// First, parse the set of optional signing options.
|
|
|
|
opts := defaultSignOptions()
|
|
|
|
for _, option := range signOpts {
|
|
|
|
option(opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute the hash of all the keys here as we'll need it do aggregate
|
|
|
|
// the keys and also at the final step of signing.
|
|
|
|
keysHash := keyHashFingerprint(pubKeys, opts.sortKeys)
|
|
|
|
uniqueKeyIndex := secondUniqueKeyIndex(pubKeys, opts.sortKeys)
|
|
|
|
|
|
|
|
keyAggOpts := []KeyAggOption{
|
|
|
|
WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
|
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case opts.bip86Tweak:
|
|
|
|
keyAggOpts = append(
|
|
|
|
keyAggOpts, WithBIP86KeyTweak(),
|
|
|
|
)
|
|
|
|
case opts.taprootTweak != nil:
|
|
|
|
keyAggOpts = append(
|
|
|
|
keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
|
|
|
|
)
|
|
|
|
case len(opts.tweaks) != 0:
|
|
|
|
keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next we'll construct the aggregated public key based on the set of
|
|
|
|
// signers.
|
|
|
|
combinedKey, parityAcc, _, err := AggregateKeys(
|
|
|
|
pubKeys, opts.sortKeys, keyAggOpts...,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next we'll compute the value b, that blinds our second public
|
|
|
|
// nonce:
|
|
|
|
// * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
|
|
|
|
var (
|
|
|
|
nonceMsgBuf bytes.Buffer
|
|
|
|
nonceBlinder btcec.ModNScalar
|
|
|
|
)
|
|
|
|
nonceMsgBuf.Write(combinedNonce[:])
|
|
|
|
nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
|
|
|
|
nonceMsgBuf.Write(msg[:])
|
|
|
|
nonceBlindHash := chainhash.TaggedHash(
|
|
|
|
NonceBlindTag, nonceMsgBuf.Bytes(),
|
|
|
|
)
|
|
|
|
nonceBlinder.SetByteSlice(nonceBlindHash[:])
|
|
|
|
|
|
|
|
// Next, we'll parse the public nonces into R1 and R2.
|
|
|
|
r1J, err := btcec.ParseJacobian(
|
|
|
|
combinedNonce[:btcec.PubKeyBytesLenCompressed],
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r2J, err := btcec.ParseJacobian(
|
|
|
|
combinedNonce[btcec.PubKeyBytesLenCompressed:],
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// With our nonce blinding value, we'll now combine both the public
|
|
|
|
// nonces, using the blinding factor to tweak the second nonce:
|
|
|
|
// * R = R_1 + b*R_2
|
|
|
|
var nonce btcec.JacobianPoint
|
|
|
|
btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
|
|
|
|
btcec.AddNonConst(&r1J, &r2J, &nonce)
|
|
|
|
|
|
|
|
// If the combined nonce it eh point at infinity, then we'll bail out.
|
|
|
|
if nonce == infinityPoint {
|
|
|
|
G := btcec.Generator()
|
|
|
|
G.AsJacobian(&nonce)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next we'll parse out our two secret nonces, which we'll be using in
|
|
|
|
// the core signing process below.
|
|
|
|
var k1, k2 btcec.ModNScalar
|
|
|
|
k1.SetByteSlice(secNonce[:btcec.PrivKeyBytesLen])
|
|
|
|
k2.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:])
|
|
|
|
|
|
|
|
if k1.IsZero() || k2.IsZero() {
|
|
|
|
return nil, ErrSecretNonceZero
|
|
|
|
}
|
|
|
|
|
|
|
|
nonce.ToAffine()
|
|
|
|
|
|
|
|
nonceKey := btcec.NewPublicKey(&nonce.X, &nonce.Y)
|
|
|
|
|
|
|
|
// If the nonce R has an odd y coordinate, then we'll negate both our
|
|
|
|
// secret nonces.
|
|
|
|
if nonce.Y.IsOdd() {
|
|
|
|
k1.Negate()
|
|
|
|
k2.Negate()
|
|
|
|
}
|
|
|
|
|
|
|
|
privKeyScalar := privKey.Key
|
|
|
|
if privKeyScalar.IsZero() {
|
|
|
|
return nil, ErrPrivKeyZero
|
|
|
|
}
|
|
|
|
|
|
|
|
pubKey := privKey.PubKey()
|
|
|
|
pubKeyYIsOdd := func() bool {
|
|
|
|
pubKeyBytes := pubKey.SerializeCompressed()
|
|
|
|
return pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd
|
|
|
|
}()
|
|
|
|
combinedKeyYIsOdd := func() bool {
|
|
|
|
combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed()
|
|
|
|
return combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Next we'll compute our two parity factors for Q the combined public
|
|
|
|
// key, and P, the public key we're signing with. If the keys are odd,
|
|
|
|
// then we'll negate them.
|
|
|
|
parityCombinedKey := new(btcec.ModNScalar).SetInt(1)
|
|
|
|
paritySignKey := new(btcec.ModNScalar).SetInt(1)
|
|
|
|
if combinedKeyYIsOdd {
|
|
|
|
parityCombinedKey.Negate()
|
|
|
|
}
|
|
|
|
if pubKeyYIsOdd {
|
|
|
|
paritySignKey.Negate()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Before we sign below, we'll multiply by our various parity factors
|
|
|
|
// to ensure that the signing key is properly negated (if necessary):
|
|
|
|
// * d = gv⋅gaccv⋅gp⋅d'
|
|
|
|
privKeyScalar.Mul(parityCombinedKey).Mul(paritySignKey).Mul(parityAcc)
|
|
|
|
|
|
|
|
// Next we'll create the challenge hash that commits to the combined
|
|
|
|
// nonce, combined public key and also the message:
|
|
|
|
// * e = H(tag=ChallengeHashTag, R || Q || m) mod n
|
|
|
|
var challengeMsg bytes.Buffer
|
|
|
|
challengeMsg.Write(schnorr.SerializePubKey(nonceKey))
|
|
|
|
challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
|
|
|
|
challengeMsg.Write(msg[:])
|
|
|
|
challengeBytes := chainhash.TaggedHash(
|
|
|
|
ChallengeHashTag, challengeMsg.Bytes(),
|
|
|
|
)
|
|
|
|
var e btcec.ModNScalar
|
|
|
|
e.SetByteSlice(challengeBytes[:])
|
|
|
|
|
|
|
|
// Next, we'll compute a, our aggregation coefficient for the key that
|
|
|
|
// we're signing with.
|
|
|
|
a := aggregationCoefficient(pubKeys, pubKey, keysHash, uniqueKeyIndex)
|
|
|
|
|
|
|
|
// With mu constructed, we can finally generate our partial signature
|
|
|
|
// as: s = (k1_1 + b*k_2 + e*a*d) mod n.
|
|
|
|
s := new(btcec.ModNScalar)
|
|
|
|
s.Add(&k1).Add(k2.Mul(&nonceBlinder)).Add(e.Mul(a).Mul(&privKeyScalar))
|
|
|
|
|
|
|
|
sig := NewPartialSignature(s, nonceKey)
|
|
|
|
|
|
|
|
// If we're not in fast sign mode, then we'll also validate our partial
|
|
|
|
// signature.
|
|
|
|
if !opts.fastSign {
|
|
|
|
pubNonce := secNonceToPubNonce(secNonce)
|
|
|
|
sigValid := sig.Verify(
|
|
|
|
pubNonce, combinedNonce, pubKeys, pubKey, msg,
|
|
|
|
signOpts...,
|
|
|
|
)
|
|
|
|
if !sigValid {
|
|
|
|
return nil, fmt.Errorf("sig is invalid!")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &sig, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify implements partial signature verification given the public nonce for
|
|
|
|
// the signer, aggregate nonce, signer set and finally the message being
|
|
|
|
// signed.
|
|
|
|
func (p *PartialSignature) Verify(pubNonce [PubNonceSize]byte,
|
|
|
|
combinedNonce [PubNonceSize]byte, keySet []*btcec.PublicKey,
|
|
|
|
signingKey *btcec.PublicKey, msg [32]byte, signOpts ...SignOption) bool {
|
|
|
|
|
|
|
|
pubKey := schnorr.SerializePubKey(signingKey)
|
|
|
|
|
|
|
|
return verifyPartialSig(
|
|
|
|
p, pubNonce, combinedNonce, keySet, pubKey, msg, signOpts...,
|
|
|
|
) == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// verifyPartialSig attempts to verify a partial schnorr signature given the
|
|
|
|
// necessary parameters. This is the internal version of Verify that returns
|
|
|
|
// detailed errors. signed.
|
|
|
|
func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte,
|
|
|
|
combinedNonce [PubNonceSize]byte, keySet []*btcec.PublicKey,
|
|
|
|
pubKey []byte, msg [32]byte, signOpts ...SignOption) error {
|
|
|
|
|
|
|
|
opts := defaultSignOptions()
|
|
|
|
for _, option := range signOpts {
|
|
|
|
option(opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// First we'll map the internal partial signature back into something
|
|
|
|
// we can manipulate.
|
|
|
|
s := partialSig.S
|
|
|
|
|
|
|
|
// Next we'll parse out the two public nonces into something we can
|
|
|
|
// use.
|
|
|
|
//
|
|
|
|
|
|
|
|
// Compute the hash of all the keys here as we'll need it do aggregate
|
|
|
|
// the keys and also at the final step of verification.
|
|
|
|
keysHash := keyHashFingerprint(keySet, opts.sortKeys)
|
|
|
|
uniqueKeyIndex := secondUniqueKeyIndex(keySet, opts.sortKeys)
|
|
|
|
|
|
|
|
keyAggOpts := []KeyAggOption{
|
|
|
|
WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
|
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case opts.bip86Tweak:
|
|
|
|
keyAggOpts = append(
|
|
|
|
keyAggOpts, WithBIP86KeyTweak(),
|
|
|
|
)
|
|
|
|
case opts.taprootTweak != nil:
|
|
|
|
keyAggOpts = append(
|
|
|
|
keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
|
|
|
|
)
|
|
|
|
case len(opts.tweaks) != 0:
|
|
|
|
keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next we'll construct the aggregated public key based on the set of
|
|
|
|
// signers.
|
|
|
|
combinedKey, parityAcc, _, err := AggregateKeys(
|
|
|
|
keySet, opts.sortKeys, keyAggOpts...,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next we'll compute the value b, that blinds our second public
|
|
|
|
// nonce:
|
|
|
|
// * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
|
|
|
|
var (
|
|
|
|
nonceMsgBuf bytes.Buffer
|
|
|
|
nonceBlinder btcec.ModNScalar
|
|
|
|
)
|
|
|
|
nonceMsgBuf.Write(combinedNonce[:])
|
|
|
|
nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
|
|
|
|
nonceMsgBuf.Write(msg[:])
|
|
|
|
nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes())
|
|
|
|
nonceBlinder.SetByteSlice(nonceBlindHash[:])
|
|
|
|
|
|
|
|
r1J, err := btcec.ParseJacobian(
|
|
|
|
combinedNonce[:btcec.PubKeyBytesLenCompressed],
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
r2J, err := btcec.ParseJacobian(
|
|
|
|
combinedNonce[btcec.PubKeyBytesLenCompressed:],
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// With our nonce blinding value, we'll now combine both the public
|
|
|
|
// nonces, using the blinding factor to tweak the second nonce:
|
|
|
|
// * R = R_1 + b*R_2
|
|
|
|
|
|
|
|
var nonce btcec.JacobianPoint
|
|
|
|
btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
|
|
|
|
btcec.AddNonConst(&r1J, &r2J, &nonce)
|
|
|
|
|
|
|
|
// Next, we'll parse out the set of public nonces this signer used to
|
|
|
|
// generate the signature.
|
|
|
|
pubNonce1J, err := btcec.ParseJacobian(
|
|
|
|
pubNonce[:btcec.PubKeyBytesLenCompressed],
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pubNonce2J, err := btcec.ParseJacobian(
|
|
|
|
pubNonce[btcec.PubKeyBytesLenCompressed:],
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the nonce is the infinity point we set it to the Generator.
|
|
|
|
if nonce == infinityPoint {
|
|
|
|
btcec.GeneratorJacobian(&nonce)
|
|
|
|
} else {
|
|
|
|
nonce.ToAffine()
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll perform a similar aggregation and blinding operator as we did
|
|
|
|
// above for the combined nonces: R' = R_1' + b*R_2'.
|
|
|
|
var pubNonceJ btcec.JacobianPoint
|
|
|
|
|
|
|
|
btcec.ScalarMultNonConst(&nonceBlinder, &pubNonce2J, &pubNonce2J)
|
|
|
|
btcec.AddNonConst(&pubNonce1J, &pubNonce2J, &pubNonceJ)
|
|
|
|
|
|
|
|
pubNonceJ.ToAffine()
|
|
|
|
|
|
|
|
// If the combined nonce used in the challenge hash has an odd y
|
|
|
|
// coordinate, then we'll negate our final public nonce.
|
|
|
|
if nonce.Y.IsOdd() {
|
|
|
|
pubNonceJ.Y.Negate(1)
|
|
|
|
pubNonceJ.Y.Normalize()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next we'll create the challenge hash that commits to the combined
|
|
|
|
// nonce, combined public key and also the message:
|
|
|
|
// * e = H(tag=ChallengeHashTag, R || Q || m) mod n
|
|
|
|
var challengeMsg bytes.Buffer
|
|
|
|
challengeMsg.Write(schnorr.SerializePubKey(btcec.NewPublicKey(
|
|
|
|
&nonce.X, &nonce.Y,
|
|
|
|
)))
|
|
|
|
challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
|
|
|
|
challengeMsg.Write(msg[:])
|
|
|
|
challengeBytes := chainhash.TaggedHash(
|
|
|
|
ChallengeHashTag, challengeMsg.Bytes(),
|
|
|
|
)
|
|
|
|
var e btcec.ModNScalar
|
|
|
|
e.SetByteSlice(challengeBytes[:])
|
|
|
|
|
|
|
|
signingKey, err := schnorr.ParsePubKey(pubKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, we'll compute a, our aggregation coefficient for the key that
|
|
|
|
// we're signing with.
|
|
|
|
a := aggregationCoefficient(keySet, signingKey, keysHash, uniqueKeyIndex)
|
|
|
|
|
|
|
|
// If the combined key has an odd y coordinate, then we'll negate
|
|
|
|
// parity factor for the signing key.
|
|
|
|
paritySignKey := new(btcec.ModNScalar).SetInt(1)
|
|
|
|
combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed()
|
|
|
|
if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd {
|
|
|
|
paritySignKey.Negate()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, we'll construct the final parity factor by multiplying the
|
|
|
|
// sign key parity factor with the accumulated parity factor for all
|
|
|
|
// the keys.
|
|
|
|
finalParityFactor := paritySignKey.Mul(parityAcc)
|
|
|
|
|
|
|
|
// Now we'll multiply the parity factor by our signing key, which'll
|
|
|
|
// take care of the amount of negation needed.
|
|
|
|
var signKeyJ btcec.JacobianPoint
|
|
|
|
signingKey.AsJacobian(&signKeyJ)
|
|
|
|
btcec.ScalarMultNonConst(finalParityFactor, &signKeyJ, &signKeyJ)
|
|
|
|
|
|
|
|
// In the final set, we'll check that: s*G == R' + e*a*P.
|
|
|
|
var sG, rP btcec.JacobianPoint
|
|
|
|
btcec.ScalarBaseMultNonConst(s, &sG)
|
|
|
|
btcec.ScalarMultNonConst(e.Mul(a), &signKeyJ, &rP)
|
|
|
|
btcec.AddNonConst(&rP, &pubNonceJ, &rP)
|
|
|
|
|
|
|
|
sG.ToAffine()
|
|
|
|
rP.ToAffine()
|
|
|
|
|
|
|
|
if sG != rP {
|
|
|
|
return ErrPartialSigInvalid
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CombineOption is a functional option argument that allows callers to modify the
|
|
|
|
// way we combine musig2 schnorr signatures.
|
|
|
|
type CombineOption func(*combineOptions)
|
|
|
|
|
|
|
|
// combineOptions houses the set of functional options that can be used to
|
|
|
|
// modify the method used to combine the musig2 partial signatures.
|
|
|
|
type combineOptions struct {
|
|
|
|
msg [32]byte
|
|
|
|
|
|
|
|
combinedKey *btcec.PublicKey
|
|
|
|
|
|
|
|
tweakAcc *btcec.ModNScalar
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultCombineOptions returns the default set of signing operations.
|
|
|
|
func defaultCombineOptions() *combineOptions {
|
|
|
|
return &combineOptions{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithTweakedCombine is a functional option that allows callers to specify
|
|
|
|
// that the signature was produced using a tweaked aggregated public key. In
|
|
|
|
// order to properly aggregate the partial signatures, the caller must specify
|
|
|
|
// enough information to reconstruct the challenge, and also the final
|
|
|
|
// accumulated tweak value.
|
|
|
|
func WithTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
|
|
|
|
tweaks []KeyTweakDesc, sort bool) CombineOption {
|
|
|
|
|
|
|
|
return func(o *combineOptions) {
|
|
|
|
combinedKey, _, tweakAcc, _ := AggregateKeys(
|
|
|
|
keys, sort, WithKeyTweaks(tweaks...),
|
|
|
|
)
|
|
|
|
|
|
|
|
o.msg = msg
|
|
|
|
o.combinedKey = combinedKey.FinalKey
|
|
|
|
o.tweakAcc = tweakAcc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithTaprootTweakedCombine is similar to the WithTweakedCombine option, but
|
|
|
|
// assumes a BIP 341 context where the final tweaked key is to be used as the
|
|
|
|
// output key, where the internal key is the aggregated key pre-tweak.
|
|
|
|
//
|
|
|
|
// This option should be used over WithTweakedCombine when attempting to
|
|
|
|
// aggregate signatures for a top-level taproot keyspend, where the output key
|
|
|
|
// commits to a script root.
|
|
|
|
func WithTaprootTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
|
|
|
|
scriptRoot []byte, sort bool) CombineOption {
|
|
|
|
|
|
|
|
return func(o *combineOptions) {
|
|
|
|
combinedKey, _, tweakAcc, _ := AggregateKeys(
|
|
|
|
keys, sort, WithTaprootKeyTweak(scriptRoot),
|
|
|
|
)
|
|
|
|
|
|
|
|
o.msg = msg
|
|
|
|
o.combinedKey = combinedKey.FinalKey
|
|
|
|
o.tweakAcc = tweakAcc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithBip86TweakedCombine is similar to the WithTaprootTweakedCombine option,
|
|
|
|
// but assumes a BIP 341 + BIP 86 context where the final tweaked key is to be
|
|
|
|
// used as the output key, where the internal key is the aggregated key
|
|
|
|
// pre-tweak.
|
|
|
|
//
|
|
|
|
// This option should be used over WithTaprootTweakedCombine when attempting to
|
|
|
|
// aggregate signatures for a top-level taproot keyspend, where the output key
|
|
|
|
// was generated using BIP 86.
|
|
|
|
func WithBip86TweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
|
|
|
|
sort bool) CombineOption {
|
|
|
|
|
|
|
|
return func(o *combineOptions) {
|
|
|
|
combinedKey, _, tweakAcc, _ := AggregateKeys(
|
|
|
|
keys, sort, WithBIP86KeyTweak(),
|
|
|
|
)
|
|
|
|
|
|
|
|
o.msg = msg
|
|
|
|
o.combinedKey = combinedKey.FinalKey
|
|
|
|
o.tweakAcc = tweakAcc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CombineSigs combines the set of public keys given the final aggregated
|
|
|
|
// nonce, and the series of partial signatures for each nonce.
|
|
|
|
func CombineSigs(combinedNonce *btcec.PublicKey,
|
|
|
|
partialSigs []*PartialSignature,
|
|
|
|
combineOpts ...CombineOption) *schnorr.Signature {
|
|
|
|
|
|
|
|
// First, parse the set of optional combine options.
|
|
|
|
opts := defaultCombineOptions()
|
|
|
|
for _, option := range combineOpts {
|
|
|
|
option(opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If signer keys and tweaks are specified, then we need to carry out
|
|
|
|
// some intermediate steps before we can combine the signature.
|
|
|
|
var tweakProduct *btcec.ModNScalar
|
|
|
|
if opts.combinedKey != nil && opts.tweakAcc != nil {
|
|
|
|
// Next, we'll construct the parity factor of the combined key,
|
|
|
|
// negating it if the combined key has an even y coordinate.
|
|
|
|
parityFactor := new(btcec.ModNScalar).SetInt(1)
|
|
|
|
combinedKeyBytes := opts.combinedKey.SerializeCompressed()
|
|
|
|
if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd {
|
|
|
|
parityFactor.Negate()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next we'll reconstruct e the challenge has based on the
|
|
|
|
// nonce and combined public key.
|
|
|
|
// * e = H(tag=ChallengeHashTag, R || Q || m) mod n
|
|
|
|
var challengeMsg bytes.Buffer
|
|
|
|
challengeMsg.Write(schnorr.SerializePubKey(combinedNonce))
|
|
|
|
challengeMsg.Write(schnorr.SerializePubKey(opts.combinedKey))
|
|
|
|
challengeMsg.Write(opts.msg[:])
|
|
|
|
challengeBytes := chainhash.TaggedHash(
|
|
|
|
ChallengeHashTag, challengeMsg.Bytes(),
|
|
|
|
)
|
|
|
|
var e btcec.ModNScalar
|
|
|
|
e.SetByteSlice(challengeBytes[:])
|
|
|
|
|
|
|
|
tweakProduct = new(btcec.ModNScalar).Set(&e)
|
|
|
|
tweakProduct.Mul(opts.tweakAcc).Mul(parityFactor)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, the tweak factor also needs to be re-computed as well.
|
|
|
|
var combinedSig btcec.ModNScalar
|
|
|
|
for _, partialSig := range partialSigs {
|
|
|
|
combinedSig.Add(partialSig.S)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the tweak product was set above, then we'll need to add the value
|
|
|
|
// at the very end in order to produce a valid signature under the
|
|
|
|
// final tweaked key.
|
|
|
|
if tweakProduct != nil {
|
|
|
|
combinedSig.Add(tweakProduct)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(roasbeef): less verbose way to get the x coord...
|
|
|
|
var nonceJ btcec.JacobianPoint
|
|
|
|
combinedNonce.AsJacobian(&nonceJ)
|
|
|
|
nonceJ.ToAffine()
|
|
|
|
|
|
|
|
return schnorr.NewSignature(&nonceJ.X, &combinedSig)
|
|
|
|
}
|