From edf88dc0b2168070966b5acb6169433c80506fa1 Mon Sep 17 00:00:00 2001 From: siv2r Date: Thu, 7 May 2026 18:55:53 +0530 Subject: [PATCH 1/4] gen_vectors: remove length checks and add preprocessing vector --- python/gen_vectors.py | 68 ++++++++----------------- python/tests.py | 2 +- python/vectors/det_sign_vectors.json | 22 -------- python/vectors/nonce_gen_vectors.json | 13 ++++- python/vectors/sig_agg_vectors.json | 25 --------- python/vectors/sign_verify_vectors.json | 43 ---------------- python/vectors/test_vectors_summary.md | 6 +-- python/vectors/tweak_vectors.json | 27 ---------- 8 files changed, 38 insertions(+), 168 deletions(-) diff --git a/python/gen_vectors.py b/python/gen_vectors.py index 8a9accb..3641c07 100755 --- a/python/gen_vectors.py +++ b/python/gen_vectors.py @@ -183,7 +183,7 @@ def frost_keygen_random(): def generate_nonce_gen_vectors(): - vectors = {"test_cases": []} + vectors = {"valid_test_cases": []} _, _, thresh_pk_ge, secshares, pubshares = frost_keygen_fixed() extra_in = bytes.fromhex( @@ -198,7 +198,7 @@ def generate_nonce_gen_vectors(): secnonce, pubnonce = nonce_gen_internal( COMMON_RAND, secshares[0], pubshares[0], xonly_thresh_pk, msg, extra_in ) - vectors["test_cases"].append( + vectors["valid_test_cases"].append( { "rand_": bytes_to_hex(COMMON_RAND), "secshare": bytes_to_hex(secshares[0]), @@ -220,7 +220,7 @@ def generate_nonce_gen_vectors(): COMMON_MSGS[1], extra_in, ) - vectors["test_cases"].append( + vectors["valid_test_cases"].append( { "rand_": bytes_to_hex(COMMON_RAND), "secshare": bytes_to_hex(secshares[0]), @@ -242,7 +242,7 @@ def generate_nonce_gen_vectors(): COMMON_MSGS[2], extra_in, ) - vectors["test_cases"].append( + vectors["valid_test_cases"].append( { "rand_": bytes_to_hex(COMMON_RAND), "secshare": bytes_to_hex(secshares[0]), @@ -257,7 +257,7 @@ def generate_nonce_gen_vectors(): ) # --- Valid Test Case 4 --- secnonce, pubnonce = nonce_gen_internal(COMMON_RAND, None, None, None, None, None) - vectors["test_cases"].append( + vectors["valid_test_cases"].append( { "rand_": bytes_to_hex(COMMON_RAND), "secshare": None, @@ -270,6 +270,23 @@ def generate_nonce_gen_vectors(): "comment": "Every optional parameter is absent", } ) + # --- Valid Test Case 5 --- + secnonce, pubnonce = nonce_gen_internal( + COMMON_RAND, secshares[0], pubshares[0], xonly_thresh_pk, None, extra_in + ) + vectors["valid_test_cases"].append( + { + "rand_": bytes_to_hex(COMMON_RAND), + "secshare": bytes_to_hex(secshares[0]), + "pubshare": bytes_to_hex(pubshares[0]), + "threshold_pubkey": bytes_to_hex(xonly_thresh_pk), + "msg": None, + "extra_in": bytes_to_hex(extra_in), + "expected_secnonce": bytes_to_hex(secnonce), + "expected_pubnonce": bytes_to_hex(pubnonce), + "comment": "Preprocessing: nonce generated before message is known", + } + ) write_test_vectors("nonce_gen_vectors.json", vectors) @@ -577,16 +594,6 @@ def generate_sign_verify_vectors(): "error": "value", "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares.", }, - { - "ids": [0, 1, 2], - "pubshares": [0, 1], - "aggnonce": 0, - "msg": 0, - "signer_idx": 0, - "secnonce": 0, - "error": "value", - "comment": "The participant identifiers count exceed the participant public shares count", - }, { "ids": [0, 1], "pubshares": [0, INVALID_PUBSHARE_IDX], @@ -755,15 +762,6 @@ def generate_sign_verify_vectors(): "error": "value", "comment": "Invalid pubshare", }, - { - "ids": [0, 1], - "pubshares": [0, 1], - "pubnonces": [0, 1, 2], - "msg": 0, - "signer": 0, - "error": "value", - "comment": "public nonces count is greater than ids and pubshares", - }, { "ids": [0, 1], "pubshares": [2, 1], @@ -912,11 +910,6 @@ def generate_tweak_vectors(): "is_xonly": [False], "comment": "Tweak is invalid because it exceeds group size", }, - { - "tweaks_indices": [0, 1, 2, 3], - "is_xonly": [True, False], - "comment": "Tweaks count doesn't match the tweak modes count", - }, ] for case in error_cases: indices = [0, 1] @@ -1142,16 +1135,6 @@ def generate_det_sign_vectors(): "error": "value", "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares.", }, - { - "ids": [0, 1, 2], - "pubshares": [0, 1], - "signer_idx": 0, - "msg": 0, - "rand": 0, - "aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9", - "error": "value", - "comment": "The participant identifiers count exceed the participant public shares count", - }, { "ids": [0, 1], "pubshares": [0, INVALID_PUBSHARE_IDX], @@ -1355,11 +1338,6 @@ def generate_sig_agg_vectors(): "error": "invalid_contrib", "comment": "Partial signature is invalid because it exceeds group size", }, - { - "indices": [0, 1], - "error": "value", - "comment": "Partial signature count doesn't match the signer set count", - }, ] for j, case in enumerate(error_cases): curr_ids = [ids[i] for i in case["indices"]] @@ -1381,8 +1359,6 @@ def generate_sig_agg_vectors(): "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" ) psigs[1] = invalid_psig - if j == 1: - psigs.pop() expected_exception = ( ValueError if case["error"] == "value" else InvalidContributionError diff --git a/python/tests.py b/python/tests.py index e29ad7d..f54d704 100755 --- a/python/tests.py +++ b/python/tests.py @@ -98,7 +98,7 @@ def test_nonce_gen_vectors(): with open(os.path.join(sys.path[0], "vectors", "nonce_gen_vectors.json")) as f: test_data = json.load(f) - for test_case in test_data["test_cases"]: + for test_case in test_data["valid_test_cases"]: def get_value(key) -> bytes: return bytes.fromhex(test_case[key]) diff --git a/python/vectors/det_sign_vectors.json b/python/vectors/det_sign_vectors.json index 904aac4..b0f7179 100644 --- a/python/vectors/det_sign_vectors.json +++ b/python/vectors/det_sign_vectors.json @@ -283,28 +283,6 @@ }, "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares." }, - { - "rand": "0000000000000000000000000000000000000000000000000000000000000000", - "aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9", - "id_indices": [ - 0, - 1, - 2 - ], - "pubshare_indices": [ - 0, - 1 - ], - "tweaks": [], - "is_xonly": [], - "msg_index": 0, - "signer_index": 0, - "error": { - "type": "ValueError", - "message": "The pubshares and ids arrays must have the same length." - }, - "comment": "The participant identifiers count exceed the participant public shares count" - }, { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", diff --git a/python/vectors/nonce_gen_vectors.json b/python/vectors/nonce_gen_vectors.json index 066b342..9400899 100644 --- a/python/vectors/nonce_gen_vectors.json +++ b/python/vectors/nonce_gen_vectors.json @@ -1,5 +1,5 @@ { - "test_cases": [ + "valid_test_cases": [ { "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", "secshare": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", @@ -43,6 +43,17 @@ "expected_secnonce": "E8E239B64F9A4D2B03508C029EEFC8156A3AD899FD58B15759C93C7DA745C3550FABE3F7CDD361407B97C1353056310D1610D478633C5DDE04DEC4917591D2E5", "expected_pubnonce": "0399059E50AF7B23F89E1ED7B17A7B24F2D746C663057F6C3B696A416C99C7A1070383C53B9CF236EADF8BDFEB1C3E9A188A1A84190687CD67916DF9BC60CD2D80EC", "comment": "Every optional parameter is absent" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "secshare": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", + "pubshare": "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", + "threshold_pubkey": "B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", + "msg": null, + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "A697FB5AEEAB824081C6A4CF126F7AD50DA3EEFC0810232DF4CA1A2C0F17683C58F976875309F1C4C07AF14A3FBC881BC768B0A6F161BD30D44F3129047B9A01", + "expected_pubnonce": "039512AFA92497D79DCD98712FB5149F60BCB580C5752D58B93B46D3A27F18FBED024206E9B03F38812C313B40C9D5435383CF140A3280F833A2EF6012E0CFB53287", + "comment": "Preprocessing: nonce generated before message is known" } ] } \ No newline at end of file diff --git a/python/vectors/sig_agg_vectors.json b/python/vectors/sig_agg_vectors.json index 0412e50..855b311 100644 --- a/python/vectors/sig_agg_vectors.json +++ b/python/vectors/sig_agg_vectors.json @@ -156,31 +156,6 @@ "contrib": "psig" }, "comment": "Partial signature is invalid because it exceeds group size" - }, - { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], - "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", - "tweak_indices": [], - "is_xonly": [], - "psigs": [ - "09CA21FA4AE22BBB49EE5ABC091519C8695FB74FC3D39C1437019EF6FE6C7AB4" - ], - "error": { - "type": "ValueError", - "message": "The psigs and ids arrays must have the same length." - }, - "comment": "Partial signature count doesn't match the signer set count" } ] } \ No newline at end of file diff --git a/python/vectors/sign_verify_vectors.json b/python/vectors/sign_verify_vectors.json index 5e28328..4bcfc3c 100644 --- a/python/vectors/sign_verify_vectors.json +++ b/python/vectors/sign_verify_vectors.json @@ -241,26 +241,6 @@ }, "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares." }, - { - "id_indices": [ - 0, - 1, - 2 - ], - "pubshare_indices": [ - 0, - 1 - ], - "aggnonce_index": 0, - "msg_index": 0, - "signer_index": 0, - "secnonce_index": 0, - "error": { - "type": "ValueError", - "message": "The pubshares and ids arrays must have the same length." - }, - "comment": "The participant identifiers count exceed the participant public shares count" - }, { "id_indices": [ 0, @@ -462,29 +442,6 @@ }, "comment": "Invalid pubshare" }, - { - "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1, - 2 - ], - "msg_index": 0, - "signer_index": 0, - "error": { - "type": "ValueError", - "message": "The pubnonces and ids arrays must have the same length." - }, - "comment": "public nonces count is greater than ids and pubshares" - }, { "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", "id_indices": [ diff --git a/python/vectors/test_vectors_summary.md b/python/vectors/test_vectors_summary.md index 7ae32ea..3a764bb 100644 --- a/python/vectors/test_vectors_summary.md +++ b/python/vectors/test_vectors_summary.md @@ -1,4 +1,4 @@ -# FROST Test Vectors +# FROST Signing Test Vectors This directory contains JSON test vectors for the BIP-FROST signing protocol. They are consumed by `python/tests.py` and are intended for @@ -6,9 +6,9 @@ cross-implementation compatibility testing. ## Files -| File | Function under test | Top-level shape | +| File name | Function under test | Structure | |---|---|---| -| `nonce_gen_vectors.json` | `nonce_gen_internal` | flat `test_cases[]` | +| `nonce_gen_vectors.json` | `nonce_gen_internal` | flat `valid_test_cases[]` | | `nonce_agg_vectors.json` | `nonce_agg` | shared `pubnonces[]` + `valid_test_cases` + `error_test_cases` | | `sign_verify_vectors.json` | `sign` and `partial_sig_verify` | shared setup + `valid_test_cases` + `sign_error_test_cases` + `verify_fail_test_cases` + `verify_error_test_cases` | | `tweak_vectors.json` | `sign` under tweak combinations | shared setup + `valid_test_cases` + `error_test_cases` | diff --git a/python/vectors/tweak_vectors.json b/python/vectors/tweak_vectors.json index 87e6d89..401a514 100644 --- a/python/vectors/tweak_vectors.json +++ b/python/vectors/tweak_vectors.json @@ -245,33 +245,6 @@ "message": "The tweak must be less than n." }, "comment": "Tweak is invalid because it exceeds group size" - }, - { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "tweak_indices": [ - 0, - 1, - 2, - 3 - ], - "aggnonce_index": 0, - "is_xonly": [ - true, - false - ], - "signer_index": 0, - "error": { - "type": "ValueError", - "message": "The tweaks and is_xonly arrays must have the same length." - }, - "comment": "Tweaks count doesn't match the tweak modes count" } ] } \ No newline at end of file From d8c2b64702eb71907a427f64f7347def2e226bc5 Mon Sep 17 00:00:00 2001 From: siv2r Date: Tue, 12 May 2026 20:37:25 +0530 Subject: [PATCH 2/4] gen_vectors: add more test coverage --- README.md | 1 + python/frost_ref/signing.py | 5 +- python/gen_vectors.py | 176 +++++++++++++++++++----- python/tests.py | 7 +- python/vectors/det_sign_vectors.json | 97 ++++++++++--- python/vectors/sign_verify_vectors.json | 144 ++++++++++++++----- python/vectors/test_vectors_summary.md | 34 ++++- python/vectors/tweak_vectors.json | 26 +++- 8 files changed, 400 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 04fd9a7..9d325f6 100644 --- a/README.md +++ b/README.md @@ -785,6 +785,7 @@ This document proposes a standard for the FROST threshold signature scheme that ## Changelog +- *0.4.3* (2026-05-13): Expand test vector coverage for signing and tweaking error cases, and add a test vectors summary document. - *0.4.2* (2026-04-14): Bind *my_id* and the signer identifiers list into the *DeterministicSign* nonce hash to prevent a secret share recovery attack via replayed signing sessions. - *0.4.1* (2026-03-03): Assign blame to signer index (of the input list) instead of their identifier value - *0.4.0* (2026-01-30): Number 445 was assigned to this BIP. diff --git a/python/frost_ref/signing.py b/python/frost_ref/signing.py index ce638f6..51a14f6 100644 --- a/python/frost_ref/signing.py +++ b/python/frost_ref/signing.py @@ -88,7 +88,9 @@ def validate_signers_ctx(signers_ctx: SignersContext) -> None: raise ValueError("The pubshares and ids arrays must have the same length.") for idx, (i, pubshare) in enumerate(zip(ids, pubshares)): if not 0 <= i <= n - 1: - raise ValueError(f"Invalid participant identifier at index {idx}.") + raise ValueError( + f"The participant identifier at index {idx} is out of range." + ) try: _ = GE.from_bytes_compressed(pubshare) except ValueError: @@ -376,7 +378,6 @@ def deterministic_sign( secshare_ = xor_bytes(secshare, tagged_hash("FROST/aux", rand)) else: secshare_ = secshare - # REVIEW: do we need to add any check for ids & pubshares (in signers_ctx context) here? validate_signers_ctx(signers_ctx) _, _, ids, _, thresh_pk = signers_ctx tweaked_tpk = get_xonly_pk(thresh_pubkey_and_tweak(thresh_pk, tweaks, is_xonly)) diff --git a/python/gen_vectors.py b/python/gen_vectors.py index 3641c07..6a9a855 100755 --- a/python/gen_vectors.py +++ b/python/gen_vectors.py @@ -18,8 +18,8 @@ partial_sig_verify, sign, ) -from frost_ref.signing import nonce_gen_internal -from secp256k1lab.secp256k1 import GE, Scalar +from frost_ref.signing import derive_interpolating_value, nonce_gen_internal +from secp256k1lab.secp256k1 import G, GE, Scalar from secp256k1lab.keys import pubkey_gen_plain from trusted_dealer import trusted_dealer_keygen @@ -396,6 +396,11 @@ def generate_sign_verify_vectors(): AGGNONCE_INVALID_TAG_IDX = 4 # Invalid tag 0x04 AGGNONCE_INVALID_XCOORD_IDX = 5 # Invalid X coordinate AGGNONCE_INVALID_EXCEEDS_FIELD_IDX = 6 # X exceeds field size + OUT_OF_RANGE_ID_IDX = 3 # identifier value n (=3), out of range [0, n-1] + + # Extend identifiers with an out-of-range value (n=3) for the L91 test case. + # Existing cases reference indices 0..2 only and are unaffected. + ids = ids + [n] vectors["n"] = n vectors["t"] = t @@ -414,6 +419,10 @@ def generate_sign_verify_vectors(): "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" ), # all zero ] + # valid first half (reused from index 0), zero second half. + k_2_zero_secnonce = secnonces_p0[0][0:32] + b"\x00" * 32 + assert Scalar.from_bytes_nonzero_checked(k_2_zero_secnonce[0:32]) + secnonces_p0.append(k_2_zero_secnonce) vectors["secnonces_p0"] = bytes_list_to_hex(secnonces_p0) # compute -(pubnonce[0] + pubnonce[1]) tmp = nonce_agg(pubnonces[:2]) @@ -564,15 +573,15 @@ def generate_sign_verify_vectors(): # --- Sign Error Test Cases --- error_cases = [ { - "ids": [2, 1], + "ids": [0, 1], "pubshares": [0, 1], "aggnonce": 0, "msg": 0, "signer_idx": None, - "signer_id": 0, + "signer_id": 2, "secnonce": 0, "error": "value", - "comment": "The signer's id is not in the participant identifier list", + "comment": "The signer's id (2) is not in the participant identifier list [0, 1].", }, { "ids": [0, 1, 1], @@ -585,14 +594,14 @@ def generate_sign_verify_vectors(): "comment": "The participant identifier list contains duplicate elements", }, { - "ids": [0, 1], - "pubshares": [2, 1], + "ids": [1, 2], + "pubshares": [1, 2], "aggnonce": 0, "msg": 0, "signer_idx": 0, "secnonce": 0, "error": "value", - "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares.", + "comment": "The signer's pubshare (derived from secshare_p0) is not in the pubshares list.", }, { "ids": [0, 1], @@ -604,6 +613,26 @@ def generate_sign_verify_vectors(): "error": "value", "comment": "Signer 1 provided an invalid participant public share", }, + { + "ids": [OUT_OF_RANGE_ID_IDX, 1], + "pubshares": [0, 1], + "aggnonce": 0, + "msg": 0, + "signer_idx": 1, + "secnonce": 0, + "error": "value", + "comment": "Participant identifier at index 0 has value 3, which is not in the valid range [0, n-1] = [0, 2]", + }, + { + "ids": [0, 1], + "pubshares": [0, 2], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "value", + "comment": "Derived threshold public key will be invalid", + }, { "ids": [0, 1], "pubshares": [0, 1], @@ -642,7 +671,38 @@ def generate_sign_verify_vectors(): "signer_idx": 0, "secnonce": SECNONCE_ZERO_IDX, "error": "value", - "comment": "Secnonce is invalid which may indicate nonce reuse", + "comment": "First-half secnonce out of range. May indicate nonce reuse.", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": 2, + "error": "value", + "comment": "Second-half secnonce out of range.", + }, + { + "ids": [0], + "pubshares": [0], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "error": "value", + "comment": "Number of signers is less than t.", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "aggnonce": 0, + "msg": 0, + "signer_idx": 0, + "secnonce": 0, + "secshare_zero": b"\x00" * 32, + "error": "value", + "comment": "Signer's secret share is zero, which is outside the valid range.", }, ] for case in error_cases: @@ -657,11 +717,13 @@ def generate_sign_verify_vectors(): curr_signers = SignersContext(n, t, curr_ids, curr_pubshares, thresh_pk) session_ctx = SessionContext(curr_aggnonce, curr_signers, [], [], curr_msg) curr_secnonce = bytearray(secnonces_p0[case["secnonce"]]) + curr_secshare = case.get("secshare_zero", secshare_p0) expected_error = ( ValueError if case["error"] == "value" else InvalidContributionError ) error = expect_exception( - lambda: sign(curr_secnonce, secshare_p0, my_id, session_ctx), expected_error + lambda: sign(curr_secnonce, curr_secshare, my_id, session_ctx), + expected_error, ) vectors["sign_error_test_cases"].append( { @@ -676,6 +738,11 @@ def generate_sign_verify_vectors(): else {} ), "secnonce_index": case["secnonce"], + **( + {"secshare_zero": case["secshare_zero"].hex().upper()} + if "secshare_zero" in case + else {} + ), "error": error, "comment": case["comment"], } @@ -762,15 +829,6 @@ def generate_sign_verify_vectors(): "error": "value", "comment": "Invalid pubshare", }, - { - "ids": [0, 1], - "pubshares": [2, 1], - "pubnonces": [0, 1], - "msg": 0, - "signer": 0, - "error": "value", - "comment": "The signer's pubshare is not in the list of pubshares", - }, ] for case in verify_error_cases: curr_ids = [ids[i] for i in case["ids"]] @@ -839,7 +897,24 @@ def generate_tweak_vectors(): ) vectors["aggnonces"] = bytes_list_to_hex(aggnonces) - vectors["tweaks"] = bytes_list_to_hex(COMMON_TWEAKS) + # Compute a plain tweak that drives Q + twk*G to the point at infinity. + # twk = -thresh_sk (mod n). + # Reconstruct thresh_sk via Lagrange interpolation + ids_sub = [0, 1] + lambda_0 = derive_interpolating_value(ids_sub, 0) + lambda_1 = derive_interpolating_value(ids_sub, 1) + s_0 = Scalar.from_bytes_checked(secshares[0]) + s_1 = Scalar.from_bytes_checked(secshares[1]) + thresh_sk = lambda_0 * s_0 + lambda_1 * s_1 + infinity_tweak_scalar = -thresh_sk + + # Q + infinity_tweak * G must be the point at infinity. + assert (GE.from_bytes_compressed(thresh_pk) + infinity_tweak_scalar * G).infinity + + INFINITY_TWEAK_IDX = len(COMMON_TWEAKS) # index 5 + all_tweaks = list(COMMON_TWEAKS) + [infinity_tweak_scalar.to_bytes()] + + vectors["tweaks"] = bytes_list_to_hex(all_tweaks) vectors["msg"] = bytes_to_hex(COMMON_MSGS[0]) vectors["valid_test_cases"] = [] @@ -877,7 +952,7 @@ def generate_tweak_vectors(): curr_pubshares = [pubshares_with_invalid[i] for i in indices] aggnonce_idx = case.get("aggnonce_idx", 0) curr_aggnonce = aggnonces[aggnonce_idx] - curr_tweaks = [COMMON_TWEAKS[i] for i in case["tweaks_indices"]] + curr_tweaks = [all_tweaks[i] for i in case["tweaks_indices"]] curr_tweak_modes = case["is_xonly"] signer_idx = 0 my_id = curr_ids[signer_idx] @@ -910,6 +985,11 @@ def generate_tweak_vectors(): "is_xonly": [False], "comment": "Tweak is invalid because it exceeds group size", }, + { + "tweaks_indices": [INFINITY_TWEAK_IDX], + "is_xonly": [False], + "comment": "Tweaked threshold public key is the point at infinity", + }, ] for case in error_cases: indices = [0, 1] @@ -917,7 +997,7 @@ def generate_tweak_vectors(): curr_pubshares = [pubshares_with_invalid[i] for i in indices] aggnonce_idx = 0 curr_aggnonce = aggnonces[aggnonce_idx] - curr_tweaks = [COMMON_TWEAKS[i] for i in case["tweaks_indices"]] + curr_tweaks = [all_tweaks[i] for i in case["tweaks_indices"]] curr_tweak_modes = case["is_xonly"] signer_idx = 0 my_id = curr_ids[signer_idx] @@ -999,7 +1079,7 @@ def generate_det_sign_vectors(): "signer": 1, "msg": 0, "rand": 0, - "comment": "Partial signature and secnonce are invariant under reordering of ids (serialize_ids sorts first).", + "comment": "Partial signature and secnonce are invariant under reordering of ids (serialize_ids sorts first). Expected values match the previous vector.", }, { "indices": [0, 2], @@ -1066,8 +1146,8 @@ def generate_det_sign_vectors(): # generate `aggothernonce` other_pubnonces = [] - for i in case["indices"]: - if i == signer_index: + for pos, i in enumerate(case["indices"]): + if signer_index is not None and pos == signer_index: continue tmp = b"" if curr_rand is None else curr_rand _, pub = nonce_gen_internal( @@ -1107,15 +1187,14 @@ def generate_det_sign_vectors(): # --- Error Test Cases --- error_cases = [ { - "ids": [2, 1], + "ids": [0, 1], "pubshares": [0, 1], "signer_idx": None, - "signer_id": 0, + "signer_id": 2, "msg": 0, "rand": 0, - "aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9", "error": "value", - "comment": "The signer's id is not in the participant identifier list", + "comment": "The signer's id (2) is not in the participant identifier list [0, 1].", }, { "ids": [0, 1, 1], @@ -1127,13 +1206,13 @@ def generate_det_sign_vectors(): "comment": "The participant identifier list contains duplicate elements", }, { - "ids": [0, 1], - "pubshares": [2, 1], + "ids": [1, 2], + "pubshares": [1, 2], "signer_idx": 0, "msg": 0, "rand": 0, "error": "value", - "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares.", + "comment": "The signer's pubshare (derived from secshare_p0) is not in the pubshares list.", }, { "ids": [0, 1], @@ -1144,6 +1223,15 @@ def generate_det_sign_vectors(): "error": "value", "comment": "Signer 1 provided an invalid participant public share", }, + { + "ids": [2, 1], + "pubshares": [0, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "error": "value", + "comment": "Derived threshold public key will be invalid.", + }, { "ids": [0, 1], "pubshares": [0, 1], @@ -1164,6 +1252,26 @@ def generate_det_sign_vectors(): "error": "invalid_contrib", "comment": "aggothernonce is invalid because first half corresponds to point at infinity", }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC020000000000000000000000000000000000000000000000000000000000000009", + "error": "invalid_contrib", + "comment": "aggothernonce is invalid because the second half does not correspond to an X coordinate", + }, + { + "ids": [0, 1], + "pubshares": [0, 1], + "signer_idx": 0, + "msg": 0, + "rand": 0, + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", + "error": "invalid_contrib", + "comment": "aggothernonce is invalid because the second half x coordinate is greater than or equal to the field prime", + }, { "ids": [0, 1], "pubshares": [0, 1], @@ -1194,8 +1302,8 @@ def generate_det_sign_vectors(): is_aggothernonce = case.get("aggothernonce", None) if is_aggothernonce is None: other_pubnonces = [] - for i in case["ids"]: - if i == signer_index: + for pos, i in enumerate(case["ids"]): + if signer_index is not None and pos == signer_index: continue tmp = b"" if curr_rand is None else curr_rand _, pub = nonce_gen_internal( diff --git a/python/tests.py b/python/tests.py index f54d704..075382e 100755 --- a/python/tests.py +++ b/python/tests.py @@ -213,12 +213,17 @@ def test_sign_verify_vectors(): test_case["signer_id"] if signer_index is None else ids_tmp[signer_index] ) secnonce_tmp = bytearray(secnonces_p0[test_case["secnonce_index"]]) + secshare_tmp = ( + bytes.fromhex(test_case["secshare_zero"]) + if "secshare_zero" in test_case + else secshare_p0 + ) signers_tmp = SignersContext(n, t, ids_tmp, pubshares_tmp, thresh_pk) session_ctx = SessionContext(aggnonce_tmp, signers_tmp, [], [], msg) assert_raises( exception, - lambda: sign(secnonce_tmp, secshare_p0, my_id, session_ctx), + lambda: sign(secnonce_tmp, secshare_tmp, my_id, session_ctx), except_fn, ) diff --git a/python/vectors/det_sign_vectors.json b/python/vectors/det_sign_vectors.json index b0f7179..e4074ae 100644 --- a/python/vectors/det_sign_vectors.json +++ b/python/vectors/det_sign_vectors.json @@ -43,7 +43,7 @@ }, { "rand": "0000000000000000000000000000000000000000000000000000000000000000", - "aggothernonce": "02A8B5F064871F3BBB06D325F5B4A2B51487E0AE24F14E2A121C39B9F7CBDE7474038161382177105511164E63DD2C73138EDB271CF11B922DBA54CA4A9B365EDB55", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", "id_indices": [ 1, 0 @@ -57,10 +57,10 @@ "msg_index": 0, "signer_index": 1, "expected": [ - "02F03FDF7CEB6C07417E151323E3D0A4B918D20EE146270F7531C6AFB890FC289C029EE54112F718D87C2D4DBEF33D173F533EEF9C58E2382EE05696160878038A84", - "E3F4282E3163F8C5525FCB34B2C29FF9CC5125E800946C4C3C8E9DC63E3FFC01" + "03822691A3FD135A5880A12E92ED62F3ECF40BC7AA0339ED2D01FBB3B44F97EE43022215BA386A89E39EE4BAC1B201C69B8706000C402F89D81799B62AAA302F0C0C", + "0A81D292AB93FC55EC047619E06A73AA41D20C0A44FF4C19B9F5052BB23F7BE1" ], - "comment": "Partial signature and secnonce are invariant under reordering of ids (serialize_ids sorts first)." + "comment": "Partial signature and secnonce are invariant under reordering of ids (serialize_ids sorts first). Expected values match the previous vector." }, { "rand": "0000000000000000000000000000000000000000000000000000000000000000", @@ -219,9 +219,9 @@ "error_test_cases": [ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", - "aggothernonce": "02FCDBEE416E4426FB4004BAB2B416164845DEC27337AD2B96184236D715965AB2039F71F389F6808DC6176F062F80531E13EA5BC2612B690FC284AE66C2CD859CE9", + "aggothernonce": "02DCE719104EE4D5E969FB5BEDB016D55B19A874A3A5EF4506E414EAE1998437D603044D6D6536C2C44136628131E6B3F4A2197E3A0BE2A4E908636B2CDB455DB018", "id_indices": [ - 2, + 0, 1 ], "pubshare_indices": [ @@ -232,12 +232,12 @@ "is_xonly": [], "msg_index": 0, "signer_index": null, - "signer_id": 0, + "signer_id": 2, "error": { "type": "ValueError", - "message": "The provided key material is incorrect." + "message": "The signer's id must be present in the participant identifier list." }, - "comment": "The signer's id is not in the participant identifier list" + "comment": "The signer's id (2) is not in the participant identifier list [0, 1]." }, { "rand": "0000000000000000000000000000000000000000000000000000000000000000", @@ -264,14 +264,14 @@ }, { "rand": "0000000000000000000000000000000000000000000000000000000000000000", - "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", + "aggothernonce": "022260912C9999C9EC5A3B8E493F5DEB76DCA3E772345905E12C24D281612DA403022508B1A355D6E94A5BB239441B38971716031EF05DE9AF952BAB69799621AE52", "id_indices": [ - 0, - 1 + 1, + 2 ], "pubshare_indices": [ - 2, - 1 + 1, + 2 ], "tweaks": [], "is_xonly": [], @@ -279,9 +279,9 @@ "signer_index": 0, "error": { "type": "ValueError", - "message": "The provided key material is incorrect." + "message": "The signer's pubshare must be included in the list of pubshares." }, - "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares." + "comment": "The signer's pubshare (derived from secshare_p0) is not in the pubshares list." }, { "rand": "0000000000000000000000000000000000000000000000000000000000000000", @@ -304,6 +304,27 @@ }, "comment": "Signer 1 provided an invalid participant public share" }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", + "id_indices": [ + 2, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The provided key material is incorrect." + }, + "comment": "Derived threshold public key will be invalid." + }, { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", @@ -348,6 +369,50 @@ }, "comment": "aggothernonce is invalid because first half corresponds to point at infinity" }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC020000000000000000000000000000000000000000000000000000000000000009", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "InvalidContributionError", + "signer_index": null, + "contrib": "aggothernonce" + }, + "comment": "aggothernonce is invalid because the second half does not correspond to an X coordinate" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "InvalidContributionError", + "signer_index": null, + "contrib": "aggothernonce" + }, + "comment": "aggothernonce is invalid because the second half x coordinate is greater than or equal to the field prime" + }, { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", diff --git a/python/vectors/sign_verify_vectors.json b/python/vectors/sign_verify_vectors.json index 4bcfc3c..c09a0b8 100644 --- a/python/vectors/sign_verify_vectors.json +++ b/python/vectors/sign_verify_vectors.json @@ -6,7 +6,8 @@ "identifiers": [ 0, 1, - 2 + 2, + 3 ], "pubshares": [ "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", @@ -16,7 +17,8 @@ ], "secnonces_p0": [ "DB26CEB14C1CF111274574860A4667E3305B9C8D47E48861562445CF2E7D2277D17751A6F6972FD753CF2B2784CF5193ADBEA4DA066526D0A9984E9C1C07179F", - "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "DB26CEB14C1CF111274574860A4667E3305B9C8D47E48861562445CF2E7D22770000000000000000000000000000000000000000000000000000000000000000" ], "pubnonces": [ "0330935948101543C50AF2FA7A7A4F7073CEB73290CA141497EF06E0269363162D0358785EB5CD7C1626CAB55C59B484E1B3147FA4EB919224ECB04BAB1271022A5C", @@ -183,7 +185,7 @@ "sign_error_test_cases": [ { "id_indices": [ - 2, + 0, 1 ], "pubshare_indices": [ @@ -193,13 +195,13 @@ "aggnonce_index": 0, "msg_index": 0, "signer_index": null, - "signer_id": 0, + "signer_id": 2, "secnonce_index": 0, "error": { "type": "ValueError", - "message": "The provided key material is incorrect." + "message": "The signer's id must be present in the participant identifier list." }, - "comment": "The signer's id is not in the participant identifier list" + "comment": "The signer's id (2) is not in the participant identifier list [0, 1]." }, { "id_indices": [ @@ -224,12 +226,12 @@ }, { "id_indices": [ - 0, - 1 + 1, + 2 ], "pubshare_indices": [ - 2, - 1 + 1, + 2 ], "aggnonce_index": 0, "msg_index": 0, @@ -237,9 +239,9 @@ "secnonce_index": 0, "error": { "type": "ValueError", - "message": "The provided key material is incorrect." + "message": "The signer's pubshare must be included in the list of pubshares." }, - "comment": "The signer's pubshare is not in the list of pubshares. This test case is optional: it can be skipped by implementations that do not check that the signer's pubshare is included in the list of pubshares." + "comment": "The signer's pubshare (derived from secshare_p0) is not in the pubshares list." }, { "id_indices": [ @@ -260,6 +262,44 @@ }, "comment": "Signer 1 provided an invalid participant public share" }, + { + "id_indices": [ + 3, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 1, + "secnonce_index": 0, + "error": { + "type": "ValueError", + "message": "The participant identifier at index 0 is out of range." + }, + "comment": "Participant identifier at index 0 has value 3, which is not in the valid range [0, n-1] = [0, 2]" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 2 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "ValueError", + "message": "The provided key material is incorrect." + }, + "comment": "Derived threshold public key will be invalid" + }, { "id_indices": [ 0, @@ -337,7 +377,63 @@ "type": "ValueError", "message": "first secnonce value is out of range." }, - "comment": "Secnonce is invalid which may indicate nonce reuse" + "comment": "First-half secnonce out of range. May indicate nonce reuse." + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 2, + "error": { + "type": "ValueError", + "message": "second secnonce value is out of range." + }, + "comment": "Second-half secnonce out of range." + }, + { + "id_indices": [ + 0 + ], + "pubshare_indices": [ + 0 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "error": { + "type": "ValueError", + "message": "The number of signers must be between t and n." + }, + "comment": "Number of signers is less than t." + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 0, + "secshare_zero": "0000000000000000000000000000000000000000000000000000000000000000", + "error": { + "type": "ValueError", + "message": "The signer's secret share value is out of range." + }, + "comment": "Signer's secret share is zero, which is outside the valid range." } ], "verify_fail_test_cases": [ @@ -441,28 +537,6 @@ "message": "Invalid pubshare at index 0." }, "comment": "Invalid pubshare" - }, - { - "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 2, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], - "msg_index": 0, - "signer_index": 0, - "error": { - "type": "ValueError", - "message": "The provided key material is incorrect." - }, - "comment": "The signer's pubshare is not in the list of pubshares" } ] } \ No newline at end of file diff --git a/python/vectors/test_vectors_summary.md b/python/vectors/test_vectors_summary.md index 3a764bb..84238cb 100644 --- a/python/vectors/test_vectors_summary.md +++ b/python/vectors/test_vectors_summary.md @@ -25,7 +25,7 @@ reuses the same FROST key material, defined by `frost_keygen_fixed()` in 03B026…D69237`, `identifiers = [0, 1, 2]`. Three real pubshares are followed by a fourth bogus pubshare (`0200…0007`) used to drive "invalid pubshare" error cases. Only the first signer's secret share -(`secshare_p0`) is exposed — all valid cases sign from signer 0's +(`secshare_p0`) is exposed; all valid cases sign from signer 0's perspective (or signer 1 in a reorder-invariance case). `secnonces_p0` may include extra entries (e.g. an all-zero entry) used exclusively to drive nonce-reuse / out-of-range error cases. Sharing the same @@ -72,3 +72,35 @@ faulty contribution is aggregator-level (e.g. an `aggnonce` or exercises a signer whose id is *not* present in the participant list, the case sets `signer_index: null` and provides `signer_id` directly, since the id cannot be looked up by index. + +### Shared nonce pools and special entries + +The `secnonces_p0` pool in `sign_verify_vectors.json` and related files +contains multiple entries to exercise different code paths. Beyond the +standard nonces, the pool includes special entries with specific invalid +properties: an all-zero entry used to drive nonce-reuse errors, and a +k_2 sibling entry where the second half of the secret nonce is zero, +used to validate out-of-range nonce component detection. + +### Optional per-case field overrides + +`sign_verify_vectors.json::sign_error_test_cases` includes one case with +an optional `secshare_zero` field that overrides the default signer's +secret share. This field appears only on the zero-valued-secshare error +case, where the hex-encoded value (64 zeros) drives the expected +validation error. The field is hex-encoded to match the format of the +top-level `secshare_p0` pool. All other cases omit this field and use +the indexed share from the pool. + +### Recent test vector expansions + +The `sign_verify_vectors.json::sign_error_test_cases` have been expanded +from 10 to 13 entries to cover additional error conditions: detection of +fewer-than-t signers in the participant list, validation of the optional +secshare override field when a secret share equals zero, and detection +of out-of-range values in the second component of the secret nonce (k_2). + +Similarly, `tweak_vectors.json::error_test_cases` was expanded from 1 to +2 entries to validate that tweaking operations detect and reject the case +where the tweaked threshold public key evaluates to the point at infinity, +which would make the signature operation impossible. diff --git a/python/vectors/tweak_vectors.json b/python/vectors/tweak_vectors.json index 401a514..cb71855 100644 --- a/python/vectors/tweak_vectors.json +++ b/python/vectors/tweak_vectors.json @@ -30,7 +30,8 @@ "AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455", "F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0", "1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D", - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "C8FA70D93D4F99492E01B34792EF6CEE74F9C216E5FE3CC80D2065B3C4C8BD5A" ], "msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", "valid_test_cases": [ @@ -245,6 +246,29 @@ "message": "The tweak must be less than n." }, "comment": "Tweak is invalid because it exceeds group size" + }, + { + "id_indices": [ + 0, + 1 + ], + "pubshare_indices": [ + 0, + 1 + ], + "tweak_indices": [ + 5 + ], + "aggnonce_index": 0, + "is_xonly": [ + false + ], + "signer_index": 0, + "error": { + "type": "ValueError", + "message": "The result of tweaking cannot be infinity." + }, + "comment": "Tweaked threshold public key is the point at infinity" } ] } \ No newline at end of file From 5228668f0cb81854d3d18fa0aae63d6127df590f Mon Sep 17 00:00:00 2001 From: siv2r Date: Wed, 13 May 2026 20:53:00 +0530 Subject: [PATCH 3/4] gen_vectors: make small json arrays inline --- python/gen_vectors.py | 36 ++- python/vectors/det_sign_vectors.json | 208 +++------------ python/vectors/nonce_agg_vectors.json | 25 +- python/vectors/sig_agg_vectors.json | 96 ++----- python/vectors/sign_verify_vectors.json | 323 +++++------------------- python/vectors/tweak_vectors.json | 218 +++------------- 6 files changed, 192 insertions(+), 714 deletions(-) diff --git a/python/gen_vectors.py b/python/gen_vectors.py index 6a9a855..daf68c2 100755 --- a/python/gen_vectors.py +++ b/python/gen_vectors.py @@ -3,6 +3,7 @@ import glob import json import os +import re import sys from typing import Dict, List, Sequence, Union import secrets @@ -111,10 +112,22 @@ def expect_exception(try_fn, expected_exception): ) +_SCALAR_TOKEN = r"-?\d+|true|false|null" +_SCALAR_ARRAY_RE = re.compile( + rf"\[\s*(?:(?:{_SCALAR_TOKEN})(?:\s*,\s*(?:{_SCALAR_TOKEN}))*)?\s*\]" +) + + +def _inline_scalar_array(match): + tokens = re.findall(_SCALAR_TOKEN, match.group(0)) + return "[" + ", ".join(tokens) + "]" + + def write_test_vectors(filename, vectors): output_file = os.path.join("vectors", filename) + text = _SCALAR_ARRAY_RE.sub(_inline_scalar_array, json.dumps(vectors, indent=4)) with open(output_file, "w") as f: - json.dump(vectors, f, indent=4) + f.write(text) def get_common_setup(): @@ -166,6 +179,13 @@ def frost_keygen_fixed(): return (t, n, thresh_pubkey_ge, secshares, pubshares) +def reconstruct_thresh_sk(ids, secshares): + result = Scalar(0) + for i, s in zip(ids, secshares): + result = result + derive_interpolating_value(ids, i) * Scalar.from_bytes_checked(s) + return result + + # NOTE: This function is used only once to generate a long-term key for frost_keygen_fixed(). It is intentionally not called anywhere else. It will be used in case we decide to change the long-term key, in future. def frost_keygen_random(): random_scalar = Scalar.from_bytes_nonzero_checked(secrets.token_bytes(32)) @@ -897,18 +917,8 @@ def generate_tweak_vectors(): ) vectors["aggnonces"] = bytes_list_to_hex(aggnonces) - # Compute a plain tweak that drives Q + twk*G to the point at infinity. - # twk = -thresh_sk (mod n). - # Reconstruct thresh_sk via Lagrange interpolation - ids_sub = [0, 1] - lambda_0 = derive_interpolating_value(ids_sub, 0) - lambda_1 = derive_interpolating_value(ids_sub, 1) - s_0 = Scalar.from_bytes_checked(secshares[0]) - s_1 = Scalar.from_bytes_checked(secshares[1]) - thresh_sk = lambda_0 * s_0 + lambda_1 * s_1 - infinity_tweak_scalar = -thresh_sk - - # Q + infinity_tweak * G must be the point at infinity. + # Compute a plain tweak that drives Q + twk*G to the point at infinity: twk = -thresh_sk. + infinity_tweak_scalar = -reconstruct_thresh_sk([0, 1], secshares[:2]) assert (GE.from_bytes_compressed(thresh_pk) + infinity_tweak_scalar * G).infinity INFINITY_TWEAK_IDX = len(COMMON_TWEAKS) # index 5 diff --git a/python/vectors/det_sign_vectors.json b/python/vectors/det_sign_vectors.json index e4074ae..43785f3 100644 --- a/python/vectors/det_sign_vectors.json +++ b/python/vectors/det_sign_vectors.json @@ -3,11 +3,7 @@ "t": 2, "threshold_pubkey": "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", "secshare_p0": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", - "identifiers": [ - 0, - 1, - 2 - ], + "identifiers": [0, 1, 2], "pubshares": [ "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", @@ -23,14 +19,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -44,14 +34,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", - "id_indices": [ - 1, - 0 - ], - "pubshare_indices": [ - 1, - 0 - ], + "id_indices": [1, 0], + "pubshare_indices": [1, 0], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -65,14 +49,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "022260912C9999C9EC5A3B8E493F5DEB76DCA3E772345905E12C24D281612DA403022508B1A355D6E94A5BB239441B38971716031EF05DE9AF952BAB69799621AE52", - "id_indices": [ - 0, - 2 - ], - "pubshare_indices": [ - 0, - 2 - ], + "id_indices": [0, 2], + "pubshare_indices": [0, 2], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -86,14 +64,8 @@ { "rand": null, "aggothernonce": "0220E5B590F7058B5E88593C8635411767B416EB53378AEE7E40CD1D35329AD2C302164F00762CDFFF9138C43884661CC93FF53D61B4BFD3CD2BD7265F41775A4F0F", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -107,14 +79,8 @@ { "rand": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "aggothernonce": "0258091883DA3CCA616F8BEC80AAE2E397E3572DFFC3C261EDC695E5FCA0BE00180251EC60E426128FA2370F13A6235040120CCDB0CA97C36BC63693C44FC5B73E44", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -128,16 +94,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "03B7F17DD6A2495898D19BDEA05C46F45B2C14BCA17F61C3DDC1D3C087CD748AB002CF7D031F5075C164D6A9D713F03B56422FD3472BDC8E0E6BB3ED6B6ED9C529AE", - "id_indices": [ - 0, - 1, - 2 - ], - "pubshare_indices": [ - 0, - 1, - 2 - ], + "id_indices": [0, 1, 2], + "pubshare_indices": [0, 1, 2], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -151,14 +109,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "02E67053C8D9A6754D8243EF13B722B4909F3037FEAB23600D019E1C307D0BA2E20381207D01C5D6F4E2E654D2CBD23C9B6E8C38407FECAC41571E0EA2B307634004", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 1, @@ -172,14 +124,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "03C71201804A346CCAF2733EB9701302FA50F4C99B053D23C20363ECE2E05DEE97027D4DBE90EC16ACA95DBC921F4BD3FBDBCF1D9F3D01627F6077BE6BD7DAEDB627", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 2, @@ -193,20 +139,12 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [ "E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB" ], - "is_xonly": [ - true - ], + "is_xonly": [true], "msg_index": 0, "signer_index": 0, "expected": [ @@ -220,14 +158,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "02DCE719104EE4D5E969FB5BEDB016D55B19A874A3A5EF4506E414EAE1998437D603044D6D6536C2C44136628131E6B3F4A2197E3A0BE2A4E908636B2CDB455DB018", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -242,16 +174,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "02C1D8D5D95E15E4B46B49AF7A309520B0D07E5386995B99A572440C658FE443DF028A89044AE2FFC00131089E7B1EB15FE8DF52282F44D5EE2FA25F0DF437082407", - "id_indices": [ - 0, - 1, - 1 - ], - "pubshare_indices": [ - 0, - 1, - 1 - ], + "id_indices": [0, 1, 1], + "pubshare_indices": [0, 1, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -265,14 +189,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "022260912C9999C9EC5A3B8E493F5DEB76DCA3E772345905E12C24D281612DA403022508B1A355D6E94A5BB239441B38971716031EF05DE9AF952BAB69799621AE52", - "id_indices": [ - 1, - 2 - ], - "pubshare_indices": [ - 1, - 2 - ], + "id_indices": [1, 2], + "pubshare_indices": [1, 2], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -286,14 +204,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 3 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 3], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -307,14 +219,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", - "id_indices": [ - 2, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [2, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -328,14 +234,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -350,14 +250,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0000000000000000000000000000000000000000000000000000000000000000000287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -372,14 +266,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC020000000000000000000000000000000000000000000000000000000000000009", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -394,14 +282,8 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [], "is_xonly": [], "msg_index": 0, @@ -416,20 +298,12 @@ { "rand": "0000000000000000000000000000000000000000000000000000000000000000", "aggothernonce": "0353BC2314D46C813AF81317AF1BDF99816B6444E416BB8D3DC04ACB2F5388D1AC02B13BC644F720223B547DB344C94E0F5E769B674D8A9C3F5E86A5231A5B9C3297", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "tweaks": [ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" ], - "is_xonly": [ - false - ], + "is_xonly": [false], "msg_index": 0, "signer_index": 0, "error": { diff --git a/python/vectors/nonce_agg_vectors.json b/python/vectors/nonce_agg_vectors.json index e6bd2fe..ada012c 100644 --- a/python/vectors/nonce_agg_vectors.json +++ b/python/vectors/nonce_agg_vectors.json @@ -10,27 +10,18 @@ ], "valid_test_cases": [ { - "pubnonce_indices": [ - 0, - 1 - ], + "pubnonce_indices": [0, 1], "expected_aggnonce": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8" }, { - "pubnonce_indices": [ - 2, - 3 - ], + "pubnonce_indices": [2, 3], "expected_aggnonce": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000", "comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes" } ], "error_test_cases": [ { - "pubnonce_indices": [ - 0, - 4 - ], + "pubnonce_indices": [0, 4], "error": { "type": "InvalidContributionError", "signer_index": 1, @@ -39,10 +30,7 @@ "comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half" }, { - "pubnonce_indices": [ - 5, - 1 - ], + "pubnonce_indices": [5, 1], "error": { "type": "InvalidContributionError", "signer_index": 0, @@ -51,10 +39,7 @@ "comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate" }, { - "pubnonce_indices": [ - 6, - 1 - ], + "pubnonce_indices": [6, 1], "error": { "type": "InvalidContributionError", "signer_index": 0, diff --git a/python/vectors/sig_agg_vectors.json b/python/vectors/sig_agg_vectors.json index 855b311..dfa2aea 100644 --- a/python/vectors/sig_agg_vectors.json +++ b/python/vectors/sig_agg_vectors.json @@ -2,11 +2,7 @@ "n": 3, "t": 2, "threshold_pubkey": "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", - "identifiers": [ - 0, - 1, - 2 - ], + "identifiers": [0, 1, 2], "pubshares": [ "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", @@ -25,18 +21,9 @@ "msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869", "valid_test_cases": [ { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", "tweak_indices": [], "is_xonly": [], @@ -48,18 +35,9 @@ "comment": "Signing with minimum number of participants" }, { - "id_indices": [ - 1, - 0 - ], - "pubshare_indices": [ - 1, - 0 - ], - "pubnonce_indices": [ - 1, - 0 - ], + "id_indices": [1, 0], + "pubshare_indices": [1, 0], + "pubnonce_indices": [1, 0], "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", "tweak_indices": [], "is_xonly": [], @@ -71,29 +49,12 @@ "comment": "Order of the singer set shouldn't affect the aggregate signature. The expected value must match the previous test vector." }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", - "tweak_indices": [ - 0, - 1, - 2 - ], - "is_xonly": [ - true, - false, - false - ], + "tweak_indices": [0, 1, 2], + "is_xonly": [true, false, false], "psigs": [ "19689B0B3F32C7696FF2C90CF1E6DC8311F96EC7D6307EF56085C78C3FF6C828", "B7290A65BE76B17C8756D77EF0395D5F219CDF7EA8D63886132C9011938F241C" @@ -102,21 +63,9 @@ "comment": "Signing with tweaked threshold public key" }, { - "id_indices": [ - 0, - 1, - 2 - ], - "pubshare_indices": [ - 0, - 1, - 2 - ], - "pubnonce_indices": [ - 0, - 1, - 2 - ], + "id_indices": [0, 1, 2], + "pubshare_indices": [0, 1, 2], + "pubnonce_indices": [0, 1, 2], "aggnonce": "0282E58B733AB1B74D1C54B960D668E4298C3EF9F406D44249FB30C403568A1B14039B38604D0FD33E07C3EB81BBDAE39A38A82A1D9E325112A24F0F5480582C9CEE", "tweak_indices": [], "is_xonly": [], @@ -131,18 +80,9 @@ ], "error_test_cases": [ { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "aggnonce": "022AAC6A4960DE35FC36D8E2DC06255C5CB7FD28250DFD84EBF1AC943B1EA22C3502178AD06BB0490BAD857446FEF55C15FD9FF4329F4EE2F23CA8B7CA0598014910", "tweak_indices": [], "is_xonly": [], diff --git a/python/vectors/sign_verify_vectors.json b/python/vectors/sign_verify_vectors.json index c09a0b8..008a95b 100644 --- a/python/vectors/sign_verify_vectors.json +++ b/python/vectors/sign_verify_vectors.json @@ -3,12 +3,7 @@ "t": 2, "threshold_pubkey": "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", "secshare_p0": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", - "identifiers": [ - 0, - 1, - 2, - 3 - ], + "identifiers": [0, 1, 2, 3], "pubshares": [ "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", @@ -43,18 +38,9 @@ ], "valid_test_cases": [ { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -62,18 +48,9 @@ "comment": "Signing with minimum number of participants" }, { - "id_indices": [ - 1, - 0 - ], - "pubshare_indices": [ - 1, - 0 - ], - "pubnonce_indices": [ - 1, - 0 - ], + "id_indices": [1, 0], + "pubshare_indices": [1, 0], + "pubnonce_indices": [1, 0], "aggnonce_index": 0, "msg_index": 0, "signer_index": 1, @@ -81,18 +58,9 @@ "comment": "Partial-signature doesn't change if the order of signers set changes (without changing secnonces)" }, { - "id_indices": [ - 0, - 2 - ], - "pubshare_indices": [ - 0, - 2 - ], - "pubnonce_indices": [ - 0, - 2 - ], + "id_indices": [0, 2], + "pubshare_indices": [0, 2], + "pubnonce_indices": [0, 2], "aggnonce_index": 1, "msg_index": 0, "signer_index": 0, @@ -100,21 +68,9 @@ "comment": "Partial-signature changes if the members of signers set changes" }, { - "id_indices": [ - 0, - 1, - 2 - ], - "pubshare_indices": [ - 0, - 1, - 2 - ], - "pubnonce_indices": [ - 0, - 1, - 2 - ], + "id_indices": [0, 1, 2], + "pubshare_indices": [0, 1, 2], + "pubnonce_indices": [0, 1, 2], "aggnonce_index": 2, "msg_index": 0, "signer_index": 0, @@ -122,21 +78,9 @@ "comment": "Signing with max number of participants" }, { - "id_indices": [ - 0, - 1, - 2 - ], - "pubshare_indices": [ - 0, - 1, - 2 - ], - "pubnonce_indices": [ - 0, - 1, - 4 - ], + "id_indices": [0, 1, 2], + "pubshare_indices": [0, 1, 2], + "pubnonce_indices": [0, 1, 4], "aggnonce_index": 3, "msg_index": 0, "signer_index": 0, @@ -144,18 +88,9 @@ "comment": "Both halves of aggregate nonce correspond to point at infinity" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "aggnonce_index": 0, "msg_index": 1, "signer_index": 0, @@ -163,18 +98,9 @@ "comment": "Empty message" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "aggnonce_index": 0, "msg_index": 2, "signer_index": 0, @@ -184,14 +110,8 @@ ], "sign_error_test_cases": [ { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "aggnonce_index": 0, "msg_index": 0, "signer_index": null, @@ -204,16 +124,8 @@ "comment": "The signer's id (2) is not in the participant identifier list [0, 1]." }, { - "id_indices": [ - 0, - 1, - 1 - ], - "pubshare_indices": [ - 0, - 1, - 1 - ], + "id_indices": [0, 1, 1], + "pubshare_indices": [0, 1, 1], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -225,14 +137,8 @@ "comment": "The participant identifier list contains duplicate elements" }, { - "id_indices": [ - 1, - 2 - ], - "pubshare_indices": [ - 1, - 2 - ], + "id_indices": [1, 2], + "pubshare_indices": [1, 2], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -244,14 +150,8 @@ "comment": "The signer's pubshare (derived from secshare_p0) is not in the pubshares list." }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 3 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 3], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -263,14 +163,8 @@ "comment": "Signer 1 provided an invalid participant public share" }, { - "id_indices": [ - 3, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [3, 1], + "pubshare_indices": [0, 1], "aggnonce_index": 0, "msg_index": 0, "signer_index": 1, @@ -282,14 +176,8 @@ "comment": "Participant identifier at index 0 has value 3, which is not in the valid range [0, n-1] = [0, 2]" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 2 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 2], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -301,14 +189,8 @@ "comment": "Derived threshold public key will be invalid" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "aggnonce_index": 4, "msg_index": 0, "signer_index": 0, @@ -321,14 +203,8 @@ "comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "aggnonce_index": 5, "msg_index": 0, "signer_index": 0, @@ -341,14 +217,8 @@ "comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "aggnonce_index": 6, "msg_index": 0, "signer_index": 0, @@ -361,14 +231,8 @@ "comment": "Aggregate nonce is invalid because second half exceeds field size" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -380,14 +244,8 @@ "comment": "First-half secnonce out of range. May indicate nonce reuse." }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -399,12 +257,8 @@ "comment": "Second-half secnonce out of range." }, { - "id_indices": [ - 0 - ], - "pubshare_indices": [ - 0 - ], + "id_indices": [0], + "pubshare_indices": [0], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -416,14 +270,8 @@ "comment": "Number of signers is less than t." }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], "aggnonce_index": 0, "msg_index": 0, "signer_index": 0, @@ -439,54 +287,27 @@ "verify_fail_test_cases": [ { "psig": "86210398630BE64583B750706AD94A29AA0438D55443C16DE1C18FEECA25EE0D", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "msg_index": 0, "signer_index": 0, "comment": "Wrong signature (which is equal to the negation of valid signature)" }, { "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "msg_index": 0, "signer_index": 1, "comment": "Wrong signer index" }, { "psig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "msg_index": 0, "signer_index": 0, "comment": "Signature value is out of range" @@ -495,18 +316,9 @@ "verify_error_test_cases": [ { "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 3, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [3, 1], "msg_index": 0, "signer_index": 0, "error": { @@ -518,18 +330,9 @@ }, { "psig": "79DEFC679CF419BA7C48AF8F9526B5D510AAA4115B04DECDDE10CE9E06105334", - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 3, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [3, 1], + "pubnonce_indices": [0, 1], "msg_index": 0, "signer_index": 0, "error": { diff --git a/python/vectors/tweak_vectors.json b/python/vectors/tweak_vectors.json index cb71855..4be20c7 100644 --- a/python/vectors/tweak_vectors.json +++ b/python/vectors/tweak_vectors.json @@ -3,11 +3,7 @@ "t": 2, "threshold_pubkey": "03B02645D79ABFC494338139410F9D7F0A72BE86C952D6BDE1A66447B8A8D69237", "secshare_p0": "CCD2EF4559DB05635091D80189AB3544D6668EFC0500A8D5FF51A1F4D32CC1F1", - "identifiers": [ - 0, - 1, - 2 - ], + "identifiers": [0, 1, 2], "pubshares": [ "022B02109FBCFB4DA3F53C7393B22E72A2A51C4AFBF0C01AAF44F73843CFB4B74B", "02EC6444271D791A1DA95300329DB2268611B9C60E193DABFDEE0AA816AE512583", @@ -36,18 +32,9 @@ "msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", "valid_test_cases": [ { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], "tweak_indices": [], "aggnonce_index": 0, "is_xonly": [], @@ -56,168 +43,67 @@ "comment": "No tweak" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], - "tweak_indices": [ - 0 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], + "tweak_indices": [0], "aggnonce_index": 0, - "is_xonly": [ - true - ], + "is_xonly": [true], "signer_index": 0, "expected": "EED33B9C85BF813CDBE4404DE8E5ACBD8FBF34D53768B45EB21CB8B653F67EA9", "comment": "A single x-only tweak" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], - "tweak_indices": [ - 0 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], + "tweak_indices": [0], "aggnonce_index": 0, - "is_xonly": [ - false - ], + "is_xonly": [false], "signer_index": 0, "expected": "65AA345505968E08C2040BC4F726AA5A382A2E578E8E580719E44B2768975B97", "comment": "A single plain tweak" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], - "tweak_indices": [ - 0, - 1 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], + "tweak_indices": [0, 1], "aggnonce_index": 0, - "is_xonly": [ - false, - true - ], + "is_xonly": [false, true], "signer_index": 0, "expected": "10EB2D7E0E1BC46B42C54E739DECCA0BA8CB819D11AF5986CB47B8AF2FA00C91", "comment": "A plain tweak followed by an x-only tweak" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], - "tweak_indices": [ - 0, - 1, - 2, - 3 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], + "tweak_indices": [0, 1, 2, 3], "aggnonce_index": 0, - "is_xonly": [ - true, - false, - true, - false - ], + "is_xonly": [true, false, true, false], "signer_index": 0, "expected": "BF9CD5C5558915E44DDBE6399EBFE824224AB23D331173BE90664ACFA7E2C6EF", "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" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "pubnonce_indices": [ - 0, - 1 - ], - "tweak_indices": [ - 0, - 1, - 2, - 3 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "pubnonce_indices": [0, 1], + "tweak_indices": [0, 1, 2, 3], "aggnonce_index": 0, - "is_xonly": [ - false, - false, - true, - true - ], + "is_xonly": [false, false, true, true], "signer_index": 0, "expected": "8789A8C4198D125220F693C837CA5896101C4F46D84769E38D284BDD462FDB25", "comment": "Four tweaks: plain, plain, x-only, x-only" }, { - "id_indices": [ - 0, - 1, - 2 - ], - "pubshare_indices": [ - 0, - 1, - 2 - ], - "pubnonce_indices": [ - 0, - 1, - 2 - ], - "tweak_indices": [ - 0, - 1, - 2, - 3 - ], + "id_indices": [0, 1, 2], + "pubshare_indices": [0, 1, 2], + "pubnonce_indices": [0, 1, 2], + "tweak_indices": [0, 1, 2, 3], "aggnonce_index": 1, - "is_xonly": [ - false, - false, - true, - true - ], + "is_xonly": [false, false, true, true], "signer_index": 0, "expected": "F114B99483FCAF269A955479F60D1AE63CF74FA15330374D2DF8523930B1558A", "comment": "Tweaking with max number of participants. The expected value (partial sig) must match the previous test vector" @@ -225,21 +111,11 @@ ], "error_test_cases": [ { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "tweak_indices": [ - 4 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "tweak_indices": [4], "aggnonce_index": 0, - "is_xonly": [ - false - ], + "is_xonly": [false], "signer_index": 0, "error": { "type": "ValueError", @@ -248,21 +124,11 @@ "comment": "Tweak is invalid because it exceeds group size" }, { - "id_indices": [ - 0, - 1 - ], - "pubshare_indices": [ - 0, - 1 - ], - "tweak_indices": [ - 5 - ], + "id_indices": [0, 1], + "pubshare_indices": [0, 1], + "tweak_indices": [5], "aggnonce_index": 0, - "is_xonly": [ - false - ], + "is_xonly": [false], "signer_index": 0, "error": { "type": "ValueError", From abd9699a90e89d742a7b60bc5a106e5ac11dc6de Mon Sep 17 00:00:00 2001 From: siv2r Date: Wed, 20 May 2026 04:33:02 +0530 Subject: [PATCH 4/4] gen_vectors: fix ci and address review comments --- python/gen_vectors.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/gen_vectors.py b/python/gen_vectors.py index daf68c2..ce57fa6 100755 --- a/python/gen_vectors.py +++ b/python/gen_vectors.py @@ -126,6 +126,7 @@ def _inline_scalar_array(match): def write_test_vectors(filename, vectors): output_file = os.path.join("vectors", filename) text = _SCALAR_ARRAY_RE.sub(_inline_scalar_array, json.dumps(vectors, indent=4)) + json.loads(text) # guard: inlining must keep the JSON parseable with open(output_file, "w") as f: f.write(text) @@ -180,9 +181,12 @@ def frost_keygen_fixed(): def reconstruct_thresh_sk(ids, secshares): + assert len(ids) == len(secshares) result = Scalar(0) for i, s in zip(ids, secshares): - result = result + derive_interpolating_value(ids, i) * Scalar.from_bytes_checked(s) + result = result + derive_interpolating_value( + ids, i + ) * Scalar.from_bytes_checked(s) return result @@ -416,9 +420,10 @@ def generate_sign_verify_vectors(): AGGNONCE_INVALID_TAG_IDX = 4 # Invalid tag 0x04 AGGNONCE_INVALID_XCOORD_IDX = 5 # Invalid X coordinate AGGNONCE_INVALID_EXCEEDS_FIELD_IDX = 6 # X exceeds field size - OUT_OF_RANGE_ID_IDX = 3 # identifier value n (=3), out of range [0, n-1] + OUT_OF_RANGE_ID_IDX = 3 # identifier value n, out of range [0, n-1] + assert OUT_OF_RANGE_ID_IDX == n - # Extend identifiers with an out-of-range value (n=3) for the L91 test case. + # Extend identifiers with an out-of-range value (n) for the L91 test case. # Existing cases reference indices 0..2 only and are unaffected. ids = ids + [n]