You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
414 lines
14 KiB
414 lines
14 KiB
// Copyright 2013-2022 The btcsuite developers |
|
|
|
package musig2 |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"sort" |
|
|
|
"next.orly.dev/pkg/utils" |
|
|
|
"next.orly.dev/pkg/crypto/ec" |
|
"next.orly.dev/pkg/crypto/ec/chainhash" |
|
"next.orly.dev/pkg/crypto/ec/schnorr" |
|
"next.orly.dev/pkg/crypto/ec/secp256k1" |
|
) |
|
|
|
var ( |
|
// KeyAggTagList is the tagged hash tag used to compute the hash of the |
|
// list of sorted public keys. |
|
KeyAggTagList = []byte("KeyAgg list") |
|
// KeyAggTagCoeff is the tagged hash tag used to compute the key |
|
// aggregation coefficient for each key. |
|
KeyAggTagCoeff = []byte("KeyAgg coefficient") |
|
// ErrTweakedKeyIsInfinity is returned if while tweaking a key, we end |
|
// up with the point at infinity. |
|
ErrTweakedKeyIsInfinity = fmt.Errorf("tweaked key is infinity point") |
|
// ErrTweakedKeyOverflows is returned if a tweaking key is larger than |
|
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141. |
|
ErrTweakedKeyOverflows = fmt.Errorf("tweaked key is too large") |
|
) |
|
|
|
// sortableKeys defines a type of slice of public keys that implements the sort |
|
// interface for BIP 340 keys. |
|
type sortableKeys []*btcec.PublicKey |
|
|
|
// Less reports whether the element with index i must sort before the element |
|
// with index j. |
|
func (s sortableKeys) Less(i, j int) bool { |
|
// TODO(roasbeef): more efficient way to compare... |
|
keyIBytes := s[i].SerializeCompressed() |
|
keyJBytes := s[j].SerializeCompressed() |
|
return bytes.Compare(keyIBytes, keyJBytes) == -1 |
|
} |
|
|
|
// Swap swaps the elements with indexes i and j. |
|
func (s sortableKeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
|
|
|
// Len is the number of elements in the collection. |
|
func (s sortableKeys) Len() int { return len(s) } |
|
|
|
// sortKeys takes a set of public keys and returns a new slice that is a copy |
|
// of the keys sorted in lexicographical order bytes on the x-only pubkey |
|
// serialization. |
|
func sortKeys(keys []*btcec.PublicKey) []*btcec.PublicKey { |
|
keySet := sortableKeys(keys) |
|
if sort.IsSorted(keySet) { |
|
return keys |
|
} |
|
sort.Sort(keySet) |
|
return keySet |
|
} |
|
|
|
// keyHashFingerprint computes the tagged hash of the series of (sorted) public |
|
// keys passed as input. This is used to compute the aggregation coefficient |
|
// for each key. The final computation is: |
|
// - H(tag=KeyAgg list, pk1 || pk2..) |
|
func keyHashFingerprint(keys []*btcec.PublicKey, sort bool) []byte { |
|
if sort { |
|
keys = sortKeys(keys) |
|
} |
|
// We'll create a single buffer and slice into that so the bytes buffer |
|
// doesn't continually need to grow the underlying buffer. |
|
keyAggBuf := make([]byte, 33*len(keys)) |
|
keyBytes := bytes.NewBuffer(keyAggBuf[0:0]) |
|
for _, key := range keys { |
|
keyBytes.Write(key.SerializeCompressed()) |
|
} |
|
h := chainhash.TaggedHash(KeyAggTagList, keyBytes.Bytes()) |
|
return h[:] |
|
} |
|
|
|
// keyBytesEqual returns true if two keys are the same based on the compressed |
|
// serialization of each key. |
|
func keyBytesEqual(a, b *btcec.PublicKey) bool { |
|
return utils.FastEqual(a.SerializeCompressed(), b.SerializeCompressed()) |
|
} |
|
|
|
// aggregationCoefficient computes the key aggregation coefficient for the |
|
// specified target key. The coefficient is computed as: |
|
// - H(tag=KeyAgg coefficient, keyHashFingerprint(pks) || pk) |
|
func aggregationCoefficient( |
|
keySet []*btcec.PublicKey, |
|
targetKey *btcec.PublicKey, keysHash []byte, |
|
secondKeyIdx int, |
|
) *btcec.ModNScalar { |
|
|
|
var mu btcec.ModNScalar |
|
// If this is the second key, then this coefficient is just one. |
|
if secondKeyIdx != -1 && keyBytesEqual(keySet[secondKeyIdx], targetKey) { |
|
return mu.SetInt(1) |
|
} |
|
// Otherwise, we'll compute the full finger print hash for this given |
|
// key and then use that to compute the coefficient tagged hash: |
|
// * H(tag=KeyAgg coefficient, keyHashFingerprint(pks, pk) || pk) |
|
var coefficientBytes [65]byte |
|
copy(coefficientBytes[:], keysHash[:]) |
|
copy(coefficientBytes[32:], targetKey.SerializeCompressed()) |
|
muHash := chainhash.TaggedHash(KeyAggTagCoeff, coefficientBytes[:]) |
|
mu.SetByteSlice(muHash[:]) |
|
return &mu |
|
} |
|
|
|
// secondUniqueKeyIndex returns the index of the second unique key. If all keys |
|
// are the same, then a value of -1 is returned. |
|
func secondUniqueKeyIndex(keySet []*btcec.PublicKey, sort bool) int { |
|
if sort { |
|
keySet = sortKeys(keySet) |
|
} |
|
// Find the first key that isn't the same as the very first key (second |
|
// unique key). |
|
for i := range keySet { |
|
if !keyBytesEqual(keySet[i], keySet[0]) { |
|
return i |
|
} |
|
} |
|
// A value of negative one is used to indicate that all the keys in the |
|
// sign set are actually equal, which in practice actually makes musig2 |
|
// useless, but we need a value to distinguish this case. |
|
return -1 |
|
} |
|
|
|
// KeyTweakDesc describes a tweak to be applied to the aggregated public key |
|
// generation and signing process. The IsXOnly specifies if the target key |
|
// should be converted to an x-only public key before tweaking. |
|
type KeyTweakDesc struct { |
|
// Tweak is the 32-byte value that will modify the public key. |
|
Tweak [32]byte |
|
// IsXOnly if true, then the public key will be mapped to an x-only key |
|
// before the tweaking operation is applied. |
|
IsXOnly bool |
|
} |
|
|
|
// KeyAggOption is a functional option argument that allows callers to specify |
|
// more or less information that has been pre-computed to the main routine. |
|
type KeyAggOption func(*keyAggOption) |
|
|
|
// keyAggOption houses the set of functional options that modify key |
|
// aggregation. |
|
type keyAggOption struct { |
|
// keyHash is the output of keyHashFingerprint for a given set of keys. |
|
keyHash []byte |
|
// uniqueKeyIndex is the pre-computed index of the second unique key. |
|
uniqueKeyIndex *int |
|
// tweaks specifies a series of tweaks to be applied to the aggregated |
|
// public key. |
|
tweaks []KeyTweakDesc |
|
// taprootTweak controls if the tweaks above should be applied in a BIP |
|
// 340 style. |
|
taprootTweak bool |
|
// bip86Tweak specifies that the taproot tweak should be done in a BIP |
|
// 86 style, where we don't expect an actual tweak and instead just |
|
// commit to the public key itself. |
|
bip86Tweak bool |
|
} |
|
|
|
// WithKeysHash allows key aggregation to be optimize, by allowing the caller |
|
// to specify the hash of all the keys. |
|
func WithKeysHash(keyHash []byte) KeyAggOption { |
|
return func(o *keyAggOption) { o.keyHash = keyHash } |
|
} |
|
|
|
// WithUniqueKeyIndex allows the caller to specify the index of the second |
|
// unique key. |
|
func WithUniqueKeyIndex(idx int) KeyAggOption { |
|
return func(o *keyAggOption) { |
|
i := idx |
|
o.uniqueKeyIndex = &i |
|
} |
|
} |
|
|
|
// WithKeyTweaks allows a caller to specify a series of 32-byte tweaks that |
|
// should be applied to the final aggregated public key. |
|
func WithKeyTweaks(tweaks ...KeyTweakDesc) KeyAggOption { |
|
return func(o *keyAggOption) { o.tweaks = tweaks } |
|
} |
|
|
|
// WithTaprootKeyTweak specifies that within this context, the final key should |
|
// use the taproot tweak as defined in BIP 341: outputKey = internalKey + |
|
// h_tapTweak(internalKey || scriptRoot). In this case, the aggregated key |
|
// before the tweak will be used as the internal key. |
|
// |
|
// This option should be used instead of WithKeyTweaks when the aggregated key |
|
// is intended to be used as a taproot output key that commits to a script |
|
// root. |
|
func WithTaprootKeyTweak(scriptRoot []byte) KeyAggOption { |
|
return func(o *keyAggOption) { |
|
var tweak [32]byte |
|
copy(tweak[:], scriptRoot[:]) |
|
o.tweaks = []KeyTweakDesc{ |
|
{ |
|
Tweak: tweak, |
|
IsXOnly: true, |
|
}, |
|
} |
|
o.taprootTweak = true |
|
} |
|
} |
|
|
|
// WithBIP86KeyTweak specifies that then during key aggregation, the BIP 86 |
|
// tweak which just commits to the hash of the serialized public key should be |
|
// used. This option should be used when signing with a key that was derived |
|
// using BIP 86. |
|
func WithBIP86KeyTweak() KeyAggOption { |
|
return func(o *keyAggOption) { |
|
o.tweaks = []KeyTweakDesc{{IsXOnly: true}} |
|
o.taprootTweak = true |
|
o.bip86Tweak = true |
|
} |
|
} |
|
|
|
// defaultKeyAggOptions returns the set of default arguments for key |
|
// aggregation. |
|
func defaultKeyAggOptions() *keyAggOption { return &keyAggOption{} } |
|
|
|
// hasEvenY returns true if the affine representation of the passed jacobian |
|
// point has an even y coordinate. |
|
// |
|
// TODO(roasbeef): double check, can just check the y coord even not jacobian? |
|
func hasEvenY(pJ btcec.JacobianPoint) bool { |
|
pJ.ToAffine() |
|
p := btcec.NewPublicKey(&pJ.X, &pJ.Y) |
|
keyBytes := p.SerializeCompressed() |
|
return keyBytes[0] == secp256k1.PubKeyFormatCompressedEven |
|
} |
|
|
|
// tweakKey applies a tweaks to the passed public key using the specified |
|
// tweak. The parityAcc and tweakAcc are returned (in that order) which |
|
// includes the accumulate ration of the parity factor and the tweak multiplied |
|
// by the parity factor. The xOnly bool specifies if this is to be an x-only |
|
// tweak or not. |
|
func tweakKey( |
|
keyJ btcec.JacobianPoint, parityAcc btcec.ModNScalar, |
|
tweak [32]byte, |
|
tweakAcc btcec.ModNScalar, |
|
xOnly bool, |
|
) (btcec.JacobianPoint, btcec.ModNScalar, btcec.ModNScalar, error) { |
|
|
|
// First we'll compute the new parity factor for this key. If the key has |
|
// an odd y coordinate (not even), then we'll need to negate it (multiply |
|
// by -1 mod n, in this case). |
|
var parityFactor btcec.ModNScalar |
|
if xOnly && !hasEvenY(keyJ) { |
|
parityFactor.SetInt(1).Negate() |
|
} else { |
|
parityFactor.SetInt(1) |
|
} |
|
|
|
// Next, map the tweak into a mod n integer so we can use it for |
|
// manipulations below. |
|
tweakInt := new(btcec.ModNScalar) |
|
overflows := tweakInt.SetBytes(&tweak) |
|
if overflows == 1 { |
|
return keyJ, parityAcc, tweakAcc, ErrTweakedKeyOverflows |
|
} |
|
// Next, we'll compute: Q_i = g*Q + t*G, where g is our parityFactor and t |
|
// is the tweakInt above. We'll space things out a bit to make it easier to |
|
// follow. |
|
// |
|
// First compute t*G: |
|
var tweakedGenerator btcec.JacobianPoint |
|
btcec.ScalarBaseMultNonConst(tweakInt, &tweakedGenerator) |
|
// Next compute g*Q: |
|
btcec.ScalarMultNonConst(&parityFactor, &keyJ, &keyJ) |
|
// Finally add both of them together to get our final |
|
// tweaked point. |
|
btcec.AddNonConst(&tweakedGenerator, &keyJ, &keyJ) |
|
// As a sanity check, make sure that we didn't just end up with the |
|
// point at infinity. |
|
if keyJ == infinityPoint { |
|
return keyJ, parityAcc, tweakAcc, ErrTweakedKeyIsInfinity |
|
} |
|
// As a final wrap up step, we'll accumulate the parity |
|
// factor and also this tweak into the final set of accumulators. |
|
parityAcc.Mul(&parityFactor) |
|
tweakAcc.Mul(&parityFactor).Add(tweakInt) |
|
return keyJ, parityAcc, tweakAcc, nil |
|
} |
|
|
|
// AggregateKey is a final aggregated key along with a possible version of the |
|
// key without any tweaks applied. |
|
type AggregateKey struct { |
|
// FinalKey is the final aggregated key which may include one or more |
|
// tweaks applied to it. |
|
FinalKey *btcec.PublicKey |
|
// PreTweakedKey is the aggregated *before* any tweaks have been |
|
// applied. This should be used as the internal key in taproot |
|
// contexts. |
|
PreTweakedKey *btcec.PublicKey |
|
} |
|
|
|
// AggregateKeys takes a list of possibly unsorted keys and returns a single |
|
// aggregated key as specified by the musig2 key aggregation algorithm. A nil |
|
// value can be passed for keyHash, which causes this function to re-derive it. |
|
// In addition to the combined public key, the parity accumulator and the tweak |
|
// accumulator are returned as well. |
|
func AggregateKeys( |
|
keys []*btcec.PublicKey, sort bool, |
|
keyOpts ...KeyAggOption, |
|
) ( |
|
*AggregateKey, *btcec.ModNScalar, *btcec.ModNScalar, error, |
|
) { |
|
// First, parse the set of optional signing options. |
|
opts := defaultKeyAggOptions() |
|
for _, option := range keyOpts { |
|
option(opts) |
|
} |
|
// Sort the set of public key so we know we're working with them in |
|
// sorted order for all the routines below. |
|
if sort { |
|
keys = sortKeys(keys) |
|
} |
|
// The caller may provide the hash of all the keys as an optimization |
|
// during signing, as it already needs to be computed. |
|
if opts.keyHash == nil { |
|
opts.keyHash = keyHashFingerprint(keys, sort) |
|
} |
|
// A caller may also specify the unique key index themselves so we |
|
// don't need to re-compute it. |
|
if opts.uniqueKeyIndex == nil { |
|
idx := secondUniqueKeyIndex(keys, sort) |
|
opts.uniqueKeyIndex = &idx |
|
} |
|
// For each key, we'll compute the intermediate blinded key: a_i*P_i, |
|
// where a_i is the aggregation coefficient for that key, and P_i is |
|
// the key itself, then accumulate that (addition) into the main final |
|
// key: P = P_1 + P_2 ... P_N. |
|
var finalKeyJ btcec.JacobianPoint |
|
for _, key := range keys { |
|
// Port the key over to Jacobian coordinates as we need it in |
|
// this format for the routines below. |
|
var keyJ btcec.JacobianPoint |
|
key.AsJacobian(&keyJ) |
|
// Compute the aggregation coefficient for the key, then |
|
// multiply it by the key itself: P_i' = a_i*P_i. |
|
var tweakedKeyJ btcec.JacobianPoint |
|
a := aggregationCoefficient( |
|
keys, key, opts.keyHash, *opts.uniqueKeyIndex, |
|
) |
|
btcec.ScalarMultNonConst(a, &keyJ, &tweakedKeyJ) |
|
// Finally accumulate this into the final key in an incremental |
|
// fashion. |
|
btcec.AddNonConst(&finalKeyJ, &tweakedKeyJ, &finalKeyJ) |
|
} |
|
|
|
// We'll copy over the key at this point, since this represents the |
|
// aggregated key before any tweaks have been applied. This'll be used |
|
// as the internal key for script path proofs. |
|
finalKeyJ.ToAffine() |
|
combinedKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y) |
|
// At this point, if this is a taproot tweak, then we'll modify the |
|
// base tweak value to use the BIP 341 tweak value. |
|
if opts.taprootTweak { |
|
// Emulate the same behavior as txscript.ComputeTaprootOutputKey |
|
// which only operates on the x-only public key. |
|
key, _ := schnorr.ParsePubKey( |
|
schnorr.SerializePubKey( |
|
combinedKey, |
|
), |
|
) |
|
// We only use the actual tweak bytes if we're not committing |
|
// to a BIP-0086 key only spend output. Otherwise, we just |
|
// commit to the internal key and an empty byte slice as the |
|
// root hash. |
|
tweakBytes := []byte{} |
|
if !opts.bip86Tweak { |
|
tweakBytes = opts.tweaks[0].Tweak[:] |
|
} |
|
// Compute the taproot key tagged hash of: |
|
// h_tapTweak(internalKey || scriptRoot). We only do this for |
|
// the first one, as you can only specify a single tweak when |
|
// using the taproot mode with this API. |
|
tapTweakHash := chainhash.TaggedHash( |
|
chainhash.TagTapTweak, schnorr.SerializePubKey(key), |
|
tweakBytes, |
|
) |
|
opts.tweaks[0].Tweak = *tapTweakHash |
|
} |
|
|
|
var ( |
|
err error |
|
tweakAcc btcec.ModNScalar |
|
parityAcc btcec.ModNScalar |
|
) |
|
parityAcc.SetInt(1) |
|
// In this case we have a set of tweaks, so we'll incrementally apply |
|
// each one, until we have our final tweaked key, and the related |
|
// accumulators. |
|
for i := 1; i <= len(opts.tweaks); i++ { |
|
finalKeyJ, parityAcc, tweakAcc, err = tweakKey( |
|
finalKeyJ, parityAcc, opts.tweaks[i-1].Tweak, tweakAcc, |
|
opts.tweaks[i-1].IsXOnly, |
|
) |
|
if err != nil { |
|
return nil, nil, nil, err |
|
} |
|
} |
|
finalKeyJ.ToAffine() |
|
finalKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y) |
|
return &AggregateKey{ |
|
PreTweakedKey: combinedKey, |
|
FinalKey: finalKey, |
|
}, &parityAcc, &tweakAcc, nil |
|
}
|
|
|