btcec/schnorr/musig2: update key agg test vectors to musig2 1.0.0

This commit is contained in:
Olaoluwa Osuntokun 2022-10-19 19:07:21 -07:00
parent 3d9f4484df
commit 4e55273815
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
4 changed files with 361 additions and 186 deletions

View file

@ -0,0 +1,88 @@
{
"pubkeys": [
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"020000000000000000000000000000000000000000000000000000000000000005",
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
"04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
],
"tweaks": [
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B"
],
"valid_test_cases": [
{
"key_indices": [0, 1, 2],
"expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C"
},
{
"key_indices": [2, 1, 0],
"expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B"
},
{
"key_indices": [0, 0, 0],
"expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935"
},
{
"key_indices": [0, 0, 1, 1],
"expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E"
}
],
"error_test_cases": [
{
"key_indices": [0, 3],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubkey"
},
"comment": "Invalid public key"
},
{
"key_indices": [0, 4],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubkey"
},
"comment": "Public key exceeds field size"
},
{
"key_indices": [5, 0],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubkey"
},
"comment": "First byte of public key is not 2 or 3"
},
{
"key_indices": [0, 1],
"tweak_indices": [0],
"is_xonly": [true],
"error": {
"type": "value",
"message": "The tweak must be less than n."
},
"comment": "Tweak is out of range"
},
{
"key_indices": [6],
"tweak_indices": [1],
"is_xonly": [false],
"error": {
"type": "value",
"message": "The result of tweaking cannot be infinity."
},
"comment": "Intermediate tweaking result is point at infinity"
}
]
}

View file

@ -0,0 +1,16 @@
{
"pubkeys": [
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8"
],
"sorted_pubkeys": [
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
]
}

View file

