initial addition of essential crypto, encoders, workflows and LLM instructions
This commit is contained in:
280
pkg/crypto/ec/musig2/bench_test.go
Normal file
280
pkg/crypto/ec/musig2/bench_test.go
Normal file
@@ -0,0 +1,280 @@
|
||||
// Copyright 2013-2022 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"next.orly.dev/pkg/crypto/ec"
|
||||
"next.orly.dev/pkg/crypto/ec/schnorr"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
)
|
||||
|
||||
var (
|
||||
testPrivBytes = hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d")
|
||||
testMsg = hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
|
||||
)
|
||||
|
||||
func hexToBytes(s string) []byte {
|
||||
b, err := hex.Dec(s)
|
||||
if err != nil {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func hexToModNScalar(s string) *btcec.ModNScalar {
|
||||
b, err := hex.Dec(s)
|
||||
if err != nil {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
var scalar btcec.ModNScalar
|
||||
if overflow := scalar.SetByteSlice(b); overflow {
|
||||
panic("hex in source file overflows mod N scalar: " + s)
|
||||
}
|
||||
return &scalar
|
||||
}
|
||||
|
||||
func genSigner(t *testing.B) signer {
|
||||
privKey, err := btcec.NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to gen priv key: %v", err)
|
||||
}
|
||||
pubKey := privKey.PubKey()
|
||||
nonces, err := GenNonces(WithPublicKey(pubKey))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to gen nonces: %v", err)
|
||||
}
|
||||
return signer{
|
||||
privKey: privKey,
|
||||
pubKey: pubKey,
|
||||
nonces: nonces,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testSig *PartialSignature
|
||||
testErr error
|
||||
)
|
||||
|
||||
// BenchmarkPartialSign benchmarks how long it takes to generate a partial
|
||||
// signature factoring in if the keys are sorted and also if we're in fast sign
|
||||
// mode.
|
||||
func BenchmarkPartialSign(b *testing.B) {
|
||||
for _, numSigners := range []int{10, 100} {
|
||||
for _, fastSign := range []bool{true, false} {
|
||||
for _, sortKeys := range []bool{true, false} {
|
||||
name := fmt.Sprintf(
|
||||
"num_signers=%v/fast_sign=%v/sort=%v",
|
||||
numSigners, fastSign, sortKeys,
|
||||
)
|
||||
signers := make(signerSet, numSigners)
|
||||
for i := 0; i < numSigners; i++ {
|
||||
signers[i] = genSigner(b)
|
||||
}
|
||||
combinedNonce, err := AggregateNonces(signers.pubNonces())
|
||||
if err != nil {
|
||||
b.Fatalf("unable to generate combined nonce: %v", err)
|
||||
}
|
||||
var sig *PartialSignature
|
||||
var msg [32]byte
|
||||
copy(msg[:], testMsg[:])
|
||||
keys := signers.keys()
|
||||
b.Run(
|
||||
name, func(b *testing.B) {
|
||||
var signOpts []SignOption
|
||||
if fastSign {
|
||||
signOpts = append(signOpts, WithFastSign())
|
||||
}
|
||||
if sortKeys {
|
||||
signOpts = append(signOpts, WithSortedKeys())
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sig, err = Sign(
|
||||
signers[0].nonces.SecNonce, signers[0].privKey,
|
||||
combinedNonce, keys, msg, signOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
b.Fatalf("unable to generate sig: %v", err)
|
||||
}
|
||||
}
|
||||
testSig = sig
|
||||
testErr = err
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this fails
|
||||
// // TODO(roasbeef): add impact of sorting ^
|
||||
//
|
||||
// var sigOk bo
|
||||
//
|
||||
// // BenchmarkPartialVerify benchmarks how long it takes to verify a partial
|
||||
// // signature.
|
||||
// func BenchmarkPartialVerify(b *testing.B) {
|
||||
// for _, numSigners := range []int{10, 100} {
|
||||
// for _, sortKeys := range []bool{true, false} {
|
||||
// name := fmt.Sprintf("sort_keys=%v/num_signers=%v",
|
||||
// sortKeys, numSigners)
|
||||
// signers := make(signerSet, numSigners)
|
||||
// for i := 0; i < numSigners; i++ {
|
||||
// signers[i] = genSigner(b)
|
||||
// }
|
||||
// combinedNonce, err := AggregateNonces(
|
||||
// signers.pubNonces(),
|
||||
// )
|
||||
// if err != nil {
|
||||
// b.Fatalf("unable to generate combined "+
|
||||
// "nonce: %v", err)
|
||||
// }
|
||||
// var sig *PartialSignature
|
||||
// var msg [32]byte
|
||||
// copy(msg[:], testMsg[:])
|
||||
// b.ReportAllocs()
|
||||
// b.ResetTimer()
|
||||
// sig, err = Sign(
|
||||
// signers[0].nonces.SecNonce, signers[0].privKey,
|
||||
// combinedNonce, signers.keys(), msg,
|
||||
// )
|
||||
// if err != nil {
|
||||
// b.Fatalf("unable to generate sig: %v", err)
|
||||
// }
|
||||
// keys := signers.keys()
|
||||
// pubKey := signers[0].pubKey
|
||||
// b.Run(name, func(b *testing.B) {
|
||||
// var signOpts []SignOption
|
||||
// if sortKeys {
|
||||
// signOpts = append(
|
||||
// signOpts, WithSortedKeys(),
|
||||
// )
|
||||
// }
|
||||
// b.ResetTimer()
|
||||
// b.ReportAllocs()
|
||||
// var ok bo
|
||||
// for i := 0; i < b.no; i++ {
|
||||
// ok = sig.Verify(
|
||||
// signers[0].nonces.PubNonce, combinedNonce,
|
||||
// keys, pubKey, msg, signOpts...,
|
||||
// )
|
||||
// if !ok {
|
||||
// b.Fatalf("generated invalid sig!")
|
||||
// }
|
||||
// }
|
||||
// sigOk = ok
|
||||
// })
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
var finalSchnorrSig *schnorr.Signature
|
||||
|
||||
// BenchmarkCombineSigs benchmarks how long it takes to combine a set amount of
|
||||
// signatures.
|
||||
func BenchmarkCombineSigs(b *testing.B) {
|
||||
for _, numSigners := range []int{10, 100} {
|
||||
signers := make(signerSet, numSigners)
|
||||
for i := 0; i < numSigners; i++ {
|
||||
signers[i] = genSigner(b)
|
||||
}
|
||||
combinedNonce, err := AggregateNonces(signers.pubNonces())
|
||||
if err != nil {
|
||||
b.Fatalf("unable to generate combined nonce: %v", err)
|
||||
}
|
||||
var msg [32]byte
|
||||
copy(msg[:], testMsg[:])
|
||||
var finalNonce *btcec.PublicKey
|
||||
for i := range signers {
|
||||
signer := signers[i]
|
||||
partialSig, err := Sign(
|
||||
signer.nonces.SecNonce, signer.privKey,
|
||||
combinedNonce, signers.keys(), msg,
|
||||
)
|
||||
if err != nil {
|
||||
b.Fatalf(
|
||||
"unable to generate partial sig: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
signers[i].partialSig = partialSig
|
||||
if finalNonce == nil {
|
||||
finalNonce = partialSig.R
|
||||
}
|
||||
}
|
||||
sigs := signers.partialSigs()
|
||||
name := fmt.Sprintf("num_signers=%v", numSigners)
|
||||
b.Run(
|
||||
name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
finalSig := CombineSigs(finalNonce, sigs)
|
||||
finalSchnorrSig = finalSig
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var testNonce [PubNonceSize]byte
|
||||
|
||||
// BenchmarkAggregateNonces benchmarks how long it takes to combine nonces.
|
||||
func BenchmarkAggregateNonces(b *testing.B) {
|
||||
for _, numSigners := range []int{10, 100} {
|
||||
signers := make(signerSet, numSigners)
|
||||
for i := 0; i < numSigners; i++ {
|
||||
signers[i] = genSigner(b)
|
||||
}
|
||||
nonces := signers.pubNonces()
|
||||
name := fmt.Sprintf("num_signers=%v", numSigners)
|
||||
b.Run(
|
||||
name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
pubNonce, err := AggregateNonces(nonces)
|
||||
if err != nil {
|
||||
b.Fatalf("unable to generate nonces: %v", err)
|
||||
}
|
||||
testNonce = pubNonce
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var testKey *btcec.PublicKey
|
||||
|
||||
// BenchmarkAggregateKeys benchmarks how long it takes to aggregate public
|
||||
// keys.
|
||||
func BenchmarkAggregateKeys(b *testing.B) {
|
||||
for _, numSigners := range []int{10, 100} {
|
||||
for _, sortKeys := range []bool{true, false} {
|
||||
signers := make(signerSet, numSigners)
|
||||
for i := 0; i < numSigners; i++ {
|
||||
signers[i] = genSigner(b)
|
||||
}
|
||||
signerKeys := signers.keys()
|
||||
name := fmt.Sprintf(
|
||||
"num_signers=%v/sort_keys=%v",
|
||||
numSigners, sortKeys,
|
||||
)
|
||||
uniqueKeyIndex := secondUniqueKeyIndex(signerKeys, false)
|
||||
b.Run(
|
||||
name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
aggKey, _, _, _ := AggregateKeys(
|
||||
signerKeys, sortKeys,
|
||||
WithUniqueKeyIndex(uniqueKeyIndex),
|
||||
)
|
||||
testKey = aggKey.FinalKey
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
584
pkg/crypto/ec/musig2/context.go
Normal file
584
pkg/crypto/ec/musig2/context.go
Normal file
@@ -0,0 +1,584 @@
|
||||
// Copyright (c) 2013-2022 The btcsuite developers
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"next.orly.dev/pkg/crypto/ec"
|
||||
"next.orly.dev/pkg/crypto/ec/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 secret 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",
|
||||
)
|
||||
// ErrAlreadyHaveAllNonces is called when RegisterPubNonce is called too
|
||||
// many times for a given signing session.
|
||||
//
|
||||
// ErrAlreadyHaveAllNonces is returned when a caller attempts to
|
||||
// register a signer, once we already have the total set of known
|
||||
// signers.
|
||||
ErrAlreadyHaveAllNonces = 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 is returned if a caller attempts to obtain an
|
||||
// early nonce when it wasn't specified
|
||||
ErrNotEnoughSigners = fmt.Errorf("not enough 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")
|
||||
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.SecretKey
|
||||
// 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.SecretKey, shouldSort bool,
|
||||
ctxOpts ...ContextOption,
|
||||
) (*Context, error) {
|
||||
|
||||
// First, parse the set of optional context options.
|
||||
opts := defaultContextOptions()
|
||||
for _, option := range ctxOpts {
|
||||
option(opts)
|
||||
}
|
||||
pubKey := signingKey.PubKey()
|
||||
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(); chk.T(err) {
|
||||
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)
|
||||
default:
|
||||
return nil, ErrSignersNotSpecified
|
||||
}
|
||||
// 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 {
|
||||
var err error
|
||||
ctx.sessionNonce, err = GenNonces(
|
||||
WithPublicKey(ctx.pubKey),
|
||||
WithNonceSecretKeyAux(signingKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
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(); chk.T(err) {
|
||||
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(
|
||||
WithPublicKey(c.pubKey),
|
||||
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, ErrAlreadyHaveAllNonces
|
||||
}
|
||||
// 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
|
||||
// funciton 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 }
|
||||
127
pkg/crypto/ec/musig2/data/key_agg_vectors.json
Normal file
127
pkg/crypto/ec/musig2/data/key_agg_vectors.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"pubkeys": [
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"020000000000000000000000000000000000000000000000000000000000000005",
|
||||
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
|
||||
"04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
|
||||
],
|
||||
"tweaks": [
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
|
||||
"252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B"
|
||||
],
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E"
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
3
|
||||
],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 1,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Invalid public key"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
4
|
||||
],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 1,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Public key exceeds field size"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
5,
|
||||
0
|
||||
],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "First byte of public key is not 2 or 3"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"tweak_indices": [
|
||||
0
|
||||
],
|
||||
"is_xonly": [
|
||||
true
|
||||
],
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The tweak must be less than n."
|
||||
},
|
||||
"comment": "Tweak is out of range"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
6
|
||||
],
|
||||
"tweak_indices": [
|
||||
1
|
||||
],
|
||||
"is_xonly": [
|
||||
false
|
||||
],
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The result of tweaking cannot be infinity."
|
||||
},
|
||||
"comment": "Intermediate tweaking result is point at infinity"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
pkg/crypto/ec/musig2/data/key_sort_vectors.json
Normal file
16
pkg/crypto/ec/musig2/data/key_sort_vectors.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"pubkeys": [
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8"
|
||||
],
|
||||
"sorted_pubkeys": [
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
|
||||
]
|
||||
}
|
||||
69
pkg/crypto/ec/musig2/data/nonce_agg_vectors.json
Normal file
69
pkg/crypto/ec/musig2/data/nonce_agg_vectors.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"pnonces": [
|
||||
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
|
||||
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
|
||||
],
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"pnonce_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8"
|
||||
},
|
||||
{
|
||||
"pnonce_indices": [
|
||||
2,
|
||||
3
|
||||
],
|
||||
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes"
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"pnonce_indices": [
|
||||
0,
|
||||
4
|
||||
],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 1,
|
||||
"contrib": "pubnonce"
|
||||
},
|
||||
"comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half",
|
||||
"btcec_err": "invalid public key: unsupported format: 4"
|
||||
},
|
||||
{
|
||||
"pnonce_indices": [
|
||||
5,
|
||||
1
|
||||
],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubnonce"
|
||||
},
|
||||
"comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate",
|
||||
"btcec_err": "invalid public key: x coordinate 48c264cdd57d3c24d79990b0f865674eb62a0f9018277a95011b41bfc193b831 is not on the secp256k1 curve"
|
||||
},
|
||||
{
|
||||
"pnonce_indices": [
|
||||
6,
|
||||
1
|
||||
],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubnonce"
|
||||
},
|
||||
"comment": "Public nonce from signer 0 is invalid because second half exceeds field size",
|
||||
"btcec_err": "invalid public key: x >= field prime"
|
||||
}
|
||||
]
|
||||
}
|
||||
40
pkg/crypto/ec/musig2/data/nonce_gen_vectors.json
Normal file
40
pkg/crypto/ec/musig2/data/nonce_gen_vectors.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"test_cases": [
|
||||
{
|
||||
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
|
||||
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
|
||||
"msg": "0101010101010101010101010101010101010101010101010101010101010101",
|
||||
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
|
||||
"expected": "227243DCB40EF2A13A981DB188FA433717B506BDFA14B1AE47D5DC027C9C3B9EF2370B2AD206E724243215137C86365699361126991E6FEC816845F837BDDAC3024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
|
||||
},
|
||||
{
|
||||
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
|
||||
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
|
||||
"msg": "",
|
||||
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
|
||||
"expected": "CD0F47FE471D6788FF3243F47345EA0A179AEF69476BE8348322EF39C2723318870C2065AFB52DEDF02BF4FDBF6D2F442E608692F50C2374C08FFFE57042A61C024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
|
||||
},
|
||||
{
|
||||
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
|
||||
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
|
||||
"msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626",
|
||||
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
|
||||
"expected": "011F8BC60EF061DEEF4D72A0A87200D9994B3F0CD9867910085C38D5366E3E6B9FF03BC0124E56B24069E91EC3F162378983F194E8BD0ED89BE3059649EAE262024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
|
||||
},
|
||||
{
|
||||
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"sk": null,
|
||||
"pk": "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"aggpk": null,
|
||||
"msg": null,
|
||||
"extra_in": null,
|
||||
"expected": "890E83616A3BC4640AB9B6374F21C81FF89CDDDBAFAA7475AE2A102A92E3EDB29FD7E874E23342813A60D9646948242646B7951CA046B4B36D7D6078506D3C9402F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"
|
||||
}
|
||||
]
|
||||
}
|
||||
151
pkg/crypto/ec/musig2/data/sig_agg_vectors.json
Normal file
151
pkg/crypto/ec/musig2/data/sig_agg_vectors.json
Normal file
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"pubkeys": [
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05",
|
||||
"03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C",
|
||||
"02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581"
|
||||
],
|
||||
"pnonces": [
|
||||
"036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E",
|
||||
"03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00",
|
||||
"02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6",
|
||||
"031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9",
|
||||
"023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A",
|
||||
"02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00"
|
||||
],
|
||||
"tweaks": [
|
||||
"B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C",
|
||||
"A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC",
|
||||
"75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8"
|
||||
],
|
||||
"psigs": [
|
||||
"B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB",
|
||||
"6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64",
|
||||
"9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505",
|
||||
"66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15",
|
||||
"4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE",
|
||||
"DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4",
|
||||
"97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC",
|
||||
"53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971",
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
||||
],
|
||||
"msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869",
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"psig_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E"
|
||||
},
|
||||
{
|
||||
"aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
2
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
2
|
||||
],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"psig_indices": [
|
||||
2,
|
||||
3
|
||||
],
|
||||
"expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9"
|
||||
},
|
||||
{
|
||||
"aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
3
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
2
|
||||
],
|
||||
"tweak_indices": [
|
||||
0
|
||||
],
|
||||
"is_xonly": [
|
||||
false
|
||||
],
|
||||
"psig_indices": [
|
||||
4,
|
||||
5
|
||||
],
|
||||
"expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC"
|
||||
},
|
||||
{
|
||||
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
4
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
3
|
||||
],
|
||||
"tweak_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"is_xonly": [
|
||||
true,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"psig_indices": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E"
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
4
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
3
|
||||
],
|
||||
"tweak_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"is_xonly": [
|
||||
true,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"psig_indices": [
|
||||
7,
|
||||
8
|
||||
],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 1
|
||||
},
|
||||
"comment": "Partial signature is invalid because it exceeds group size"
|
||||
}
|
||||
]
|
||||
}
|
||||
287
pkg/crypto/ec/musig2/data/sign_verify_vectors.json
Normal file
287
pkg/crypto/ec/musig2/data/sign_verify_vectors.json
Normal file
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
|
||||
"pubkeys": [
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661",
|
||||
"020000000000000000000000000000000000000000000000000000000000000007"
|
||||
],
|
||||
"secnonces": [
|
||||
"508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
|
||||
],
|
||||
"pnonces": [
|
||||
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046",
|
||||
"0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"020000000000000000000000000000000000000000000000000000000000000009"
|
||||
],
|
||||
"aggnonces": [
|
||||
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
|
||||
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009",
|
||||
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
|
||||
],
|
||||
"msgs": [
|
||||
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
|
||||
"",
|
||||
"2626262626262626262626262626262626262626262626262626262626262626262626262626"
|
||||
],
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"nonce_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"expected": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
0,
|
||||
2
|
||||
],
|
||||
"nonce_indices": [
|
||||
1,
|
||||
0,
|
||||
2
|
||||
],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"signer_index": 1,
|
||||
"expected": "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"nonce_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"signer_index": 2,
|
||||
"expected": "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"nonce_indices": [
|
||||
0,
|
||||
3
|
||||
],
|
||||
"aggnonce_index": 1,
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"expected": "AE386064B26105404798F75DE2EB9AF5EDA5387B064B83D049CB7C5E08879531",
|
||||
"comment": "Both halves of aggregate nonce correspond to point at infinity"
|
||||
}
|
||||
],
|
||||
"sign_error_test_cases": [
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The signer's pubkey must be included in the list of pubkeys."
|
||||
},
|
||||
"comment": "The signers pubkey is not in the list of pubkeys"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
0,
|
||||
3
|
||||
],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 2,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Signer 2 provided an invalid public key"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"aggnonce_index": 2,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": null,
|
||||
"contrib": "aggnonce"
|
||||
},
|
||||
"comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"aggnonce_index": 3,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": null,
|
||||
"contrib": "aggnonce"
|
||||
},
|
||||
"comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"aggnonce_index": 4,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": null,
|
||||
"contrib": "aggnonce"
|
||||
},
|
||||
"comment": "Aggregate nonce is invalid because second half exceeds field size"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"secnonce_index": 1,
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "first secnonce value is out of range."
|
||||
},
|
||||
"comment": "Secnonce is invalid which may indicate nonce reuse"
|
||||
}
|
||||
],
|
||||
"verify_fail_test_cases": [
|
||||
{
|
||||
"sig": "97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406",
|
||||
"key_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"nonce_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"comment": "Wrong signature (which is equal to the negation of valid signature)"
|
||||
},
|
||||
{
|
||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
||||
"key_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"nonce_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"msg_index": 0,
|
||||
"signer_index": 1,
|
||||
"comment": "Wrong signer"
|
||||
},
|
||||
{
|
||||
"sig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
|
||||
"key_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"nonce_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"comment": "Signature exceeds group size"
|
||||
}
|
||||
],
|
||||
"verify_error_test_cases": [
|
||||
{
|
||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
||||
"key_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"nonce_indices": [
|
||||
4,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubnonce"
|
||||
},
|
||||
"comment": "Invalid pubnonce"
|
||||
},
|
||||
{
|
||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
||||
"key_indices": [
|
||||
3,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"nonce_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Invalid pubkey"
|
||||
}
|
||||
]
|
||||
}
|
||||
170
pkg/crypto/ec/musig2/data/tweak_vectors.json
Normal file
170
pkg/crypto/ec/musig2/data/tweak_vectors.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
|
||||
"pubkeys": [
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
|
||||
],
|
||||
"secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"pnonces": [
|
||||
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046"
|
||||
],
|
||||
"aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
|
||||
"tweaks": [
|
||||
"E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB",
|
||||
"AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455",
|
||||
"F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0",
|
||||
"1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D",
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
||||
],
|
||||
"msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"nonce_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"tweak_indices": [
|
||||
0
|
||||
],
|
||||
"is_xonly": [
|
||||
true
|
||||
],
|
||||
"signer_index": 2,
|
||||
"expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91",
|
||||
"comment": "A single x-only tweak"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"nonce_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"tweak_indices": [
|
||||
0
|
||||
],
|
||||
"is_xonly": [
|
||||
false
|
||||
],
|
||||
"signer_index": 2,
|
||||
"expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D",
|
||||
"comment": "A single plain tweak"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"nonce_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"tweak_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"is_xonly": [
|
||||
false,
|
||||
true
|
||||
],
|
||||
"signer_index": 2,
|
||||
"expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408",
|
||||
"comment": "A plain tweak followed by an x-only tweak"
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"nonce_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"tweak_indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"is_xonly": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"signer_index": 2,
|
||||
"expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435",
|
||||
"comment": "Four tweaks: plain, plain, x-only, x-only."
|
||||
},
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"nonce_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"tweak_indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"is_xonly": [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"signer_index": 2,
|
||||
"expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239",
|
||||
"comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error."
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"key_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"nonce_indices": [
|
||||
1,
|
||||
2,
|
||||
0
|
||||
],
|
||||
"tweak_indices": [
|
||||
4
|
||||
],
|
||||
"is_xonly": [
|
||||
false
|
||||
],
|
||||
"signer_index": 2,
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The tweak must be less than n."
|
||||
},
|
||||
"comment": "Tweak is invalid because it exceeds group size"
|
||||
}
|
||||
]
|
||||
}
|
||||
2
pkg/crypto/ec/musig2/doc.go
Normal file
2
pkg/crypto/ec/musig2/doc.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package musig2 provides an implementation of the musig2 protocol for bitcoin.
|
||||
package musig2
|
||||
414
pkg/crypto/ec/musig2/keys.go
Normal file
414
pkg/crypto/ec/musig2/keys.go
Normal file
@@ -0,0 +1,414 @@
|
||||
// Copyright 2013-2022 The btcsuite developers
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"next.orly.dev/pkg/utils"
|
||||
|
||||
"next.orly.dev/pkg/crypto/ec"
|
||||
"next.orly.dev/pkg/crypto/ec/chainhash"
|
||||
"next.orly.dev/pkg/crypto/ec/schnorr"
|
||||
"next.orly.dev/pkg/crypto/ec/secp256k1"
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyAggTagList is the tagged hash tag used to compute the hash of the
|
||||
// list of sorted public keys.
|
||||
KeyAggTagList = []byte("KeyAgg list")
|
||||
// KeyAggTagCoeff is the tagged hash tag used to compute the key
|
||||
// aggregation coefficient for each key.
|
||||
KeyAggTagCoeff = []byte("KeyAgg coefficient")
|
||||
// ErrTweakedKeyIsInfinity is returned if while tweaking a key, we end
|
||||
// up with the point at infinity.
|
||||
ErrTweakedKeyIsInfinity = fmt.Errorf("tweaked key is infinity point")
|
||||
// ErrTweakedKeyOverflows is returned if a tweaking key is larger than
|
||||
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141.
|
||||
ErrTweakedKeyOverflows = fmt.Errorf("tweaked key is too large")
|
||||
)
|
||||
|
||||
// sortableKeys defines a type of slice of public keys that implements the sort
|
||||
// interface for BIP 340 keys.
|
||||
type sortableKeys []*btcec.PublicKey
|
||||
|
||||
// Less reports whether the element with index i must sort before the element
|
||||
// with index j.
|
||||
func (s sortableKeys) Less(i, j int) bool {
|
||||
// TODO(roasbeef): more efficient way to compare...
|
||||
keyIBytes := s[i].SerializeCompressed()
|
||||
keyJBytes := s[j].SerializeCompressed()
|
||||
return bytes.Compare(keyIBytes, keyJBytes) == -1
|
||||
}
|
||||
|
||||
// Swap swaps the elements with indexes i and j.
|
||||
func (s sortableKeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// Len is the number of elements in the collection.
|
||||
func (s sortableKeys) Len() int { return len(s) }
|
||||
|
||||
// sortKeys takes a set of public keys and returns a new slice that is a copy
|
||||
// of the keys sorted in lexicographical order bytes on the x-only pubkey
|
||||
// serialization.
|
||||
func sortKeys(keys []*btcec.PublicKey) []*btcec.PublicKey {
|
||||
keySet := sortableKeys(keys)
|
||||
if sort.IsSorted(keySet) {
|
||||
return keys
|
||||
}
|
||||
sort.Sort(keySet)
|
||||
return keySet
|
||||
}
|
||||
|
||||
// keyHashFingerprint computes the tagged hash of the series of (sorted) public
|
||||
// keys passed as input. This is used to compute the aggregation coefficient
|
||||
// for each key. The final computation is:
|
||||
// - H(tag=KeyAgg list, pk1 || pk2..)
|
||||
func keyHashFingerprint(keys []*btcec.PublicKey, sort bool) []byte {
|
||||
if sort {
|
||||
keys = sortKeys(keys)
|
||||
}
|
||||
// We'll create a single buffer and slice into that so the bytes buffer
|
||||
// doesn't continually need to grow the underlying buffer.
|
||||
keyAggBuf := make([]byte, 33*len(keys))
|
||||
keyBytes := bytes.NewBuffer(keyAggBuf[0:0])
|
||||
for _, key := range keys {
|
||||
keyBytes.Write(key.SerializeCompressed())
|
||||
}
|
||||
h := chainhash.TaggedHash(KeyAggTagList, keyBytes.Bytes())
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// keyBytesEqual returns true if two keys are the same based on the compressed
|
||||
// serialization of each key.
|
||||
func keyBytesEqual(a, b *btcec.PublicKey) bool {
|
||||
return utils.FastEqual(a.SerializeCompressed(), b.SerializeCompressed())
|
||||
}
|
||||
|
||||
// aggregationCoefficient computes the key aggregation coefficient for the
|
||||
// specified target key. The coefficient is computed as:
|
||||
// - H(tag=KeyAgg coefficient, keyHashFingerprint(pks) || pk)
|
||||
func aggregationCoefficient(
|
||||
keySet []*btcec.PublicKey,
|
||||
targetKey *btcec.PublicKey, keysHash []byte,
|
||||
secondKeyIdx int,
|
||||
) *btcec.ModNScalar {
|
||||
|
||||
var mu btcec.ModNScalar
|
||||
// If this is the second key, then this coefficient is just one.
|
||||
if secondKeyIdx != -1 && keyBytesEqual(keySet[secondKeyIdx], targetKey) {
|
||||
return mu.SetInt(1)
|
||||
}
|
||||
// Otherwise, we'll compute the full finger print hash for this given
|
||||
// key and then use that to compute the coefficient tagged hash:
|
||||
// * H(tag=KeyAgg coefficient, keyHashFingerprint(pks, pk) || pk)
|
||||
var coefficientBytes [65]byte
|
||||
copy(coefficientBytes[:], keysHash[:])
|
||||
copy(coefficientBytes[32:], targetKey.SerializeCompressed())
|
||||
muHash := chainhash.TaggedHash(KeyAggTagCoeff, coefficientBytes[:])
|
||||
mu.SetByteSlice(muHash[:])
|
||||
return &mu
|
||||
}
|
||||
|
||||
// secondUniqueKeyIndex returns the index of the second unique key. If all keys
|
||||
// are the same, then a value of -1 is returned.
|
||||
func secondUniqueKeyIndex(keySet []*btcec.PublicKey, sort bool) int {
|
||||
if sort {
|
||||
keySet = sortKeys(keySet)
|
||||
}
|
||||
// Find the first key that isn't the same as the very first key (second
|
||||
// unique key).
|
||||
for i := range keySet {
|
||||
if !keyBytesEqual(keySet[i], keySet[0]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
// A value of negative one is used to indicate that all the keys in the
|
||||
// sign set are actually equal, which in practice actually makes musig2
|
||||
// useless, but we need a value to distinguish this case.
|
||||
return -1
|
||||
}
|
||||
|
||||
// KeyTweakDesc describes a tweak to be applied to the aggregated public key
|
||||
// generation and signing process. The IsXOnly specifies if the target key
|
||||
// should be converted to an x-only public key before tweaking.
|
||||
type KeyTweakDesc struct {
|
||||
// Tweak is the 32-byte value that will modify the public key.
|
||||
Tweak [32]byte
|
||||
// IsXOnly if true, then the public key will be mapped to an x-only key
|
||||
// before the tweaking operation is applied.
|
||||
IsXOnly bool
|
||||
}
|
||||
|
||||
// KeyAggOption is a functional option argument that allows callers to specify
|
||||
// more or less information that has been pre-computed to the main routine.
|
||||
type KeyAggOption func(*keyAggOption)
|
||||
|
||||
// keyAggOption houses the set of functional options that modify key
|
||||
// aggregation.
|
||||
type keyAggOption struct {
|
||||
// keyHash is the output of keyHashFingerprint for a given set of keys.
|
||||
keyHash []byte
|
||||
// uniqueKeyIndex is the pre-computed index of the second unique key.
|
||||
uniqueKeyIndex *int
|
||||
// tweaks specifies a series of tweaks to be applied to the aggregated
|
||||
// public key.
|
||||
tweaks []KeyTweakDesc
|
||||
// taprootTweak controls if the tweaks above should be applied in a BIP
|
||||
// 340 style.
|
||||
taprootTweak bool
|
||||
// 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
|
||||
}
|
||||
|
||||
// WithKeysHash allows key aggregation to be optimize, by allowing the caller
|
||||
// to specify the hash of all the keys.
|
||||
func WithKeysHash(keyHash []byte) KeyAggOption {
|
||||
return func(o *keyAggOption) { o.keyHash = keyHash }
|
||||
}
|
||||
|
||||
// WithUniqueKeyIndex allows the caller to specify the index of the second
|
||||
// unique key.
|
||||
func WithUniqueKeyIndex(idx int) KeyAggOption {
|
||||
return func(o *keyAggOption) {
|
||||
i := idx
|
||||
o.uniqueKeyIndex = &i
|
||||
}
|
||||
}
|
||||
|
||||
// WithKeyTweaks allows a caller to specify a series of 32-byte tweaks that
|
||||
// should be applied to the final aggregated public key.
|
||||
func WithKeyTweaks(tweaks ...KeyTweakDesc) KeyAggOption {
|
||||
return func(o *keyAggOption) { o.tweaks = tweaks }
|
||||
}
|
||||
|
||||
// WithTaprootKeyTweak 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 aggregated key
|
||||
// before the tweak will be used as the internal key.
|
||||
//
|
||||
// This option should be used instead of WithKeyTweaks when the aggregated key
|
||||
// is intended to be used as a taproot output key that commits to a script
|
||||
// root.
|
||||
func WithTaprootKeyTweak(scriptRoot []byte) KeyAggOption {
|
||||
return func(o *keyAggOption) {
|
||||
var tweak [32]byte
|
||||
copy(tweak[:], scriptRoot[:])
|
||||
o.tweaks = []KeyTweakDesc{
|
||||
{
|
||||
Tweak: tweak,
|
||||
IsXOnly: true,
|
||||
},
|
||||
}
|
||||
o.taprootTweak = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithBIP86KeyTweak specifies that then during key aggregation, the BIP 86
|
||||
// tweak which just commits to the hash of the serialized public key should be
|
||||
// used. This option should be used when signing with a key that was derived
|
||||
// using BIP 86.
|
||||
func WithBIP86KeyTweak() KeyAggOption {
|
||||
return func(o *keyAggOption) {
|
||||
o.tweaks = []KeyTweakDesc{{IsXOnly: true}}
|
||||
o.taprootTweak = true
|
||||
o.bip86Tweak = true
|
||||
}
|
||||
}
|
||||
|
||||
// defaultKeyAggOptions returns the set of default arguments for key
|
||||
// aggregation.
|
||||
func defaultKeyAggOptions() *keyAggOption { return &keyAggOption{} }
|
||||
|
||||
// hasEvenY returns true if the affine representation of the passed jacobian
|
||||
// point has an even y coordinate.
|
||||
//
|
||||
// TODO(roasbeef): double check, can just check the y coord even not jacobian?
|
||||
func hasEvenY(pJ btcec.JacobianPoint) bool {
|
||||
pJ.ToAffine()
|
||||
p := btcec.NewPublicKey(&pJ.X, &pJ.Y)
|
||||
keyBytes := p.SerializeCompressed()
|
||||
return keyBytes[0] == secp256k1.PubKeyFormatCompressedEven
|
||||
}
|
||||
|
||||
// tweakKey applies a tweaks to the passed public key using the specified
|
||||
// tweak. The parityAcc and tweakAcc are returned (in that order) which
|
||||
// includes the accumulate ration of the parity factor and the tweak multiplied
|
||||
// by the parity factor. The xOnly bool specifies if this is to be an x-only
|
||||
// tweak or not.
|
||||
func tweakKey(
|
||||
keyJ btcec.JacobianPoint, parityAcc btcec.ModNScalar,
|
||||
tweak [32]byte,
|
||||
tweakAcc btcec.ModNScalar,
|
||||
xOnly bool,
|
||||
) (btcec.JacobianPoint, btcec.ModNScalar, btcec.ModNScalar, error) {
|
||||
|
||||
// First we'll compute the new parity factor for this key. If the key has
|
||||
// an odd y coordinate (not even), then we'll need to negate it (multiply
|
||||
// by -1 mod n, in this case).
|
||||
var parityFactor btcec.ModNScalar
|
||||
if xOnly && !hasEvenY(keyJ) {
|
||||
parityFactor.SetInt(1).Negate()
|
||||
} else {
|
||||
parityFactor.SetInt(1)
|
||||
}
|
||||
|
||||
// Next, map the tweak into a mod n integer so we can use it for
|
||||
// manipulations below.
|
||||
tweakInt := new(btcec.ModNScalar)
|
||||
overflows := tweakInt.SetBytes(&tweak)
|
||||
if overflows == 1 {
|
||||
return keyJ, parityAcc, tweakAcc, ErrTweakedKeyOverflows
|
||||
}
|
||||
// Next, we'll compute: Q_i = g*Q + t*G, where g is our parityFactor and t
|
||||
// is the tweakInt above. We'll space things out a bit to make it easier to
|
||||
// follow.
|
||||
//
|
||||
// First compute t*G:
|
||||
var tweakedGenerator btcec.JacobianPoint
|
||||
btcec.ScalarBaseMultNonConst(tweakInt, &tweakedGenerator)
|
||||
// Next compute g*Q:
|
||||
btcec.ScalarMultNonConst(&parityFactor, &keyJ, &keyJ)
|
||||
// Finally add both of them together to get our final
|
||||
// tweaked point.
|
||||
btcec.AddNonConst(&tweakedGenerator, &keyJ, &keyJ)
|
||||
// As a sanity check, make sure that we didn't just end up with the
|
||||
// point at infinity.
|
||||
if keyJ == infinityPoint {
|
||||
return keyJ, parityAcc, tweakAcc, ErrTweakedKeyIsInfinity
|
||||
}
|
||||
// As a final wrap up step, we'll accumulate the parity
|
||||
// factor and also this tweak into the final set of accumulators.
|
||||
parityAcc.Mul(&parityFactor)
|
||||
tweakAcc.Mul(&parityFactor).Add(tweakInt)
|
||||
return keyJ, parityAcc, tweakAcc, nil
|
||||
}
|
||||
|
||||
// AggregateKey is a final aggregated key along with a possible version of the
|
||||
// key without any tweaks applied.
|
||||
type AggregateKey struct {
|
||||
// FinalKey is the final aggregated key which may include one or more
|
||||
// tweaks applied to it.
|
||||
FinalKey *btcec.PublicKey
|
||||
// PreTweakedKey is the aggregated *before* any tweaks have been
|
||||
// applied. This should be used as the internal key in taproot
|
||||
// contexts.
|
||||
PreTweakedKey *btcec.PublicKey
|
||||
}
|
||||
|
||||
// AggregateKeys takes a list of possibly unsorted keys and returns a single
|
||||
// aggregated key as specified by the musig2 key aggregation algorithm. A nil
|
||||
// value can be passed for keyHash, which causes this function to re-derive it.
|
||||
// In addition to the combined public key, the parity accumulator and the tweak
|
||||
// accumulator are returned as well.
|
||||
func AggregateKeys(
|
||||
keys []*btcec.PublicKey, sort bool,
|
||||
keyOpts ...KeyAggOption,
|
||||
) (
|
||||
*AggregateKey, *btcec.ModNScalar, *btcec.ModNScalar, error,
|
||||
) {
|
||||
// First, parse the set of optional signing options.
|
||||
opts := defaultKeyAggOptions()
|
||||
for _, option := range keyOpts {
|
||||
option(opts)
|
||||
}
|
||||
// Sort the set of public key so we know we're working with them in
|
||||
// sorted order for all the routines below.
|
||||
if sort {
|
||||
keys = sortKeys(keys)
|
||||
}
|
||||
// The caller may provide the hash of all the keys as an optimization
|
||||
// during signing, as it already needs to be computed.
|
||||
if opts.keyHash == nil {
|
||||
opts.keyHash = keyHashFingerprint(keys, sort)
|
||||
}
|
||||
// A caller may also specify the unique key index themselves so we
|
||||
// don't need to re-compute it.
|
||||
if opts.uniqueKeyIndex == nil {
|
||||
idx := secondUniqueKeyIndex(keys, sort)
|
||||
opts.uniqueKeyIndex = &idx
|
||||
}
|
||||
// For each key, we'll compute the intermediate blinded key: a_i*P_i,
|
||||
// where a_i is the aggregation coefficient for that key, and P_i is
|
||||
// the key itself, then accumulate that (addition) into the main final
|
||||
// key: P = P_1 + P_2 ... P_N.
|
||||
var finalKeyJ btcec.JacobianPoint
|
||||
for _, key := range keys {
|
||||
// Port the key over to Jacobian coordinates as we need it in
|
||||
// this format for the routines below.
|
||||
var keyJ btcec.JacobianPoint
|
||||
key.AsJacobian(&keyJ)
|
||||
// Compute the aggregation coefficient for the key, then
|
||||
// multiply it by the key itself: P_i' = a_i*P_i.
|
||||
var tweakedKeyJ btcec.JacobianPoint
|
||||
a := aggregationCoefficient(
|
||||
keys, key, opts.keyHash, *opts.uniqueKeyIndex,
|
||||
)
|
||||
btcec.ScalarMultNonConst(a, &keyJ, &tweakedKeyJ)
|
||||
// Finally accumulate this into the final key in an incremental
|
||||
// fashion.
|
||||
btcec.AddNonConst(&finalKeyJ, &tweakedKeyJ, &finalKeyJ)
|
||||
}
|
||||
|
||||
// We'll copy over the key at this point, since this represents the
|
||||
// aggregated key before any tweaks have been applied. This'll be used
|
||||
// as the internal key for script path proofs.
|
||||
finalKeyJ.ToAffine()
|
||||
combinedKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y)
|
||||
// At this point, if this is a taproot tweak, then we'll modify the
|
||||
// base tweak value to use the BIP 341 tweak value.
|
||||
if opts.taprootTweak {
|
||||
// Emulate the same behavior as txscript.ComputeTaprootOutputKey
|
||||
// which only operates on the x-only public key.
|
||||
key, _ := schnorr.ParsePubKey(
|
||||
schnorr.SerializePubKey(
|
||||
combinedKey,
|
||||
),
|
||||
)
|
||||
// We only use the actual tweak bytes if we're not committing
|
||||
// to a BIP-0086 key only spend output. Otherwise, we just
|
||||
// commit to the internal key and an empty byte slice as the
|
||||
// root hash.
|
||||
tweakBytes := []byte{}
|
||||
if !opts.bip86Tweak {
|
||||
tweakBytes = opts.tweaks[0].Tweak[:]
|
||||
}
|
||||
// Compute the taproot key tagged hash of:
|
||||
// h_tapTweak(internalKey || scriptRoot). We only do this for
|
||||
// the first one, as you can only specify a single tweak when
|
||||
// using the taproot mode with this API.
|
||||
tapTweakHash := chainhash.TaggedHash(
|
||||
chainhash.TagTapTweak, schnorr.SerializePubKey(key),
|
||||
tweakBytes,
|
||||
)
|
||||
opts.tweaks[0].Tweak = *tapTweakHash
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
tweakAcc btcec.ModNScalar
|
||||
parityAcc btcec.ModNScalar
|
||||
)
|
||||
parityAcc.SetInt(1)
|
||||
// In this case we have a set of tweaks, so we'll incrementally apply
|
||||
// each one, until we have our final tweaked key, and the related
|
||||
// accumulators.
|
||||
for i := 1; i <= len(opts.tweaks); i++ {
|
||||
finalKeyJ, parityAcc, tweakAcc, err = tweakKey(
|
||||
finalKeyJ, parityAcc, opts.tweaks[i-1].Tweak, tweakAcc,
|
||||
opts.tweaks[i-1].IsXOnly,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
finalKeyJ.ToAffine()
|
||||
finalKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y)
|
||||
return &AggregateKey{
|
||||
PreTweakedKey: combinedKey,
|
||||
FinalKey: finalKey,
|
||||
}, &parityAcc, &tweakAcc, nil
|
||||
}
|
||||
332
pkg/crypto/ec/musig2/keys_test.go
Normal file
332
pkg/crypto/ec/musig2/keys_test.go
Normal file
@@ -0,0 +1,332 @@
|
||||
// Copyright 2013-2022 The btcsuite developers
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"next.orly.dev/pkg/crypto/ec"
|
||||
"next.orly.dev/pkg/crypto/ec/schnorr"
|
||||
"next.orly.dev/pkg/crypto/ec/secp256k1"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
keySortTestVectorFileName = "key_sort_vectors.json"
|
||||
keyAggTestVectorFileName = "key_agg_vectors.json"
|
||||
keyTweakTestVectorFileName = "tweak_vectors.json"
|
||||
)
|
||||
|
||||
type keySortTestVector struct {
|
||||
PubKeys []string `json:"pubkeys"`
|
||||
SortedKeys []string `json:"sorted_pubkeys"`
|
||||
}
|
||||
|
||||
// TestMusig2KeySort tests that keys are properly sorted according to the
|
||||
// musig2 test vectors.
|
||||
func TestMusig2KeySort(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVectorPath := path.Join(
|
||||
testVectorBaseDir, keySortTestVectorFileName,
|
||||
)
|
||||
testVectorBytes, err := os.ReadFile(testVectorPath)
|
||||
require.NoError(t, err)
|
||||
var testCase keySortTestVector
|
||||
require.NoError(t, json.Unmarshal(testVectorBytes, &testCase))
|
||||
keys := make([]*btcec.PublicKey, len(testCase.PubKeys))
|
||||
for i, keyStr := range testCase.PubKeys {
|
||||
pubKey, err := btcec.ParsePubKey(mustParseHex(keyStr))
|
||||
require.NoError(t, err)
|
||||
keys[i] = pubKey
|
||||
}
|
||||
sortedKeys := sortKeys(keys)
|
||||
expectedKeys := make([]*btcec.PublicKey, len(testCase.PubKeys))
|
||||
for i, keyStr := range testCase.SortedKeys {
|
||||
pubKey, err := btcec.ParsePubKey(mustParseHex(keyStr))
|
||||
require.NoError(t, err)
|
||||
expectedKeys[i] = pubKey
|
||||
}
|
||||
require.Equal(t, sortedKeys, expectedKeys)
|
||||
}
|
||||
|
||||
type keyAggValidTest struct {
|
||||
Indices []int `json:"key_indices"`
|
||||
Expected string `json:"expected"`
|
||||
}
|
||||
|
||||
type keyAggError struct {
|
||||
Type string `json:"type"`
|
||||
Signer int `json:"signer"`
|
||||
Contring string `json:"contrib"`
|
||||
}
|
||||
|
||||
type keyAggInvalidTest struct {
|
||||
Indices []int `json:"key_indices"`
|
||||
TweakIndices []int `json:"tweak_indices"`
|
||||
IsXOnly []bool `json:"is_xonly"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type keyAggTestVectors struct {
|
||||
PubKeys []string `json:"pubkeys"`
|
||||
Tweaks []string `json:"tweaks"`
|
||||
ValidCases []keyAggValidTest `json:"valid_test_cases"`
|
||||
InvalidCases []keyAggInvalidTest `json:"error_test_cases"`
|
||||
}
|
||||
|
||||
func keysFromIndices(
|
||||
t *testing.T, indices []int,
|
||||
pubKeys []string,
|
||||
) ([]*btcec.PublicKey, error) {
|
||||
t.Helper()
|
||||
inputKeys := make([]*btcec.PublicKey, len(indices))
|
||||
for i, keyIdx := range indices {
|
||||
var err error
|
||||
inputKeys[i], err = btcec.ParsePubKey(
|
||||
mustParseHex(pubKeys[keyIdx]),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return inputKeys, nil
|
||||
}
|
||||
|
||||
func tweaksFromIndices(
|
||||
t *testing.T, indices []int,
|
||||
tweaks []string, isXonly []bool,
|
||||
) []KeyTweakDesc {
|
||||
|
||||
t.Helper()
|
||||
testTweaks := make([]KeyTweakDesc, len(indices))
|
||||
for i, idx := range indices {
|
||||
var rawTweak [32]byte
|
||||
copy(rawTweak[:], mustParseHex(tweaks[idx]))
|
||||
testTweaks[i] = KeyTweakDesc{
|
||||
Tweak: rawTweak,
|
||||
IsXOnly: isXonly[i],
|
||||
}
|
||||
}
|
||||
return testTweaks
|
||||
}
|
||||
|
||||
// TestMuSig2KeyAggTestVectors tests that this implementation of musig2 key
|
||||
// aggregation lines up with the secp256k1-zkp test vectors.
|
||||
func TestMuSig2KeyAggTestVectors(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVectorPath := path.Join(
|
||||
testVectorBaseDir, keyAggTestVectorFileName,
|
||||
)
|
||||
testVectorBytes, err := os.ReadFile(testVectorPath)
|
||||
require.NoError(t, err)
|
||||
var testCases keyAggTestVectors
|
||||
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
|
||||
tweaks := make([][]byte, len(testCases.Tweaks))
|
||||
for i := range testCases.Tweaks {
|
||||
tweaks[i] = mustParseHex(testCases.Tweaks[i])
|
||||
}
|
||||
for i, testCase := range testCases.ValidCases {
|
||||
testCase := testCase
|
||||
// Assemble the set of keys we'll pass in based on their key
|
||||
// index. We don't use sorting to ensure we send the keys in
|
||||
// the exact same order as the test vectors do.
|
||||
inputKeys, err := keysFromIndices(
|
||||
t, testCase.Indices, testCases.PubKeys,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Run(
|
||||
fmt.Sprintf("test_case=%v", i), func(t *testing.T) {
|
||||
uniqueKeyIndex := secondUniqueKeyIndex(inputKeys, false)
|
||||
opts := []KeyAggOption{WithUniqueKeyIndex(uniqueKeyIndex)}
|
||||
|
||||
combinedKey, _, _, err := AggregateKeys(
|
||||
inputKeys, false, opts...,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(
|
||||
t, schnorr.SerializePubKey(combinedKey.FinalKey),
|
||||
mustParseHex(testCase.Expected),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
for _, testCase := range testCases.InvalidCases {
|
||||
testCase := testCase
|
||||
testName := fmt.Sprintf(
|
||||
"invalid_%v",
|
||||
strings.ToLower(testCase.Comment),
|
||||
)
|
||||
t.Run(
|
||||
testName, func(t *testing.T) {
|
||||
// For each test, we'll extract the set of input keys
|
||||
// as well as the tweaks since this set of cases also
|
||||
// exercises error cases related to the set of tweaks.
|
||||
inputKeys, err := keysFromIndices(
|
||||
t, testCase.Indices, testCases.PubKeys,
|
||||
)
|
||||
// In this set of test cases, we should only get this
|
||||
// for the very first vector.
|
||||
if err != nil {
|
||||
switch testCase.Comment {
|
||||
case "Invalid public key":
|
||||
require.ErrorIs(
|
||||
t, err,
|
||||
secp256k1.ErrPubKeyNotOnCurve,
|
||||
)
|
||||
case "Public key exceeds field size":
|
||||
require.ErrorIs(
|
||||
t, err, secp256k1.ErrPubKeyXTooBig,
|
||||
)
|
||||
case "First byte of public key is not 2 or 3":
|
||||
require.ErrorIs(
|
||||
t, err,
|
||||
secp256k1.ErrPubKeyInvalidFormat,
|
||||
)
|
||||
default:
|
||||
t.Fatalf("uncaught err: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var tweaks []KeyTweakDesc
|
||||
if len(testCase.TweakIndices) != 0 {
|
||||
tweaks = tweaksFromIndices(
|
||||
t, testCase.TweakIndices, testCases.Tweaks,
|
||||
testCase.IsXOnly,
|
||||
)
|
||||
}
|
||||
uniqueKeyIndex := secondUniqueKeyIndex(inputKeys, false)
|
||||
opts := []KeyAggOption{
|
||||
WithUniqueKeyIndex(uniqueKeyIndex),
|
||||
}
|
||||
if len(tweaks) != 0 {
|
||||
opts = append(opts, WithKeyTweaks(tweaks...))
|
||||
}
|
||||
_, _, _, err = AggregateKeys(
|
||||
inputKeys, false, opts...,
|
||||
)
|
||||
require.Error(t, err)
|
||||
switch testCase.Comment {
|
||||
case "Tweak is out of range":
|
||||
require.ErrorIs(t, err, ErrTweakedKeyOverflows)
|
||||
case "Intermediate tweaking result is point at infinity":
|
||||
require.ErrorIs(t, err, ErrTweakedKeyIsInfinity)
|
||||
default:
|
||||
t.Fatalf("uncaught err: %v", err)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type keyTweakInvalidTest struct {
|
||||
Indices []int `json:"key_indices"`
|
||||
NonceIndices []int `json:"nonce_indices"`
|
||||
TweakIndices []int `json:"tweak_indices"`
|
||||
IsXOnly []bool `json:"is_only"`
|
||||
SignerIndex int `json:"signer_index"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type keyTweakValidTest struct {
|
||||
Indices []int `json:"key_indices"`
|
||||
NonceIndices []int `json:"nonce_indices"`
|
||||
TweakIndices []int `json:"tweak_indices"`
|
||||
IsXOnly []bool `json:"is_xonly"`
|
||||
SignerIndex int `json:"signer_index"`
|
||||
Expected string `json:"expected"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type keyTweakVector struct {
|
||||
SecKey string `json:"sk"`
|
||||
PubKeys []string `json:"pubkeys"`
|
||||
PrivNonce string `json:"secnonce"`
|
||||
PubNonces []string `json:"pnonces"`
|
||||
AggNnoce string `json:"aggnonce"`
|
||||
Tweaks []string `json:"tweaks"`
|
||||
Msg string `json:"msg"`
|
||||
ValidCases []keyTweakValidTest `json:"valid_test_cases"`
|
||||
InvalidCases []keyTweakInvalidTest `json:"error_test_cases"`
|
||||
}
|
||||
|
||||
func pubNoncesFromIndices(
|
||||
t *testing.T, nonceIndices []int,
|
||||
pubNonces []string,
|
||||
) [][PubNonceSize]byte {
|
||||
|
||||
nonces := make([][PubNonceSize]byte, len(nonceIndices))
|
||||
for i, idx := range nonceIndices {
|
||||
var pubNonce [PubNonceSize]byte
|
||||
copy(pubNonce[:], mustParseHex(pubNonces[idx]))
|
||||
nonces[i] = pubNonce
|
||||
}
|
||||
return nonces
|
||||
}
|
||||
|
||||
// TestMuSig2TweakTestVectors tests that we properly handle the various edge
|
||||
// cases related to tweaking public keys.
|
||||
func TestMuSig2TweakTestVectors(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVectorPath := path.Join(
|
||||
testVectorBaseDir, keyTweakTestVectorFileName,
|
||||
)
|
||||
testVectorBytes, err := os.ReadFile(testVectorPath)
|
||||
require.NoError(t, err)
|
||||
var testCases keyTweakVector
|
||||
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
|
||||
privKey, _ := btcec.SecKeyFromBytes(mustParseHex(testCases.SecKey))
|
||||
var msg [32]byte
|
||||
copy(msg[:], mustParseHex(testCases.Msg))
|
||||
var secNonce [SecNonceSize]byte
|
||||
copy(secNonce[:], mustParseHex(testCases.PrivNonce))
|
||||
for _, testCase := range testCases.ValidCases {
|
||||
testCase := testCase
|
||||
testName := fmt.Sprintf(
|
||||
"valid_%v",
|
||||
strings.ToLower(testCase.Comment),
|
||||
)
|
||||
t.Run(
|
||||
testName, func(t *testing.T) {
|
||||
pubKeys, err := keysFromIndices(
|
||||
t, testCase.Indices, testCases.PubKeys,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
var tweaks []KeyTweakDesc
|
||||
if len(testCase.TweakIndices) != 0 {
|
||||
tweaks = tweaksFromIndices(
|
||||
t, testCase.TweakIndices,
|
||||
testCases.Tweaks, testCase.IsXOnly,
|
||||
)
|
||||
}
|
||||
pubNonces := pubNoncesFromIndices(
|
||||
t, testCase.NonceIndices, testCases.PubNonces,
|
||||
)
|
||||
combinedNonce, err := AggregateNonces(pubNonces)
|
||||
require.NoError(t, err)
|
||||
var opts []SignOption
|
||||
if len(tweaks) != 0 {
|
||||
opts = append(opts, WithTweaks(tweaks...))
|
||||
}
|
||||
partialSig, err := Sign(
|
||||
secNonce, privKey, combinedNonce, pubKeys,
|
||||
msg, opts...,
|
||||
)
|
||||
var partialSigBytes [32]byte
|
||||
partialSig.S.PutBytesUnchecked(partialSigBytes[:])
|
||||
require.Equal(
|
||||
t, hex.Enc(partialSigBytes[:]),
|
||||
hex.Enc(mustParseHex(testCase.Expected)),
|
||||
)
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
409
pkg/crypto/ec/musig2/musig2_test.go
Normal file
409
pkg/crypto/ec/musig2/musig2_test.go
Normal file
@@ -0,0 +1,409 @@
|
||||
// Copyright 2013-2022 The btcsuite developers
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"next.orly.dev/pkg/crypto/ec"
|
||||
"next.orly.dev/pkg/crypto/sha256"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
)
|
||||
|
||||
const (
|
||||
testVectorBaseDir = "data"
|
||||
)
|
||||
|
||||
func mustParseHex(str string) []byte {
|
||||
b, err := hex.Dec(str)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to parse hex: %v", err))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type signer struct {
|
||||
privKey *btcec.SecretKey
|
||||
pubKey *btcec.PublicKey
|
||||
nonces *Nonces
|
||||
partialSig *PartialSignature
|
||||
}
|
||||
|
||||
type signerSet []signer
|
||||
|
||||
func (s signerSet) keys() []*btcec.PublicKey {
|
||||
keys := make([]*btcec.PublicKey, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
keys[i] = s[i].pubKey
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (s signerSet) partialSigs() []*PartialSignature {
|
||||
sigs := make([]*PartialSignature, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
sigs[i] = s[i].partialSig
|
||||
}
|
||||
return sigs
|
||||
}
|
||||
|
||||
func (s signerSet) pubNonces() [][PubNonceSize]byte {
|
||||
nonces := make([][PubNonceSize]byte, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
nonces[i] = s[i].nonces.PubNonce
|
||||
}
|
||||
return nonces
|
||||
}
|
||||
|
||||
func (s signerSet) combinedKey() *btcec.PublicKey {
|
||||
uniqueKeyIndex := secondUniqueKeyIndex(s.keys(), false)
|
||||
key, _, _, _ := AggregateKeys(
|
||||
s.keys(), false, WithUniqueKeyIndex(uniqueKeyIndex),
|
||||
)
|
||||
return key.FinalKey
|
||||
}
|
||||
|
||||
// testMultiPartySign executes a multi-party signing context w/ 100 signers.
|
||||
func testMultiPartySign(
|
||||
t *testing.T, taprootTweak []byte,
|
||||
tweaks ...KeyTweakDesc,
|
||||
) {
|
||||
|
||||
const numSigners = 100
|
||||
// First generate the set of signers along with their public keys.
|
||||
signerKeys := make([]*btcec.SecretKey, numSigners)
|
||||
signSet := make([]*btcec.PublicKey, numSigners)
|
||||
for i := 0; i < numSigners; i++ {
|
||||
privKey, err := btcec.NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to gen priv key: %v", err)
|
||||
}
|
||||
pubKey := privKey.PubKey()
|
||||
signerKeys[i] = privKey
|
||||
signSet[i] = pubKey
|
||||
}
|
||||
var combinedKey *btcec.PublicKey
|
||||
var ctxOpts []ContextOption
|
||||
switch {
|
||||
case len(taprootTweak) == 0:
|
||||
ctxOpts = append(ctxOpts, WithBip86TweakCtx())
|
||||
case taprootTweak != nil:
|
||||
ctxOpts = append(ctxOpts, WithTaprootTweakCtx(taprootTweak))
|
||||
case len(tweaks) != 0:
|
||||
ctxOpts = append(ctxOpts, WithTweakedContext(tweaks...))
|
||||
}
|
||||
ctxOpts = append(ctxOpts, WithKnownSigners(signSet))
|
||||
// Now that we have all the signers, we'll make a new context, then
|
||||
// generate a new session for each of them(which handles nonce
|
||||
// generation).
|
||||
signers := make([]*Session, numSigners)
|
||||
for i, signerKey := range signerKeys {
|
||||
signCtx, err := NewContext(
|
||||
signerKey, false, ctxOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate context: %v", err)
|
||||
}
|
||||
if combinedKey == nil {
|
||||
combinedKey, err = signCtx.CombinedKey()
|
||||
if err != nil {
|
||||
t.Fatalf("combined key not available: %v", err)
|
||||
}
|
||||
}
|
||||
session, err := signCtx.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate new session: %v", err)
|
||||
}
|
||||
signers[i] = session
|
||||
}
|
||||
// Next, in the pre-signing phase, we'll send all the nonces to each
|
||||
// signer.
|
||||
var wg sync.WaitGroup
|
||||
for i, signCtx := range signers {
|
||||
signCtx := signCtx
|
||||
wg.Add(1)
|
||||
go func(idx int, signer *Session) {
|
||||
defer wg.Done()
|
||||
for j, otherCtx := range signers {
|
||||
if idx == j {
|
||||
continue
|
||||
}
|
||||
nonce := otherCtx.PublicNonce()
|
||||
haveAll, err := signer.RegisterPubNonce(nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add public nonce")
|
||||
}
|
||||
if j == len(signers)-1 && !haveAll {
|
||||
t.Fatalf("all public nonces should have been detected")
|
||||
}
|
||||
}
|
||||
}(i, signCtx)
|
||||
}
|
||||
wg.Wait()
|
||||
msg := sha256.Sum256([]byte("let's get taprooty"))
|
||||
// In the final step, we'll use the first signer as our combiner, and
|
||||
// generate a signature for each signer, and then accumulate that with
|
||||
// the combiner.
|
||||
combiner := signers[0]
|
||||
for i := range signers {
|
||||
signer := signers[i]
|
||||
partialSig, err := signer.Sign(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate partial sig: %v", err)
|
||||
}
|
||||
// We don't need to combine the signature for the very first
|
||||
// signer, as it already has that partial signature.
|
||||
if i != 0 {
|
||||
haveAll, err := combiner.CombineSig(partialSig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to combine sigs: %v", err)
|
||||
}
|
||||
|
||||
if i == len(signers)-1 && !haveAll {
|
||||
t.Fatalf("final sig wasn't reconstructed")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally we'll combined all the nonces, and ensure that it validates
|
||||
// as a single schnorr signature.
|
||||
finalSig := combiner.FinalSig()
|
||||
if !finalSig.Verify(msg[:], combinedKey) {
|
||||
t.Fatalf("final sig is invalid!")
|
||||
}
|
||||
// Verify that if we try to sign again with any of the existing
|
||||
// signers, then we'll get an error as the nonces have already been
|
||||
// used.
|
||||
for _, signer := range signers {
|
||||
_, err := signer.Sign(msg)
|
||||
if err != ErrSigningContextReuse {
|
||||
t.Fatalf("expected to get signing context reuse")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMuSigMultiParty tests that for a given set of 100 signers, we're able to
|
||||
// properly generate valid sub signatures, which ultimately can be combined
|
||||
// into a single valid signature.
|
||||
func TestMuSigMultiParty(t *testing.T) {
|
||||
t.Parallel()
|
||||
testTweak := [32]byte{
|
||||
0xE8, 0xF7, 0x91, 0xFF, 0x92, 0x25, 0xA2, 0xAF,
|
||||
0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, 0x72, 0x3D,
|
||||
0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79,
|
||||
0x80, 0x2B, 0x26, 0x3C, 0xDF, 0xCD, 0x83, 0xBB,
|
||||
}
|
||||
t.Run(
|
||||
"no_tweak", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testMultiPartySign(t, nil)
|
||||
},
|
||||
)
|
||||
t.Run(
|
||||
"tweaked", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testMultiPartySign(
|
||||
t, nil, KeyTweakDesc{
|
||||
Tweak: testTweak,
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
t.Run(
|
||||
"tweaked_x_only", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testMultiPartySign(
|
||||
t, nil, KeyTweakDesc{
|
||||
Tweak: testTweak,
|
||||
IsXOnly: true,
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
t.Run(
|
||||
"taproot_tweaked_x_only", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testMultiPartySign(t, testTweak[:])
|
||||
},
|
||||
)
|
||||
t.Run(
|
||||
"taproot_bip_86", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testMultiPartySign(t, []byte{})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestMuSigEarlyNonce tests that for protocols where nonces need to be
|
||||
// exchagned before all signers are known, the context API works as expected.
|
||||
func TestMuSigEarlyNonce(t *testing.T) {
|
||||
t.Parallel()
|
||||
privKey1, err := btcec.NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to gen priv key: %v", err)
|
||||
}
|
||||
privKey2, err := btcec.NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to gen priv key: %v", err)
|
||||
}
|
||||
// If we try to make a context, with just the secret key and sorting
|
||||
// value, we should get an error.
|
||||
_, err = NewContext(privKey1, true)
|
||||
if !errors.Is(err, ErrSignersNotSpecified) {
|
||||
t.Fatalf("unexpected ctx error: %v", err)
|
||||
}
|
||||
signers := []*btcec.PublicKey{privKey1.PubKey(), privKey2.PubKey()}
|
||||
numSigners := len(signers)
|
||||
ctx1, err := NewContext(
|
||||
privKey1, true, WithNumSigners(numSigners), WithEarlyNonceGen(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make ctx: %v", err)
|
||||
}
|
||||
pubKey1 := ctx1.PubKey()
|
||||
ctx2, err := NewContext(
|
||||
privKey2, true, WithKnownSigners(signers), WithEarlyNonceGen(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make ctx: %v", err)
|
||||
}
|
||||
pubKey2 := ctx2.PubKey()
|
||||
// At this point, the combined key shouldn't be available for signer 1,
|
||||
// but should be for signer 2, as they know about all signers.
|
||||
if _, err := ctx1.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) {
|
||||
t.Fatalf("unepxected error: %v", err)
|
||||
}
|
||||
_, err = ctx2.CombinedKey()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get combined key: %v", err)
|
||||
}
|
||||
// The early nonces _should_ be available at this point.
|
||||
nonce1, err := ctx1.EarlySessionNonce()
|
||||
if err != nil {
|
||||
t.Fatalf("session nonce not available: %v", err)
|
||||
}
|
||||
nonce2, err := ctx2.EarlySessionNonce()
|
||||
if err != nil {
|
||||
t.Fatalf("session nonce not available: %v", err)
|
||||
}
|
||||
// The number of registered signers should still be 1 for both parties.
|
||||
if ctx1.NumRegisteredSigners() != 1 {
|
||||
t.Fatalf(
|
||||
"expected 1 signer, instead have: %v",
|
||||
ctx1.NumRegisteredSigners(),
|
||||
)
|
||||
}
|
||||
if ctx2.NumRegisteredSigners() != 2 {
|
||||
t.Fatalf(
|
||||
"expected 2 signers, instead have: %v",
|
||||
ctx2.NumRegisteredSigners(),
|
||||
)
|
||||
}
|
||||
// If we try to make a session, we should get an error since we dn't
|
||||
// have all the signers yet.
|
||||
if _, err := ctx1.NewSession(); !errors.Is(err, ErrNotEnoughSigners) {
|
||||
t.Fatalf("unexpected session key error: %v", err)
|
||||
}
|
||||
// The combined key should also be unavailable as well.
|
||||
if _, err := ctx1.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) {
|
||||
t.Fatalf("unexpected combined key error: %v", err)
|
||||
}
|
||||
// We'll now register the other signer for party 1.
|
||||
done, err := ctx1.RegisterSigner(&pubKey2)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register signer: %v", err)
|
||||
}
|
||||
if !done {
|
||||
t.Fatalf("signer 1 doesn't have all keys")
|
||||
}
|
||||
// If we try to register the signer again, we should get an error.
|
||||
_, err = ctx2.RegisterSigner(&pubKey1)
|
||||
if !errors.Is(err, ErrAlreadyHaveAllSigners) {
|
||||
t.Fatalf("should not be able to register too many signers")
|
||||
}
|
||||
// We should be able to create the session at this point.
|
||||
session1, err := ctx1.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create new session: %v", err)
|
||||
}
|
||||
session2, err := ctx2.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create new session: %v", err)
|
||||
}
|
||||
msg := sha256.Sum256([]byte("let's get taprooty, LN style"))
|
||||
// If we try to sign before we have the combined nonce, we shoudl get
|
||||
// an error.
|
||||
_, err = session1.Sign(msg)
|
||||
if !errors.Is(err, ErrCombinedNonceUnavailable) {
|
||||
t.Fatalf("unable to gen sig: %v", err)
|
||||
}
|
||||
// Now we can exchange nonces to continue with the rest of the signing
|
||||
// process as normal.
|
||||
done, err = session1.RegisterPubNonce(nonce2.PubNonce)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register nonce: %v", err)
|
||||
}
|
||||
if !done {
|
||||
t.Fatalf("signer 1 doesn't have all nonces")
|
||||
}
|
||||
done, err = session2.RegisterPubNonce(nonce1.PubNonce)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register nonce: %v", err)
|
||||
}
|
||||
if !done {
|
||||
t.Fatalf("signer 2 doesn't have all nonces")
|
||||
}
|
||||
// Registering the nonce again should error out.
|
||||
_, err = session2.RegisterPubNonce(nonce1.PubNonce)
|
||||
if !errors.Is(err, ErrAlreadyHaveAllNonces) {
|
||||
t.Fatalf("shouldn't be able to register nonces twice")
|
||||
}
|
||||
// Sign the message and combine the two partial sigs into one.
|
||||
_, err = session1.Sign(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to gen sig: %v", err)
|
||||
}
|
||||
sig2, err := session2.Sign(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to gen sig: %v", err)
|
||||
}
|
||||
done, err = session1.CombineSig(sig2)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to combine sig: %v", err)
|
||||
}
|
||||
if !done {
|
||||
t.Fatalf("all sigs should be known now: %v", err)
|
||||
}
|
||||
// If we try to combine another sig, then we should get an error.
|
||||
_, err = session1.CombineSig(sig2)
|
||||
if !errors.Is(err, ErrAlredyHaveAllSigs) {
|
||||
t.Fatalf("shouldn't be able to combine again")
|
||||
}
|
||||
// Finally, verify that the final signature is valid.
|
||||
combinedKey, err := ctx1.CombinedKey()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected combined key error: %v", err)
|
||||
}
|
||||
finalSig := session1.FinalSig()
|
||||
if !finalSig.Verify(msg[:], combinedKey) {
|
||||
t.Fatalf("final sig is invalid!")
|
||||
}
|
||||
}
|
||||
|
||||
type memsetRandReader struct {
|
||||
i int
|
||||
}
|
||||
|
||||
func (mr *memsetRandReader) Read(buf []byte) (n int, err error) {
|
||||
for i := range buf {
|
||||
buf[i] = byte(mr.i)
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
407
pkg/crypto/ec/musig2/nonces.go
Normal file
407
pkg/crypto/ec/musig2/nonces.go
Normal file
@@ -0,0 +1,407 @@
|
||||
// Copyright 2013-2022 The btcsuite developers
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"next.orly.dev/pkg/crypto/ec"
|
||||
"next.orly.dev/pkg/crypto/ec/chainhash"
|
||||
"next.orly.dev/pkg/crypto/ec/schnorr"
|
||||
)
|
||||
|
||||
const (
|
||||
// PubNonceSize is the size of the public nonces. Each public nonce is
|
||||
// serialized the full compressed encoding, which uses 32 bytes for each
|
||||
// nonce.
|
||||
PubNonceSize = 66
|
||||
// SecNonceSize is the size of the secret nonces for musig2. The secret
|
||||
// nonces are the corresponding secret keys to the public nonce points.
|
||||
SecNonceSize = 97
|
||||
)
|
||||
|
||||
var (
|
||||
// NonceAuxTag is the tag used to optionally mix in the secret key with
|
||||
// the set of aux randomness.
|
||||
NonceAuxTag = []byte("MuSig/aux")
|
||||
// NonceGenTag is used to generate the value (from a set of required an
|
||||
// optional field) that will be used as the part of the secret nonce.
|
||||
NonceGenTag = []byte("MuSig/nonce")
|
||||
byteOrder = binary.BigEndian
|
||||
// ErrPubkeyInvalid is returned when the pubkey of the WithPublicKey
|
||||
// option is not passed or of invalid length.
|
||||
ErrPubkeyInvalid = errors.New("nonce generation requires a valid pubkey")
|
||||
)
|
||||
|
||||
// zeroSecNonce is a secret nonce that's all zeroes. This is used to check that
|
||||
// we're not attempting to re-use a nonce, and also protect callers from it.
|
||||
var zeroSecNonce [SecNonceSize]byte
|
||||
|
||||
// Nonces holds the public and secret nonces required for musig2.
|
||||
//
|
||||
// TODO(roasbeef): methods on this to help w/ parsing, etc?
|
||||
type Nonces struct {
|
||||
// PubNonce holds the two 33-byte compressed encoded points that serve
|
||||
// as the public set of nonces.
|
||||
PubNonce [PubNonceSize]byte
|
||||
// SecNonce holds the two 32-byte scalar values that are the secret
|
||||
// keys to the two public nonces.
|
||||
SecNonce [SecNonceSize]byte
|
||||
}
|
||||
|
||||
// secNonceToPubNonce takes our two secrete nonces, and produces their two
|
||||
// corresponding EC points, serialized in compressed format.
|
||||
func secNonceToPubNonce(secNonce [SecNonceSize]byte) [PubNonceSize]byte {
|
||||
var k1Mod, k2Mod btcec.ModNScalar
|
||||
k1Mod.SetByteSlice(secNonce[:btcec.SecKeyBytesLen])
|
||||
k2Mod.SetByteSlice(secNonce[btcec.SecKeyBytesLen:])
|
||||
var r1, r2 btcec.JacobianPoint
|
||||
btcec.ScalarBaseMultNonConst(&k1Mod, &r1)
|
||||
btcec.ScalarBaseMultNonConst(&k2Mod, &r2)
|
||||
// Next, we'll convert the key in jacobian format to a normal public
|
||||
// key expressed in affine coordinates.
|
||||
r1.ToAffine()
|
||||
r2.ToAffine()
|
||||
r1Pub := btcec.NewPublicKey(&r1.X, &r1.Y)
|
||||
r2Pub := btcec.NewPublicKey(&r2.X, &r2.Y)
|
||||
var pubNonce [PubNonceSize]byte
|
||||
// The public nonces are serialized as: R1 || R2, where both keys are
|
||||
// serialized in compressed format.
|
||||
copy(pubNonce[:], r1Pub.SerializeCompressed())
|
||||
copy(
|
||||
pubNonce[btcec.PubKeyBytesLenCompressed:],
|
||||
r2Pub.SerializeCompressed(),
|
||||
)
|
||||
return pubNonce
|
||||
}
|
||||
|
||||
// NonceGenOption is a function option that allows callers to modify how nonce
|
||||
// generation happens.
|
||||
type NonceGenOption func(*nonceGenOpts)
|
||||
|
||||
// nonceGenOpts is the set of options that control how nonce generation
|
||||
// happens.
|
||||
type nonceGenOpts struct {
|
||||
// randReader is what we'll use to generate a set of random bytes. If
|
||||
// unspecified, then the normal crypto/rand rand.Read method will be
|
||||
// used in place.
|
||||
randReader io.Reader
|
||||
// publicKey is the mandatory public key that will be mixed into the nonce
|
||||
// generation.
|
||||
publicKey []byte
|
||||
// secretKey is an optional argument that's used to further augment the
|
||||
// generated nonce by xor'ing it with this secret key.
|
||||
secretKey []byte
|
||||
// combinedKey is an optional argument that if specified, will be
|
||||
// combined along with the nonce generation.
|
||||
combinedKey []byte
|
||||
// msg is an optional argument that will be mixed into the nonce
|
||||
// derivation algorithm.
|
||||
msg []byte
|
||||
// auxInput is an optional argument that will be mixed into the nonce
|
||||
// derivation algorithm.
|
||||
auxInput []byte
|
||||
}
|
||||
|
||||
// cryptoRandAdapter is an adapter struct that allows us to pass in the package
|
||||
// level Read function from crypto/rand into a context that accepts an
|
||||
// io.Reader.
|
||||
type cryptoRandAdapter struct{}
|
||||
|
||||
// Read implements the io.Reader interface for the crypto/rand package. By
|
||||
// default, we always use the crypto/rand reader, but the caller is able to
|
||||
// specify their own generation, which can be useful for deterministic tests.
|
||||
func (c *cryptoRandAdapter) Read(p []byte) (n int, err error) {
|
||||
return rand.Read(p)
|
||||
}
|
||||
|
||||
// defaultNonceGenOpts returns the default set of nonce generation options.
|
||||
func defaultNonceGenOpts() *nonceGenOpts {
|
||||
return &nonceGenOpts{randReader: &cryptoRandAdapter{}}
|
||||
}
|
||||
|
||||
// WithCustomRand allows a caller to use a custom random number generator in
|
||||
// place for crypto/rand. This should only really be used to generate
|
||||
// determinstic tests.
|
||||
func WithCustomRand(r io.Reader) NonceGenOption {
|
||||
return func(o *nonceGenOpts) { o.randReader = r }
|
||||
}
|
||||
|
||||
// WithPublicKey is the mandatory public key that will be mixed into the nonce
|
||||
// generation.
|
||||
func WithPublicKey(pubKey *btcec.PublicKey) NonceGenOption {
|
||||
return func(o *nonceGenOpts) {
|
||||
o.publicKey = pubKey.SerializeCompressed()
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonceSecretKeyAux allows a caller to optionally specify a secret key
|
||||
// that should be used to augment the randomness used to generate the nonces.
|
||||
func WithNonceSecretKeyAux(secKey *btcec.SecretKey) NonceGenOption {
|
||||
return func(o *nonceGenOpts) { o.secretKey = secKey.Serialize() }
|
||||
}
|
||||
|
||||
var WithNoncePrivateKeyAux = WithNonceSecretKeyAux
|
||||
|
||||
// WithNonceCombinedKeyAux allows a caller to optionally specify the combined
|
||||
// key used in this signing session to further augment the randomness used to
|
||||
// generate nonces.
|
||||
func WithNonceCombinedKeyAux(combinedKey *btcec.PublicKey) NonceGenOption {
|
||||
return func(o *nonceGenOpts) {
|
||||
o.combinedKey = schnorr.SerializePubKey(combinedKey)
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonceMessageAux allows a caller to optionally specify a message to be
|
||||
// mixed into the randomness generated to create the nonce.
|
||||
func WithNonceMessageAux(msg [32]byte) NonceGenOption {
|
||||
return func(o *nonceGenOpts) { o.msg = msg[:] }
|
||||
}
|
||||
|
||||
// WithNonceAuxInput is a set of auxiliary randomness, similar to BIP 340 that
|
||||
// can be used to further augment the nonce generation process.
|
||||
func WithNonceAuxInput(aux []byte) NonceGenOption {
|
||||
return func(o *nonceGenOpts) { o.auxInput = aux }
|
||||
}
|
||||
|
||||
// withCustomOptions allows a caller to pass a complete set of custom
|
||||
// nonceGenOpts, without needing to create custom and checked structs such as
|
||||
// *btcec.SecretKey. This is mainly used to match the testcases provided by
|
||||
// the MuSig2 BIP.
|
||||
func withCustomOptions(customOpts nonceGenOpts) NonceGenOption {
|
||||
return func(o *nonceGenOpts) {
|
||||
o.randReader = customOpts.randReader
|
||||
o.secretKey = customOpts.secretKey
|
||||
o.combinedKey = customOpts.combinedKey
|
||||
o.auxInput = customOpts.auxInput
|
||||
o.msg = customOpts.msg
|
||||
o.publicKey = customOpts.publicKey
|
||||
}
|
||||
}
|
||||
|
||||
// lengthWriter is a function closure that allows a caller to control how the
|
||||
// length prefix of a byte slice is written.
|
||||
//
|
||||
// TODO(roasbeef): use type params once we bump repo version
|
||||
type lengthWriter func(w io.Writer, b []byte) error
|
||||
|
||||
// uint8Writer is an implementation of lengthWriter that writes the length of
|
||||
// the byte slice using 1 byte.
|
||||
func uint8Writer(w io.Writer, b []byte) error {
|
||||
return binary.Write(w, byteOrder, uint8(len(b)))
|
||||
}
|
||||
|
||||
// uint32Writer is an implementation of lengthWriter that writes the length of
|
||||
// the byte slice using 4 bytes.
|
||||
func uint32Writer(w io.Writer, b []byte) error {
|
||||
return binary.Write(w, byteOrder, uint32(len(b)))
|
||||
}
|
||||
|
||||
// uint32Writer is an implementation of lengthWriter that writes the length of
|
||||
// the byte slice using 8 bytes.
|
||||
func uint64Writer(w io.Writer, b []byte) error {
|
||||
return binary.Write(w, byteOrder, uint64(len(b)))
|
||||
}
|
||||
|
||||
// writeBytesPrefix is used to write out: len(b) || b, to the passed io.Writer.
|
||||
// The lengthWriter function closure is used to allow the caller to specify the
|
||||
// precise byte packing of the length.
|
||||
func writeBytesPrefix(w io.Writer, b []byte, lenWriter lengthWriter) error {
|
||||
// Write out the length of the byte first, followed by the set of bytes
|
||||
// itself.
|
||||
if err := lenWriter(w, b); chk.T(err) {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(b); chk.T(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// genNonceAuxBytes writes out the full byte string used to derive a secret
|
||||
// nonce based on some initial randomness as well as the series of optional
|
||||
// fields. The byte string used for derivation is:
|
||||
// - tagged_hash("MuSig/nonce", rand || len(pk) || pk ||
|
||||
// len(aggpk) || aggpk || m_prefixed || len(in) || in || i).
|
||||
//
|
||||
// where i is the ith secret nonce being generated and m_prefixed is:
|
||||
// - bytes(1, 0) if the message is blank
|
||||
// - bytes(1, 1) || bytes(8, len(m)) || m if the message is present.
|
||||
func genNonceAuxBytes(
|
||||
rand []byte, pubkey []byte, i int,
|
||||
opts *nonceGenOpts,
|
||||
) (*chainhash.Hash, error) {
|
||||
|
||||
var w bytes.Buffer
|
||||
// First, write out the randomness generated in the prior step.
|
||||
if _, err := w.Write(rand); chk.T(err) {
|
||||
return nil, err
|
||||
}
|
||||
// Next, we'll write out: len(pk) || pk
|
||||
err := writeBytesPrefix(&w, pubkey, uint8Writer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Next, we'll write out: len(aggpk) || aggpk.
|
||||
err = writeBytesPrefix(&w, opts.combinedKey, uint8Writer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
// If the message isn't present, then we'll just write out a single
|
||||
// uint8 of a zero byte: m_prefixed = bytes(1, 0).
|
||||
case opts.msg == nil:
|
||||
if _, err := w.Write([]byte{0x00}); chk.T(err) {
|
||||
return nil, err
|
||||
}
|
||||
// Otherwise, we'll write a single byte of 0x01 with a 1 byte length
|
||||
// prefix, followed by the message itself with an 8 byte length prefix:
|
||||
// m_prefixed = bytes(1, 1) || bytes(8, len(m)) || m.
|
||||
case len(opts.msg) == 0:
|
||||
fallthrough
|
||||
default:
|
||||
if _, err := w.Write([]byte{0x01}); chk.T(err) {
|
||||
return nil, err
|
||||
}
|
||||
err = writeBytesPrefix(&w, opts.msg, uint64Writer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Finally we'll write out the auxiliary input.
|
||||
err = writeBytesPrefix(&w, opts.auxInput, uint32Writer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Next we'll write out the interaction/index number which will
|
||||
// uniquely generate two nonces given the rest of the possibly static
|
||||
// parameters.
|
||||
if err := binary.Write(&w, byteOrder, uint8(i)); chk.T(err) {
|
||||
return nil, err
|
||||
}
|
||||
// With the message buffer complete, we'll now derive the tagged hash
|
||||
// using our set of params.
|
||||
return chainhash.TaggedHash(NonceGenTag, w.Bytes()), nil
|
||||
}
|
||||
|
||||
// GenNonces generates the secret nonces, as well as the public nonces which
|
||||
// correspond to an EC point generated using the secret nonce as a secret key.
|
||||
func GenNonces(options ...NonceGenOption) (*Nonces, error) {
|
||||
opts := defaultNonceGenOpts()
|
||||
for _, opt := range options {
|
||||
opt(opts)
|
||||
}
|
||||
// We require the pubkey option.
|
||||
if opts.publicKey == nil || len(opts.publicKey) != 33 {
|
||||
return nil, ErrPubkeyInvalid
|
||||
}
|
||||
// First, we'll start out by generating 32 random bytes drawn from our
|
||||
// CSPRNG.
|
||||
var randBytes [32]byte
|
||||
if _, err := opts.randReader.Read(randBytes[:]); chk.T(err) {
|
||||
return nil, err
|
||||
}
|
||||
// If the options contain a secret key, we XOR it with with the tagged
|
||||
// random bytes.
|
||||
if len(opts.secretKey) == 32 {
|
||||
taggedHash := chainhash.TaggedHash(NonceAuxTag, randBytes[:])
|
||||
|
||||
for i := 0; i < chainhash.HashSize; i++ {
|
||||
randBytes[i] = opts.secretKey[i] ^ taggedHash[i]
|
||||
}
|
||||
}
|
||||
// Using our randomness, pubkey and the set of optional params, generate our
|
||||
// two secret nonces: k1 and k2.
|
||||
k1, err := genNonceAuxBytes(randBytes[:], opts.publicKey, 0, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k2, err := genNonceAuxBytes(randBytes[:], opts.publicKey, 1, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var k1Mod, k2Mod btcec.ModNScalar
|
||||
k1Mod.SetBytes((*[32]byte)(k1))
|
||||
k2Mod.SetBytes((*[32]byte)(k2))
|
||||
// The secret nonces are serialized as the concatenation of the two 32
|
||||
// byte secret nonce values and the pubkey.
|
||||
var nonces Nonces
|
||||
k1Mod.PutBytesUnchecked(nonces.SecNonce[:])
|
||||
k2Mod.PutBytesUnchecked(nonces.SecNonce[btcec.SecKeyBytesLen:])
|
||||
copy(nonces.SecNonce[btcec.SecKeyBytesLen*2:], opts.publicKey)
|
||||
// Next, we'll generate R_1 = k_1*G and R_2 = k_2*G. Along the way we
|
||||
// need to map our nonce values into mod n scalars so we can work with
|
||||
// the btcec API.
|
||||
nonces.PubNonce = secNonceToPubNonce(nonces.SecNonce)
|
||||
return &nonces, nil
|
||||
}
|
||||
|
||||
// AggregateNonces aggregates the set of a pair of public nonces for each party
|
||||
// into a single aggregated nonces to be used for multi-signing.
|
||||
func AggregateNonces(pubNonces [][PubNonceSize]byte) (
|
||||
[PubNonceSize]byte,
|
||||
error,
|
||||
) {
|
||||
|
||||
// combineNonces is a helper function that aggregates (adds) up a
|
||||
// series of nonces encoded in compressed format. It uses a slicing
|
||||
// function to extra 33 bytes at a time from the packed 2x public
|
||||
// nonces.
|
||||
type nonceSlicer func([PubNonceSize]byte) []byte
|
||||
combineNonces := func(slicer nonceSlicer) (btcec.JacobianPoint, error) {
|
||||
// Convert the set of nonces into jacobian coordinates we can
|
||||
// use to accumulate them all into each other.
|
||||
pubNonceJs := make([]*btcec.JacobianPoint, len(pubNonces))
|
||||
for i, pubNonceBytes := range pubNonces {
|
||||
// Using the slicer, extract just the bytes we need to
|
||||
// decode.
|
||||
var nonceJ btcec.JacobianPoint
|
||||
nonceJ, err := btcec.ParseJacobian(slicer(pubNonceBytes))
|
||||
if err != nil {
|
||||
return btcec.JacobianPoint{}, err
|
||||
}
|
||||
pubNonceJs[i] = &nonceJ
|
||||
}
|
||||
// Now that we have the set of complete nonces, we'll aggregate
|
||||
// them: R = R_i + R_i+1 + ... + R_i+n.
|
||||
var aggregateNonce btcec.JacobianPoint
|
||||
for _, pubNonceJ := range pubNonceJs {
|
||||
btcec.AddNonConst(
|
||||
&aggregateNonce, pubNonceJ, &aggregateNonce,
|
||||
)
|
||||
}
|
||||
aggregateNonce.ToAffine()
|
||||
return aggregateNonce, nil
|
||||
}
|
||||
// The final nonce public nonce is actually two nonces, one that
|
||||
// aggregate the first nonce of all the parties, and the other that
|
||||
// aggregates the second nonce of all the parties.
|
||||
var finalNonce [PubNonceSize]byte
|
||||
combinedNonce1, err := combineNonces(
|
||||
func(n [PubNonceSize]byte) []byte {
|
||||
return n[:btcec.PubKeyBytesLenCompressed]
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return finalNonce, err
|
||||
}
|
||||
combinedNonce2, err := combineNonces(
|
||||
func(n [PubNonceSize]byte) []byte {
|
||||
return n[btcec.PubKeyBytesLenCompressed:]
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return finalNonce, err
|
||||
}
|
||||
copy(finalNonce[:], btcec.JacobianToByteSlice(combinedNonce1))
|
||||
copy(
|
||||
finalNonce[btcec.PubKeyBytesLenCompressed:],
|
||||
btcec.JacobianToByteSlice(combinedNonce2),
|
||||
)
|
||||
return finalNonce, nil
|
||||
}
|
||||
151
pkg/crypto/ec/musig2/nonces_test.go
Normal file
151
pkg/crypto/ec/musig2/nonces_test.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2013-2022 The btcsuite developers
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"next.orly.dev/pkg/utils"
|
||||
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type nonceGenTestCase struct {
|
||||
Rand string `json:"rand_"`
|
||||
Sk string `json:"sk"`
|
||||
AggPk string `json:"aggpk"`
|
||||
Msg *string `json:"msg"`
|
||||
ExtraIn string `json:"extra_in"`
|
||||
Pk string `json:"pk"`
|
||||
Expected string `json:"expected"`
|
||||
}
|
||||
|
||||
type nonceGenTestCases struct {
|
||||
TestCases []nonceGenTestCase `json:"test_cases"`
|
||||
}
|
||||
|
||||
const (
|
||||
nonceGenTestVectorsFileName = "nonce_gen_vectors.json"
|
||||
nonceAggTestVectorsFileName = "nonce_agg_vectors.json"
|
||||
)
|
||||
|
||||
// TestMusig2NonceGenTestVectors tests the nonce generation function with the
|
||||
// testvectors defined in the Musig2 BIP.
|
||||
func TestMusig2NonceGenTestVectors(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVectorPath := path.Join(
|
||||
testVectorBaseDir, nonceGenTestVectorsFileName,
|
||||
)
|
||||
testVectorBytes, err := os.ReadFile(testVectorPath)
|
||||
require.NoError(t, err)
|
||||
var testCases nonceGenTestCases
|
||||
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
|
||||
for i, testCase := range testCases.TestCases {
|
||||
testCase := testCase
|
||||
customOpts := nonceGenOpts{
|
||||
randReader: &memsetRandReader{i: 0},
|
||||
secretKey: mustParseHex(testCase.Sk),
|
||||
combinedKey: mustParseHex(testCase.AggPk),
|
||||
auxInput: mustParseHex(testCase.ExtraIn),
|
||||
publicKey: mustParseHex(testCase.Pk),
|
||||
}
|
||||
if testCase.Msg != nil {
|
||||
customOpts.msg = mustParseHex(*testCase.Msg)
|
||||
}
|
||||
t.Run(
|
||||
fmt.Sprintf("test_case=%v", i), func(t *testing.T) {
|
||||
nonce, err := GenNonces(withCustomOptions(customOpts))
|
||||
if err != nil {
|
||||
t.Fatalf("err gen nonce aux bytes %v", err)
|
||||
}
|
||||
expectedBytes, _ := hex.Dec(testCase.Expected)
|
||||
if !utils.FastEqual(nonce.SecNonce[:], expectedBytes) {
|
||||
t.Fatalf(
|
||||
"nonces don't match: expected %x, got %x",
|
||||
expectedBytes, nonce.SecNonce[:],
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type nonceAggError struct {
|
||||
Type string `json:"type"`
|
||||
Signer int `json:"signer"`
|
||||
Contrib string `json:"contrib"`
|
||||
}
|
||||
|
||||
type nonceAggValidCase struct {
|
||||
Indices []int `json:"pnonce_indices"`
|
||||
Expected string `json:"expected"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type nonceAggInvalidCase struct {
|
||||
Indices []int `json:"pnonce_indices"`
|
||||
Error nonceAggError `json:"error"`
|
||||
Comment string `json:"comment"`
|
||||
ExpectedErr string `json:"btcec_err"`
|
||||
}
|
||||
|
||||
type nonceAggTestCases struct {
|
||||
Nonces []string `json:"pnonces"`
|
||||
ValidCases []nonceAggValidCase `json:"valid_test_cases"`
|
||||
InvalidCases []nonceAggInvalidCase `json:"error_test_cases"`
|
||||
}
|
||||
|
||||
// TestMusig2AggregateNoncesTestVectors tests that the musig2 implementation
|
||||
// passes the nonce aggregration test vectors for musig2 1.0.
|
||||
func TestMusig2AggregateNoncesTestVectors(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVectorPath := path.Join(
|
||||
testVectorBaseDir, nonceAggTestVectorsFileName,
|
||||
)
|
||||
testVectorBytes, err := os.ReadFile(testVectorPath)
|
||||
require.NoError(t, err)
|
||||
var testCases nonceAggTestCases
|
||||
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
|
||||
nonces := make([][PubNonceSize]byte, len(testCases.Nonces))
|
||||
for i := range testCases.Nonces {
|
||||
var nonce [PubNonceSize]byte
|
||||
copy(nonce[:], mustParseHex(testCases.Nonces[i]))
|
||||
nonces[i] = nonce
|
||||
}
|
||||
for i, testCase := range testCases.ValidCases {
|
||||
testCase := testCase
|
||||
var testNonces [][PubNonceSize]byte
|
||||
for _, idx := range testCase.Indices {
|
||||
testNonces = append(testNonces, nonces[idx])
|
||||
}
|
||||
t.Run(
|
||||
fmt.Sprintf("valid_case=%v", i), func(t *testing.T) {
|
||||
aggregatedNonce, err := AggregateNonces(testNonces)
|
||||
require.NoError(t, err)
|
||||
var expectedNonce [PubNonceSize]byte
|
||||
copy(expectedNonce[:], mustParseHex(testCase.Expected))
|
||||
require.Equal(t, aggregatedNonce[:], expectedNonce[:])
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for i, testCase := range testCases.InvalidCases {
|
||||
var testNonces [][PubNonceSize]byte
|
||||
for _, idx := range testCase.Indices {
|
||||
testNonces = append(testNonces, nonces[idx])
|
||||
}
|
||||
t.Run(
|
||||
fmt.Sprintf("invalid_case=%v", i), func(t *testing.T) {
|
||||
_, err := AggregateNonces(testNonces)
|
||||
require.True(t, err != nil)
|
||||
require.Equal(t, testCase.ExpectedErr, err.Error())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
768
pkg/crypto/ec/musig2/sign.go
Normal file
768
pkg/crypto/ec/musig2/sign.go
Normal file
@@ -0,0 +1,768 @@
|
||||
// Copyright 2013-2022 The btcsuite developers
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"next.orly.dev/pkg/utils"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"next.orly.dev/pkg/crypto/ec"
|
||||
"next.orly.dev/pkg/crypto/ec/chainhash"
|
||||
"next.orly.dev/pkg/crypto/ec/schnorr"
|
||||
"next.orly.dev/pkg/crypto/ec/secp256k1"
|
||||
)
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
// ErrSecKeyZero is returned when the secret key for signing is
|
||||
// actually zero.
|
||||
ErrSecKeyZero = 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")
|
||||
|
||||
// ErrSecNoncePubkey is returned when the signing key does not match the
|
||||
// sec nonce pubkey
|
||||
ErrSecNoncePubkey = fmt.Errorf("public key does not match secnonce")
|
||||
|
||||
// ErrPubkeyNotIncluded is returned when the signers pubkey is not included
|
||||
// in the list of pubkeys.
|
||||
ErrPubkeyNotIncluded = fmt.Errorf(
|
||||
"signer's pubkey must be included" +
|
||||
" in the list of pubkeys",
|
||||
)
|
||||
)
|
||||
|
||||
// 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[:]); chk.T(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode attempts to parse a serialized PartialSignature stored in the 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[:]); chk.T(err) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// computeSigningNonce calculates the final nonce used for signing. This will
|
||||
// be the R value used in the final signature.
|
||||
func computeSigningNonce(
|
||||
combinedNonce [PubNonceSize]byte,
|
||||
combinedKey *btcec.PublicKey, msg [32]byte,
|
||||
) (
|
||||
*btcec.JacobianPoint, *btcec.ModNScalar, error,
|
||||
) {
|
||||
|
||||
// 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[:])
|
||||
|
||||
// Next, we'll parse the public nonces into R1 and R2.
|
||||
r1J, err := btcec.ParseJacobian(
|
||||
combinedNonce[:btcec.PubKeyBytesLenCompressed],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r2J, err := btcec.ParseJacobian(
|
||||
combinedNonce[btcec.PubKeyBytesLenCompressed:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 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 is the point at infinity, we'll use the
|
||||
// generator point instead.
|
||||
if nonce == infinityPoint {
|
||||
G := btcec.Generator()
|
||||
G.AsJacobian(&nonce)
|
||||
}
|
||||
|
||||
return &nonce, &nonceBlinder, nil
|
||||
}
|
||||
|
||||
// Sign generates a musig2 partial signature given the passed key set, secret
|
||||
// nonce, public nonce, and secret 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.SecretKey,
|
||||
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)
|
||||
}
|
||||
|
||||
// Check that our signing key belongs to the secNonce
|
||||
if !utils.FastEqual(
|
||||
secNonce[btcec.SecKeyBytesLen*2:],
|
||||
privKey.PubKey().SerializeCompressed(),
|
||||
) {
|
||||
|
||||
return nil, ErrSecNoncePubkey
|
||||
}
|
||||
|
||||
// Check that the key set contains the public key to our secret key.
|
||||
var containsSecKey bool
|
||||
for _, pk := range pubKeys {
|
||||
if privKey.PubKey().IsEqual(pk) {
|
||||
containsSecKey = true
|
||||
}
|
||||
}
|
||||
|
||||
if !containsSecKey {
|
||||
return nil, ErrPubkeyNotIncluded
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// We'll now combine both the public nonces, using the blinding factor
|
||||
// to tweak the second nonce:
|
||||
// * R = R_1 + b*R_2
|
||||
nonce, nonceBlinder, err := computeSigningNonce(
|
||||
combinedNonce, combinedKey.FinalKey, msg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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.SecKeyBytesLen])
|
||||
k2.SetByteSlice(secNonce[btcec.SecKeyBytesLen:])
|
||||
|
||||
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, ErrSecKeyZero
|
||||
}
|
||||
|
||||
pubKey := privKey.PubKey()
|
||||
combinedKeyYIsOdd := func() bool {
|
||||
combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed()
|
||||
return combinedKeyBytes[0] == secp256k1.PubKeyFormatCompressedOdd
|
||||
}()
|
||||
|
||||
// Next we'll compute the two parity factors for Q, the combined key.
|
||||
// If the key is odd, then we'll negate it.
|
||||
parityCombinedKey := new(btcec.ModNScalar).SetInt(1)
|
||||
if combinedKeyYIsOdd {
|
||||
parityCombinedKey.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 = g⋅gacc⋅d'
|
||||
privKeyScalar.Mul(parityCombinedKey).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 := signingKey.SerializeCompressed()
|
||||
|
||||
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 := btcec.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.
|
||||
parityCombinedKey := new(btcec.ModNScalar).SetInt(1)
|
||||
combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed()
|
||||
if combinedKeyBytes[0] == secp256k1.PubKeyFormatCompressedOdd {
|
||||
parityCombinedKey.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 := parityCombinedKey.Mul(parityAcc)
|
||||
|
||||
var signKeyJ btcec.JacobianPoint
|
||||
signingKey.AsJacobian(&signKeyJ)
|
||||
|
||||
// In the final set, we'll check that: s*G == R' + e*a*g*P.
|
||||
var sG, rP btcec.JacobianPoint
|
||||
btcec.ScalarBaseMultNonConst(s, &sG)
|
||||
btcec.ScalarMultNonConst(e.Mul(a).Mul(finalParityFactor), &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] == secp256k1.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)
|
||||
}
|
||||
330
pkg/crypto/ec/musig2/sign_test.go
Normal file
330
pkg/crypto/ec/musig2/sign_test.go
Normal file
@@ -0,0 +1,330 @@
|
||||
// Copyright 2013-2022 The btcsuite developers
|
||||
|
||||
package musig2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"next.orly.dev/pkg/crypto/ec"
|
||||
"next.orly.dev/pkg/crypto/ec/secp256k1"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
signVerifyTestVectorFileName = "sign_verify_vectors.json"
|
||||
sigCombineTestVectorFileName = "sig_agg_vectors.json"
|
||||
)
|
||||
|
||||
type signVerifyValidCase struct {
|
||||
Indices []int `json:"key_indices"`
|
||||
NonceIndices []int `json:"nonce_indices"`
|
||||
AggNonceIndex int `json:"aggnonce_index"`
|
||||
MsgIndex int `json:"msg_index"`
|
||||
SignerIndex int `json:"signer_index"`
|
||||
Expected string `json:"expected"`
|
||||
}
|
||||
|
||||
type signErrorCase struct {
|
||||
Indices []int `json:"key_indices"`
|
||||
AggNonceIndex int `json:"aggnonce_index"`
|
||||
MsgIndex int `json:"msg_index"`
|
||||
SecNonceIndex int `json:"secnonce_index"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type verifyFailCase struct {
|
||||
Sig string `json:"sig"`
|
||||
Indices []int `json:"key_indices"`
|
||||
NonceIndices []int `json:"nonce_indices"`
|
||||
MsgIndex int `json:"msg_index"`
|
||||
SignerIndex int `json:"signer_index"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type verifyErrorCase struct {
|
||||
Sig string `json:"sig"`
|
||||
Indices []int `json:"key_indices"`
|
||||
NonceIndices []int `json:"nonce_indices"`
|
||||
MsgIndex int `json:"msg_index"`
|
||||
SignerIndex int `json:"signer_index"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type signVerifyTestVectors struct {
|
||||
SecKey string `json:"sk"`
|
||||
PubKeys []string `json:"pubkeys"`
|
||||
PrivNonces []string `json:"secnonces"`
|
||||
PubNonces []string `json:"pnonces"`
|
||||
AggNonces []string `json:"aggnonces"`
|
||||
Msgs []string `json:"msgs"`
|
||||
ValidCases []signVerifyValidCase `json:"valid_test_cases"`
|
||||
SignErrorCases []signErrorCase `json:"sign_error_test_cases"`
|
||||
VerifyFailCases []verifyFailCase `json:"verify_fail_test_cases"`
|
||||
VerifyErrorCases []verifyErrorCase `json:"verify_error_test_cases"`
|
||||
}
|
||||
|
||||
// TestMusig2SignVerify tests that we pass the musig2 verification tests.
|
||||
func TestMusig2SignVerify(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVectorPath := path.Join(
|
||||
testVectorBaseDir, signVerifyTestVectorFileName,
|
||||
)
|
||||
testVectorBytes, err := os.ReadFile(testVectorPath)
|
||||
require.NoError(t, err)
|
||||
var testCases signVerifyTestVectors
|
||||
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
|
||||
privKey, _ := btcec.SecKeyFromBytes(mustParseHex(testCases.SecKey))
|
||||
for i, testCase := range testCases.ValidCases {
|
||||
testCase := testCase
|
||||
testName := fmt.Sprintf("valid_case_%v", i)
|
||||
t.Run(
|
||||
testName, func(t *testing.T) {
|
||||
pubKeys, err := keysFromIndices(
|
||||
t, testCase.Indices, testCases.PubKeys,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
pubNonces := pubNoncesFromIndices(
|
||||
t, testCase.NonceIndices, testCases.PubNonces,
|
||||
)
|
||||
combinedNonce, err := AggregateNonces(pubNonces)
|
||||
require.NoError(t, err)
|
||||
var msg [32]byte
|
||||
copy(msg[:], mustParseHex(testCases.Msgs[testCase.MsgIndex]))
|
||||
var secNonce [SecNonceSize]byte
|
||||
copy(secNonce[:], mustParseHex(testCases.PrivNonces[0]))
|
||||
partialSig, err := Sign(
|
||||
secNonce, privKey, combinedNonce, pubKeys,
|
||||
msg,
|
||||
)
|
||||
var partialSigBytes [32]byte
|
||||
partialSig.S.PutBytesUnchecked(partialSigBytes[:])
|
||||
require.Equal(
|
||||
t, hex.Enc(partialSigBytes[:]),
|
||||
hex.Enc(mustParseHex(testCase.Expected)),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
for _, testCase := range testCases.SignErrorCases {
|
||||
testCase := testCase
|
||||
testName := fmt.Sprintf(
|
||||
"invalid_case_%v",
|
||||
strings.ToLower(testCase.Comment),
|
||||
)
|
||||
t.Run(
|
||||
testName, func(t *testing.T) {
|
||||
pubKeys, err := keysFromIndices(
|
||||
t, testCase.Indices, testCases.PubKeys,
|
||||
)
|
||||
if err != nil {
|
||||
require.ErrorIs(t, err, secp256k1.ErrPubKeyNotOnCurve)
|
||||
return
|
||||
}
|
||||
var aggNonce [PubNonceSize]byte
|
||||
copy(
|
||||
aggNonce[:],
|
||||
mustParseHex(
|
||||
testCases.AggNonces[testCase.AggNonceIndex],
|
||||
),
|
||||
)
|
||||
var msg [32]byte
|
||||
copy(msg[:], mustParseHex(testCases.Msgs[testCase.MsgIndex]))
|
||||
var secNonce [SecNonceSize]byte
|
||||
copy(
|
||||
secNonce[:],
|
||||
mustParseHex(
|
||||
testCases.PrivNonces[testCase.SecNonceIndex],
|
||||
),
|
||||
)
|
||||
_, err = Sign(
|
||||
secNonce, privKey, aggNonce, pubKeys,
|
||||
msg,
|
||||
)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
}
|
||||
for _, testCase := range testCases.VerifyFailCases {
|
||||
testCase := testCase
|
||||
testName := fmt.Sprintf(
|
||||
"verify_fail_%v",
|
||||
strings.ToLower(testCase.Comment),
|
||||
)
|
||||
t.Run(
|
||||
testName, func(t *testing.T) {
|
||||
pubKeys, err := keysFromIndices(
|
||||
t, testCase.Indices, testCases.PubKeys,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
pubNonces := pubNoncesFromIndices(
|
||||
t, testCase.NonceIndices, testCases.PubNonces,
|
||||
)
|
||||
combinedNonce, err := AggregateNonces(pubNonces)
|
||||
require.NoError(t, err)
|
||||
var msg [32]byte
|
||||
copy(
|
||||
msg[:],
|
||||
mustParseHex(testCases.Msgs[testCase.MsgIndex]),
|
||||
)
|
||||
var secNonce [SecNonceSize]byte
|
||||
copy(secNonce[:], mustParseHex(testCases.PrivNonces[0]))
|
||||
signerNonce := secNonceToPubNonce(secNonce)
|
||||
var partialSig PartialSignature
|
||||
err = partialSig.Decode(
|
||||
bytes.NewReader(mustParseHex(testCase.Sig)),
|
||||
)
|
||||
if err != nil && strings.Contains(
|
||||
testCase.Comment, "group size",
|
||||
) {
|
||||
require.ErrorIs(t, err, ErrPartialSigInvalid)
|
||||
}
|
||||
err = verifyPartialSig(
|
||||
&partialSig, signerNonce, combinedNonce,
|
||||
pubKeys, privKey.PubKey().SerializeCompressed(),
|
||||
msg,
|
||||
)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for _, testCase := range testCases.VerifyErrorCases {
|
||||
testCase := testCase
|
||||
testName := fmt.Sprintf(
|
||||
"verify_error_%v",
|
||||
strings.ToLower(testCase.Comment),
|
||||
)
|
||||
t.Run(
|
||||
testName, func(t *testing.T) {
|
||||
switch testCase.Comment {
|
||||
case "Invalid pubnonce":
|
||||
pubNonces := pubNoncesFromIndices(
|
||||
t, testCase.NonceIndices, testCases.PubNonces,
|
||||
)
|
||||
_, err := AggregateNonces(pubNonces)
|
||||
require.ErrorIs(t, err, secp256k1.ErrPubKeyNotOnCurve)
|
||||
|
||||
case "Invalid pubkey":
|
||||
_, err := keysFromIndices(
|
||||
t, testCase.Indices, testCases.PubKeys,
|
||||
)
|
||||
require.ErrorIs(t, err, secp256k1.ErrPubKeyNotOnCurve)
|
||||
|
||||
default:
|
||||
t.Fatalf("unhandled case: %v", testCase.Comment)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type sigCombineValidCase struct {
|
||||
AggNonce string `json:"aggnonce"`
|
||||
NonceIndices []int `json:"nonce_indices"`
|
||||
Indices []int `json:"key_indices"`
|
||||
TweakIndices []int `json:"tweak_indices"`
|
||||
IsXOnly []bool `json:"is_xonly"`
|
||||
PSigIndices []int `json:"psig_indices"`
|
||||
Expected string `json:"expected"`
|
||||
}
|
||||
|
||||
type sigCombineTestVectors struct {
|
||||
PubKeys []string `json:"pubkeys"`
|
||||
PubNonces []string `json:"pnonces"`
|
||||
Tweaks []string `json:"tweaks"`
|
||||
Psigs []string `json:"psigs"`
|
||||
Msg string `json:"msg"`
|
||||
ValidCases []sigCombineValidCase `json:"valid_test_cases"`
|
||||
}
|
||||
|
||||
func pSigsFromIndicies(
|
||||
t *testing.T, sigs []string,
|
||||
indices []int,
|
||||
) []*PartialSignature {
|
||||
pSigs := make([]*PartialSignature, len(indices))
|
||||
for i, idx := range indices {
|
||||
var pSig PartialSignature
|
||||
err := pSig.Decode(bytes.NewReader(mustParseHex(sigs[idx])))
|
||||
require.NoError(t, err)
|
||||
pSigs[i] = &pSig
|
||||
}
|
||||
return pSigs
|
||||
}
|
||||
|
||||
// TestMusig2SignCombine tests that we pass the musig2 sig combination tests.
|
||||
func TestMusig2SignCombine(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVectorPath := path.Join(
|
||||
testVectorBaseDir, sigCombineTestVectorFileName,
|
||||
)
|
||||
testVectorBytes, err := os.ReadFile(testVectorPath)
|
||||
require.NoError(t, err)
|
||||
var testCases sigCombineTestVectors
|
||||
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
|
||||
var msg [32]byte
|
||||
copy(msg[:], mustParseHex(testCases.Msg))
|
||||
for i, testCase := range testCases.ValidCases {
|
||||
testCase := testCase
|
||||
testName := fmt.Sprintf("valid_case_%v", i)
|
||||
t.Run(
|
||||
testName, func(t *testing.T) {
|
||||
pubKeys, err := keysFromIndices(
|
||||
t, testCase.Indices, testCases.PubKeys,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
pubNonces := pubNoncesFromIndices(
|
||||
t, testCase.NonceIndices, testCases.PubNonces,
|
||||
)
|
||||
partialSigs := pSigsFromIndicies(
|
||||
t, testCases.Psigs, testCase.PSigIndices,
|
||||
)
|
||||
var (
|
||||
combineOpts []CombineOption
|
||||
keyOpts []KeyAggOption
|
||||
)
|
||||
if len(testCase.TweakIndices) > 0 {
|
||||
tweaks := tweaksFromIndices(
|
||||
t, testCase.TweakIndices,
|
||||
testCases.Tweaks, testCase.IsXOnly,
|
||||
)
|
||||
combineOpts = append(
|
||||
combineOpts, WithTweakedCombine(
|
||||
msg, pubKeys, tweaks, false,
|
||||
),
|
||||
)
|
||||
keyOpts = append(keyOpts, WithKeyTweaks(tweaks...))
|
||||
}
|
||||
combinedKey, _, _, err := AggregateKeys(
|
||||
pubKeys, false, keyOpts...,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
combinedNonce, err := AggregateNonces(pubNonces)
|
||||
require.NoError(t, err)
|
||||
finalNonceJ, _, err := computeSigningNonce(
|
||||
combinedNonce, combinedKey.FinalKey, msg,
|
||||
)
|
||||
finalNonceJ.ToAffine()
|
||||
finalNonce := btcec.NewPublicKey(
|
||||
&finalNonceJ.X, &finalNonceJ.Y,
|
||||
)
|
||||
combinedSig := CombineSigs(
|
||||
finalNonce, partialSigs, combineOpts...,
|
||||
)
|
||||
require.Equal(
|
||||
t,
|
||||
strings.ToLower(testCase.Expected),
|
||||
hex.Enc(combinedSig.Serialize()),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user