diff --git a/.golangci.yml b/.golangci.yml index 5629e199e..2c5092588 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,11 +2,13 @@ run: # timeout for analysis deadline: 10m - # Skip autogenerated files for mobile and gRPC. + # Skip autogenerated files for mobile and gRPC as well as copied code for + # internal use. skip-files: - "mobile\\/.*generated\\.go" - "\\.pb\\.go$" - "\\.pb\\.gw\\.go$" + - "internal\\/musig2v040" skip-dirs: - channeldb/migration_01_to_11 diff --git a/go.mod b/go.mod index 066f25bf6..91f2df835 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/btcsuite/btcwallet/wtxmgr v1.5.0 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/davecgh/go-spew v1.1.1 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 github.com/go-errors/errors v1.0.1 github.com/golang/protobuf v1.5.2 github.com/gorilla/websocket v1.4.2 @@ -76,7 +77,6 @@ require ( github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/lru v1.0.0 // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect diff --git a/internal/musig2/bench_test.go b/internal/musig2/bench_test.go new file mode 100644 index 000000000..70d7e931c --- /dev/null +++ b/internal/musig2/bench_test.go @@ -0,0 +1,313 @@ +// 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 ( + "encoding/hex" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" +) + +var ( + testPrivBytes = hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d") + + testMsg = hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7") +) + +func hexToBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic("invalid hex in source file: " + s) + } + return b +} + +func hexToModNScalar(s string) *btcec.ModNScalar { + b, err := hex.DecodeString(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.NewPrivateKey() + if err != nil { + t.Fatalf("unable to gen priv key: %v", err) + } + + pubKey, err := schnorr.ParsePubKey( + schnorr.SerializePubKey(privKey.PubKey()), + ) + if err != nil { + t.Fatalf("unable to gen key: %v", err) + } + + nonces, err := GenNonces() + 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(roasbeef): add impact of sorting ^ + +var sigOk bool + +// 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 bool + for i := 0; i < b.N; i++ { + ok = sig.Verify( + signers[0].nonces.PubNonce, combinedNonce, + keys, pubKey, msg, + ) + 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 + }) + } + } +} diff --git a/internal/musig2/context.go b/internal/musig2/context.go new file mode 100644 index 000000000..19cef34fa --- /dev/null +++ b/internal/musig2/context.go @@ -0,0 +1,670 @@ +// Copyright (c) 2013-2022 The btcsuite developers + +package musig2 + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" +) + +var ( + // ErrSignersNotSpecified is returned when a caller attempts to create + // a context without specifying either the total number of signers, or + // the complete set of singers. + ErrSignersNotSpecified = fmt.Errorf("total number of signers or all " + + "signers must be known") + + // ErrSignerNotInKeySet is returned when a the private key for a signer + // isn't included in the set of signing public keys. + ErrSignerNotInKeySet = fmt.Errorf("signing key is not found in key" + + " set") + + // ErrAlredyHaveAllNonces is called when RegisterPubNonce is called too + // many times for a given signing session. + ErrAlredyHaveAllNonces = fmt.Errorf("already have all nonces") + + // ErrNotEnoughSigners is returned when a caller attempts to create a + // session from a context, but before all the required signers are + // known. + ErrNotEnoughSigners = fmt.Errorf("not enough signers") + + // ErrAlredyHaveAllNonces is returned when a caller attempts to + // register a signer, once we already have the total set of known + // signers. + ErrAlreadyHaveAllSigners = fmt.Errorf("all signers registered") + + // ErrAlredyHaveAllSigs is called when CombineSig is called too many + // times for a given signing session. + ErrAlredyHaveAllSigs = fmt.Errorf("already have all sigs") + + // ErrSigningContextReuse is returned if a user attempts to sign using + // the same signing context more than once. + ErrSigningContextReuse = fmt.Errorf("nonce already used") + + // ErrFinalSigInvalid is returned when the combined signature turns out + // to be invalid. + ErrFinalSigInvalid = fmt.Errorf("final signature is invalid") + + // ErrCombinedNonceUnavailable is returned when a caller attempts to + // sign a partial signature, without first having collected all the + // required combined nonces. + ErrCombinedNonceUnavailable = fmt.Errorf("missing combined nonce") + + // ErrTaprootInternalKeyUnavailable is returned when a user attempts to + // obtain the + ErrTaprootInternalKeyUnavailable = fmt.Errorf("taproot tweak not used") + + // ErrNotEnoughSigners is returned if a caller attempts to obtain an + // early nonce when it wasn't specified + ErrNoEarlyNonce = fmt.Errorf("no early nonce available") +) + +// Context is a managed signing context for musig2. It takes care of things +// like securely generating secret nonces, aggregating keys and nonces, etc. +type Context struct { + // signingKey is the key we'll use for signing. + signingKey *btcec.PrivateKey + + // pubKey is our even-y coordinate public key. + pubKey *btcec.PublicKey + + // combinedKey is the aggregated public key. + combinedKey *AggregateKey + + // uniqueKeyIndex is the index of the second unique key in the keySet. + // This is used to speed up signing and verification computations. + uniqueKeyIndex int + + // keysHash is the hash of all the keys as defined in musig2. + keysHash []byte + + // opts is the set of options for the context. + opts *contextOptions + + // shouldSort keeps track of if the public keys should be sorted before + // any operations. + shouldSort bool + + // sessionNonce will be populated if the earlyNonce option is true. + // After the first session is created, this nonce will be blanked out. + sessionNonce *Nonces +} + +// ContextOption is a functional option argument that allows callers to modify +// the musig2 signing is done within a context. +type ContextOption func(*contextOptions) + +// contextOptions houses the set of functional options that can be used to +// musig2 signing protocol. +type contextOptions struct { + // tweaks is the set of optinoal tweaks to apply to the combined public + // key. + tweaks []KeyTweakDesc + + // taprootTweak specifies the taproot tweak. If specified, then we'll + // use this as the script root for the BIP 341 taproot (x-only) tweak. + // Normally we'd just apply the raw 32 byte tweak, but for taproot, we + // first need to compute the aggregated key before tweaking, and then + // use it as the internal key. This is required as the taproot tweak + // also commits to the public key, which in this case is the aggregated + // key before the tweak. + taprootTweak []byte + + // bip86Tweak if true, then the weak will just be + // h_tapTweak(internalKey) as there is no true script root. + bip86Tweak bool + + // keySet is the complete set of signers for this context. + keySet []*btcec.PublicKey + + // numSigners is the total number of signers that will eventually be a + // part of the context. + numSigners int + + // earlyNonce determines if a nonce should be generated during context + // creation, to be automatically passed to the created session. + earlyNonce bool +} + +// defaultContextOptions returns the default context options. +func defaultContextOptions() *contextOptions { + return &contextOptions{} +} + +// WithTweakedContext specifies that within the context, the aggregated public +// key should be tweaked with the specified tweaks. +func WithTweakedContext(tweaks ...KeyTweakDesc) ContextOption { + return func(o *contextOptions) { + o.tweaks = tweaks + } +} + +// WithTaprootTweakCtx specifies that within this context, the final key should +// use the taproot tweak as defined in BIP 341: outputKey = internalKey + +// h_tapTweak(internalKey || scriptRoot). In this case, the aggreaged key +// before the tweak will be used as the internal key. +func WithTaprootTweakCtx(scriptRoot []byte) ContextOption { + return func(o *contextOptions) { + o.taprootTweak = scriptRoot + } +} + +// WithBip86TweakCtx specifies that within this context, the final key should +// use the taproot tweak as defined in BIP 341, with the BIP 86 modification: +// outputKey = internalKey + h_tapTweak(internalKey)*G. In this case, the +// aggreaged key before the tweak will be used as the internal key. +func WithBip86TweakCtx() ContextOption { + return func(o *contextOptions) { + o.bip86Tweak = true + } +} + +// WithKnownSigners is an optional parameter that should be used if a session +// can be created as soon as all the singers are known. +func WithKnownSigners(signers []*btcec.PublicKey) ContextOption { + return func(o *contextOptions) { + o.keySet = signers + o.numSigners = len(signers) + } +} + +// WithNumSigners is a functional option used to specify that a context should +// be created without knowing all the signers. Instead the total number of +// signers is specified to ensure that a session can only be created once all +// the signers are known. +// +// NOTE: Either WithKnownSigners or WithNumSigners MUST be specified. +func WithNumSigners(n int) ContextOption { + return func(o *contextOptions) { + o.numSigners = n + } +} + +// WithEarlyNonceGen allow a caller to specify that a nonce should be generated +// early, before the session is created. This should be used in protocols that +// require some partial nonce exchange before all the signers are known. +// +// NOTE: This option must only be specified with the WithNumSigners option. +func WithEarlyNonceGen() ContextOption { + return func(o *contextOptions) { + o.earlyNonce = true + } +} + +// NewContext creates a new signing context with the passed singing key and set +// of public keys for each of the other signers. +// +// NOTE: This struct should be used over the raw Sign API whenever possible. +func NewContext(signingKey *btcec.PrivateKey, shouldSort bool, + ctxOpts ...ContextOption) (*Context, error) { + + // First, parse the set of optional context options. + opts := defaultContextOptions() + for _, option := range ctxOpts { + option(opts) + } + + pubKey, err := schnorr.ParsePubKey( + schnorr.SerializePubKey(signingKey.PubKey()), + ) + if err != nil { + return nil, err + } + + ctx := &Context{ + signingKey: signingKey, + pubKey: pubKey, + opts: opts, + shouldSort: shouldSort, + } + + switch { + + // We know all the signers, so we can compute the aggregated key, along + // with all the other intermediate state we need to do signing and + // verification. + case opts.keySet != nil: + if err := ctx.combineSignerKeys(); err != nil { + return nil, err + } + + // The total signers are known, so we add ourselves, and skip key + // aggregation. + case opts.numSigners != 0: + // Otherwise, we'll add ourselves as the only known signer, and + // await further calls to RegisterSigner before a session can + // be created. + opts.keySet = make([]*btcec.PublicKey, 0, opts.numSigners) + opts.keySet = append(opts.keySet, pubKey) + + // If early nonce generation is specified, then we'll generate + // the nonce now to pass in to the session once all the callers + // are known. + if opts.earlyNonce { + ctx.sessionNonce, err = GenNonces() + if err != nil { + return nil, err + } + } + + default: + return nil, ErrSignersNotSpecified + } + + return ctx, nil +} + +// combineSignerKeys is used to compute the aggregated signer key once all the +// signers are known. +func (c *Context) combineSignerKeys() error { + // As a sanity check, make sure the signing key is actually + // amongst the sit of signers. + var keyFound bool + for _, key := range c.opts.keySet { + if key.IsEqual(c.pubKey) { + keyFound = true + break + } + } + if !keyFound { + return ErrSignerNotInKeySet + } + + // Now that we know that we're actually a signer, we'll + // generate the key hash finger print and second unique key + // index so we can speed up signing later. + c.keysHash = keyHashFingerprint(c.opts.keySet, c.shouldSort) + c.uniqueKeyIndex = secondUniqueKeyIndex( + c.opts.keySet, c.shouldSort, + ) + + keyAggOpts := []KeyAggOption{ + WithKeysHash(c.keysHash), + WithUniqueKeyIndex(c.uniqueKeyIndex), + } + switch { + case c.opts.bip86Tweak: + keyAggOpts = append( + keyAggOpts, WithBIP86KeyTweak(), + ) + case c.opts.taprootTweak != nil: + keyAggOpts = append( + keyAggOpts, WithTaprootKeyTweak(c.opts.taprootTweak), + ) + case len(c.opts.tweaks) != 0: + keyAggOpts = append(keyAggOpts, WithKeyTweaks(c.opts.tweaks...)) + } + + // Next, we'll use this information to compute the aggregated + // public key that'll be used for signing in practice. + var err error + c.combinedKey, _, _, err = AggregateKeys( + c.opts.keySet, c.shouldSort, keyAggOpts..., + ) + if err != nil { + return err + } + + return nil +} + +// EarlySessionNonce returns the early session nonce, if available. +func (c *Context) EarlySessionNonce() (*Nonces, error) { + if c.sessionNonce == nil { + return nil, ErrNoEarlyNonce + } + + return c.sessionNonce, nil +} + +// RegisterSigner allows a caller to register a signer after the context has +// been created. This will be used in scenarios where the total number of +// signers is known, but nonce exchange needs to happen before all the signers +// are known. +// +// A bool is returned which indicates if all the signers have been registered. +// +// NOTE: If the set of keys are not to be sorted during signing, then the +// ordering each key is registered with MUST match the desired ordering. +func (c *Context) RegisterSigner(pub *btcec.PublicKey) (bool, error) { + haveAllSigners := len(c.opts.keySet) == c.opts.numSigners + if haveAllSigners { + return false, ErrAlreadyHaveAllSigners + } + + c.opts.keySet = append(c.opts.keySet, pub) + + // If we have the expected number of signers at this point, then we can + // generate the aggregated key and other necessary information. + haveAllSigners = len(c.opts.keySet) == c.opts.numSigners + if haveAllSigners { + if err := c.combineSignerKeys(); err != nil { + return false, err + } + } + + return haveAllSigners, nil +} + +// NumRegisteredSigners returns the total number of registered signers. +func (c *Context) NumRegisteredSigners() int { + return len(c.opts.keySet) +} + +// CombinedKey returns the combined public key that will be used to generate +// multi-signatures against. +func (c *Context) CombinedKey() (*btcec.PublicKey, error) { + // If the caller hasn't registered all the signers at this point, then + // the combined key won't be available. + if c.combinedKey == nil { + return nil, ErrNotEnoughSigners + } + + return c.combinedKey.FinalKey, nil +} + +// PubKey returns the public key of the signer of this session. +func (c *Context) PubKey() btcec.PublicKey { + return *c.pubKey +} + +// SigningKeys returns the set of keys used for signing. +func (c *Context) SigningKeys() []*btcec.PublicKey { + keys := make([]*btcec.PublicKey, len(c.opts.keySet)) + copy(keys, c.opts.keySet) + + return keys +} + +// TaprootInternalKey returns the internal taproot key, which is the aggregated +// key _before_ the tweak is applied. If a taproot tweak was specified, then +// CombinedKey() will return the fully tweaked output key, with this method +// returning the internal key. If a taproot tweak wasn't specified, then this +// method will return an error. +func (c *Context) TaprootInternalKey() (*btcec.PublicKey, error) { + // If the caller hasn't registered all the signers at this point, then + // the combined key won't be available. + if c.combinedKey == nil { + return nil, ErrNotEnoughSigners + } + + if c.opts.taprootTweak == nil && !c.opts.bip86Tweak { + return nil, ErrTaprootInternalKeyUnavailable + } + + return c.combinedKey.PreTweakedKey, nil +} + +// SessionOption is a functional option argument that allows callers to modify +// the musig2 signing is done within a session. +type SessionOption func(*sessionOptions) + +// sessionOptions houses the set of functional options that can be used to +// modify the musig2 signing protocol. +type sessionOptions struct { + externalNonce *Nonces +} + +// defaultSessionOptions returns the default session options. +func defaultSessionOptions() *sessionOptions { + return &sessionOptions{} +} + +// WithPreGeneratedNonce allows a caller to start a session using a nonce +// they've generated themselves. This may be useful in protocols where all the +// signer keys may not be known before nonce exchange needs to occur. +func WithPreGeneratedNonce(nonce *Nonces) SessionOption { + return func(o *sessionOptions) { + o.externalNonce = nonce + } +} + +// Session represents a musig2 signing session. A new instance should be +// created each time a multi-signature is needed. The session struct handles +// nonces management, incremental partial sig vitrifaction, as well as final +// signature combination. Errors are returned when unsafe behavior such as +// nonce re-use is attempted. +// +// NOTE: This struct should be used over the raw Sign API whenever possible. +type Session struct { + opts *sessionOptions + + ctx *Context + + localNonces *Nonces + + pubNonces [][PubNonceSize]byte + + combinedNonce *[PubNonceSize]byte + + msg [32]byte + + ourSig *PartialSignature + sigs []*PartialSignature + + finalSig *schnorr.Signature +} + +// NewSession creates a new musig2 signing session. +func (c *Context) NewSession(options ...SessionOption) (*Session, error) { + opts := defaultSessionOptions() + for _, opt := range options { + opt(opts) + } + + // At this point we verify that we know of all the signers, as + // otherwise we can't proceed with the session. This check is intended + // to catch misuse of the API wherein a caller forgets to register the + // remaining signers if they're doing nonce generation ahead of time. + if len(c.opts.keySet) != c.opts.numSigners { + return nil, ErrNotEnoughSigners + } + + // If an early nonce was specified, then we'll automatically add the + // corresponding session option for the caller. + var localNonces *Nonces + if c.sessionNonce != nil { + // Apply the early nonce to the session, and also blank out the + // session nonce on the context to ensure it isn't ever re-used + // for another session. + localNonces = c.sessionNonce + c.sessionNonce = nil + } else if opts.externalNonce != nil { + // Otherwise if there's a custom nonce passed in via the + // session options, then use that instead. + localNonces = opts.externalNonce + } + + // Now that we know we have enough signers, we'll either use the caller + // specified nonce, or generate a fresh set. + var err error + if localNonces == nil { + // At this point we need to generate a fresh nonce. We'll pass + // in some auxiliary information to strengthen the nonce + // generated. + localNonces, err = GenNonces( + WithNonceSecretKeyAux(c.signingKey), + WithNonceCombinedKeyAux(c.combinedKey.FinalKey), + ) + if err != nil { + return nil, err + } + } + + s := &Session{ + opts: opts, + ctx: c, + localNonces: localNonces, + pubNonces: make([][PubNonceSize]byte, 0, c.opts.numSigners), + sigs: make([]*PartialSignature, 0, c.opts.numSigners), + } + + s.pubNonces = append(s.pubNonces, localNonces.PubNonce) + + return s, nil +} + +// PublicNonce returns the public nonce for a signer. This should be sent to +// other parties before signing begins, so they can compute the aggregated +// public nonce. +func (s *Session) PublicNonce() [PubNonceSize]byte { + return s.localNonces.PubNonce +} + +// NumRegisteredNonces returns the total number of nonces that have been +// regsitered so far. +func (s *Session) NumRegisteredNonces() int { + return len(s.pubNonces) +} + +// RegisterPubNonce should be called for each public nonce from the set of +// signers. This method returns true once all the public nonces have been +// accounted for. +func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) { + // If we already have all the nonces, then this method was called too + // many times. + haveAllNonces := len(s.pubNonces) == s.ctx.opts.numSigners + if haveAllNonces { + return false, ErrAlredyHaveAllNonces + } + + // Add this nonce and check again if we already have tall the nonces we + // need. + s.pubNonces = append(s.pubNonces, nonce) + haveAllNonces = len(s.pubNonces) == s.ctx.opts.numSigners + + // If we have all the nonces, then we can go ahead and combine them + // now. + if haveAllNonces { + combinedNonce, err := AggregateNonces(s.pubNonces) + if err != nil { + return false, err + } + + s.combinedNonce = &combinedNonce + } + + return haveAllNonces, nil +} + +// Sign generates a partial signature for the target message, using the target +// context. If this method is called more than once per context, then an error +// is returned, as that means a nonce was re-used. +func (s *Session) Sign(msg [32]byte, + signOpts ...SignOption) (*PartialSignature, error) { + + switch { + // If no local nonce is present, then this means we already signed, so + // we'll return an error to prevent nonce re-use. + case s.localNonces == nil: + return nil, ErrSigningContextReuse + + // We also need to make sure we have the combined nonce, otherwise this + // 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 +} diff --git a/internal/musig2/keys.go b/internal/musig2/keys.go new file mode 100644 index 000000000..9acdca367 --- /dev/null +++ b/internal/musig2/keys.go @@ -0,0 +1,459 @@ +// Copyright 2013-2022 The btcsuite developers + +package musig2 + +import ( + "bytes" + "fmt" + "sort" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var ( + // 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 to 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 := schnorr.SerializePubKey(s[i]) + keyJBytes := schnorr.SerializePubKey(s[j]) + + 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 schnorr 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, 32*len(keys)) + keyBytes := bytes.NewBuffer(keyAggBuf[0:0]) + for _, key := range keys { + keyBytes.Write(schnorr.SerializePubKey(key)) + } + + h := chainhash.TaggedHash(KeyAggTagList, keyBytes.Bytes()) + return h[:] +} + +// keyBytesEqual returns true if two keys are the same from the PoV of BIP +// 340's 32-byte x-only public keys. +func keyBytesEqual(a, b *btcec.PublicKey) bool { + return bytes.Equal( + schnorr.SerializePubKey(a), + schnorr.SerializePubKey(b), + ) +} + +// 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 [64]byte + copy(coefficientBytes[:], keysHash[:]) + copy(coefficientBytes[32:], schnorr.SerializePubKey(targetKey)) + + 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] == secp.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 +} diff --git a/internal/musig2/musig2_test.go b/internal/musig2/musig2_test.go new file mode 100644 index 000000000..b38090908 --- /dev/null +++ b/internal/musig2/musig2_test.go @@ -0,0 +1,1897 @@ +// Copyright 2013-2022 The btcsuite developers + +package musig2 + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "flag" + "fmt" + "io/ioutil" + "sync" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var ( + key1Bytes, _ = hex.DecodeString("F9308A019258C31049344F85F89D5229B53" + + "1C845836F99B08601F113BCE036F9") + key2Bytes, _ = hex.DecodeString("DFF1D77F2A671C5F36183726DB2341BE58F" + + "EAE1DA2DECED843240F7B502BA659") + key3Bytes, _ = hex.DecodeString("3590A94E768F8E1815C2F24B4D80A8E3149" + + "316C3518CE7B7AD338368D038CA66") + + invalidPk1, _ = hex.DecodeString("00000000000000000000000000000000" + + "00000000000000000000000000000005") + invalidPk2, _ = hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30") + invalidTweak, _ = hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" + + "BAAEDCE6AF48A03BBFD25E8CD0364141") + + testKeys = [][]byte{key1Bytes, key2Bytes, key3Bytes, invalidPk1, + invalidPk2} + + keyCombo1, _ = hex.DecodeString("E5830140512195D74C8307E39637CBE5FB73" + + "0EBEAB80EC514CF88A877CEEEE0B") + keyCombo2, _ = hex.DecodeString("D70CD69A2647F7390973DF48CBFA2CCC407B" + + "8B2D60B08C5F1641185C7998A290") + keyCombo3, _ = hex.DecodeString("81A8B093912C9E481408D09776CEFB48AEB8" + + "B65481B6BAAFB3C5810106717BEB") + keyCombo4, _ = hex.DecodeString("2EB18851887E7BDC5E830E89B19DDBC28078" + + "F1FA88AAD0AD01CA06FE4F80210B") +) + +// getInfinityTweak returns a tweak that, when tweaking the Generator, triggers +// the ErrTweakedKeyIsInfinity error. +func getInfinityTweak() KeyTweakDesc { + generator := btcec.Generator() + + keySet := []*btcec.PublicKey{generator} + + keysHash := keyHashFingerprint(keySet, true) + uniqueKeyIndex := secondUniqueKeyIndex(keySet, true) + + n := &btcec.ModNScalar{} + + n.SetByteSlice(invalidTweak) + + coeff := aggregationCoefficient( + keySet, generator, keysHash, uniqueKeyIndex, + ).Negate().Add(n) + + return KeyTweakDesc{ + Tweak: coeff.Bytes(), + IsXOnly: false, + } +} + +const ( + keyAggTestVectorName = "key_agg_vectors.json" + + nonceAggTestVectorName = "nonce_agg_vectors.json" + + signTestVectorName = "sign_vectors.json" +) + +var dumpJson = flag.Bool("dumpjson", false, "if true, a JSON version of the "+ + "test vectors will be written to the cwd") + +type jsonKeyAggTestCase struct { + Keys []string `json:"keys"` + Tweaks []jsonTweak `json:"tweaks"` + ExpectedKey string `json:"expected_key"` + ExpectedError string `json:"expected_error"` +} + +// TestMuSig2KeyAggTestVectors tests that this implementation of musig2 key +// aggregation lines up with the secp256k1-zkp test vectors. +func TestMuSig2KeyAggTestVectors(t *testing.T) { + t.Parallel() + + var jsonCases []jsonKeyAggTestCase + + testCases := []struct { + keyOrder []int + explicitKeys []*btcec.PublicKey + tweaks []KeyTweakDesc + expectedKey []byte + expectedError error + }{ + // Keys in backwards lexicographical order. + { + keyOrder: []int{0, 1, 2}, + expectedKey: keyCombo1, + }, + + // Keys in sorted order. + { + keyOrder: []int{2, 1, 0}, + expectedKey: keyCombo2, + }, + + // Only the first key. + { + keyOrder: []int{0, 0, 0}, + expectedKey: keyCombo3, + }, + + // Duplicate the first key and second keys. + { + keyOrder: []int{0, 0, 1, 1}, + expectedKey: keyCombo4, + }, + + // Invalid public key. + { + keyOrder: []int{0, 3}, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + + // Public key exceeds field size. + { + keyOrder: []int{0, 4}, + expectedError: secp256k1.ErrPubKeyXTooBig, + }, + + // Tweak is out of range. + { + keyOrder: []int{0, 1}, + tweaks: []KeyTweakDesc{ + { + Tweak: to32ByteSlice(invalidTweak), + IsXOnly: true, + }, + }, + expectedError: ErrTweakedKeyOverflows, + }, + + // Intermediate tweaking result is point at infinity. + { + explicitKeys: []*secp256k1.PublicKey{btcec.Generator()}, + tweaks: []KeyTweakDesc{ + getInfinityTweak(), + }, + expectedError: ErrTweakedKeyIsInfinity, + }, + } + for i, testCase := range testCases { + testName := fmt.Sprintf("%v", testCase.keyOrder) + t.Run(testName, func(t *testing.T) { + var ( + keys []*btcec.PublicKey + strKeys []string + strTweaks []jsonTweak + jsonError string + ) + for _, keyIndex := range testCase.keyOrder { + keyBytes := testKeys[keyIndex] + pub, err := schnorr.ParsePubKey(keyBytes) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + return + case err != nil: + t.Fatalf("unable to parse pubkeys: %v", err) + } + + keys = append(keys, pub) + strKeys = append(strKeys, hex.EncodeToString(keyBytes)) + } + + for _, explicitKey := range testCase.explicitKeys { + keys = append(keys, explicitKey) + strKeys = append( + strKeys, + hex.EncodeToString( + explicitKey.SerializeCompressed(), + )) + } + + for _, tweak := range testCase.tweaks { + strTweaks = append( + strTweaks, + jsonTweak{ + Tweak: hex.EncodeToString( + tweak.Tweak[:], + ), + XOnly: tweak.IsXOnly, + }) + } + + if testCase.expectedError != nil { + jsonError = testCase.expectedError.Error() + } + + jsonCases = append( + jsonCases, + jsonKeyAggTestCase{ + Keys: strKeys, + Tweaks: strTweaks, + ExpectedKey: hex.EncodeToString( + testCase.expectedKey), + ExpectedError: jsonError, + }) + + uniqueKeyIndex := secondUniqueKeyIndex(keys, false) + opts := []KeyAggOption{WithUniqueKeyIndex(uniqueKeyIndex)} + if len(testCase.tweaks) > 0 { + opts = append(opts, WithKeyTweaks(testCase.tweaks...)) + } + + combinedKey, _, _, err := AggregateKeys( + keys, false, opts..., + ) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + return + + case err != nil: + t.Fatalf("case #%v, got error %v", i, err) + } + + combinedKeyBytes := schnorr.SerializePubKey(combinedKey.FinalKey) + if !bytes.Equal(combinedKeyBytes, testCase.expectedKey) { + t.Fatalf("case: #%v, invalid aggregation: "+ + "expected %x, got %x", i, testCase.expectedKey, + combinedKeyBytes) + } + }) + } + + if *dumpJson { + jsonBytes, err := json.Marshal(jsonCases) + if err != nil { + t.Fatalf("unable to encode json: %v", err) + } + + var formattedJson bytes.Buffer + json.Indent(&formattedJson, jsonBytes, "", "\t") + err = ioutil.WriteFile( + keyAggTestVectorName, formattedJson.Bytes(), 0644, + ) + if err != nil { + t.Fatalf("unable to write file: %v", err) + } + } +} + +func mustParseHex(str string) []byte { + b, err := hex.DecodeString(str) + if err != nil { + panic(fmt.Errorf("unable to parse hex: %v", err)) + } + + return b +} + +func parseKey(xHex string) *btcec.PublicKey { + xB, err := hex.DecodeString(xHex) + if err != nil { + panic(err) + } + + var x, y btcec.FieldVal + x.SetByteSlice(xB) + if !btcec.DecompressY(&x, false, &y) { + panic("x not on curve") + } + y.Normalize() + + return btcec.NewPublicKey(&x, &y) +} + +var ( + signSetPrivKey, _ = btcec.PrivKeyFromBytes( + mustParseHex("7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DF" + + "E8D76D7F2D1007671"), + ) + signSetPubKey = schnorr.SerializePubKey(signSetPrivKey.PubKey()) + + signTestMsg = mustParseHex("F95466D086770E689964664219266FE5ED215C92A" + + "E20BAB5C9D79ADDDDF3C0CF") + + signSetKey2 = mustParseHex("F9308A019258C31049344F85F89D5229B531C8458" + + "36F99B08601F113BCE036F9") + + signSetKey3 = mustParseHex("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA" + + "2DECED843240F7B502BA659") + + invalidSetKey1 = mustParseHex("00000000000000000000000000000000" + + "00000000000000000000000000000007") + + signExpected1 = mustParseHex("68537CC5234E505BD14061F8DA9E90C220A1818" + + "55FD8BDB7F127BB12403B4D3B") + signExpected2 = mustParseHex("2DF67BFFF18E3DE797E13C6475C963048138DAE" + + "C5CB20A357CECA7C8424295EA") + signExpected3 = mustParseHex("0D5B651E6DE34A29A12DE7A8B4183B4AE6A7F7F" + + "BE15CDCAFA4A3D1BCAABC7517") + + signExpected4 = mustParseHex("8D5E0407FB4756EEBCD86264C32D792EE36EEB6" + + "9E952BBB30B8E41BEBC4D22FA") + + signSetKeys = [][]byte{signSetPubKey, signSetKey2, signSetKey3, invalidPk1} + + aggregatedNonce = toPubNonceSlice(mustParseHex("028465FCF0BBDBCF443AA" + + "BCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926" + + "D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9")) + verifyPnonce1 = mustParsePubNonce("0337C87821AFD50A8644D820A8F3E02E49" + + "9C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352A" + + "A9405D1428C15F4B75F04DAE642A95C2548480") + verifyPnonce2 = mustParsePubNonce("0279BE667EF9DCBBAC55A06295CE870B07" + + "029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE" + + "870B07029BFCDB2DCE28D959F2815B16F81798") + verifyPnonce3 = mustParsePubNonce("032DE2662628C90B03F5E720284EB52FF7" + + "D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1C" + + "A6BEB2090C93D930321071AD40B2F44E599046") + verifyPnonce4 = mustParsePubNonce("0237C87821AFD50A8644D820A8F3E02E49" + + "9C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352A" + + "A9405D1428C15F4B75F04DAE642A95C2548480") + + tweak1 = KeyTweakDesc{ + Tweak: [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, + }, + } + tweak2 = KeyTweakDesc{ + Tweak: [32]byte{ + 0xae, 0x2e, 0xa7, 0x97, 0xcc, 0xf, 0xe7, 0x2a, + 0xc5, 0xb9, 0x7b, 0x97, 0xf3, 0xc6, 0x95, 0x7d, + 0x7e, 0x41, 0x99, 0xa1, 0x67, 0xa5, 0x8e, 0xb0, + 0x8b, 0xca, 0xff, 0xda, 0x70, 0xac, 0x4, 0x55, + }, + } + tweak3 = KeyTweakDesc{ + Tweak: [32]byte{ + 0xf5, 0x2e, 0xcb, 0xc5, 0x65, 0xb3, 0xd8, 0xbe, + 0xa2, 0xdf, 0xd5, 0xb7, 0x5a, 0x4f, 0x45, 0x7e, + 0x54, 0x36, 0x98, 0x9, 0x32, 0x2e, 0x41, 0x20, + 0x83, 0x16, 0x26, 0xf2, 0x90, 0xfa, 0x87, 0xe0, + }, + } + tweak4 = KeyTweakDesc{ + Tweak: [32]byte{ + 0x19, 0x69, 0xad, 0x73, 0xcc, 0x17, 0x7f, 0xa0, + 0xb4, 0xfc, 0xed, 0x6d, 0xf1, 0xf7, 0xbf, 0x99, + 0x7, 0xe6, 0x65, 0xfd, 0xe9, 0xba, 0x19, 0x6a, + 0x74, 0xfe, 0xd0, 0xa3, 0xcf, 0x5a, 0xef, 0x9d, + }, + } +) + +func formatTweakParity(tweaks []KeyTweakDesc) string { + var s string + for _, tweak := range tweaks { + s += fmt.Sprintf("%v/", tweak.IsXOnly) + } + + // Snip off that last '/'. + s = s[:len(s)-1] + + return s +} + +func genTweakParity(tweak KeyTweakDesc, isXOnly bool) KeyTweakDesc { + tweak.IsXOnly = isXOnly + return tweak +} + +type jsonTweak struct { + Tweak string `json:"tweak"` + XOnly bool `json:"x_only"` +} + +type jsonTweakSignCase struct { + Keys []string `json:"keys"` + Tweaks []jsonTweak `json:"tweaks,omitempty"` + AggNonce string `json:"agg_nonce"` + + ExpectedSig string `json:"expected_sig"` + ExpectedError string `json:"expected_error` +} + +type jsonSignTestCase struct { + SecNonce string `json:"secret_nonce"` + SigningKey string `json:"signing_key"` + Msg string `json:"msg"` + + TestCases []jsonTweakSignCase `json:"test_cases"` +} + +// TestMuSig2SigningTestVectors tests that the musig2 implementation produces +// the same set of signatures. +func TestMuSig2SigningTestVectors(t *testing.T) { + t.Parallel() + + var jsonCases jsonSignTestCase + + jsonCases.SigningKey = hex.EncodeToString(signSetPrivKey.Serialize()) + jsonCases.Msg = hex.EncodeToString(signTestMsg) + + var secNonce [SecNonceSize]byte + copy(secNonce[:], mustParseHex("508B81A611F100A6B2B6B29656590898AF488B"+ + "CF2E1F55CF22E5CFB84421FE61")) + copy(secNonce[32:], mustParseHex("FA27FD49B1D50085B481285E1CA205D55C82"+ + "CC1B31FF5CD54A489829355901F7")) + + jsonCases.SecNonce = hex.EncodeToString(secNonce[:]) + + testCases := []struct { + keyOrder []int + aggNonce [66]byte + expectedPartialSig []byte + tweaks []KeyTweakDesc + expectedError error + }{ + // Vector 1 + { + keyOrder: []int{0, 1, 2}, + aggNonce: aggregatedNonce, + expectedPartialSig: signExpected1, + }, + + // Vector 2 + { + keyOrder: []int{1, 0, 2}, + aggNonce: aggregatedNonce, + expectedPartialSig: signExpected2, + }, + + // Vector 3 + { + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: signExpected3, + }, + // Vector 4 Both halves of aggregate nonce correspond to point at infinity + { + keyOrder: []int{0, 1}, + aggNonce: mustNonceAgg([][66]byte{verifyPnonce1, verifyPnonce4}), + expectedPartialSig: signExpected4, + }, + + // Vector 5: Signer 2 provided an invalid public key + { + keyOrder: []int{1, 0, 3}, + aggNonce: aggregatedNonce, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + + // Vector 6: Aggregate nonce is invalid due wrong tag, 0x04, + // in the first half. + { + + keyOrder: []int{1, 2, 0}, + aggNonce: toPubNonceSlice( + mustParseHex("048465FCF0BBDBCF443AABCCE533D42" + + "B4B5A10966AC09A49655E8C42DAAB8FCD610" + + "37496A3CC86926D452CAFCFD55D25972CA16" + + "75D549310DE296BFF42F72EEEA8C9")), + expectedError: secp256k1.ErrPubKeyInvalidFormat, + }, + + // Vector 7: Aggregate nonce is invalid because the second half + // does not correspond to an X coordinate. + { + + keyOrder: []int{1, 2, 0}, + aggNonce: toPubNonceSlice( + mustParseHex("028465FCF0BBDBCF443AABCCE533D42" + + "B4B5A10966AC09A49655E8C42DAAB8FCD610" + + "200000000000000000000000000000000000" + + "00000000000000000000000000009")), + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + + // Vector 8: Aggregate nonce is invalid because the second half + // exceeds field size. + { + + keyOrder: []int{1, 2, 0}, + aggNonce: toPubNonceSlice( + mustParseHex("028465FCF0BBDBCF443AABCCE533D42" + + "B4B5A10966AC09A49655E8C42DAAB8FCD610" + + "2FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFEFFFFFC30")), + expectedError: secp256k1.ErrPubKeyXTooBig, + }, + + // A single x-only tweak. + { + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("5e24c7496b565debc3b" + + "9639e6f1304a21597f9603d3ab05b4913641775e1375b"), + tweaks: []KeyTweakDesc{genTweakParity(tweak1, true)}, + }, + + // A single ordinary tweak. + { + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("78408ddcab4813d1394c" + + "97d493ef1084195c1d4b52e63ecd7bc5991644e44ddd"), + tweaks: []KeyTweakDesc{genTweakParity(tweak1, false)}, + }, + + // An ordinary tweak then an x-only tweak. + { + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("C3A829A81480E36EC3A" + + "B052964509A94EBF34210403D16B226A6F16EC85B7357"), + tweaks: []KeyTweakDesc{ + genTweakParity(tweak1, false), + genTweakParity(tweak2, true), + }, + }, + + // Four tweaks, in the order: x-only, ordinary, x-only, ordinary. + { + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("8C4473C6A382BD3C4AD" + + "7BE59818DA5ED7CF8CEC4BC21996CFDA08BB4316B8BC7"), + tweaks: []KeyTweakDesc{ + genTweakParity(tweak1, true), + genTweakParity(tweak2, false), + genTweakParity(tweak3, true), + genTweakParity(tweak4, false), + }, + }, + } + + var msg [32]byte + copy(msg[:], signTestMsg) + + for _, testCase := range testCases { + testName := fmt.Sprintf("%v/tweak=%v", testCase.keyOrder, len(testCase.tweaks) != 0) + if len(testCase.tweaks) != 0 { + testName += fmt.Sprintf("/x_only=%v", formatTweakParity(testCase.tweaks)) + } + t.Run(testName, func(t *testing.T) { + keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder)) + for _, keyIndex := range testCase.keyOrder { + keyBytes := signSetKeys[keyIndex] + pub, err := schnorr.ParsePubKey(keyBytes) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to parse pubkeys: %v", err) + } + + keySet = append(keySet, pub) + } + + var opts []SignOption + if len(testCase.tweaks) != 0 { + opts = append( + opts, WithTweaks(testCase.tweaks...), + ) + } + + partialSig, err := Sign( + secNonce, signSetPrivKey, testCase.aggNonce, + keySet, msg, opts..., + ) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to generate partial sig: %v", err) + } + + var partialSigBytes [32]byte + partialSig.S.PutBytesUnchecked(partialSigBytes[:]) + + if !bytes.Equal(partialSigBytes[:], testCase.expectedPartialSig) { + t.Fatalf("sigs don't match: expected %x, got %x", + testCase.expectedPartialSig, partialSigBytes, + ) + } + + }) + + if *dumpJson { + var ( + strKeys []string + jsonError string + ) + + for _, keyIndex := range testCase.keyOrder { + keyBytes := signSetKeys[keyIndex] + strKeys = append(strKeys, hex.EncodeToString(keyBytes)) + } + + if testCase.expectedError != nil { + jsonError = testCase.expectedError.Error() + } + + tweakSignCase := jsonTweakSignCase{ + Keys: strKeys, + ExpectedSig: hex.EncodeToString(testCase.expectedPartialSig), + AggNonce: hex.EncodeToString(testCase.aggNonce[:]), + ExpectedError: jsonError, + } + + var jsonTweaks []jsonTweak + for _, tweak := range testCase.tweaks { + jsonTweaks = append( + jsonTweaks, + jsonTweak{ + Tweak: hex.EncodeToString(tweak.Tweak[:]), + XOnly: tweak.IsXOnly, + }) + } + tweakSignCase.Tweaks = jsonTweaks + + jsonCases.TestCases = append(jsonCases.TestCases, tweakSignCase) + } + } + + if *dumpJson { + jsonBytes, err := json.Marshal(jsonCases) + if err != nil { + t.Fatalf("unable to encode json: %v", err) + } + + var formattedJson bytes.Buffer + json.Indent(&formattedJson, jsonBytes, "", "\t") + err = ioutil.WriteFile( + signTestVectorName, formattedJson.Bytes(), 0644, + ) + if err != nil { + t.Fatalf("unable to write file: %v", err) + } + } +} + +func TestMusig2PartialSigVerifyTestVectors(t *testing.T) { + t.Parallel() + + testCases := []struct { + partialSig []byte + nonces [][66]byte + pubnonceIndex int + keyOrder []int + tweaks []KeyTweakDesc + expectedError error + }{ + // A single x-only tweak. + { + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + pubnonceIndex: 2, + partialSig: mustParseHex("5e24c7496b565debc3b9639e" + + "6f1304a21597f9603d3ab05b4913641775e1375b"), + tweaks: []KeyTweakDesc{genTweakParity(tweak1, true)}, + }, + // A single ordinary tweak. + { + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + pubnonceIndex: 2, + partialSig: mustParseHex("78408ddcab4813d1394c97d4" + + "93ef1084195c1d4b52e63ecd7bc5991644e44ddd"), + tweaks: []KeyTweakDesc{genTweakParity(tweak1, false)}, + }, + // An ordinary tweak then an x-only tweak. + { + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + pubnonceIndex: 2, + partialSig: mustParseHex("C3A829A81480E36EC3AB0529" + + "64509A94EBF34210403D16B226A6F16EC85B7357"), + tweaks: []KeyTweakDesc{ + genTweakParity(tweak1, false), + genTweakParity(tweak2, true), + }, + }, + + // Four tweaks, in the order: x-only, ordinary, x-only, ordinary. + { + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + pubnonceIndex: 2, + partialSig: mustParseHex("8C4473C6A382BD3C4AD7BE5" + + "9818DA5ED7CF8CEC4BC21996CFDA08BB4316B8BC7"), + tweaks: []KeyTweakDesc{ + genTweakParity(tweak1, true), + genTweakParity(tweak2, false), + genTweakParity(tweak3, true), + genTweakParity(tweak4, false), + }, + }, + // Vector 9. + { + + partialSig: signExpected1, + pubnonceIndex: 0, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + }, + // Vector 10. + { + + partialSig: signExpected2, + pubnonceIndex: 1, + keyOrder: []int{1, 0, 2}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce1, + verifyPnonce3, + }, + }, + // Vector 11. + { + + partialSig: signExpected3, + pubnonceIndex: 2, + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + }, + // Vector 12: Both halves of aggregate nonce correspond to + // point at infinity. + { + + partialSig: signExpected4, + pubnonceIndex: 0, + keyOrder: []int{0, 1}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce4, + }, + }, + // Vector 13: Wrong signature (which is equal to the negation + // of valid signature expected[0]). + { + + partialSig: mustParseHex("97AC833ADCB1AFA42EBF9E0" + + "725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406"), + pubnonceIndex: 0, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + expectedError: ErrPartialSigInvalid, + }, + // Vector 12: Wrong signer. + { + + partialSig: signExpected1, + pubnonceIndex: 1, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + expectedError: ErrPartialSigInvalid, + }, + // Vector 13: Signature exceeds group size. + { + + partialSig: mustParseHex("FFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"), + pubnonceIndex: 0, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + expectedError: ErrPartialSigInvalid, + }, + // Vector 14: Invalid pubnonce. + { + + partialSig: signExpected1, + pubnonceIndex: 0, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + canParsePubNonce("020000000000000000000000000" + + "000000000000000000000000000000000000009"), + verifyPnonce2, + verifyPnonce3, + }, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + // Vector 15: Invalid public key. + { + + partialSig: signExpected1, + pubnonceIndex: 0, + keyOrder: []int{3, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + } + + for _, testCase := range testCases { + + // todo find name + testName := fmt.Sprintf("%v/tweak=%v", testCase.pubnonceIndex, testCase.keyOrder) + + t.Run(testName, func(t *testing.T) { + + combinedNonce, err := AggregateNonces(testCase.nonces) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to aggregate nonces %v", err) + } + + keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder)) + for _, keyIndex := range testCase.keyOrder { + keyBytes := signSetKeys[keyIndex] + pub, err := schnorr.ParsePubKey(keyBytes) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to parse pubkeys: %v", err) + } + + keySet = append(keySet, pub) + } + + ps := &PartialSignature{} + err = ps.Decode(bytes.NewBuffer(testCase.partialSig)) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatal(err) + } + + var opts []SignOption + if len(testCase.tweaks) != 0 { + opts = append( + opts, WithTweaks(testCase.tweaks...), + ) + } + + err = verifyPartialSig( + ps, + testCase.nonces[testCase.pubnonceIndex], + combinedNonce, + keySet, + signSetKeys[testCase.keyOrder[testCase.pubnonceIndex]], + to32ByteSlice(signTestMsg), + opts..., + ) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to aggregate nonces %v", err) + } + }) + } +} + +type signer struct { + privKey *btcec.PrivateKey + 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.PrivateKey, numSigners) + signSet := make([]*btcec.PublicKey, numSigners) + for i := 0; i < numSigners; i++ { + privKey, err := btcec.NewPrivateKey() + if err != nil { + t.Fatalf("unable to gen priv key: %v", err) + } + + pubKey, err := schnorr.ParsePubKey( + schnorr.SerializePubKey(privKey.PubKey()), + ) + if err != nil { + t.Fatalf("unable to gen key: %v", err) + } + + 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.NewPrivateKey() + if err != nil { + t.Fatalf("unable to gen priv key: %v", err) + } + privKey2, err := btcec.NewPrivateKey() + if err != nil { + t.Fatalf("unable to gen priv key: %v", err) + } + + // If we try to make a context, with just the private 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) + } + + numSigners := 2 + + 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, WithNumSigners(numSigners), 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 both + // signers, since we only know of the sole signers. + if _, err := ctx1.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) { + t.Fatalf("unepxected error: %v", err) + } + if _, err := ctx2.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) { + t.Fatalf("unepxected error: %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() != 1 { + t.Fatalf("expected 1 signer, 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 both parties. + 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") + } + done, err = ctx2.RegisterSigner(&pubKey1) + if err != nil { + t.Fatalf("unable to register signer: %v", err) + } + if !done { + t.Fatalf("signer 2 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, ErrAlredyHaveAllNonces) { + 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!") + } +} + +// TestMusig2NonceGenTestVectors tests the nonce generation function with +// the testvectors defined in the Musig2 BIP. +func TestMusig2NonceGenTestVectors(t *testing.T) { + t.Parallel() + + msg := bytes.Repeat([]byte{0x01}, 32) + sk := bytes.Repeat([]byte{0x02}, 32) + aggpk := bytes.Repeat([]byte{0x07}, 32) + extra_in := bytes.Repeat([]byte{0x08}, 32) + + testCases := []struct { + opts nonceGenOpts + expectedNonce string + }{ + { + opts: nonceGenOpts{ + randReader: &memsetRandReader{i: 0}, + secretKey: sk[:], + combinedKey: aggpk[:], + auxInput: extra_in[:], + msg: msg[:], + }, + expectedNonce: "E8F2E103D86800F19A4E97338D371CB885DB2" + + "F19D08C0BD205BBA9B906C971D0D786A17718AAFAD6D" + + "E025DDDD99DC823E2DFC1AE1DDFE920888AD53FFF423FC4", + }, + { + opts: nonceGenOpts{ + randReader: &memsetRandReader{i: 0}, + secretKey: sk[:], + combinedKey: aggpk[:], + auxInput: extra_in[:], + msg: nil, + }, + expectedNonce: "8A633F5EECBDB690A6BE4921426F41BE78D50" + + "9DC1CE894C1215844C0E4C6DE7ABC9A5BE0A3BF3FE31" + + "2CCB7E4817D2CB17A7CEA8382B73A99A583E323387B3C32", + }, + { + opts: nonceGenOpts{ + randReader: &memsetRandReader{i: 0}, + secretKey: nil, + combinedKey: nil, + auxInput: nil, + msg: nil, + }, + expectedNonce: "7B3B5A002356471AF0E961DE2549C121BD0D4" + + "8ABCEEDC6E034BDDF86AD3E0A187ECEE674CEF7364B0" + + "BC4BEEFB8B66CAD89F98DE2F8C5A5EAD5D1D1E4BD7D04CD", + }, + } + + for _, testCase := range testCases { + nonce, err := GenNonces(withCustomOptions(testCase.opts)) + if err != nil { + t.Fatalf("err gen nonce aux bytes %v", err) + } + + expectedBytes, _ := hex.DecodeString(testCase.expectedNonce) + if !bytes.Equal(nonce.SecNonce[:], expectedBytes) { + + t.Fatalf("nonces don't match: expected %x, got %x", + expectedBytes, nonce.SecNonce[:]) + } + } + +} + +var ( + pNonce1, _ = hex.DecodeString("020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E666" + + "03BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641") + pNonce2, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + + "0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833") + + expectedNonce, _ = hex.DecodeString("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B" + + "024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8") + + invalidNonce1, _ = hex.DecodeString("04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833") + invalidNonce2, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831") + invalidNonce3, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30") +) + +type jsonNonceAggTestCase struct { + Nonces []string `json:"nonces"` + ExpectedNonce string `json:"expected_key"` + ExpectedError string `json:"expected_error"` +} + +func TestMusig2AggregateNoncesTestVectors(t *testing.T) { + t.Parallel() + + var jsonCases []jsonNonceAggTestCase + + testCases := []struct { + nonces [][]byte + expectedNonce []byte + expectedError error + }{ + // Vector 1: Valid. + { + nonces: [][]byte{pNonce1, pNonce2}, + expectedNonce: expectedNonce, + }, + + // Vector 2: Public nonce from signer 1 is invalid due wrong + // tag, 0x04, inthe first half. + { + nonces: [][]byte{pNonce1, invalidNonce1}, + expectedError: secp256k1.ErrPubKeyInvalidFormat, + }, + + // Vector 3: Public nonce from signer 0 is invalid because the + // second half does not correspond to an X coordinate. + { + nonces: [][]byte{invalidNonce2, pNonce2}, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + + // Vector 4: Public nonce from signer 0 is invalid because + // second half exceeds field size. + { + nonces: [][]byte{invalidNonce3, pNonce2}, + expectedError: secp256k1.ErrPubKeyXTooBig, + }, + + // Vector 5: Sum of second points encoded in the nonces would + // be point at infinity, therefore set sum to base point G. + { + nonces: [][]byte{ + append( + append([]byte{}, pNonce1[0:33]...), + getGBytes()..., + ), + append( + append([]byte{}, pNonce2[0:33]...), + getNegGBytes()..., + ), + }, + expectedNonce: append( + append([]byte{}, expectedNonce[0:33]...), + getInfinityBytes()..., + ), + }, + } + for i, testCase := range testCases { + testName := fmt.Sprintf("Vector %v", i+1) + t.Run(testName, func(t *testing.T) { + var ( + nonces [][66]byte + strNonces []string + jsonError string + ) + for _, nonce := range testCase.nonces { + nonces = append(nonces, toPubNonceSlice(nonce)) + strNonces = append(strNonces, hex.EncodeToString(nonce)) + } + + if testCase.expectedError != nil { + jsonError = testCase.expectedError.Error() + } + + jsonCases = append(jsonCases, jsonNonceAggTestCase{ + Nonces: strNonces, + ExpectedNonce: hex.EncodeToString(expectedNonce), + ExpectedError: jsonError, + }) + + aggregatedNonce, err := AggregateNonces(nonces) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("aggregating nonce error: %v", err) + } + + if !bytes.Equal(testCase.expectedNonce, aggregatedNonce[:]) { + t.Fatalf("case: #%v, invalid nonce aggregation: "+ + "expected %x, got %x", i, testCase.expectedNonce, + aggregatedNonce) + } + + }) + } + + if *dumpJson { + jsonBytes, err := json.Marshal(jsonCases) + if err != nil { + t.Fatalf("unable to encode json: %v", err) + } + + var formattedJson bytes.Buffer + json.Indent(&formattedJson, jsonBytes, "", "\t") + err = ioutil.WriteFile( + nonceAggTestVectorName, formattedJson.Bytes(), 0644, + ) + if err != nil { + t.Fatalf("unable to write file: %v", err) + } + } +} + +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 +} + +var ( + combineSigKey0 = mustParseHex("487D1B83B41B4CBBD07A111F1BBC7BDC8864CF" + + "EF5DBF96E46E51C68399B0BEF6") + combineSigKey1 = mustParseHex("4795C22501BF534BC478FF619407A7EC9E8D88" + + "83646D69BD43A0728944EA802F") + combineSigKey2 = mustParseHex("0F5BE837F3AB7E7FEFF1FAA44D673C2017206A" + + "E836D2C7893CDE4ACB7D55EDEB") + combineSigKey3 = mustParseHex("0FD453223E444FCA91FB5310990AE8A0C5DAA1" + + "4D2A4C8944E1C0BC80C30DF682") + + combineSigKeys = [][]byte{combineSigKey0, combineSigKey1, + combineSigKey2, combineSigKey3} + + combineSigAggNonce0 = mustParsePubNonce("024FA51009A56F0D6DF737131CE1" + + "FBBD833797AF3B4FE6BF0D68F4D49F68B0947E0248FB3BB9191F0CFF1380" + + "6A3A2F1429C23012654FCE4E41F7EC9169EAA6056B21") + combineSigAggNonce1 = mustParsePubNonce("023B11E63E2460E5E0F1561BB700" + + "FEA95B991DD9CA2CBBE92A3960641FA7469F6702CA4CD38375FE8BEB857C" + + "770807225BFC7D712F42BA896B83FC71138E56409B21") + combineSigAggNonce2 = mustParsePubNonce("03F98BEAA32B8A38FE3797C4E813" + + "DC9CE05ADBE32200035FB37EB0A030B735E9B6030E6118EC98EA2BA7A358" + + "C2E38E7E13E63681EEB683E067061BF7D52DCF08E615") + combineSigAggNonce3 = mustParsePubNonce("026491FBCFD47148043A0F7310E6" + + "2EF898C10F2D0376EE6B232EAAD36F3C2E29E303020CB17D168908E2904D" + + "E2EB571CD232CA805A6981D0F86CDBBD2F12BD91F6D0") + + psig0 = mustParseHex("E5C1CBD6E7E89FE9EE30D5F3B6D06B9C218846E4A1DEF4E" + + "E851410D51ABBD850") + psig1 = mustParseHex("9BC470F7F1C9BC848BDF179B0023282FFEF40908E0EF884" + + "59784A4355FC86D0C") + psig2 = mustParseHex("D5D8A09929BA264B2F5DF15ACA1CF2DEFA47C048DF0C323" + + "2E965FFE2F2831B1D") + psig3 = mustParseHex("A915197503C1051EA77DC91F01C3A0E60BFD64473BD536C" + + "B613F9645BD61C843") + psig4 = mustParseHex("99A144D7076A128022134E036B8BDF33811F7EAED9A1E48" + + "549B46D8A63D64DC9") + psig5 = mustParseHex("716A72A0C1E531EBB4555C8E29FD35C796F4F231C3B0391" + + "93D7E8D7AEFBDF5F7") + psig6 = mustParseHex("06B6DD04BC0F1EF740916730AD7DAC794255B1612217197" + + "65BDE9686A26633DC") + psig7 = mustParseHex("BF6D85D4930062726EBC6EBB184AFD68DBB3FED159C5019" + + "89690A62600D6FBAB") + + combineSigExpected0 = mustParseHex("4006D4D069F3B51E968762FF8074153E2" + + "78E5BCD221AABE0743CA001B77E79F581863CCED9B25C6E7A0FED8EB6F39" + + "3CD65CD7306D385DCF85CC6567DAA4E041B") + combineSigExpected1 = mustParseHex("98BCD40DFD94B47A3DA37D7B78EB6CCE8" + + "ABEACA23C3ADE6F4678902410EB35C67EEDBA0E2D7B2B69D6DBBA79CBE09" + + "3C64B9647A96B98C8C28AD3379BDFAEA21F") + combineSigExpected2 = mustParseHex("3741FEDCCDD7508B58DCB9A780FF5D974" + + "52EC8C0448D8C97004EA7175C14F2007A54D1DE356EBA6719278436EF111" + + "DFA8F1B832368371B9B7A25001709039679") + combineSigExpected3 = mustParseHex("F4B3DA3CF0D0F7CF5C1840593BF1A1A41" + + "5DA341619AE848F2210696DC8C7512540962C84EF7F0CEC491065F2D5772" + + "13CF10E8A63D153297361B3B172BE27B61F") + + combineSigTweak0 = mustParseHex32("B511DA492182A91B0FFB9A98020D55F260" + + "AE86D7ECBD0399C7383D59A5F2AF7C") + combineSigTweak1 = mustParseHex32("A815FE049EE3C5AAB66310477FBC8BCCCA" + + "C2F3395F59F921C364ACD78A2F48DC") + combineSigTweak2 = mustParseHex32("75448A87274B056468B977BE06EB1E9F65" + + "7577B7320B0A3376EA51FD420D18A8") + tweak0False = KeyTweakDesc{ + Tweak: combineSigTweak0, + IsXOnly: false, + } + tweak0True = KeyTweakDesc{ + Tweak: combineSigTweak0, + IsXOnly: true, + } + tweak1False = KeyTweakDesc{ + Tweak: combineSigTweak1, + IsXOnly: false, + } + tweak2True = KeyTweakDesc{ + Tweak: combineSigTweak2, + IsXOnly: true, + } + combineSigsMsg = mustParseHex32("599C67EA410D005B9DA90817CF03ED3B1C86" + + "8E4DA4EDF00A5880B0082C237869") +) + +func TestMusig2CombineSigsTestVectors(t *testing.T) { + + testCases := []struct { + partialSigs [][]byte + aggNonce [66]byte + keyOrder []int + expected []byte + tweaks []KeyTweakDesc + expectedError error + }{ + // Vector 1 + { + partialSigs: [][]byte{psig0, psig1}, + aggNonce: combineSigAggNonce0, + keyOrder: []int{0, 1}, + expected: combineSigExpected0, + }, + // Vector 2 + { + partialSigs: [][]byte{psig2, psig3}, + aggNonce: combineSigAggNonce1, + keyOrder: []int{0, 2}, + expected: combineSigExpected1, + }, + // Vector 3 + { + partialSigs: [][]byte{psig4, psig5}, + aggNonce: combineSigAggNonce2, + keyOrder: []int{0, 2}, + expected: combineSigExpected2, + tweaks: []KeyTweakDesc{tweak0False}, + }, + // Vector 4 + { + partialSigs: [][]byte{psig6, psig7}, + aggNonce: combineSigAggNonce3, + keyOrder: []int{0, 3}, + expected: combineSigExpected3, + tweaks: []KeyTweakDesc{ + tweak0True, + tweak1False, + tweak2True, + }, + }, + // Vector 5: Partial signature is invalid because it exceeds group size + { + partialSigs: [][]byte{ + psig7, + mustParseHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "EBAAEDCE6AF48A03BBFD25E8CD0364141"), + }, + aggNonce: combineSigAggNonce3, + expectedError: ErrPartialSigInvalid, + }, + } + + for _, testCase := range testCases { + var pSigs []*PartialSignature + for _, partialSig := range testCase.partialSigs { + pSig := &PartialSignature{} + err := pSig.Decode(bytes.NewReader(partialSig)) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatal(err) + } + + pSigs = append(pSigs, pSig) + } + + keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder)) + for _, keyIndex := range testCase.keyOrder { + keyBytes := combineSigKeys[keyIndex] + pub, err := schnorr.ParsePubKey(keyBytes) + if err != nil { + t.Fatalf("unable to parse pubkeys: %v", err) + } + keySet = append(keySet, pub) + } + + uniqueKeyIndex := secondUniqueKeyIndex(keySet, false) + aggOpts := []KeyAggOption{ + WithUniqueKeyIndex(uniqueKeyIndex), + } + if len(testCase.tweaks) > 0 { + aggOpts = append(aggOpts, WithKeyTweaks(testCase.tweaks...)) + } + + combinedKey, _, _, err := AggregateKeys( + keySet, false, aggOpts..., + ) + if err != nil { + t.Fatal(err) + } + + aggPubkey, err := aggNonceToPubkey( + testCase.aggNonce, combinedKey, combineSigsMsg, + ) + if err != nil { + t.Fatal(err) + } + + var opts []CombineOption + if len(testCase.tweaks) > 0 { + opts = append(opts, WithTweakedCombine( + combineSigsMsg, keySet, testCase.tweaks, false, + )) + } + + sig := CombineSigs(aggPubkey, pSigs, opts...) + expectedSig, err := schnorr.ParseSignature(testCase.expected) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(sig.Serialize(), expectedSig.Serialize()) { + t.Fatalf("sigs not expected %x \n got %x", expectedSig.Serialize(), sig.Serialize()) + } + + if !sig.Verify(combineSigsMsg[:], combinedKey.FinalKey) { + t.Fatal("sig not valid for m") + } + } +} + +// aggNonceToPubkey gets a nonce as a public key for the TestMusig2CombineSigsTestVectors +// test. +// TODO(sputn1ck): build into intermediate routine. +func aggNonceToPubkey(combinedNonce [66]byte, combinedKey *AggregateKey, + msg [32]byte) (*btcec.PublicKey, error) { + + // b = int_from_bytes(tagged_hash('MuSig/noncecoef', aggnonce + bytes_from_point(Q) + msg)) % n + 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[:]) + + r1, err := btcec.ParsePubKey( + combinedNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return nil, err + } + + r2, err := btcec.ParsePubKey( + combinedNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return nil, err + } + + var nonce, r1J, r2J btcec.JacobianPoint + r1.AsJacobian(&r1J) + r2.AsJacobian(&r2J) + + // With our nonce blinding value, we'll now combine both the public + // nonces, using the blinding factor to tweak the second nonce: + // * R = R_1 + b*R_2 + btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J) + btcec.AddNonConst(&r1J, &r2J, &nonce) + + nonce.ToAffine() + + return btcec.NewPublicKey( + &nonce.X, &nonce.Y, + ), nil + +} + +func mustNonceAgg(nonces [][66]byte) [66]byte { + aggNonce, err := AggregateNonces(nonces) + if err != nil { + panic("can't aggregate nonces") + } + return aggNonce +} + +func memsetLoop(a []byte, v uint8) { + for i := range a { + a[i] = byte(v) + } +} + +func to32ByteSlice(input []byte) [32]byte { + if len(input) != 32 { + panic("input byte slice has invalid length") + } + var output [32]byte + copy(output[:], input) + return output +} + +func toPubNonceSlice(input []byte) [PubNonceSize]byte { + var output [PubNonceSize]byte + copy(output[:], input) + + return output +} + +func getGBytes() []byte { + return btcec.Generator().SerializeCompressed() +} + +func getNegGBytes() []byte { + pk := getGBytes() + pk[0] = 0x3 + + return pk +} + +func getInfinityBytes() []byte { + return make([]byte, 33) +} + +func mustParseHex32(str string) [32]byte { + b, err := hex.DecodeString(str) + if err != nil { + panic(fmt.Errorf("unable to parse hex: %v", err)) + } + if len(b) != 32 { + panic(fmt.Errorf("not a 32 byte slice: %v", err)) + } + + return to32ByteSlice(b) +} + +func mustParsePubNonce(str string) [PubNonceSize]byte { + b, err := hex.DecodeString(str) + if err != nil { + panic(fmt.Errorf("unable to parse hex: %v", err)) + } + if len(b) != PubNonceSize { + panic(fmt.Errorf("not a public nonce: %v", err)) + } + return toPubNonceSlice(b) +} + +func canParsePubNonce(str string) [PubNonceSize]byte { + b, err := hex.DecodeString(str) + if err != nil { + panic(fmt.Errorf("unable to parse hex: %v", err)) + } + return toPubNonceSlice(b) +} diff --git a/internal/musig2/nonces.go b/internal/musig2/nonces.go new file mode 100644 index 000000000..c989d7068 --- /dev/null +++ b/internal/musig2/nonces.go @@ -0,0 +1,389 @@ +// Copyright 2013-2022 The btcsuite developers + +package musig2 + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +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 private keys to the public nonce points. + SecNonceSize = 64 +) + +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 +) + +// 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 private + // 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.PrivKeyBytesLen]) + k2Mod.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:]) + + 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 + + // 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 + } +} + +// 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.PrivateKey) NonceGenOption { + return func(o *nonceGenOpts) { + o.secretKey = secKey.Serialize() + } +} + +// 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.PrivateKey. 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.msg = customOpts.msg + o.auxInput = customOpts.auxInput + } +} + +// lengthWriter is a function closure that allows a caller to control how the +// length prefix of a byte slice is written. +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))) +} + +// 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); err != nil { + return err + } + + if _, err := w.Write(b); err != nil { + 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(aggpk) || aggpk || len(m) +// || m || len(in) || in || i). +// +// where i is the ith secret nonce being generated. +func genNonceAuxBytes(rand []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); 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 + } + + // Next, we'll write out the length prefixed message. + err = writeBytesPrefix(&w, opts.msg, uint8Writer) + 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)); err != nil { + 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 private key. +func GenNonces(options ...NonceGenOption) (*Nonces, error) { + opts := defaultNonceGenOpts() + for _, opt := range options { + opt(opts) + } + + // First, we'll start out by generating 32 random bytes drawn from our + // CSPRNG. + var randBytes [32]byte + if _, err := opts.randReader.Read(randBytes[:]); err != nil { + 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 and the set of optional params, generate our + // two secret nonces: k1 and k2. + k1, err := genNonceAuxBytes(randBytes[:], 0, opts) + if err != nil { + return nil, err + } + k2, err := genNonceAuxBytes(randBytes[:], 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. + var nonces Nonces + k1Mod.PutBytesUnchecked(nonces.SecNonce[:]) + k2Mod.PutBytesUnchecked(nonces.SecNonce[btcec.PrivKeyBytesLen:]) + + // 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 +} diff --git a/internal/musig2/sign.go b/internal/musig2/sign.go new file mode 100644 index 000000000..b100dabf2 --- /dev/null +++ b/internal/musig2/sign.go @@ -0,0 +1,703 @@ +// Copyright 2013-2022 The btcsuite developers + +package musig2 + +import ( + "bytes" + "fmt" + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var ( + // NonceBlindTag is that tag used to construct the value b, which + // blinds the second public nonce of each party. + NonceBlindTag = []byte("MuSig/noncecoef") + + // ChallengeHashTag is the tag used to construct the challenge hash + ChallengeHashTag = []byte("BIP0340/challenge") + + // ErrNoncePointAtInfinity is returned if during signing, the fully + // combined public nonce is the point at infinity. + ErrNoncePointAtInfinity = fmt.Errorf("signing nonce is the infinity " + + "point") + + // ErrPrivKeyZero is returned when the private key for signing is + // actually zero. + ErrPrivKeyZero = fmt.Errorf("priv key is zero") + + // ErrPartialSigInvalid is returned when a partial is found to be + // invalid. + ErrPartialSigInvalid = fmt.Errorf("partial signature is invalid") + + // ErrSecretNonceZero is returned when a secret nonce is passed in a + // zero. + ErrSecretNonceZero = fmt.Errorf("secret nonce is blank") +) + +// infinityPoint is the jacobian representation of the point at infinity. +var infinityPoint btcec.JacobianPoint + +// PartialSignature reprints a partial (s-only) musig2 multi-signature. This +// isn't a valid schnorr signature by itself, as it needs to be aggregated +// along with the other partial signatures to be completed. +type PartialSignature struct { + S *btcec.ModNScalar + + R *btcec.PublicKey +} + +// NewPartialSignature returns a new instances of the partial sig struct. +func NewPartialSignature(s *btcec.ModNScalar, + r *btcec.PublicKey) PartialSignature { + + return PartialSignature{ + S: s, + R: r, + } +} + +// Encode writes a serialized version of the partial signature to the passed +// io.Writer +func (p *PartialSignature) Encode(w io.Writer) error { + var sBytes [32]byte + p.S.PutBytes(&sBytes) + + if _, err := w.Write(sBytes[:]); err != nil { + return err + } + + return nil +} + +// Decode attempts to parse a serialized PartialSignature stored in the passed +// io reader. +func (p *PartialSignature) Decode(r io.Reader) error { + p.S = new(btcec.ModNScalar) + + var sBytes [32]byte + if _, err := io.ReadFull(r, sBytes[:]); err != nil { + return nil + } + + overflows := p.S.SetBytes(&sBytes) + if overflows == 1 { + return ErrPartialSigInvalid + } + + return nil +} + +// SignOption is a functional option argument that allows callers to modify the +// way we generate musig2 schnorr signatures. +type SignOption func(*signOptions) + +// signOptions houses the set of functional options that can be used to modify +// the method used to generate the musig2 partial signature. +type signOptions struct { + // fastSign determines if we'll skip the check at the end of the + // routine where we attempt to verify the produced signature. + fastSign bool + + // sortKeys determines if the set of keys should be sorted before doing + // key aggregation. + sortKeys bool + + // tweaks specifies a series of tweaks to be applied to the aggregated + // public key, which also partially carries over into the signing + // process. + tweaks []KeyTweakDesc + + // taprootTweak specifies a taproot specific tweak. of the tweaks + // specified above. Normally we'd just apply the raw 32 byte tweak, but + // for taproot, we first need to compute the aggregated key before + // tweaking, and then use it as the internal key. This is required as + // the taproot tweak also commits to the public key, which in this case + // is the aggregated key before the tweak. + taprootTweak []byte + + // bip86Tweak specifies that the taproot tweak should be done in a BIP + // 86 style, where we don't expect an actual tweak and instead just + // commit to the public key itself. + bip86Tweak bool +} + +// defaultSignOptions returns the default set of signing operations. +func defaultSignOptions() *signOptions { + return &signOptions{} +} + +// WithFastSign forces signing to skip the extra verification step at the end. +// Performance sensitive applications may opt to use this option to speed up +// the signing operation. +func WithFastSign() SignOption { + return func(o *signOptions) { + o.fastSign = true + } +} + +// WithSortedKeys determines if the set of signing public keys are to be sorted +// or not before doing key aggregation. +func WithSortedKeys() SignOption { + return func(o *signOptions) { + o.sortKeys = true + } +} + +// WithTweaks determines if the aggregated public key used should apply a +// series of tweaks before key aggregation. +func WithTweaks(tweaks ...KeyTweakDesc) SignOption { + return func(o *signOptions) { + o.tweaks = tweaks + } +} + +// WithTaprootSignTweak allows a caller to specify a tweak that should be used +// in a bip 340 manner when signing. This differs from WithTweaks as the tweak +// will be assumed to always be x-only and the intermediate aggregate key +// before tweaking will be used to generate part of the tweak (as the taproot +// tweak also commits to the internal key). +// +// This option should be used in the taproot context to create a valid +// signature for the keypath spend for taproot, when the output key is actually +// committing to a script path, or some other data. +func WithTaprootSignTweak(scriptRoot []byte) SignOption { + return func(o *signOptions) { + o.taprootTweak = scriptRoot + } +} + +// WithBip86SignTweak allows a caller to specify a tweak that should be used in +// a bip 340 manner when signing, factoring in BIP 86 as well. This differs +// from WithTaprootSignTweak as no true script root will be committed to, +// instead we just commit to the internal key. +// +// This option should be used in the taproot context to create a valid +// signature for the keypath spend for taproot, when the output key was +// generated using BIP 86. +func WithBip86SignTweak() SignOption { + return func(o *signOptions) { + o.bip86Tweak = true + } +} + +// Sign generates a musig2 partial signature given the passed key set, secret +// nonce, public nonce, and private keys. This method returns an error if the +// generated nonces are either too large, or end up mapping to the point at +// infinity. +func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey, + combinedNonce [PubNonceSize]byte, pubKeys []*btcec.PublicKey, + msg [32]byte, signOpts ...SignOption) (*PartialSignature, error) { + + // First, parse the set of optional signing options. + opts := defaultSignOptions() + for _, option := range signOpts { + option(opts) + } + + // Compute the hash of all the keys here as we'll need it do aggregate + // the keys and also at the final step of signing. + keysHash := keyHashFingerprint(pubKeys, opts.sortKeys) + uniqueKeyIndex := secondUniqueKeyIndex(pubKeys, opts.sortKeys) + + keyAggOpts := []KeyAggOption{ + WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex), + } + switch { + case opts.bip86Tweak: + keyAggOpts = append( + keyAggOpts, WithBIP86KeyTweak(), + ) + case opts.taprootTweak != nil: + keyAggOpts = append( + keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak), + ) + case len(opts.tweaks) != 0: + keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...)) + } + + // Next we'll construct the aggregated public key based on the set of + // signers. + combinedKey, parityAcc, _, err := AggregateKeys( + pubKeys, opts.sortKeys, keyAggOpts..., + ) + if err != nil { + return nil, err + } + + // Next we'll compute the value b, that blinds our second public + // nonce: + // * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m). + var ( + nonceMsgBuf bytes.Buffer + nonceBlinder btcec.ModNScalar + ) + nonceMsgBuf.Write(combinedNonce[:]) + nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey)) + nonceMsgBuf.Write(msg[:]) + nonceBlindHash := chainhash.TaggedHash( + NonceBlindTag, nonceMsgBuf.Bytes(), + ) + nonceBlinder.SetByteSlice(nonceBlindHash[:]) + + // Next, we'll parse the public nonces into R1 and R2. + r1J, err := btcec.ParseJacobian( + combinedNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return nil, err + } + r2J, err := btcec.ParseJacobian( + combinedNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return nil, err + } + + // With our nonce blinding value, we'll now combine both the public + // nonces, using the blinding factor to tweak the second nonce: + // * R = R_1 + b*R_2 + var nonce btcec.JacobianPoint + btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J) + btcec.AddNonConst(&r1J, &r2J, &nonce) + + // If the combined nonce it eh point at infinity, then we'll bail out. + if nonce == infinityPoint { + G := btcec.Generator() + G.AsJacobian(&nonce) + } + + // Next we'll parse out our two secret nonces, which we'll be using in + // the core signing process below. + var k1, k2 btcec.ModNScalar + k1.SetByteSlice(secNonce[:btcec.PrivKeyBytesLen]) + k2.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:]) + + if k1.IsZero() || k2.IsZero() { + return nil, ErrSecretNonceZero + } + + nonce.ToAffine() + + nonceKey := btcec.NewPublicKey(&nonce.X, &nonce.Y) + + // If the nonce R has an odd y coordinate, then we'll negate both our + // secret nonces. + if nonce.Y.IsOdd() { + k1.Negate() + k2.Negate() + } + + privKeyScalar := privKey.Key + if privKeyScalar.IsZero() { + return nil, ErrPrivKeyZero + } + + pubKey := privKey.PubKey() + pubKeyYIsOdd := func() bool { + pubKeyBytes := pubKey.SerializeCompressed() + return pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd + }() + combinedKeyYIsOdd := func() bool { + combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed() + return combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd + }() + + // Next we'll compute our two parity factors for Q the combined public + // key, and P, the public key we're signing with. If the keys are odd, + // then we'll negate them. + parityCombinedKey := new(btcec.ModNScalar).SetInt(1) + paritySignKey := new(btcec.ModNScalar).SetInt(1) + if combinedKeyYIsOdd { + parityCombinedKey.Negate() + } + if pubKeyYIsOdd { + paritySignKey.Negate() + } + + // Before we sign below, we'll multiply by our various parity factors + // to ensure that the signing key is properly negated (if necessary): + // * d = gv⋅gaccv⋅gp⋅d' + privKeyScalar.Mul(parityCombinedKey).Mul(paritySignKey).Mul(parityAcc) + + // Next we'll create the challenge hash that commits to the combined + // nonce, combined public key and also the message: + // * e = H(tag=ChallengeHashTag, R || Q || m) mod n + var challengeMsg bytes.Buffer + challengeMsg.Write(schnorr.SerializePubKey(nonceKey)) + challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey)) + challengeMsg.Write(msg[:]) + challengeBytes := chainhash.TaggedHash( + ChallengeHashTag, challengeMsg.Bytes(), + ) + var e btcec.ModNScalar + e.SetByteSlice(challengeBytes[:]) + + // Next, we'll compute a, our aggregation coefficient for the key that + // we're signing with. + a := aggregationCoefficient(pubKeys, pubKey, keysHash, uniqueKeyIndex) + + // With mu constructed, we can finally generate our partial signature + // as: s = (k1_1 + b*k_2 + e*a*d) mod n. + s := new(btcec.ModNScalar) + s.Add(&k1).Add(k2.Mul(&nonceBlinder)).Add(e.Mul(a).Mul(&privKeyScalar)) + + sig := NewPartialSignature(s, nonceKey) + + // If we're not in fast sign mode, then we'll also validate our partial + // signature. + if !opts.fastSign { + pubNonce := secNonceToPubNonce(secNonce) + sigValid := sig.Verify( + pubNonce, combinedNonce, pubKeys, pubKey, msg, + signOpts..., + ) + if !sigValid { + return nil, fmt.Errorf("sig is invalid!") + } + } + + return &sig, nil +} + +// Verify implements partial signature verification given the public nonce for +// the signer, aggregate nonce, signer set and finally the message being +// signed. +func (p *PartialSignature) Verify(pubNonce [PubNonceSize]byte, + combinedNonce [PubNonceSize]byte, keySet []*btcec.PublicKey, + signingKey *btcec.PublicKey, msg [32]byte, signOpts ...SignOption) bool { + + pubKey := schnorr.SerializePubKey(signingKey) + + return verifyPartialSig( + p, pubNonce, combinedNonce, keySet, pubKey, msg, signOpts..., + ) == nil +} + +// verifyPartialSig attempts to verify a partial schnorr signature given the +// necessary parameters. This is the internal version of Verify that returns +// detailed errors. signed. +func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte, + combinedNonce [PubNonceSize]byte, keySet []*btcec.PublicKey, + pubKey []byte, msg [32]byte, signOpts ...SignOption) error { + + opts := defaultSignOptions() + for _, option := range signOpts { + option(opts) + } + + // First we'll map the internal partial signature back into something + // we can manipulate. + s := partialSig.S + + // Next we'll parse out the two public nonces into something we can + // use. + // + + // Compute the hash of all the keys here as we'll need it do aggregate + // the keys and also at the final step of verification. + keysHash := keyHashFingerprint(keySet, opts.sortKeys) + uniqueKeyIndex := secondUniqueKeyIndex(keySet, opts.sortKeys) + + keyAggOpts := []KeyAggOption{ + WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex), + } + switch { + case opts.bip86Tweak: + keyAggOpts = append( + keyAggOpts, WithBIP86KeyTweak(), + ) + case opts.taprootTweak != nil: + keyAggOpts = append( + keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak), + ) + case len(opts.tweaks) != 0: + keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...)) + } + + // Next we'll construct the aggregated public key based on the set of + // signers. + combinedKey, parityAcc, _, err := AggregateKeys( + keySet, opts.sortKeys, keyAggOpts..., + ) + if err != nil { + return err + } + + // Next we'll compute the value b, that blinds our second public + // nonce: + // * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m). + var ( + nonceMsgBuf bytes.Buffer + nonceBlinder btcec.ModNScalar + ) + nonceMsgBuf.Write(combinedNonce[:]) + nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey)) + nonceMsgBuf.Write(msg[:]) + nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes()) + nonceBlinder.SetByteSlice(nonceBlindHash[:]) + + r1J, err := btcec.ParseJacobian( + combinedNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return err + } + r2J, err := btcec.ParseJacobian( + combinedNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return err + } + + // With our nonce blinding value, we'll now combine both the public + // nonces, using the blinding factor to tweak the second nonce: + // * R = R_1 + b*R_2 + + var nonce btcec.JacobianPoint + btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J) + btcec.AddNonConst(&r1J, &r2J, &nonce) + + // Next, we'll parse out the set of public nonces this signer used to + // generate the signature. + pubNonce1J, err := btcec.ParseJacobian( + pubNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return err + } + pubNonce2J, err := btcec.ParseJacobian( + pubNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return err + } + + // If the nonce is the infinity point we set it to the Generator. + if nonce == infinityPoint { + btcec.GeneratorJacobian(&nonce) + } else { + nonce.ToAffine() + } + + // We'll perform a similar aggregation and blinding operator as we did + // above for the combined nonces: R' = R_1' + b*R_2'. + var pubNonceJ btcec.JacobianPoint + + btcec.ScalarMultNonConst(&nonceBlinder, &pubNonce2J, &pubNonce2J) + btcec.AddNonConst(&pubNonce1J, &pubNonce2J, &pubNonceJ) + + pubNonceJ.ToAffine() + + // If the combined nonce used in the challenge hash has an odd y + // coordinate, then we'll negate our final public nonce. + if nonce.Y.IsOdd() { + pubNonceJ.Y.Negate(1) + pubNonceJ.Y.Normalize() + } + + // Next we'll create the challenge hash that commits to the combined + // nonce, combined public key and also the message: + // * e = H(tag=ChallengeHashTag, R || Q || m) mod n + var challengeMsg bytes.Buffer + challengeMsg.Write(schnorr.SerializePubKey(btcec.NewPublicKey( + &nonce.X, &nonce.Y, + ))) + challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey)) + challengeMsg.Write(msg[:]) + challengeBytes := chainhash.TaggedHash( + ChallengeHashTag, challengeMsg.Bytes(), + ) + var e btcec.ModNScalar + e.SetByteSlice(challengeBytes[:]) + + signingKey, err := schnorr.ParsePubKey(pubKey) + if err != nil { + return err + } + + // Next, we'll compute a, our aggregation coefficient for the key that + // we're signing with. + a := aggregationCoefficient(keySet, signingKey, keysHash, uniqueKeyIndex) + + // If the combined key has an odd y coordinate, then we'll negate + // parity factor for the signing key. + paritySignKey := new(btcec.ModNScalar).SetInt(1) + combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed() + if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd { + paritySignKey.Negate() + } + + // Next, we'll construct the final parity factor by multiplying the + // sign key parity factor with the accumulated parity factor for all + // the keys. + finalParityFactor := paritySignKey.Mul(parityAcc) + + // Now we'll multiply the parity factor by our signing key, which'll + // take care of the amount of negation needed. + var signKeyJ btcec.JacobianPoint + signingKey.AsJacobian(&signKeyJ) + btcec.ScalarMultNonConst(finalParityFactor, &signKeyJ, &signKeyJ) + + // In the final set, we'll check that: s*G == R' + e*a*P. + var sG, rP btcec.JacobianPoint + btcec.ScalarBaseMultNonConst(s, &sG) + btcec.ScalarMultNonConst(e.Mul(a), &signKeyJ, &rP) + btcec.AddNonConst(&rP, &pubNonceJ, &rP) + + sG.ToAffine() + rP.ToAffine() + + if sG != rP { + return ErrPartialSigInvalid + } + + return nil +} + +// CombineOption is a functional option argument that allows callers to modify the +// way we combine musig2 schnorr signatures. +type CombineOption func(*combineOptions) + +// combineOptions houses the set of functional options that can be used to +// modify the method used to combine the musig2 partial signatures. +type combineOptions struct { + msg [32]byte + + combinedKey *btcec.PublicKey + + tweakAcc *btcec.ModNScalar +} + +// defaultCombineOptions returns the default set of signing operations. +func defaultCombineOptions() *combineOptions { + return &combineOptions{} +} + +// WithTweakedCombine is a functional option that allows callers to specify +// that the signature was produced using a tweaked aggregated public key. In +// order to properly aggregate the partial signatures, the caller must specify +// enough information to reconstruct the challenge, and also the final +// accumulated tweak value. +func WithTweakedCombine(msg [32]byte, keys []*btcec.PublicKey, + tweaks []KeyTweakDesc, sort bool) CombineOption { + + return func(o *combineOptions) { + combinedKey, _, tweakAcc, _ := AggregateKeys( + keys, sort, WithKeyTweaks(tweaks...), + ) + + o.msg = msg + o.combinedKey = combinedKey.FinalKey + o.tweakAcc = tweakAcc + } +} + +// WithTaprootTweakedCombine is similar to the WithTweakedCombine option, but +// assumes a BIP 341 context where the final tweaked key is to be used as the +// output key, where the internal key is the aggregated key pre-tweak. +// +// This option should be used over WithTweakedCombine when attempting to +// aggregate signatures for a top-level taproot keyspend, where the output key +// commits to a script root. +func WithTaprootTweakedCombine(msg [32]byte, keys []*btcec.PublicKey, + scriptRoot []byte, sort bool) CombineOption { + + return func(o *combineOptions) { + combinedKey, _, tweakAcc, _ := AggregateKeys( + keys, sort, WithTaprootKeyTweak(scriptRoot), + ) + + o.msg = msg + o.combinedKey = combinedKey.FinalKey + o.tweakAcc = tweakAcc + } +} + +// WithBip86TweakedCombine is similar to the WithTaprootTweakedCombine option, +// but assumes a BIP 341 + BIP 86 context where the final tweaked key is to be +// used as the output key, where the internal key is the aggregated key +// pre-tweak. +// +// This option should be used over WithTaprootTweakedCombine when attempting to +// aggregate signatures for a top-level taproot keyspend, where the output key +// was generated using BIP 86. +func WithBip86TweakedCombine(msg [32]byte, keys []*btcec.PublicKey, + sort bool) CombineOption { + + return func(o *combineOptions) { + combinedKey, _, tweakAcc, _ := AggregateKeys( + keys, sort, WithBIP86KeyTweak(), + ) + + o.msg = msg + o.combinedKey = combinedKey.FinalKey + o.tweakAcc = tweakAcc + } +} + +// CombineSigs combines the set of public keys given the final aggregated +// nonce, and the series of partial signatures for each nonce. +func CombineSigs(combinedNonce *btcec.PublicKey, + partialSigs []*PartialSignature, + combineOpts ...CombineOption) *schnorr.Signature { + + // First, parse the set of optional combine options. + opts := defaultCombineOptions() + for _, option := range combineOpts { + option(opts) + } + + // If signer keys and tweaks are specified, then we need to carry out + // some intermediate steps before we can combine the signature. + var tweakProduct *btcec.ModNScalar + if opts.combinedKey != nil && opts.tweakAcc != nil { + // Next, we'll construct the parity factor of the combined key, + // negating it if the combined key has an even y coordinate. + parityFactor := new(btcec.ModNScalar).SetInt(1) + combinedKeyBytes := opts.combinedKey.SerializeCompressed() + if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd { + parityFactor.Negate() + } + + // Next we'll reconstruct e the challenge has based on the + // nonce and combined public key. + // * e = H(tag=ChallengeHashTag, R || Q || m) mod n + var challengeMsg bytes.Buffer + challengeMsg.Write(schnorr.SerializePubKey(combinedNonce)) + challengeMsg.Write(schnorr.SerializePubKey(opts.combinedKey)) + challengeMsg.Write(opts.msg[:]) + challengeBytes := chainhash.TaggedHash( + ChallengeHashTag, challengeMsg.Bytes(), + ) + var e btcec.ModNScalar + e.SetByteSlice(challengeBytes[:]) + + tweakProduct = new(btcec.ModNScalar).Set(&e) + tweakProduct.Mul(opts.tweakAcc).Mul(parityFactor) + } + + // Finally, the tweak factor also needs to be re-computed as well. + var combinedSig btcec.ModNScalar + for _, partialSig := range partialSigs { + combinedSig.Add(partialSig.S) + } + + // If the tweak product was set above, then we'll need to add the value + // at the very end in order to produce a valid signature under the + // final tweaked key. + if tweakProduct != nil { + combinedSig.Add(tweakProduct) + } + + // TODO(roasbeef): less verbose way to get the x coord... + var nonceJ btcec.JacobianPoint + combinedNonce.AsJacobian(&nonceJ) + nonceJ.ToAffine() + + return schnorr.NewSignature(&nonceJ.X, &combinedSig) +}