mirror of
https://github.com/btcsuite/btcd.git
synced 2025-02-24 14:51:01 +01:00
In this commit, we build on the prior two commits by adding the ability to generate partial musig2 signatures, validate them individually, and finally combine them into a single signature. Much of the logic here is unoptimized, and will be optimized in a later commit. In addition, we also want to eventually have a nicer API to support the book keeping necessary during multi signing.
423 lines
13 KiB
Go
423 lines
13 KiB
Go
// Copyright 2013-2016 The btcsuite developers
|
|
// Copyright (c) 2015-2021 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package musig2
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
)
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Next, we'll parse the public nonces into R1 and R2.
|
|
r1, err := btcec.ParsePubKey(
|
|
combinedNonce[:btcec.PubKeyBytesLenCompressed],
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r2, err := btcec.ParsePubKey(
|
|
combinedNonce[btcec.PubKeyBytesLenCompressed:],
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Next we'll construct the aggregated public key based on the set of
|
|
// signers.
|
|
combinedKey := AggregateKeys(pubKeys, opts.sortKeys)
|
|
|
|
// 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))
|
|
nonceMsgBuf.Write(msg[:])
|
|
nonceBlindHash := chainhash.TaggedHash(
|
|
NonceBlindTag, nonceMsgBuf.Bytes(),
|
|
)
|
|
nonceBlinder.SetByteSlice(nonceBlindHash[:])
|
|
|
|
var nonce, r1J, r2J btcec.JacobianPoint
|
|
r1.AsJacobian(&r1J)
|
|
r2.AsJacobian(&r2J)
|
|
|
|
// 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
|
|
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 {
|
|
return nil, ErrNoncePointAtInfinity
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// If the y coordinate of the public key is odd xor the y coordinate of
|
|
// the combined public key is odd, then we'll negate the private key.
|
|
pubKey := privKey.PubKey()
|
|
pubKeyYIsOdd := func() bool {
|
|
pubKeyBytes := pubKey.SerializeCompressed()
|
|
return pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd
|
|
}()
|
|
combinedKeyYIsOdd := func() bool {
|
|
combinedKeyBytes := combinedKey.SerializeCompressed()
|
|
return combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd
|
|
}()
|
|
if pubKeyYIsOdd != combinedKeyYIsOdd {
|
|
privKeyScalar.Negate()
|
|
}
|
|
|
|
// 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))
|
|
challengeMsg.Write(msg[:])
|
|
challengeBytes := chainhash.TaggedHash(
|
|
ChallengeHashTag, challengeMsg.Bytes(),
|
|
)
|
|
var e btcec.ModNScalar
|
|
e.SetByteSlice(challengeBytes[:])
|
|
|
|
// Next, we'll compute mu, our aggregation coefficient for the key that
|
|
// we're signing with.
|
|
mu := aggregationCoefficient(pubKeys, pubKey, opts.sortKeys)
|
|
|
|
// With mu constructed, we can finally generate our partial signature
|
|
// as: s = (k1_1 + b*k_2 + e*mu*d) mod n.
|
|
s := new(btcec.ModNScalar)
|
|
s.Add(&k1).Add(k2.Mul(&nonceBlinder)).Add(e.Mul(mu).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.
|
|
//
|
|
// TODO(roasbeef): consolidate, new method
|
|
r1, err := btcec.ParsePubKey(
|
|
combinedNonce[:btcec.PubKeyBytesLenCompressed],
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r2, err := btcec.ParsePubKey(
|
|
combinedNonce[btcec.PubKeyBytesLenCompressed:],
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Next we'll construct the aggregated public key based on the set of
|
|
// signers.
|
|
combinedKey := AggregateKeys(keySet, opts.sortKeys)
|
|
|
|
// 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))
|
|
nonceMsgBuf.Write(msg[:])
|
|
nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes())
|
|
nonceBlinder.SetByteSlice(nonceBlindHash[:])
|
|
|
|
var nonce, r1J, r2J btcec.JacobianPoint
|
|
r1.AsJacobian(&r1J)
|
|
r2.AsJacobian(&r2J)
|
|
|
|
// 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
|
|
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.
|
|
pubNonce1, err := btcec.ParsePubKey(
|
|
pubNonce[:btcec.PubKeyBytesLenCompressed],
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pubNonce2, err := btcec.ParsePubKey(
|
|
pubNonce[btcec.PubKeyBytesLenCompressed:],
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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, pubNonce1J, pubNonce2J btcec.JacobianPoint
|
|
pubNonce1.AsJacobian(&pubNonce1J)
|
|
pubNonce2.AsJacobian(&pubNonce2J)
|
|
btcec.ScalarMultNonConst(&nonceBlinder, &pubNonce2J, &pubNonce2J)
|
|
btcec.AddNonConst(&pubNonce1J, &pubNonce2J, &pubNonceJ)
|
|
|
|
nonce.ToAffine()
|
|
|
|
// If the combined nonce used in the challenge hash has an odd y
|
|
// coordinate, then we'll negate our final public nonce.
|
|
//
|
|
// TODO(roasbeef): make into func
|
|
if nonce.Y.IsOdd() {
|
|
pubNonceJ.ToAffine()
|
|
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))
|
|
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 mu, our aggregation coefficient for the key that
|
|
// we're signing with.
|
|
mu := aggregationCoefficient(keySet, signingKey, opts.sortKeys)
|
|
|
|
// If the combined key has an odd y coordinate, then we'll negate the
|
|
// signer key.
|
|
var signKeyJ btcec.JacobianPoint
|
|
signingKey.AsJacobian(&signKeyJ)
|
|
combinedKeyBytes := combinedKey.SerializeCompressed()
|
|
if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd {
|
|
signKeyJ.ToAffine()
|
|
signKeyJ.Y.Negate(1)
|
|
signKeyJ.Y.Normalize()
|
|
}
|
|
|
|
// In the final set, we'll check that: s*G == R' + e*mu*P.
|
|
var sG, rP btcec.JacobianPoint
|
|
btcec.ScalarBaseMultNonConst(s, &sG)
|
|
btcec.ScalarMultNonConst(e.Mul(mu), &signKeyJ, &rP)
|
|
btcec.AddNonConst(&rP, &pubNonceJ, &rP)
|
|
|
|
sG.ToAffine()
|
|
rP.ToAffine()
|
|
|
|
if sG != rP {
|
|
return ErrPartialSigInvalid
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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) *schnorr.Signature {
|
|
|
|
var combinedSig btcec.ModNScalar
|
|
for _, partialSig := range partialSigs {
|
|
combinedSig.Add(partialSig.S)
|
|
}
|
|
|
|
// 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)
|
|
}
|