mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
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:
parent
ff8f1371b5
commit
3012f5e17d
@ -2,11 +2,13 @@ run:
|
||||
# timeout for analysis
|
||||
deadline: 10m
|
||||
|
||||
# Skip autogenerated files for mobile and gRPC.
|
||||
# Skip autogenerated files for mobile and gRPC as well as copied code for
|
||||
# internal use.
|
||||
skip-files:
|
||||
- "mobile\\/.*generated\\.go"
|
||||
- "\\.pb\\.go$"
|
||||
- "\\.pb\\.gw\\.go$"
|
||||
- "internal\\/musig2v040"
|
||||
|
||||
skip-dirs:
|
||||
- channeldb/migration_01_to_11
|
||||
|
2
go.mod
2
go.mod
@ -16,6 +16,7 @@ require (
|
||||
github.com/btcsuite/btcwallet/wtxmgr v1.5.0
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
@ -76,7 +77,6 @@ require (
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/decred/dcrd/lru v1.0.0 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
|
313
internal/musig2/bench_test.go
Normal file
313
internal/musig2/bench_test.go
Normal file
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
670
internal/musig2/context.go
Normal file
670
internal/musig2/context.go
Normal file
@ -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
|
||||
}
|
459
internal/musig2/keys.go
Normal file
459
internal/musig2/keys.go
Normal file
@ -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
|
||||
}
|
1897
internal/musig2/musig2_test.go
Normal file
1897
internal/musig2/musig2_test.go
Normal file
File diff suppressed because it is too large
Load Diff
389
internal/musig2/nonces.go
Normal file
389
internal/musig2/nonces.go
Normal file
@ -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
|
||||
}
|
703
internal/musig2/sign.go
Normal file
703
internal/musig2/sign.go
Normal file
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user