From cc12483f0aae6d8f5e08bac15ceb8309b6589c8c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 19 Oct 2022 20:15:16 -0700 Subject: [PATCH] btcec/schnorr/musig2: add key tweak sign test vectors --- btcec/schnorr/musig2/data/tweak_vectors.json | 84 +++++++++++ btcec/schnorr/musig2/keys.go | 2 +- btcec/schnorr/musig2/keys_test.go | 139 ++++++++++++++++++- 3 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 btcec/schnorr/musig2/data/tweak_vectors.json diff --git a/btcec/schnorr/musig2/data/tweak_vectors.json b/btcec/schnorr/musig2/data/tweak_vectors.json new file mode 100644 index 0000000000..01ccb8b1b3 --- /dev/null +++ b/btcec/schnorr/musig2/data/tweak_vectors.json @@ -0,0 +1,84 @@ +{ + "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ], + "secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F7", + "pnonces": [ + "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046" + ], + "aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "tweaks": [ + "E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB", + "AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455", + "F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0", + "1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "valid_test_cases": [ + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0], + "is_xonly": [true], + "signer_index": 2, + "expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91", + "comment": "A single x-only tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0], + "is_xonly": [false], + "signer_index": 2, + "expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D", + "comment": "A single plain tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1], + "is_xonly": [false, true], + "signer_index": 2, + "expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408", + "comment": "A plain tweak followed by an x-only tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1, 2, 3], + "is_xonly": [false, false, true, true], + "signer_index": 2, + "expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435", + "comment": "Four tweaks: plain, plain, x-only, x-only." + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1, 2, 3], + "is_xonly": [true, false, true, false], + "signer_index": 2, + "expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239", + "comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error." + } + ], + "error_test_cases": [ + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [4], + "is_xonly": [false], + "signer_index": 2, + "error": { + "type": "value", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is invalid because it exceeds group size" + } + ] +} diff --git a/btcec/schnorr/musig2/keys.go b/btcec/schnorr/musig2/keys.go index 7e84ba57e9..4ee63be2eb 100644 --- a/btcec/schnorr/musig2/keys.go +++ b/btcec/schnorr/musig2/keys.go @@ -29,7 +29,7 @@ var ( // ErrTweakedKeyOverflows is returned if a tweaking key is larger than // 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141. - ErrTweakedKeyOverflows = fmt.Errorf("tweaked key is to large") + ErrTweakedKeyOverflows = fmt.Errorf("tweaked key is too large") ) // sortableKeys defines a type of slice of public keys that implements the sort diff --git a/btcec/schnorr/musig2/keys_test.go b/btcec/schnorr/musig2/keys_test.go index f9a6e05665..941ab14c7a 100644 --- a/btcec/schnorr/musig2/keys_test.go +++ b/btcec/schnorr/musig2/keys_test.go @@ -3,6 +3,7 @@ package musig2 import ( + "encoding/hex" "encoding/json" "fmt" "os" @@ -20,6 +21,8 @@ const ( keySortTestVectorFileName = "key_sort_vectors.json" keyAggTestVectorFileName = "key_agg_vectors.json" + + keyTweakTestVectorFileName = "tweak_vectors.json" ) type keySortTestVector struct { @@ -114,7 +117,7 @@ func keysFromIndices(t *testing.T, indices []int, } func tweaksFromIndices(t *testing.T, indices []int, - tweaks []string, isXonly bool) []KeyTweakDesc { + tweaks []string, isXonly []bool) []KeyTweakDesc { t.Helper() @@ -125,7 +128,7 @@ func tweaksFromIndices(t *testing.T, indices []int, testTweaks[i] = KeyTweakDesc{ Tweak: rawTweak, - IsXOnly: isXonly, + IsXOnly: isXonly[i], } } @@ -255,3 +258,135 @@ func TestMuSig2KeyAggTestVectors(t *testing.T) { }) } } + +type keyTweakInvalidTest struct { + Indices []int `json:"key_indices"` + + NonceIndices []int `json:"nonce_indices"` + + TweakIndices []int `json:"tweak_indices"` + + IsXOnly []bool `json:"is_only"` + + SignerIndex int `json:"signer_index"` + + Comment string `json:"comment"` +} + +type keyTweakValidTest struct { + Indices []int `json:"key_indices"` + + NonceIndices []int `json:"nonce_indices"` + + TweakIndices []int `json:"tweak_indices"` + + IsXOnly []bool `json:"is_xonly"` + + SignerIndex int `json:"signer_index"` + + Expected string `json:"expected"` + + Comment string `json:"comment"` +} + +type keyTweakVector struct { + PrivKey string `json:"sk"` + + PubKeys []string `json:"pubkeys"` + + PrivNonce string `json:"secnonce"` + + PubNonces []string `json:"pnonces"` + + AggNnoce string `json:"aggnonce"` + + Tweaks []string `json:"tweaks"` + + Msg string `json:"msg"` + + ValidCases []keyTweakValidTest `json:"valid_test_cases"` + + InvalidCases []keyTweakInvalidTest `json:"error_test_cases"` +} + +func pubNoncesFromIndices(t *testing.T, nonceIndices []int, pubNonces []string) [][PubNonceSize]byte { + + nonces := make([][PubNonceSize]byte, len(nonceIndices)) + + for i, idx := range nonceIndices { + var pubNonce [PubNonceSize]byte + copy(pubNonce[:], mustParseHex(pubNonces[idx])) + + nonces[i] = pubNonce + } + + return nonces +} + +// TestMuSig2TweakTestVectors tests that we properly handle the various edge +// cases related to tweaking public keys. +func TestMuSig2TweakTestVectors(t *testing.T) { + t.Parallel() + + testVectorPath := path.Join( + testVectorBaseDir, keyTweakTestVectorFileName, + ) + testVectorBytes, err := os.ReadFile(testVectorPath) + require.NoError(t, err) + + var testCases keyTweakVector + require.NoError(t, json.Unmarshal(testVectorBytes, &testCases)) + + privKey, _ := btcec.PrivKeyFromBytes(mustParseHex(testCases.PrivKey)) + + var msg [32]byte + copy(msg[:], mustParseHex(testCases.Msg)) + + var secNonce [SecNonceSize]byte + copy(secNonce[:], mustParseHex(testCases.PrivNonce)) + + for _, testCase := range testCases.ValidCases { + testName := fmt.Sprintf("valid_%v", + strings.ToLower(testCase.Comment)) + t.Run(testName, func(t *testing.T) { + pubKeys, err := keysFromIndices( + t, testCase.Indices, testCases.PubKeys, + ) + require.NoError(t, err) + + var tweaks []KeyTweakDesc + if len(testCase.TweakIndices) != 0 { + tweaks = tweaksFromIndices( + t, testCase.TweakIndices, + testCases.Tweaks, testCase.IsXOnly, + ) + } + + pubNonces := pubNoncesFromIndices( + t, testCase.NonceIndices, testCases.PubNonces, + ) + + combinedNonce, err := AggregateNonces(pubNonces) + require.NoError(t, err) + + var opts []SignOption + if len(tweaks) != 0 { + opts = append(opts, WithTweaks(tweaks...)) + } + + partialSig, err := Sign( + secNonce, privKey, combinedNonce, pubKeys, + msg, opts..., + ) + + var partialSigBytes [32]byte + partialSig.S.PutBytesUnchecked(partialSigBytes[:]) + + require.Equal( + t, hex.EncodeToString(partialSigBytes[:]), + hex.EncodeToString(mustParseHex(testCase.Expected)), + ) + + }) + } +}