@ -0,0 +1,257 @@
// Copyright 2013-2022 The btcsuite developers
package musig2
import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/stretchr/testify/require"
)
const (
keySortTestVectorFileName = "key_sort_vectors.json"
keyAggTestVectorFileName = "key_agg_vectors.json"
)
type keySortTestVector struct {
PubKeys []string `json:"pubkeys"`
SortedKeys []string `json:"sorted_pubkeys"`
}
// TestMusig2KeySort tests that keys are properly sorted according to the
// musig2 test vectors.
func TestMusig2KeySort(t *testing.T) {
t.Parallel()
testVectorPath := path.Join(
testVectorBaseDir, keySortTestVectorFileName,
)
testVectorBytes, err := os.ReadFile(testVectorPath)
require.NoError(t, err)
var testCase keySortTestVector
require.NoError(t, json.Unmarshal(testVectorBytes, &testCase))
keys := make([]*btcec.PublicKey, len(testCase.PubKeys))
for i, keyStr := range testCase.PubKeys {
pubKey, err := btcec.ParsePubKey(mustParseHex(keyStr))
require.NoError(t, err)
keys[i] = pubKey
}
sortedKeys := sortKeys(keys)
expectedKeys := make([]*btcec.PublicKey, len(testCase.PubKeys))
for i, keyStr := range testCase.SortedKeys {
pubKey, err := btcec.ParsePubKey(mustParseHex(keyStr))
require.NoError(t, err)
expectedKeys[i] = pubKey
}
require.Equal(t, sortedKeys, expectedKeys)
}
type keyAggValidTest struct {
Indices []int `json:"key_indices"`
Expected string `json:"expected"`
}
type keyAggError struct {
Type string `json:"type"`
Signer int `json:"signer"`
Contring string `json:"contrib"`
}
type keyAggInvalidTest struct {
Indices []int `json:"key_indices"`
TweakIndices []int `json:"tweak_indices"`
IsXOnly []bool `json:"is_xonly"`
Comment string `json:"comment"`
}
type keyAggTestVectors struct {
PubKeys []string `json:"pubkeys"`
Tweaks []string `json:"tweaks"`
ValidCases []keyAggValidTest `json:"valid_test_cases"`
InvalidCases []keyAggInvalidTest `json:"error_test_cases"`
}
func keysFromIndices(t *testing.T, indices []int,
pubKeys []string) ([]*btcec.PublicKey, error) {
t.Helper()
inputKeys := make([]*btcec.PublicKey, len(indices))
for i, keyIdx := range indices {
var err error
inputKeys[i], err = btcec.ParsePubKey(
mustParseHex(pubKeys[keyIdx]),
)
if err != nil {
return nil, err
}
}
return inputKeys, nil
}
func tweaksFromIndices(t *testing.T, indices []int,
tweaks []string, isXonly bool) []KeyTweakDesc {
t.Helper()
testTweaks := make([]KeyTweakDesc, len(indices))
for i, idx := range indices {
var rawTweak [32]byte
copy(rawTweak[:], mustParseHex(tweaks[idx]))
testTweaks[i] = KeyTweakDesc{
Tweak: rawTweak,
IsXOnly: isXonly,
}
}
return testTweaks
}
// TestMuSig2KeyAggTestVectors tests that this implementation of musig2 key
// aggregation lines up with the secp256k1-zkp test vectors.
func TestMuSig2KeyAggTestVectors(t *testing.T) {
t.Parallel()
testVectorPath := path.Join(
testVectorBaseDir, keyAggTestVectorFileName,
)
testVectorBytes, err := os.ReadFile(testVectorPath)
require.NoError(t, err)
var testCases keyAggTestVectors
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
tweaks := make([][]byte, len(testCases.Tweaks))
for i := range testCases.Tweaks {
tweaks[i] = mustParseHex(testCases.Tweaks[i])
}
for i, testCase := range testCases.ValidCases {
testCase := testCase
// Assemble the set of keys we'll pass in based on their key
// index. We don't use sorting to ensure we send the keys in
// the exact same order as the test vectors do.
inputKeys, err := keysFromIndices(
t, testCase.Indices, testCases.PubKeys,
)
require.NoError(t, err)
t.Run(fmt.Sprintf("test_case=%v", i), func(t *testing.T) {
uniqueKeyIndex := secondUniqueKeyIndex(inputKeys, false)
opts := []KeyAggOption{WithUniqueKeyIndex(uniqueKeyIndex)}
combinedKey, _, _, err := AggregateKeys(
inputKeys, false, opts...,
)
require.NoError(t, err)
require.Equal(
t, schnorr.SerializePubKey(combinedKey.FinalKey),
mustParseHex(testCase.Expected),
)
})
}
for _, testCase := range testCases.InvalidCases {
testCase := testCase
testName := fmt.Sprintf("invalid_%v",
strings.ToLower(testCase.Comment))
t.Run(testName, func(t *testing.T) {
// For each test, we'll extract the set of input keys
// as well as the tweaks since this set of cases also
// exercises error cases related to the set of tweaks.
inputKeys, err := keysFromIndices(
t, testCase.Indices, testCases.PubKeys,
)
// In this set of test cases, we should only get this
// for the very first vector.
if err != nil {
switch testCase.Comment {
case "Invalid public key":
require.ErrorIs(
t, err,
secp.ErrPubKeyNotOnCurve,
)
case "Public key exceeds field size":
require.ErrorIs(
t, err, secp.ErrPubKeyXTooBig,
)
case "First byte of public key is not 2 or 3":
require.ErrorIs(
t, err,
secp.ErrPubKeyInvalidFormat,
)
default:
t.Fatalf("uncaught err: %v", err)
}
return
}
var tweaks []KeyTweakDesc
if len(testCase.TweakIndices) != 0 {
tweaks = tweaksFromIndices(
t, testCase.TweakIndices, testCases.Tweaks,
testCase.IsXOnly,
)
}
uniqueKeyIndex := secondUniqueKeyIndex(inputKeys, false)
opts := []KeyAggOption{
WithUniqueKeyIndex(uniqueKeyIndex),
}
if len(tweaks) != 0 {
opts = append(opts, WithKeyTweaks(tweaks...))
}
_, _, _, err = AggregateKeys(
inputKeys, false, opts...,
)
require.Error(t, err)
switch testCase.Comment {
case "Tweak is out of range":
require.ErrorIs(t, err, ErrTweakedKeyOverflows)
case "Intermediate tweaking result is point at infinity":
require.ErrorIs(t, err, ErrTweakedKeyIsInfinity)
default:
t.Fatalf("uncaught err: %v", err)
}
})
}
}

