diff --git a/generators/all.sh b/generators/all.sh index e79836d..28bae70 100755 --- a/generators/all.sh +++ b/generators/all.sh @@ -1,16 +1,22 @@ #!/bin/bash TESTS_PATH=./test-vectors/txn/tests/precompile FIXTS_PATH=./test-vectors/txn/fixtures/precompile +LIB_SOLFUZZ_AGAVE=../solfuzz-agave/target/debug/libsolfuzz_agave.so rm -r ${TESTS_PATH}/ed25519 ; mkdir -p ${TESTS_PATH}/ed25519 python3 generators/ed25519.py rm -r ${FIXTS_PATH}/ed25519 ; mkdir -p ${FIXTS_PATH}/ed25519 -HARNESS_TYPE=TxnHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/ed25519 -o ${FIXTS_PATH}/ed25519 -s ./impl/lib/libsolfuzz_agave_v2.0.so +solana-test-suite create-fixtures -i ${TESTS_PATH}/ed25519 -o ${FIXTS_PATH}/ed25519 -s ${LIB_SOLFUZZ_AGAVE} -h TxnHarness rm -r ${TESTS_PATH}/secp256k1 ; mkdir -p ${TESTS_PATH}/secp256k1 python3 generators/secp256k1.py rm -r ${FIXTS_PATH}/secp256k1 ; mkdir -p ${FIXTS_PATH}/secp256k1 -HARNESS_TYPE=TxnHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/secp256k1 -o ${FIXTS_PATH}/secp256k1 -s ./impl/lib/libsolfuzz_agave_v2.0.so +solana-test-suite create-fixtures -i ${TESTS_PATH}/secp256k1 -o ${FIXTS_PATH}/secp256k1 -s ${LIB_SOLFUZZ_AGAVE} -h TxnHarness + +rm -r ${TESTS_PATH}/secp256r1 ; mkdir -p ${TESTS_PATH}/secp256r1 +python3 generators/precompile_secp256r1.py +rm -r ${FIXTS_PATH}/secp256r1 ; mkdir -p ${FIXTS_PATH}/secp256r1 +solana-test-suite create-fixtures -i ${TESTS_PATH}/secp256r1 -o ${FIXTS_PATH}/secp256r1 -s ${LIB_SOLFUZZ_AGAVE} -h TxnHarness TESTS_PATH=./test-vectors/syscall/tests FIXTS_PATH=./test-vectors/syscall/fixtures @@ -18,22 +24,22 @@ FIXTS_PATH=./test-vectors/syscall/fixtures rm -r ${TESTS_PATH}/curve25519 ; mkdir -p ${TESTS_PATH}/curve25519 python3 generators/syscalls_curve25519.py rm -r ${FIXTS_PATH}/curve25519 ; mkdir -p ${FIXTS_PATH}/curve25519 -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/curve25519 -o ${FIXTS_PATH}/curve25519 -s ./impl/lib/libsolfuzz_agave_v2.0.so +solana-test-suite create-fixtures -i ${TESTS_PATH}/curve25519 -o ${FIXTS_PATH}/curve25519 -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness rm -r ${TESTS_PATH}/secp256k1 ; mkdir -p ${TESTS_PATH}/secp256k1 python3 generators/syscalls_secp256k1.py rm -r ${FIXTS_PATH}/secp256k1 ; mkdir -p ${FIXTS_PATH}/secp256k1 -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/secp256k1 -o ${FIXTS_PATH}/secp256k1 -s ./impl/lib/libsolfuzz_agave_v2.0.so +solana-test-suite create-fixtures -i ${TESTS_PATH}/secp256k1 -o ${FIXTS_PATH}/secp256k1 -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness rm -r ${TESTS_PATH}/poseidon ; mkdir -p ${TESTS_PATH}/poseidon python3 generators/syscalls_poseidon.py rm -r ${FIXTS_PATH}/poseidon ; mkdir -p ${FIXTS_PATH}/poseidon -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/poseidon -o ${FIXTS_PATH}/poseidon -s ./impl/lib/libsolfuzz_agave_v2.0.so +solana-test-suite create-fixtures -i ${TESTS_PATH}/poseidon -o ${FIXTS_PATH}/poseidon -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness rm -r ${TESTS_PATH}/alt_bn128 ; mkdir -p ${TESTS_PATH}/alt_bn128 python3 generators/syscalls_alt_bn128.py rm -r ${FIXTS_PATH}/alt_bn128 ; mkdir -p ${FIXTS_PATH}/alt_bn128 -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/alt_bn128 -o ${FIXTS_PATH}/alt_bn128 -s ./impl/lib/libsolfuzz_agave_v2.0.so +solana-test-suite create-fixtures -i ${TESTS_PATH}/alt_bn128 -o ${FIXTS_PATH}/alt_bn128 -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness rm -r ${TESTS_PATH}/sha256 ; mkdir -p ${TESTS_PATH}/sha256 rm -r ${TESTS_PATH}/keccak256 ; mkdir -p ${TESTS_PATH}/keccak256 @@ -42,9 +48,9 @@ python3 generators/syscalls_hash.py rm -r ${FIXTS_PATH}/sha256 ; mkdir -p ${FIXTS_PATH}/sha256 rm -r ${FIXTS_PATH}/keccak256 ; mkdir -p ${FIXTS_PATH}/keccak256 rm -r ${FIXTS_PATH}/blake3 ; mkdir -p ${FIXTS_PATH}/blake3 -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/sha256 -o ${FIXTS_PATH}/sha256 -s ./impl/lib/libsolfuzz_agave_v2.0.so -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/keccak256 -o ${FIXTS_PATH}/keccak256 -s ./impl/lib/libsolfuzz_agave_v2.0.so -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/blake3 -o ${FIXTS_PATH}/blake3 -s ./impl/lib/libsolfuzz_agave_v2.0.so +solana-test-suite create-fixtures -i ${TESTS_PATH}/sha256 -o ${FIXTS_PATH}/sha256 -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness +solana-test-suite create-fixtures -i ${TESTS_PATH}/keccak256 -o ${FIXTS_PATH}/keccak256 -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness +solana-test-suite create-fixtures -i ${TESTS_PATH}/blake3 -o ${FIXTS_PATH}/blake3 -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness rm -r ${TESTS_PATH}/panic ; mkdir -p ${TESTS_PATH}/panic rm -r ${TESTS_PATH}/abort ; mkdir -p ${TESTS_PATH}/abort @@ -55,7 +61,7 @@ rm -r ${FIXTS_PATH}/panic ; mkdir -p ${FIXTS_PATH}/panic rm -r ${FIXTS_PATH}/abort ; mkdir -p ${FIXTS_PATH}/abort rm -r ${FIXTS_PATH}/log ; mkdir -p ${FIXTS_PATH}/log rm -r ${FIXTS_PATH}/log_data ; mkdir -p ${FIXTS_PATH}/log_data -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/panic -o ${FIXTS_PATH}/panic -s ./impl/lib/libsolfuzz_agave_v2.0.so -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/abort -o ${FIXTS_PATH}/abort -s ./impl/lib/libsolfuzz_agave_v2.0.so -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/log -o ${FIXTS_PATH}/log -s ./impl/lib/libsolfuzz_agave_v2.0.so -HARNESS_TYPE=SyscallHarness solana-test-suite create-fixtures -i ${TESTS_PATH}/log_data -o ${FIXTS_PATH}/log_data -s ./impl/lib/libsolfuzz_agave_v2.0.so +solana-test-suite create-fixtures -i ${TESTS_PATH}/panic -o ${FIXTS_PATH}/panic -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness +solana-test-suite create-fixtures -i ${TESTS_PATH}/abort -o ${FIXTS_PATH}/abort -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness +solana-test-suite create-fixtures -i ${TESTS_PATH}/log -o ${FIXTS_PATH}/log -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness +solana-test-suite create-fixtures -i ${TESTS_PATH}/log_data -o ${FIXTS_PATH}/log_data -s ${LIB_SOLFUZZ_AGAVE} -h SyscallHarness diff --git a/generators/precompile_secp256r1.py b/generators/precompile_secp256r1.py new file mode 100644 index 0000000..6f744a9 --- /dev/null +++ b/generators/precompile_secp256r1.py @@ -0,0 +1,289 @@ +import fd58 +import hashlib +import test_suite.context_pb2 as context_pb +import test_suite.txn_pb2 as txn_pb +from dataclasses import dataclass +import binascii +import requests +import json + +OUTPUT_DIR = "./test-vectors/txn/tests/precompile/secp256r1" + +program_id = "Secp256r1SigVerify1111111111111111111111111" +accounts = [] + +msg0 = bytes("hello", "ascii") +x = bytes.fromhex("d8c82b3791c8b51cfe44aa50226217159596ca26e6075aaf8bf8be2d351b96ae") +pub0 = bytes([2]) + x +r = bytes.fromhex("a940d67c9560a47c5dafb45ab1f39eb68c8fac9b51fc8c4e30b1f0e63e4967d3") +s = bytes.fromhex("586569a56364c3b03eefd421aa7fc750f6fa187210c3206c55602f96e0ecaa4d") +sig0 = r + s + +msg = bytes.fromhex("deadbeef0002") +r = bytes.fromhex("16b60d19ba508f8bdac4c768bc868b9d2d458fc1c61a94af9423f6f850970eeb") +s = bytes.fromhex("3c18d9642122ce6f02c1a9b2a489feb3862e9682f21ea0c7c70a839ea2152f92") +x = bytes.fromhex("7929da089cf19abefaa8c13ade1de9b1c01291a261ebe9ae9a8f798936d56592") +y = bytes.fromhex("d46cb01e60e978e0a19730b6be922ecaec6e4f2e78707b6b3ead2453503bb111") +pub = bytes([3]) + x +sig = r + s + +offset0 = len(pub0 + sig0 + msg0) + +# fmt: off +test_vectors_agave = [ + # InvalidDataOffsets (result: 4) + # invalid offset + [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + # invalid data offset + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 0, 1, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 1, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 232, 3, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0], + # invalid pubkey offset + [1, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # invalid signature offset + [1, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # Ok() (result: 0) + # valid signature + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg0), 0, 255, 255] + list(pub0) + list(sig0) + list(msg0), + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub) + list(sig) + list(msg), + [2, 0] + [63, 0, 255, 255, 30, 0, 255, 255, 127, 0, len(msg0), 0, 255, 255] + [63+offset0, 0, 255, 255, 30+offset0, 0, 255, 255, 127+offset0, 0, len(msg), 0, 255, 255] + list(pub0) + list(sig0) + list(msg0) + list(pub) + list(sig) + list(msg), +] + + +zero = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000") +n = bytes.fromhex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551") +n_half = bytes.fromhex("7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8") + +sig_r_0 = zero + s +sig_r_n = n + s +sig_s_0 = r + zero +sig_s_n = r + n +sig_s_n_half = r + n_half + +pub_0 = bytes([0]) + x +pub_1 = bytes([1]) + x +pub_4 = bytes([4]) + x +pub_5 = bytes([5]) + x + +pub_4_valid = pub_4 + y + +# manual code cov +test_vectors_manual = [ + # InvalidInstructionDataSize + # https://github.com/anza-xyz/agave/blob/v2.1.4/sdk/secp256r1-program/src/lib.rs#L185-L187 + [0], + # https://github.com/anza-xyz/agave/blob/v2.1.4/sdk/secp256r1-program/src/lib.rs#L189-L191 + [0, 0], + [0, 2], + # https://github.com/anza-xyz/agave/blob/v2.1.4/sdk/secp256r1-program/src/lib.rs#L192-L194 + [9, 0], + # https://github.com/anza-xyz/agave/blob/v2.1.4/sdk/secp256r1-program/src/lib.rs#L201-L203 + [1], + [0, 0, 0], + [1, 0, 0], + # https://github.com/anza-xyz/agave/blob/v2.1.4/sdk/secp256r1-program/src/lib.rs#L259-L262 + + # https://github.com/anza-xyz/agave/blob/v2.1.4/sdk/secp256r1-program/src/lib.rs#L265-L272 + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub) + list(sig_r_0) + list(msg), + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub) + list(sig_r_n) + list(msg), + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub) + list(sig_s_0) + list(msg), + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub) + list(sig_s_n) + list(msg), + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub) + list(sig_s_n_half) + list(msg), + + # https://github.com/anza-xyz/agave/blob/v2.1.4/sdk/secp256r1-program/src/lib.rs#L279-L284 + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub_0) + list(sig) + list(msg), + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub_1) + list(sig) + list(msg), + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub_4) + list(sig) + list(msg), + [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + [len(msg), 0, 255, 255] + list(pub_5) + list(sig) + list(msg), + + [1, 0, 81, 0, 255, 255, 16, 0, 255, 255, 145, 0] + [len(msg), 0, 255, 255] + list(pub_4_valid) + list(sig) + list(msg), +] +# fmt: on + + +# test vectors from wycheproofs and cctv +@dataclass +class EddsaVerify: + tcId: int + comment: str + msg: bytes + sig: bytes + pub: bytes + ok: bool + strict: bool + + +def simd_tests(name, max_lines=200): + req = requests.get( + f"https://raw.githubusercontent.com/Bunkr-2FA/SIMD-48-Testing/refs/heads/main/test_vectors/vectors_{name}.jsonl" + ) + tests = [] + n = int.from_bytes( + bytes.fromhex( + "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551" + ), + byteorder="big", + ) + n_half = (n - 1) // 2 + for j, line in enumerate(req.iter_lines()): + test = json.loads(line) + r = bytes.fromhex(test.get("r")) + s = bytes.fromhex(test.get("s")) + s_int = int.from_bytes(s, byteorder="big") + s_norm = s_int if s_int <= n_half else n - s_int + if s_norm < 0: + continue + s_norm = s_norm.to_bytes(32, "big") + x = bytes.fromhex(test.get("x")) + y = bytes.fromhex(test.get("y")) + y_last_byte_mod2 = y[len(y) - 1] % 2 + msg = bytes.fromhex(test.get("msg")) + + # print("-------") + # print("r = ", binascii.hexlify(r)) + # print("s = ", binascii.hexlify(s)) + # print("x = ", binascii.hexlify(x)) + # print("y = ", binascii.hexlify(y)) + # print("pub = ", binascii.hexlify(bytes([2 + y_last_byte_mod2]) + x)) + # print("msg = ", binascii.hexlify(msg)) + + tests.append( + { + "sig": r + s_norm, + "pub": bytes([2 + y_last_byte_mod2]) + x, + "msg": msg, + } + ) + if j < 5: + tests.append( + { + "sig": r + s_norm, + "pub": bytes([3 - y_last_byte_mod2]) + x, + "msg": msg, + } + ) + + if j >= max_lines: + break + return tests + + +def simd_tests_wycheproof(): + req = requests.get( + f"https://raw.githubusercontent.com/Bunkr-2FA/SIMD-48-Testing/refs/heads/main/test_vectors/vectors_wycheproof.jsonl" + ) + tests = [] + for line in req.iter_lines(): + test = json.loads(line) + r = bytes.fromhex(test.get("r")) + s = bytes.fromhex(test.get("s")) + x = bytes.fromhex(test.get("x")) + y = bytes.fromhex(test.get("y")) + y_last_byte_mod2 = y[len(y) - 1] % 2 + msg = bytes.fromhex(test.get("msg")) + + if len(r) != 32: + print("len r") + if len(s) != 32: + print("len s") + if len(x) != 32: + print("len x") + + tests.append( + { + "sig": r + s, + "pub": bytes([2 + y_last_byte_mod2]) + x, + "msg": msg, + } + ) + return tests + + +# fmt: off +cache = {} +def _into_instr_data(key_prefix, verify_tests): + test_vectors = [] + prefix = [1, 0, 49, 0, 255, 255, 16, 0, 255, 255, 113, 0] + for j, test in enumerate(verify_tests): + pub = test.get("pub") + sig = test.get("sig") + msg = test.get("msg") + k = pub+sig+msg + if k in cache: + continue + else: + cache[k] = True + l = len(msg) + if l >= 1 << 16: + print("msg too long, skipping") + continue + data = ( + prefix + + [l % 256, l >> 8, 255, 255] + + list(pub) + + list(sig) + + list(msg) + ) + key = key_prefix + str(j) + test_vectors.append((key, data)) + return test_vectors +# fmt: on + + +def _into_key_data(key_prefix, test_vectors): + return [(key_prefix + str(j), data) for j, data in enumerate(test_vectors)] + + +print("Generating secp256r1 tests...") + +test_vectors = ( + _into_key_data("a", test_vectors_agave) + + _into_key_data("m", test_vectors_manual) + + _into_instr_data("vm", simd_tests("random_mixed", 3)) + + _into_instr_data("w", simd_tests_wycheproof()) +) + +program_id = fd58.dec32(bytes(program_id, "utf-8")) + +signer = fd58.dec32(bytes("BWbmXj5ckAaWCAtzMZ97qnJhBAKegoXtgNrv9BUpAB11", "utf-8")) +signer_owner = bytes([0] * 32) +signer_lamports = 1_000_000_000 + +for key, test in test_vectors: + txn_ctx = txn_pb.TxnContext() + + txn_ctx.tx.message.account_keys.extend( + [ + signer, + program_id, + ] + ) + + signer_account = context_pb.AcctState() + signer_account.address = signer + signer_account.owner = signer_owner + signer_account.lamports = signer_lamports + txn_ctx.tx.message.account_shared_data.extend([signer_account]) + + ix = txn_pb.CompiledInstruction() + ix.program_id_index = 1 + ix.data = bytes(test) + txn_ctx.tx.message.instructions.extend([ix]) + + txn_ctx.epoch_ctx.features.features.extend( + [ + 0x2C38E34FF071060D, # enable_secp256r1_precompile + ] + ) + + serialized_txn = txn_ctx.SerializeToString(deterministic=True) + filename = str(key) + "_" + hashlib.sha3_256(serialized_txn).hexdigest()[:16] + with open(f"{OUTPUT_DIR}/{filename}.bin", "wb") as f: + f.write(serialized_txn) + +print("done!")