bitcoin/tools/test_vectors_musig2_generate.py
Ava Chow 2d46a89386 Squashed 'src/secp256k1/' changes from 2f2ccc46954..0cdc758a563
0cdc758a563 Merge bitcoin-core/secp256k1#1631: release: prepare for 0.6.0
39d5dfd542a release: prepare for 0.6.0
df2eceb2790 build: add ellswift.md and musig.md to release tarball
a306bb7e903 tools: fix check-abi.sh after cmake out locations were changed
145868a84d2 Do not export `secp256k1_musig_nonce_gen_internal`
b161bffb8bf Merge bitcoin-core/secp256k1#1579: Clear sensitive memory without getting optimized out (revival of #636)
a38d879a1a6 Merge bitcoin-core/secp256k1#1628: Name public API structs
7d48f5ed02e Merge bitcoin-core/secp256k1#1581: test, ci: Lower default iteration count to 16
694342fdb71 Name public API structs
0f73caf7c62 test, ci: Lower default iteration count to 16
9a8db52f4e9 Merge bitcoin-core/secp256k1#1582: cmake, test: Add `secp256k1_` prefix to test names
765ef53335a Clear _gej instances after point multiplication to avoid potential leaks
349e6ab916b Introduce separate _clear functions for hash module
99cc9fd6d01 Don't rely on memset to set signed integers to 0
97c57f42ba8 Implement various _clear() functions with secp256k1_memclear()
9bb368d1466 Use secp256k1_memclear() to clear stack memory instead of memset()
e3497bbf001 Separate between clearing memory and setting to zero in tests
d79a6ccd43a Separate secp256k1_fe_set_int( . , 0 ) from secp256k1_fe_clear()
1c081262227 Add secp256k1_memclear() for clearing secret data
1464f15c812 Merge bitcoin-core/secp256k1#1625: util: Remove unused (u)int64_t formatting macros
980c08df80a util: Remove unused (u)int64_t formatting macros
9b7c59cbb90 Merge bitcoin-core/secp256k1#1624: ci: Update macOS image
096e3e23f63 ci: Update macOS image
e7d384488e8 Don't clear secrets in pippenger implementation
68b55209f1b Merge bitcoin-core/secp256k1#1619: musig: ctimetests: fix _declassify range for generated nonce points
f0868a9b3d8 Merge bitcoin-core/secp256k1#1595: build: 45839th attempt to fix symbol visibility on Windows
1fae76f50c0 Merge bitcoin-core/secp256k1#1620: Remove unused scratch space from API
8be3839fb2e Remove unused scratch space from API
57eda3ba300 musig: ctimetests: fix _declassify range for generated nonce points
87384f5c0f2 cmake, test: Add `secp256k1_` prefix to test names
e59158b6eb7 Merge bitcoin-core/secp256k1#1553: cmake: Set top-level target output locations
18f9b967c25 Merge bitcoin-core/secp256k1#1616: examples: do not retry generating seckey randomness in musig
5bab8f6d3c4 examples: make key generation doc consistent
e8908221a45 examples: do not retry generating seckey randomness in musig
70b6be1834e extrakeys: improve doc of keypair_create (don't suggest retry)
01b5893389e Merge bitcoin-core/secp256k1#1599: #1570 improve examples: remove key generation loop
cd4f84f3ba8 Improve examples/documentation: remove key generation loops
a88aa935063 Merge bitcoin-core/secp256k1#1603: f can never equal -m
3660fe5e2a9 Merge bitcoin-core/secp256k1#1479: Add module "musig" that implements MuSig2 multi-signatures (BIP 327)
168c92011f5 build: allow enabling the musig module in cmake
f411841a46b Add module "musig" that implements MuSig2 multi-signatures (BIP 327)
0be79660f38 util: add constant-time is_zero_array function
c8fbdb1b972 group: add ge_to_bytes_ext and ge_from_bytes_ext
ef7ff03407f f can never equal -m
c232486d84e Revert "cmake: Set `ENVIRONMENT` property for examples on Windows"
26e4a7c2146 cmake: Set top-level target output locations
4c57c7a5a95 Merge bitcoin-core/secp256k1#1554: cmake: Clean up testing code
447334cb06d include: Avoid visibility("default") on Windows
472faaa8ee6 Merge bitcoin-core/secp256k1#1604: doc: fix typos in `secp256k1_ecdsa_{recoverable_,}signature` API description
292310fbb24 doc: fix typos in `secp256k1_ecdsa_{recoverable_,}signature` API description
85e224dd97f group: add ge_to_bytes and ge_from_bytes
7c987ec89e6 cmake: Call `enable_testing()` unconditionally
6aa576515ef cmake: Delete `CTest` module

git-subtree-dir: src/secp256k1
git-subtree-split: 0cdc758a56360bf58a851fe91085a327ec97685a
2024-11-04 14:59:46 -05:00

656 lines
18 KiB
Python
Executable file

#!/usr/bin/env python3
import sys
import json
import textwrap
max_pubkeys = 0
if len(sys.argv) < 2:
print(
"This script converts BIP MuSig2 test vectors in a given directory to a C file that can be used in the test framework."
)
print("Usage: %s <dir>" % sys.argv[0])
sys.exit(1)
def hexstr_to_intarray(str):
return ", ".join([f"0x{b:02X}" for b in bytes.fromhex(str)])
def create_init(name):
return """
static const struct musig_%s_vector musig_%s_vector = {
""" % (
name,
name,
)
def init_array(key):
return textwrap.indent("{ %s },\n" % hexstr_to_intarray(data[key]), 4 * " ")
def init_arrays(key):
s = textwrap.indent("{\n", 4 * " ")
s += textwrap.indent(
",\n".join(["{ %s }" % hexstr_to_intarray(x) for x in data[key]]), 8 * " "
)
s += textwrap.indent("\n},\n", 4 * " ")
return s
def init_indices(array):
return " %d, { %s }" % (
len(array),
", ".join(map(str, array) if len(array) > 0 else "0"),
)
def init_is_xonly(case):
if len(case["tweak_indices"]) > 0:
return ", ".join(map(lambda x: "1" if x else "0", case["is_xonly"]))
return "0"
def init_optional_expected(case):
return hexstr_to_intarray(case["expected"]) if "expected" in case else 0
def init_cases(cases, f):
s = textwrap.indent("{\n", 4 * " ")
for (i, case) in enumerate(cases):
s += textwrap.indent("%s\n" % f(case), 8 * " ")
s += textwrap.indent("},\n", 4 * " ")
return s
def finish_init():
return "};\n"
s = (
"""/**
* Automatically generated by %s.
*
* The test vectors for the KeySort function are included in this file. They can
* be found in src/modules/extrakeys/tests_impl.h. */
"""
% sys.argv[0]
)
s += """
enum MUSIG_ERROR {
MUSIG_PUBKEY,
MUSIG_TWEAK,
MUSIG_PUBNONCE,
MUSIG_AGGNONCE,
MUSIG_SECNONCE,
MUSIG_SIG,
MUSIG_SIG_VERIFY,
MUSIG_OTHER
};
"""
# key agg vectors
with open(sys.argv[1] + "/key_agg_vectors.json", "r") as f:
data = json.load(f)
max_key_indices = max(
len(test_case["key_indices"]) for test_case in data["valid_test_cases"]
)
max_tweak_indices = max(
len(test_case["tweak_indices"]) for test_case in data["error_test_cases"]
)
num_pubkeys = len(data["pubkeys"])
max_pubkeys = max(num_pubkeys, max_pubkeys)
num_tweaks = len(data["tweaks"])
num_valid_cases = len(data["valid_test_cases"])
num_error_cases = len(data["error_test_cases"])
# Add structures for valid and error cases
s += (
"""
struct musig_key_agg_valid_test_case {
size_t key_indices_len;
size_t key_indices[%d];
unsigned char expected[32];
};
"""
% max_key_indices
)
s += """
struct musig_key_agg_error_test_case {
size_t key_indices_len;
size_t key_indices[%d];
size_t tweak_indices_len;
size_t tweak_indices[%d];
int is_xonly[%d];
enum MUSIG_ERROR error;
};
""" % (
max_key_indices,
max_tweak_indices,
max_tweak_indices,
)
# Add structure for entire vector
s += """
struct musig_key_agg_vector {
unsigned char pubkeys[%d][33];
unsigned char tweaks[%d][32];
struct musig_key_agg_valid_test_case valid_case[%d];
struct musig_key_agg_error_test_case error_case[%d];
};
""" % (
num_pubkeys,
num_tweaks,
num_valid_cases,
num_error_cases,
)
s += create_init("key_agg")
# Add pubkeys and tweaks to the vector
s += init_arrays("pubkeys")
s += init_arrays("tweaks")
# Add valid cases to the vector
s += init_cases(
data["valid_test_cases"],
lambda case: "{ %s, { %s }},"
% (init_indices(case["key_indices"]), hexstr_to_intarray(case["expected"])),
)
def comment_to_error(case):
comment = case["comment"]
if "public key" in comment.lower():
return "MUSIG_PUBKEY"
elif "tweak" in comment.lower():
return "MUSIG_TWEAK"
else:
sys.exit("Unknown error")
# Add error cases to the vector
s += init_cases(
data["error_test_cases"],
lambda case: "{ %s, %s, { %s }, %s },"
% (
init_indices(case["key_indices"]),
init_indices(case["tweak_indices"]),
init_is_xonly(case),
comment_to_error(case),
),
)
s += finish_init()
# nonce gen vectors
with open(sys.argv[1] + "/nonce_gen_vectors.json", "r") as f:
data = json.load(f)
# The MuSig2 implementation only allows messages of length 32
data["test_cases"] = list(
filter(lambda c: c["msg"] is None or len(c["msg"]) == 64, data["test_cases"])
)
num_tests = len(data["test_cases"])
s += """
struct musig_nonce_gen_test_case {
unsigned char rand_[32];
int has_sk;
unsigned char sk[32];
unsigned char pk[33];
int has_aggpk;
unsigned char aggpk[32];
int has_msg;
unsigned char msg[32];
int has_extra_in;
unsigned char extra_in[32];
unsigned char expected_secnonce[97];
unsigned char expected_pubnonce[66];
};
"""
s += (
"""
struct musig_nonce_gen_vector {
struct musig_nonce_gen_test_case test_case[%d];
};
"""
% num_tests
)
s += create_init("nonce_gen")
def init_array_maybe(array):
return "%d , { %s }" % (
0 if array is None else 1,
hexstr_to_intarray(array) if array is not None else 0,
)
s += init_cases(
data["test_cases"],
lambda case: "{ { %s }, %s, { %s }, %s, %s, %s, { %s }, { %s } },"
% (
hexstr_to_intarray(case["rand_"]),
init_array_maybe(case["sk"]),
hexstr_to_intarray(case["pk"]),
init_array_maybe(case["aggpk"]),
init_array_maybe(case["msg"]),
init_array_maybe(case["extra_in"]),
hexstr_to_intarray(case["expected_secnonce"]),
hexstr_to_intarray(case["expected_pubnonce"]),
),
)
s += finish_init()
# nonce agg vectors
with open(sys.argv[1] + "/nonce_agg_vectors.json", "r") as f:
data = json.load(f)
num_pnonces = len(data["pnonces"])
num_valid_cases = len(data["valid_test_cases"])
num_error_cases = len(data["error_test_cases"])
pnonce_indices_len = 2
for case in data["valid_test_cases"] + data["error_test_cases"]:
assert len(case["pnonce_indices"]) == pnonce_indices_len
# Add structures for valid and error cases
s += """
struct musig_nonce_agg_test_case {
size_t pnonce_indices[2];
/* if valid case */
unsigned char expected[66];
/* if error case */
int invalid_nonce_idx;
};
"""
# Add structure for entire vector
s += """
struct musig_nonce_agg_vector {
unsigned char pnonces[%d][66];
struct musig_nonce_agg_test_case valid_case[%d];
struct musig_nonce_agg_test_case error_case[%d];
};
""" % (
num_pnonces,
num_valid_cases,
num_error_cases,
)
s += create_init("nonce_agg")
s += init_arrays("pnonces")
for cases in (data["valid_test_cases"], data["error_test_cases"]):
s += init_cases(
cases,
lambda case: "{ { %s }, { %s }, %d },"
% (
", ".join(map(str, case["pnonce_indices"])),
init_optional_expected(case),
case["error"]["signer"] if "error" in case else 0,
),
)
s += finish_init()
# sign/verify vectors
with open(sys.argv[1] + "/sign_verify_vectors.json", "r") as f:
data = json.load(f)
# The MuSig2 implementation only allows messages of length 32
assert list(filter(lambda x: len(x) == 64, data["msgs"]))[0] == data["msgs"][0]
data["msgs"] = [data["msgs"][0]]
def filter_msg32(k):
return list(filter(lambda x: x["msg_index"] == 0, data[k]))
data["valid_test_cases"] = filter_msg32("valid_test_cases")
data["sign_error_test_cases"] = filter_msg32("sign_error_test_cases")
data["verify_error_test_cases"] = filter_msg32("verify_error_test_cases")
data["verify_fail_test_cases"] = filter_msg32("verify_fail_test_cases")
num_pubkeys = len(data["pubkeys"])
max_pubkeys = max(num_pubkeys, max_pubkeys)
num_secnonces = len(data["secnonces"])
num_pubnonces = len(data["pnonces"])
num_aggnonces = len(data["aggnonces"])
num_msgs = len(data["msgs"])
num_valid_cases = len(data["valid_test_cases"])
num_sign_error_cases = len(data["sign_error_test_cases"])
num_verify_fail_cases = len(data["verify_fail_test_cases"])
num_verify_error_cases = len(data["verify_error_test_cases"])
all_cases = (
data["valid_test_cases"]
+ data["sign_error_test_cases"]
+ data["verify_error_test_cases"]
+ data["verify_fail_test_cases"]
)
max_key_indices = max(len(test_case["key_indices"]) for test_case in all_cases)
max_nonce_indices = max(
len(test_case["nonce_indices"]) if "nonce_indices" in test_case else 0
for test_case in all_cases
)
# Add structures for valid and error cases
s += (
"""
/* Omit pubnonces in the test vectors because our partial signature verification
* implementation is able to accept the aggnonce directly. */
struct musig_valid_case {
size_t key_indices_len;
size_t key_indices[%d];
size_t aggnonce_index;
size_t msg_index;
size_t signer_index;
unsigned char expected[32];
};
"""
% max_key_indices
)
s += (
"""
struct musig_sign_error_case {
size_t key_indices_len;
size_t key_indices[%d];
size_t aggnonce_index;
size_t msg_index;
size_t secnonce_index;
enum MUSIG_ERROR error;
};
"""
% max_key_indices
)
s += """
struct musig_verify_fail_error_case {
unsigned char sig[32];
size_t key_indices_len;
size_t key_indices[%d];
size_t nonce_indices_len;
size_t nonce_indices[%d];
size_t msg_index;
size_t signer_index;
enum MUSIG_ERROR error;
};
""" % (
max_key_indices,
max_nonce_indices,
)
# Add structure for entire vector
s += """
struct musig_sign_verify_vector {
unsigned char sk[32];
unsigned char pubkeys[%d][33];
unsigned char secnonces[%d][194];
unsigned char pubnonces[%d][194];
unsigned char aggnonces[%d][66];
unsigned char msgs[%d][32];
struct musig_valid_case valid_case[%d];
struct musig_sign_error_case sign_error_case[%d];
struct musig_verify_fail_error_case verify_fail_case[%d];
struct musig_verify_fail_error_case verify_error_case[%d];
};
""" % (
num_pubkeys,
num_secnonces,
num_pubnonces,
num_aggnonces,
num_msgs,
num_valid_cases,
num_sign_error_cases,
num_verify_fail_cases,
num_verify_error_cases,
)
s += create_init("sign_verify")
s += init_array("sk")
s += init_arrays("pubkeys")
s += init_arrays("secnonces")
s += init_arrays("pnonces")
s += init_arrays("aggnonces")
s += init_arrays("msgs")
s += init_cases(
data["valid_test_cases"],
lambda case: "{ %s, %d, %d, %d, { %s }},"
% (
init_indices(case["key_indices"]),
case["aggnonce_index"],
case["msg_index"],
case["signer_index"],
init_optional_expected(case),
),
)
def sign_error(case):
comment = case["comment"]
if "pubkey" in comment or "public key" in comment:
return "MUSIG_PUBKEY"
elif "Aggregate nonce" in comment:
return "MUSIG_AGGNONCE"
elif "Secnonce" in comment:
return "MUSIG_SECNONCE"
else:
sys.exit("Unknown sign error")
s += init_cases(
data["sign_error_test_cases"],
lambda case: "{ %s, %d, %d, %d, %s },"
% (
init_indices(case["key_indices"]),
case["aggnonce_index"],
case["msg_index"],
case["secnonce_index"],
sign_error(case),
),
)
def verify_error(case):
comment = case["comment"]
if "exceeds" in comment:
return "MUSIG_SIG"
elif "Wrong signer" in comment or "Wrong signature" in comment:
return "MUSIG_SIG_VERIFY"
elif "pubnonce" in comment:
return "MUSIG_PUBNONCE"
elif "pubkey" in comment:
return "MUSIG_PUBKEY"
else:
sys.exit("Unknown verify error")
for cases in ("verify_fail_test_cases", "verify_error_test_cases"):
s += init_cases(
data[cases],
lambda case: "{ { %s }, %s, %s, %d, %d, %s },"
% (
hexstr_to_intarray(case["sig"]),
init_indices(case["key_indices"]),
init_indices(case["nonce_indices"]),
case["msg_index"],
case["signer_index"],
verify_error(case),
),
)
s += finish_init()
# tweak vectors
with open(sys.argv[1] + "/tweak_vectors.json", "r") as f:
data = json.load(f)
num_pubkeys = len(data["pubkeys"])
max_pubkeys = max(num_pubkeys, max_pubkeys)
num_pubnonces = len(data["pnonces"])
num_tweaks = len(data["tweaks"])
num_valid_cases = len(data["valid_test_cases"])
num_error_cases = len(data["error_test_cases"])
all_cases = data["valid_test_cases"] + data["error_test_cases"]
max_key_indices = max(len(test_case["key_indices"]) for test_case in all_cases)
max_tweak_indices = max(len(test_case["tweak_indices"]) for test_case in all_cases)
max_nonce_indices = max(len(test_case["nonce_indices"]) for test_case in all_cases)
# Add structures for valid and error cases
s += """
struct musig_tweak_case {
size_t key_indices_len;
size_t key_indices[%d];
size_t nonce_indices_len;
size_t nonce_indices[%d];
size_t tweak_indices_len;
size_t tweak_indices[%d];
int is_xonly[%d];
size_t signer_index;
unsigned char expected[32];
};
""" % (
max_key_indices,
max_nonce_indices,
max_tweak_indices,
max_tweak_indices,
)
# Add structure for entire vector
s += """
struct musig_tweak_vector {
unsigned char sk[32];
unsigned char secnonce[97];
unsigned char aggnonce[66];
unsigned char msg[32];
unsigned char pubkeys[%d][33];
unsigned char pubnonces[%d][194];
unsigned char tweaks[%d][32];
struct musig_tweak_case valid_case[%d];
struct musig_tweak_case error_case[%d];
};
""" % (
num_pubkeys,
num_pubnonces,
num_tweaks,
num_valid_cases,
num_error_cases,
)
s += create_init("tweak")
s += init_array("sk")
s += init_array("secnonce")
s += init_array("aggnonce")
s += init_array("msg")
s += init_arrays("pubkeys")
s += init_arrays("pnonces")
s += init_arrays("tweaks")
s += init_cases(
data["valid_test_cases"],
lambda case: "{ %s, %s, %s, { %s }, %d, { %s }},"
% (
init_indices(case["key_indices"]),
init_indices(case["nonce_indices"]),
init_indices(case["tweak_indices"]),
init_is_xonly(case),
case["signer_index"],
init_optional_expected(case),
),
)
s += init_cases(
data["error_test_cases"],
lambda case: "{ %s, %s, %s, { %s }, %d, { %s }},"
% (
init_indices(case["key_indices"]),
init_indices(case["nonce_indices"]),
init_indices(case["tweak_indices"]),
init_is_xonly(case),
case["signer_index"],
init_optional_expected(case),
),
)
s += finish_init()
# sigagg vectors
with open(sys.argv[1] + "/sig_agg_vectors.json", "r") as f:
data = json.load(f)
num_pubkeys = len(data["pubkeys"])
max_pubkeys = max(num_pubkeys, max_pubkeys)
num_tweaks = len(data["tweaks"])
num_psigs = len(data["psigs"])
num_valid_cases = len(data["valid_test_cases"])
num_error_cases = len(data["error_test_cases"])
all_cases = data["valid_test_cases"] + data["error_test_cases"]
max_key_indices = max(len(test_case["key_indices"]) for test_case in all_cases)
max_tweak_indices = max(len(test_case["tweak_indices"]) for test_case in all_cases)
max_psig_indices = max(len(test_case["psig_indices"]) for test_case in all_cases)
# Add structures for valid and error cases
s += """
/* Omit pubnonces in the test vectors because they're only needed for
* implementations that do not directly accept an aggnonce. */
struct musig_sig_agg_case {
size_t key_indices_len;
size_t key_indices[%d];
size_t tweak_indices_len;
size_t tweak_indices[%d];
int is_xonly[%d];
unsigned char aggnonce[66];
size_t psig_indices_len;
size_t psig_indices[%d];
/* if valid case */
unsigned char expected[64];
/* if error case */
int invalid_sig_idx;
};
""" % (
max_key_indices,
max_tweak_indices,
max_tweak_indices,
max_psig_indices,
)
# Add structure for entire vector
s += """
struct musig_sig_agg_vector {
unsigned char pubkeys[%d][33];
unsigned char tweaks[%d][32];
unsigned char psigs[%d][32];
unsigned char msg[32];
struct musig_sig_agg_case valid_case[%d];
struct musig_sig_agg_case error_case[%d];
};
""" % (
num_pubkeys,
num_tweaks,
num_psigs,
num_valid_cases,
num_error_cases,
)
s += create_init("sig_agg")
s += init_arrays("pubkeys")
s += init_arrays("tweaks")
s += init_arrays("psigs")
s += init_array("msg")
for cases in (data["valid_test_cases"], data["error_test_cases"]):
s += init_cases(
cases,
lambda case: "{ %s, %s, { %s }, { %s }, %s, { %s }, %d },"
% (
init_indices(case["key_indices"]),
init_indices(case["tweak_indices"]),
init_is_xonly(case),
hexstr_to_intarray(case["aggnonce"]),
init_indices(case["psig_indices"]),
init_optional_expected(case),
case["error"]["signer"] if "error" in case else 0,
),
)
s += finish_init()
s += "enum { MUSIG_VECTORS_MAX_PUBKEYS = %d };" % max_pubkeys
print(s)