View file

@ -75,198 +75,12 @@ func getInfinityTweak() KeyTweakDesc {
}
const (
keyAggTestVectorName = "key_agg_vectors.json"
nonceAggTestVectorName = "nonce_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")
type jsonKeyAggTestCase struct {
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
// aggregation lines up with the secp256k1-zkp test vectors.
func TestMuSig2KeyAggTestVectors(t *testing.T) {
t.Parallel()
var jsonCases []jsonKeyAggTestCase
testCases := []struct {
keyOrder []int
explicitKeys []*btcec.PublicKey
tweaks []KeyTweakDesc
expectedKey []byte
expectedError error
}{
// Keys in backwards lexicographical order.
{
keyOrder: []int{0, 1, 2},
expectedKey: keyCombo1,
},
// Keys in sorted order.
{
keyOrder: []int{2, 1, 0},
expectedKey: keyCombo2,
},
// Only the first key.
{
keyOrder: []int{0, 0, 0},
expectedKey: keyCombo3,
},
// Duplicate the first key and second keys.
{
keyOrder: []int{0, 0, 1, 1},
expectedKey: keyCombo4,
},
// Invalid public key.
{
keyOrder: []int{0, 3},
expectedError: secp256k1.ErrPubKeyNotOnCurve,
},
// Public key exceeds field size.
{
keyOrder: []int{0, 4},
expectedError: secp256k1.ErrPubKeyXTooBig,
},
// Tweak is out of range.
{
keyOrder: []int{0, 1},
tweaks: []KeyTweakDesc{
KeyTweakDesc{
Tweak: to32ByteSlice(invalidTweak),
IsXOnly: true,
},
},
expectedError: ErrTweakedKeyOverflows,
},
// Intermediate tweaking result is point at infinity.
{
explicitKeys: []*secp256k1.PublicKey{btcec.Generator()},
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
strTweaks []jsonTweak
jsonError string
)
for _, keyIndex := range testCase.keyOrder {
keyBytes := testKeys[keyIndex]
pub, err := schnorr.ParsePubKey(keyBytes)
switch {
case testCase.expectedError != nil &&
errors.Is(err, testCase.expectedError):
return
case err != nil:
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()
}
jsonCases = append(
jsonCases,
jsonKeyAggTestCase{
Keys: strKeys,
Tweaks: strTweaks,
ExpectedKey: hex.EncodeToString(
testCase.expectedKey),
ExpectedError: jsonError,
})
uniqueKeyIndex := secondUniqueKeyIndex(keys, false)
opts := []KeyAggOption{WithUniqueKeyIndex(uniqueKeyIndex)}
if len(testCase.tweaks) > 0 {
opts = append(opts, WithKeyTweaks(testCase.tweaks...))
}
combinedKey, _, _, err := AggregateKeys(
keys, false, opts...,
)
switch {
case testCase.expectedError != nil &&
errors.Is(err, testCase.expectedError):
return
case err != nil:
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: "+
"expected %x, got %x", i, testCase.expectedKey,
combinedKeyBytes)
}
})
}
if *dumpJson {
jsonBytes, err := json.Marshal(jsonCases)
if err != nil {
t.Fatalf("unable to encode json: %v", err)
}
var formattedJson bytes.Buffer
json.Indent(&formattedJson, jsonBytes, "", "\t")
err = ioutil.WriteFile(
keyAggTestVectorName, formattedJson.Bytes(), 0644,
)
if err != nil {
t.Fatalf("unable to write file: %v", err)
}
}
}
func mustParseHex(str string) []byte {
b, err := hex.DecodeString(str)
if err != nil {