mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-11 09:44:59 +01:00
In this commit, we optimize signing+verification mainly by only computing values once, and reducing allocations when possible. The following optimizations have been implemented: * Use a single buffer allocation in keyHashFingerprint to avoid dynamic buffer growth+re-sizing * Remove the isSecondKey computation and replace that with a single routine that computes the index of the second unique key. * Optimize keyHashFingerprint usage by only computing it once during signing +verification. A further optimization is possible: use the x coordinate of a key for comparisons instead of computing the full sexualision. We need to do the latter atm, as the X() method of the public key struct will allocate more memory as it allocate and sets the buffer in place. The final benchmarks of before and after this commit: benchmark old ns/op new ns/op delta BenchmarkPartialSign/num_signers=10/fast_sign=true/sort=true-8 1227374 1194047 -2.72% BenchmarkPartialSign/num_signers=10/fast_sign=true/sort=false-8 1217743 1191468 -2.16% BenchmarkPartialSign/num_signers=10/fast_sign=false/sort=true-8 2755544 2698827 -2.06% BenchmarkPartialSign/num_signers=10/fast_sign=false/sort=false-8 2754749 2694547 -2.19% BenchmarkPartialSign/num_signers=100/fast_sign=true/sort=true-8 12382654 10561204 -14.71% BenchmarkPartialSign/num_signers=100/fast_sign=true/sort=false-8 12260134 10315376 -15.86% BenchmarkPartialSign/num_signers=100/fast_sign=false/sort=true-8 24832061 22009935 -11.36% BenchmarkPartialSign/num_signers=100/fast_sign=false/sort=false-8 24650086 21022833 -14.71% BenchmarkPartialVerify/sort_keys=true/num_signers=10-8 1485787 1473377 -0.84% BenchmarkPartialVerify/sort_keys=false/num_signers=10-8 1447275 1465139 +1.23% BenchmarkPartialVerify/sort_keys=true/num_signers=100-8 12503482 10672618 -14.64% BenchmarkPartialVerify/sort_keys=false/num_signers=100-8 12388289 10581398 -14.59% BenchmarkCombineSigs/num_signers=10-8 0.00 0.00 +0.00% BenchmarkCombineSigs/num_signers=100-8 0.00 0.00 -1.95% BenchmarkAggregateNonces/num_signers=10-8 0.00 0.00 -0.76% BenchmarkAggregateNonces/num_signers=100-8 0.00 0.00 +1.13% BenchmarkAggregateKeys/num_signers=10/sort_keys=true-8 0.00 0.00 -0.09% BenchmarkAggregateKeys/num_signers=10/sort_keys=false-8 0.00 0.01 +559.94% BenchmarkAggregateKeys/num_signers=100/sort_keys=true-8 0.01 0.01 -11.30% BenchmarkAggregateKeys/num_signers=100/sort_keys=false-8 0.01 0.01 -11.66% benchmark old allocs new allocs delta BenchmarkPartialSign/num_signers=10/fast_sign=true/sort=true-8 458 269 -41.27% BenchmarkPartialSign/num_signers=10/fast_sign=true/sort=false-8 409 222 -45.72% BenchmarkPartialSign/num_signers=10/fast_sign=false/sort=true-8 892 524 -41.26% BenchmarkPartialSign/num_signers=10/fast_sign=false/sort=false-8 841 467 -44.47% BenchmarkPartialSign/num_signers=100/fast_sign=true/sort=true-8 14366 3089 -78.50% BenchmarkPartialSign/num_signers=100/fast_sign=true/sort=false-8 13143 1842 -85.98% BenchmarkPartialSign/num_signers=100/fast_sign=false/sort=true-8 27596 4964 -82.01% BenchmarkPartialSign/num_signers=100/fast_sign=false/sort=false-8 26309 3707 -85.91% BenchmarkPartialVerify/sort_keys=true/num_signers=10-8 430 243 -43.49% BenchmarkPartialVerify/sort_keys=false/num_signers=10-8 430 243 -43.49% BenchmarkPartialVerify/sort_keys=true/num_signers=100-8 13164 1863 -85.85% BenchmarkPartialVerify/sort_keys=false/num_signers=100-8 13164 1863 -85.85% BenchmarkCombineSigs/num_signers=10-8 0 0 +0.00% BenchmarkCombineSigs/num_signers=100-8 0 0 +0.00% BenchmarkAggregateNonces/num_signers=10-8 0 0 +0.00% BenchmarkAggregateNonces/num_signers=100-8 0 0 +0.00% BenchmarkAggregateKeys/num_signers=10/sort_keys=true-8 0 0 +0.00% BenchmarkAggregateKeys/num_signers=10/sort_keys=false-8 0 0 +0.00% BenchmarkAggregateKeys/num_signers=100/sort_keys=true-8 0 0 +0.00% BenchmarkAggregateKeys/num_signers=100/sort_keys=false-8 0 0 +0.00% benchmark old bytes new bytes delta BenchmarkPartialSign/num_signers=10/fast_sign=true/sort=true-8 27854 14878 -46.59% BenchmarkPartialSign/num_signers=10/fast_sign=true/sort=false-8 25508 12605 -50.58% BenchmarkPartialSign/num_signers=10/fast_sign=false/sort=true-8 54982 29476 -46.39% BenchmarkPartialSign/num_signers=10/fast_sign=false/sort=false-8 52581 26805 -49.02% BenchmarkPartialSign/num_signers=100/fast_sign=true/sort=true-8 1880138 166996 -91.12% BenchmarkPartialSign/num_signers=100/fast_sign=true/sort=false-8 1820561 106295 -94.16% BenchmarkPartialSign/num_signers=100/fast_sign=false/sort=true-8 3706291 275344 -92.57% BenchmarkPartialSign/num_signers=100/fast_sign=false/sort=false-8 3642725 214122 -94.12% BenchmarkPartialVerify/sort_keys=true/num_signers=10-8 26995 14078 -47.85% BenchmarkPartialVerify/sort_keys=false/num_signers=10-8 26980 14078 -47.82% BenchmarkPartialVerify/sort_keys=true/num_signers=100-8 1822043 107767 -94.09% BenchmarkPartialVerify/sort_keys=false/num_signers=100-8 1822046 107752 -94.09% BenchmarkCombineSigs/num_signers=10-8 0 0 +0.00% BenchmarkCombineSigs/num_signers=100-8 0 0 +0.00% BenchmarkAggregateNonces/num_signers=10-8 0 0 +0.00% BenchmarkAggregateNonces/num_signers=100-8 0 0 +0.00% BenchmarkAggregateKeys/num_signers=10/sort_keys=true-8 0 0 +0.00% BenchmarkAggregateKeys/num_signers=10/sort_keys=false-8 0 0 +0.00% BenchmarkAggregateKeys/num_signers=100/sort_keys=true-8 0 0 +0.00% BenchmarkAggregateKeys/num_signers=100/sort_keys=false-8 0 0 +0.00%
230 lines
7.1 KiB
Go
230 lines
7.1 KiB
Go
// Copyright 2013-2022 The btcsuite developers
|
|
|
|
package musig2
|
|
|
|
import (
|
|
"bytes"
|
|
"sort"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
)
|
|
|
|
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")
|
|
)
|
|
|
|
// 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) int {
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// defaultKeyAggOptions returns the set of default arguments for key
|
|
// aggregation.
|
|
func defaultKeyAggOptions() *keyAggOption {
|
|
return &keyAggOption{}
|
|
}
|
|
|
|
// 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.
|
|
func AggregateKeys(keys []*btcec.PublicKey, sort bool,
|
|
keyOpts ...KeyAggOption) *btcec.PublicKey {
|
|
|
|
// 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)
|
|
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)
|
|
}
|
|
|
|
finalKeyJ.ToAffine()
|
|
return btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y)
|
|
}
|