Skip to content

Commit

Permalink
btcec/schnorr/musig2: Add AggregateKeys testvectors
Browse files Browse the repository at this point in the history
This commit adds the testvectors from
jonasnick/bips@20f60b0
to the testcases
  • Loading branch information
sputn1ck committed Jun 27, 2022
1 parent d93f140 commit b58124c
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 16 deletions.
10 changes: 8 additions & 2 deletions btcec/schnorr/musig2/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ var (
// 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
Expand Down Expand Up @@ -286,8 +290,10 @@ func tweakKey(keyJ btcec.JacobianPoint, parityAcc btcec.ModNScalar, tweak [32]by
// Next, map the tweak into a mod n integer so we can use it for
// manipulations below.
tweakInt := new(btcec.ModNScalar)
tweakInt.SetBytes(&tweak)

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.
Expand Down
159 changes: 145 additions & 14 deletions btcec/schnorr/musig2/musig2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"sync"
"testing"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)

var (
Expand All @@ -26,26 +26,80 @@ var (
key3Bytes, _ = hex.DecodeString("3590A94E768F8E1815C2F24B4D80A8E3149" +
"316C3518CE7B7AD338368D038CA66")

testKeys = [][]byte{key1Bytes, key2Bytes, key3Bytes}
invalidPk1, _ = hex.DecodeString("00000000000000000000000000000000" +
"00000000000000000000000000000005")
invalidPk2, _ = hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +
"FFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30")
invalidTweak, _ = hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" +
"BAAEDCE6AF48A03BBFD25E8CD0364141")

testKeys = [][]byte{key1Bytes, key2Bytes, key3Bytes, invalidPk1, invalidPk2}

keyCombo1, _ = hex.DecodeString("E5830140512195D74C8307E39637CBE5FB730EBEAB80EC514CF88A877CEEEE0B")
keyCombo2, _ = hex.DecodeString("D70CD69A2647F7390973DF48CBFA2CCC407B8B2D60B08C5F1641185C7998A290")
keyCombo3, _ = hex.DecodeString("81A8B093912C9E481408D09776CEFB48AEB8B65481B6BAAFB3C5810106717BEB")
keyCombo4, _ = hex.DecodeString("2EB18851887E7BDC5E830E89B19DDBC28078F1FA88AAD0AD01CA06FE4F80210B")
)

// getG_ returns a public key that is used for the ErrTweakedKeyIsInfinity
// testcase.
func getG_() *btcec.PublicKey {
GX, _ := hex.DecodeString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")
GY, _ := hex.DecodeString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8")

var x, y btcec.FieldVal
x.SetByteSlice(GX)
y.SetByteSlice(GY)

return btcec.NewPublicKey(&x, &y)
}

// getInfinityTweak returns a tweak that, when tweaking G_, triggers
// the ErrTweakedKeyIsInfinity error.
func getInfinityTweak() KeyTweakDesc {
G_ := getG_()

keySet := []*secp256k1.PublicKey{G_}

keysHash := keyHashFingerprint(keySet, true)
uniqueKeyIndex := secondUniqueKeyIndex(keySet, true)

n := &secp256k1.ModNScalar{}

n.SetByteSlice(invalidTweak)

coeff := aggregationCoefficient(keySet, G_, keysHash, uniqueKeyIndex).Negate().Add(n)

tweak := KeyTweakDesc{
Tweak: coeff.Bytes(),
IsXOnly: false,
}

return tweak
}

const (
keyAggTestVectorName = "key_agg_vectors.json"

signTestVectorName = "sign_vectors.json"
)

var dumpJson = flag.Bool("dumpjson", false, "if true, a JSON version of the "+
"test vectors will be written to the cwd")
// var dumpJson = flag.Bool("dumpjson", false, "if true, a JSON version of the "+
// "test vectors will be written to the cwd")

var dumpJson = yesDumpJson()

func yesDumpJson() *bool {
p := new(bool)
*p = true
return p
}

type jsonKeyAggTestCase struct {
Keys []string `json:"keys"`
ExpectedKey string `json:"expected_key"`
Keys []string `json:"keys"`
Tweaks []jsonTweak `json:"tweaks"`
ExpectedKey string `json:"expected_key"`
ExpectedError string `json:"expected_error"`
}

