mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
0154226233
Since we explicitly keep an old version of a library in lnd for backward compatibility we want to make sure the purpose and version of it is clear and not misleading.
704 lines
22 KiB
Go
704 lines
22 KiB
Go
// Copyright 2013-2022 The btcsuite developers
|
|
|
|
package musig2v040
|
|
|
|
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)
|
|
}
|