From b8f8a38b2e83d1c7122b6e4e99d06616d728d577 Mon Sep 17 00:00:00 2001 From: Jonathan Downing Date: Tue, 24 Sep 2024 12:56:28 -0500 Subject: [PATCH] Added musig2 vector tests --- core/types/key_agg_vectors.json | 88 ++++++++++++++ core/types/sig_agg_vectors.json | 160 ++++++++++++++++++++++++ core/types/utxo_test.go | 208 ++++++++++++++++++++++++++++++++ 3 files changed, 456 insertions(+) create mode 100644 core/types/key_agg_vectors.json create mode 100644 core/types/sig_agg_vectors.json diff --git a/core/types/key_agg_vectors.json b/core/types/key_agg_vectors.json new file mode 100644 index 0000000000..9cc9af4c59 --- /dev/null +++ b/core/types/key_agg_vectors.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/core/types/sig_agg_vectors.json b/core/types/sig_agg_vectors.json new file mode 100644 index 0000000000..e72cc06a12 --- /dev/null +++ b/core/types/sig_agg_vectors.json @@ -0,0 +1,160 @@ +{ + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05", + "03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C", + "02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581" + ], + "secnonces": [ + "803B1A9843BBB36CF28F81E49FDE20031BCC6F41E1654758EA44501856DFA6B696B5084A3512DCD821059B3EF039431574D7662478CEB399C7098ABC2EC6722603935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "41F401C558584F0412DAE913BC61BE593319E2D83381B8AB5312B92D7FC9B6198B4AD586D0C923A814CB6CCA0657AC49DE647A86C7BB7F2369760CD75B37E55003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "7AAA2D3A63747122CE58F5D551F9D56BC55688303FC392A2D2B95C91F59DC38E844760BDCDD2C1A147B3E980B61B58603BDA2121D41BB4C872953F55C6BF3EEF03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "541D936F1DC072CF08B0D67362241C39D809F72674FCB88D7D204B5E8818558E6CC74897B71F950307AFBC9132F739D3C82F55D9D69251DD97FDC170B058B76B03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "77316AF26135BBD2DB480830631D3F6471C16BAD064866F593693BD26ACDD9A0EE5A69FDFA0D223FDC2C60D6E5981B6A2746B5B36003F843B65074A90730ED1003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "1CE5CB6D3CDD597EF2C8A0EE2044038E4F6B1F1A2A942A59F5F161D9644A57B9853751A8CF701D5C079EC567EAE41383249A683285E2EB469A4686A0C8FDC24C03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" + ], + "pnonces": [ + "036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E", + "03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00", + "02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6", + "031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9", + "023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A", + "02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00" + ], + "tweaks": [ + "B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C", + "A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC", + "75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8" + ], + "psigs": [ + "B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB", + "6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64", + "9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505", + "66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15", + "4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE", + "DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4", + "97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC", + "53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869", + "valid_test_cases": [ + { + "aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B", + "nonce_indices": [ + 0, + 1 + ], + "key_indices": [ + 0, + 1 + ], + "tweak_indices": [], + "is_xonly": [], + "psig_indices": [ + 0, + 1 + ], + "expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E" + }, + { + "aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20", + "nonce_indices": [ + 0, + 2 + ], + "key_indices": [ + 0, + 2 + ], + "tweak_indices": [], + "is_xonly": [], + "psig_indices": [ + 2, + 3 + ], + "expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9" + }, + { + "aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D", + "nonce_indices": [ + 0, + 3 + ], + "key_indices": [ + 0, + 2 + ], + "tweak_indices": [ + 0 + ], + "is_xonly": [ + false + ], + "psig_indices": [ + 4, + 5 + ], + "expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC" + }, + { + "aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD", + "nonce_indices": [ + 0, + 4 + ], + "key_indices": [ + 0, + 3 + ], + "tweak_indices": [ + 0, + 1, + 2 + ], + "is_xonly": [ + true, + false, + true + ], + "psig_indices": [ + 6, + 7 + ], + "expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E" + } + ], + "error_test_cases": [ + { + "aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD", + "nonce_indices": [ + 0, + 4 + ], + "key_indices": [ + 0, + 3 + ], + "tweak_indices": [ + 0, + 1, + 2 + ], + "is_xonly": [ + true, + false, + true + ], + "psig_indices": [ + 7, + 8 + ], + "error": { + "type": "invalid_contribution", + "signer": 1, + "contrib": "psig" + }, + "comment": "Partial signature is invalid because it exceeds group size" + } + ] +} \ No newline at end of file diff --git a/core/types/utxo_test.go b/core/types/utxo_test.go index 5ec82ee3e1..c8f25cc495 100644 --- a/core/types/utxo_test.go +++ b/core/types/utxo_test.go @@ -1,9 +1,12 @@ package types import ( + "bytes" "crypto/sha256" "encoding/hex" + "encoding/json" "fmt" + "io/ioutil" "strings" "sync" "testing" @@ -15,6 +18,211 @@ import ( "github.com/dominant-strategies/go-quai/crypto" ) +type KeyAggVectors struct { + Pubkeys []string `json:"pubkeys"` + Tweaks []string `json:"tweaks"` + ValidTestCases []struct { + KeyIndices []int `json:"key_indices"` + Expected string `json:"expected"` + } `json:"valid_test_cases"` + ErrorTestCases []struct { + KeyIndices []int `json:"key_indices"` + TweakIndices []int `json:"tweak_indices"` + IsXonly []bool `json:"is_xonly"` + Error struct { + Type string `json:"type"` + Signer int `json:"signer"` + Contrib string `json:"contrib"` + Message string `json:"message"` + } `json:"error"` + Comment string `json:"comment"` + } `json:"error_test_cases"` +} + +type SigAggVectors struct { + Pubkeys []string `json:"pubkeys"` + PNonces []string `json:"pnonces"` + Tweaks []string `json:"tweaks"` + PSigs []string `json:"psigs"` + Msg string `json:"msg"` + ValidTestCases []struct { + AggNonce string `json:"aggnonce"` + NonceIndices []int `json:"nonce_indices"` + KeyIndices []int `json:"key_indices"` + TweakIndices []int `json:"tweak_indices"` + IsXonly []bool `json:"is_xonly"` + PSigIndices []int `json:"psig_indices"` + Expected string `json:"expected"` + } `json:"valid_test_cases"` + ErrorTestCases []struct { + AggNonce string `json:"aggnonce"` + NonceIndices []int `json:"nonce_indices"` + KeyIndices []int `json:"key_indices"` + TweakIndices []int `json:"tweak_indices"` + IsXonly []bool `json:"is_xonly"` + PSigIndices []int `json:"psig_indices"` + Error struct { + Type string `json:"type"` + Signer int `json:"signer"` + Contrib string `json:"contrib"` + Message string `json:"message"` + } `json:"error"` + Comment string `json:"comment"` + } `json:"error_test_cases"` +} + +func TestKeyAggregation(t *testing.T) { + vectors, err := loadKeyAggVectors("key_agg_vectors.json") + if err != nil { + t.Fatal(err) + } + + for _, testCase := range vectors.ValidTestCases { + t.Run(fmt.Sprintf("KeyIndices_%v", testCase.KeyIndices), func(t *testing.T) { + // Collect the public keys + var pubKeys []*btcec.PublicKey + for _, idx := range testCase.KeyIndices { + pubKeyBytes, err := hex.DecodeString(vectors.Pubkeys[idx]) + if err != nil { + t.Fatalf("Failed to decode pubkey: %v", err) + } + pubKey, err := btcec.ParsePubKey(pubKeyBytes) + if err != nil { + t.Fatalf("Failed to parse pubkey: %v", err) + } + pubKeys = append(pubKeys, pubKey) + } + + // Perform key aggregation + aggKey, _, _, err := musig2.AggregateKeys(pubKeys, false) + if err != nil { + t.Fatalf("Key aggregation failed: %v", err) + } + + // Compare the aggregated key with the expected value + expectedAggKeyBytes, err := hex.DecodeString(testCase.Expected) + if err != nil { + t.Fatalf("Failed to decode expected aggregated key: %v", err) + } + + // The expected aggregated key is an x-only public key. + // Convert the aggregated key to x-only format. + aggKeyBytes := aggKey.FinalKey.SerializeCompressed()[1:] // Remove the prefix byte + + if !bytes.Equal(aggKeyBytes, expectedAggKeyBytes) { + t.Errorf("Aggregated key mismatch.\nExpected: %x\nGot: %x", + expectedAggKeyBytes, aggKeyBytes) + } else { + t.Logf("Success: Aggregated key matches expected value.") + } + }) + } +} + +func TestSignatureAggregation(t *testing.T) { + vectors, err := loadSigAggVectors("sig_agg_vectors.json") + if err != nil { + t.Fatal(err) + } + + for _, testCase := range vectors.ValidTestCases { + t.Run(fmt.Sprintf("KeyIndices_%v_NonceIndices_%v", testCase.KeyIndices, testCase.NonceIndices), func(t *testing.T) { + // Collect the public keys + var pubKeys []*btcec.PublicKey + for _, idx := range testCase.KeyIndices { + pubKeyBytes, err := hex.DecodeString(vectors.Pubkeys[idx]) + if err != nil { + t.Fatalf("Failed to decode pubkey: %v", err) + } + pubKey, err := btcec.ParsePubKey(pubKeyBytes) + if err != nil { + t.Fatalf("Failed to parse pubkey: %v", err) + } + pubKeys = append(pubKeys, pubKey) + } + + // Compare the aggregated signature with the expected value + expectedSigBytes, err := hex.DecodeString(testCase.Expected) + if err != nil { + t.Fatalf("Failed to decode expected signature: %v", err) + } + + // For Musig2, the final signature is a 64-byte Schnorr signature. + + // Verify the aggregated signature + msgBytes, err := hex.DecodeString(vectors.Msg) + if err != nil { + t.Fatalf("Failed to decode message: %v", err) + } + + tweaks := make([]musig2.KeyTweakDesc, 0) + for _, idx := range testCase.TweakIndices { + tweakHex, err := hex.DecodeString(vectors.Tweaks[testCase.TweakIndices[idx]]) + if err != nil { + t.Fatalf("Failed to decode tweak at index %d, %v", idx, err) + } + tweak := musig2.KeyTweakDesc{ + Tweak: [32]byte(tweakHex), + IsXOnly: testCase.IsXonly[idx], + } + tweaks = append(tweaks, tweak) + } + + // Use the aggregated key from the key aggregation step + aggKey, _, _, err := musig2.AggregateKeys(pubKeys, false, musig2.WithKeyTweaks(tweaks...)) + if err != nil { + t.Fatalf("Key aggregation failed: %v", err) + } + + // Verify the signature + // Parse r (first 32 bytes) into a btcec.FieldVal + var r btcec.FieldVal + r.SetByteSlice(expectedSigBytes[:32]) + + // Parse s (next 32 bytes) into a btcec.ModNScalar + var s btcec.ModNScalar + if s.SetByteSlice(expectedSigBytes[32:]) { + t.Fatalf("Invalid s value in signature: overflow occurred") + } + + // Create the signature using NewSignature + finalSig := schnorr.NewSignature(&r, &s) + + if !finalSig.Verify(msgBytes, aggKey.FinalKey) { + t.Errorf("Signature verification failed.") + } else { + t.Logf("Success: Signature verification passed.") + } + }) + } +} + +func loadKeyAggVectors(filename string) (*KeyAggVectors, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var vectors KeyAggVectors + err = json.Unmarshal(data, &vectors) + if err != nil { + return nil, err + } + return &vectors, nil +} + +func loadSigAggVectors(filename string) (*SigAggVectors, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var vectors SigAggVectors + err = json.Unmarshal(data, &vectors) + if err != nil { + return nil, err + } + return &vectors, nil +} + func TestSingleSigner(t *testing.T) { location := common.Location{0, 0}