// TestMuSig2KeyAggTestVectors tests that this implementation of musig2 key
Expand All @@ -56,8 +110,12 @@ func TestMuSig2KeyAggTestVectors(t *testing.T) {
var jsonCases []jsonKeyAggTestCase

testCases := []struct {
keyOrder []int
expectedKey []byte
keyOrder []int
explicitKeys []*secp256k1.PublicKey
tweaks []KeyTweakDesc
expectedKey []byte
expectedError error
expectedSecpError secp256k1.ErrorKind
}{
// Keys in backwards lexicographical order.
{
Expand All @@ -82,34 +140,101 @@ func TestMuSig2KeyAggTestVectors(t *testing.T) {
keyOrder: []int{0, 0, 1, 1},
expectedKey: keyCombo4,
},

// Invalid public key
{
keyOrder: []int{0, 3},
expectedSecpError: secp256k1.ErrPubKeyNotOnCurve,
},

// Public key exceeds field size
{
keyOrder: []int{0, 4},
expectedSecpError: secp256k1.ErrPubKeyXTooBig,
},

// Tweak is out of range
{
keyOrder: []int{0, 1},
tweaks: []KeyTweakDesc{
KeyTweakDesc{
Tweak: byteSliceTo32ByteSlice(invalidTweak),
IsXOnly: true,
},
},
expectedError: ErrTweakedKeyOverflows,
},

// Intermediate tweaking result is point at infinity
{
explicitKeys: []*secp256k1.PublicKey{getG_()},
tweaks: []KeyTweakDesc{
getInfinityTweak(),
},
expectedError: ErrTweakedKeyIsInfinity,
},
}
for i, testCase := range testCases {
testName := fmt.Sprintf("%v", testCase.keyOrder)
t.Run(testName, func(t *testing.T) {
var (
keys []*btcec.PublicKey
strKeys []string
keys []*btcec.PublicKey
strKeys []string
strTweaks []jsonTweak
jsonError string
)
for _, keyIndex := range testCase.keyOrder {
keyBytes := testKeys[keyIndex]
pub, err := schnorr.ParsePubKey(keyBytes)
if err != nil {
if errors.Is(err, testCase.expectedError) ||
errors.Is(err, testCase.expectedSecpError) {
return
}
t.Fatalf("unable to parse pubkeys: %v", err)
}

keys = append(keys, pub)
strKeys = append(strKeys, hex.EncodeToString(keyBytes))
}

for _, explicitKey := range testCase.explicitKeys {
keys = append(keys, explicitKey)
strKeys = append(strKeys, hex.EncodeToString(explicitKey.SerializeCompressed()))
}

for _, tweak := range testCase.tweaks {
strTweaks = append(strTweaks, jsonTweak{Tweak: hex.EncodeToString(tweak.Tweak[:]), XOnly: tweak.IsXOnly})
}

if testCase.expectedError != nil {
jsonError = testCase.expectedError.Error()
} else if testCase.expectedSecpError != "" {
jsonError = testCase.expectedSecpError.Error()
}

jsonCases = append(jsonCases, jsonKeyAggTestCase{
Keys: strKeys,
ExpectedKey: hex.EncodeToString(testCase.expectedKey),
Keys: strKeys,
Tweaks: strTweaks,
ExpectedKey: hex.EncodeToString(testCase.expectedKey),
ExpectedError: jsonError,
})

uniqueKeyIndex := secondUniqueKeyIndex(keys, false)
combinedKey, _, _, _ := AggregateKeys(
keys, false, WithUniqueKeyIndex(uniqueKeyIndex),
opts := []KeyAggOption{WithUniqueKeyIndex(uniqueKeyIndex)}
if len(testCase.tweaks) > 0 {
opts = append(opts, WithKeyTweaks(testCase.tweaks...))
}

combinedKey, _, _, err := AggregateKeys(
keys, false, opts...,
)
if err != nil {
if errors.Is(err, testCase.expectedError) {
return
}
t.Fatalf("case #%v, got error %v", i, err)
}
combinedKeyBytes := schnorr.SerializePubKey(combinedKey.FinalKey)
if !bytes.Equal(combinedKeyBytes, testCase.expectedKey) {
t.Fatalf("case: #%v, invalid aggregation: "+
Expand Down Expand Up @@ -920,3 +1045,9 @@ func memsetLoop(a []byte, v uint8) {
a[i] = byte(v)
}
}

func byteSliceTo32ByteSlice(input []byte) [32]byte {
var output [32]byte
copy(output[:], input)
return output
}

0 comments on commit b58124c

Please sign in to comment.