mod+internal: copy MuSig2 v0.4.0 code from btcd/btcec/v2
With this commit we copy the exact code of the MuSig2 code as found in github.com/btcsuite/btcec/v2/schnorr/musig2 at the tag btcec/v2.2.2. This corresponds to the MuSig2 BIP specification version of v0.4.0.
This commit is contained in:
8 files changed
+4435
-2
No files matched your search
+3
-1
@@ -2,11 +2,13 @@ run:
|
|||||||
# timeout for analysis
|
# timeout for analysis
|
||||||
deadline: 10m
|
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:
|
skip-files:
|
||||||
- "mobile\\/.*generated\\.go"
|
- "mobile\\/.*generated\\.go"
|
||||||
- "\\.pb\\.go$"
|
- "\\.pb\\.go$"
|
||||||
- "\\.pb\\.gw\\.go$"
|
- "\\.pb\\.gw\\.go$"
|
||||||
|
- "internal\\/musig2v040"
|
||||||
|
|
||||||
skip-dirs:
|
skip-dirs:
|
||||||
- channeldb/migration_01_to_11
|
- channeldb/migration_01_to_11
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ require (
|
|||||||
github.com/btcsuite/btcwallet/wtxmgr v1.5.0
|
github.com/btcsuite/btcwallet/wtxmgr v1.5.0
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
|
||||||
github.com/davecgh/go-spew v1.1.1
|
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/go-errors/errors v1.0.1
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/gorilla/websocket v1.4.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/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // 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/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/decred/dcrd/lru v1.0.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.1 // indirect
|
github.com/dsnet/compress v0.0.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
|
|||||||
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user