from __future__ import annotations
import json
import os
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
FIXTURES = ROOT / "tests" / "fixtures"
DEFAULT_LIBBITCOINPQC_SRC = Path.home() / "Projects" / "surmount" / "libbitcoinpqc"
def resolve_libbitcoinpqc_src() -> Path:
env = os.environ.get("LIBBITCOINPQC_SRC", "").strip()
if env:
return Path(env).expanduser().resolve()
if DEFAULT_LIBBITCOINPQC_SRC.is_dir() and (DEFAULT_LIBBITCOINPQC_SRC / ".git").exists():
return DEFAULT_LIBBITCOINPQC_SRC.resolve()
return (ROOT / "libbitcoinpqc").resolve()
def hex_to_bytes(hex_str: str) -> bytes:
return bytes.fromhex(hex_str)
def chunk_hex(hex_str: str, width: int = 16) -> list[str]:
return [hex_str[i : i + width] for i in range(0, len(hex_str), width)]
def rust_byte_array(name: str, data: bytes, items_per_line: int = 16) -> str:
lines = [f"pub const {name}: &[u8] = &["]
for i in range(0, len(data), items_per_line):
chunk = data[i : i + items_per_line]
hex_part = ", ".join(f"0x{b:02x}" for b in chunk)
lines.append(f" {hex_part},")
lines.append("];")
return "\n".join(lines)
def escape_c_string(value: str) -> str:
return (
value.replace("\\", "\\\\")
.replace('"', '\\"')
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t")
)
def escape_rust_byte_string(value: str) -> str:
return value.replace("\\", "\\\\").replace('"', '\\"')
def escape_js_string(value: str) -> str:
return escape_c_string(value)
def escape_python_string(value: str) -> str:
return (
value.replace("\\", "\\\\")
.replace('"', '\\"')
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t")
)
def rust_message_const(name: str, message: str) -> str:
return f'pub const {name}: &[u8] = b"{escape_rust_byte_string(message)}";'
def c_message_array(name: str, message: str) -> str:
return f'static const char {name}[] = "{escape_c_string(message)}";'
def c_byte_array(name: str, data: bytes, items_per_line: int = 8) -> str:
lines = [f"static const uint8_t {name}[] = {{"]
for i in range(0, len(data), items_per_line):
chunk = data[i : i + items_per_line]
hex_part = ", ".join(f"0x{b:02x}" for b in chunk)
lines.append(f" {hex_part},")
lines.append("};")
return "\n".join(lines)
def write_rust_vectors(slh: dict, ml: dict, secp: dict) -> None:
slh_entropy = hex_to_bytes(slh["entropy_hex"])
slh_pk = hex_to_bytes(slh["expected_pk_hex"])
slh_sig = hex_to_bytes(slh["expected_sig_hex"])
ml_entropy = hex_to_bytes(ml["entropy_hex"])
ml_pk = hex_to_bytes(ml["expected_pk_hex"])
ml_sig = hex_to_bytes(ml["expected_sig_hex"])
secp_secret = hex_to_bytes(secp["secret_hex"])
secp_pk = hex_to_bytes(secp["expected_pk_hex"])
secp_sig = hex_to_bytes(secp["expected_sig_hex"])
secp_message = hex_to_bytes(secp["message_hex"])
slh_rs = ROOT / "tests" / "vectors" / "slh_dsa_sha2_golden_vectors.rs"
slh_rs.write_text(
"\n".join(
[
"//! Golden test vectors from tests/fixtures/slh_dsa_sha2_golden_vectors.json",
"//! Auto-generated by scripts/sync-golden-vectors.py — do not edit by hand.",
"#![allow(dead_code)]",
"",
rust_byte_array("SLH_DSA_SHA2_TEST_ENTROPY", slh_entropy),
"",
rust_byte_array("SLH_DSA_SHA2_EXPECTED_PK", slh_pk),
"",
rust_byte_array("SLH_DSA_SHA2_EXPECTED_SIG", slh_sig),
"",
rust_message_const("SLH_DSA_SHA2_TEST_MESSAGE", slh["message"]),
"",
]
)
)
ml_rs = ROOT / "tests" / "vectors" / "ml_dsa_44_golden_vectors.rs"
ml_rs.write_text(
"\n".join(
[
"//! Golden test vectors from tests/fixtures/ml_dsa_44_golden.json",
"//! Auto-generated by scripts/sync-golden-vectors.py — do not edit by hand.",
"#![allow(dead_code)]",
"",
rust_byte_array("ML_DSA_44_TEST_ENTROPY", ml_entropy),
"",
rust_byte_array("ML_DSA_44_EXPECTED_PK", ml_pk),
"",
rust_byte_array("ML_DSA_44_EXPECTED_SIG", ml_sig),
"",
rust_message_const("ML_DSA_44_TEST_MESSAGE", ml["message"]),
"",
]
)
)
secp_rs = ROOT / "tests" / "vectors" / "secp256k1_bip340_golden_vectors.rs"
secp_rs.write_text(
"\n".join(
[
"//! Golden test vectors from tests/fixtures/secp256k1_bip340_row0.json",
"//! Auto-generated by scripts/sync-golden-vectors.py — do not edit by hand.",
"#![allow(dead_code)]",
"",
rust_byte_array("SECP256K1_BIP340_ROW0_SECRET", secp_secret),
"",
rust_byte_array("SECP256K1_BIP340_ROW0_EXPECTED_PK", secp_pk),
"",
rust_byte_array("SECP256K1_BIP340_ROW0_MESSAGE", secp_message),
"",
rust_byte_array("SECP256K1_BIP340_ROW0_EXPECTED_SIG", secp_sig),
"",
]
)
)
def write_python_vectors(slh: dict, ml: dict, secp: dict) -> None:
def py_module(
stem: str,
header: str,
constants: list[tuple[str, str, str]],
) -> None:
lines = [
f'"""{header}"""',
"",
]
for name, kind, value in constants:
if kind == "hex":
lines.append(f"{name} = bytes.fromhex(\"{value}\")")
elif kind == "str":
lines.append(f'{name} = "{escape_python_string(value)}"')
lines.append("")
path = ROOT / "python" / "tests" / f"{stem}.py"
path.write_text("\n".join(lines))
py_module(
"slh_dsa_sha2_golden_vectors",
"Golden vectors from tests/fixtures/slh_dsa_sha2_golden_vectors.json",
[
("SLH_DSA_SHA2_TEST_ENTROPY", "hex", slh["entropy_hex"]),
("SLH_DSA_SHA2_EXPECTED_PK", "hex", slh["expected_pk_hex"]),
("SLH_DSA_SHA2_EXPECTED_SIG", "hex", slh["expected_sig_hex"]),
("SLH_DSA_SHA2_TEST_MESSAGE", "str", slh["message"]),
],
)
py_module(
"ml_dsa_44_golden_vectors",
"Golden vectors from tests/fixtures/ml_dsa_44_golden.json",
[
("ML_DSA_44_TEST_ENTROPY", "hex", ml["entropy_hex"]),
("ML_DSA_44_EXPECTED_PK", "hex", ml["expected_pk_hex"]),
("ML_DSA_44_EXPECTED_SIG", "hex", ml["expected_sig_hex"]),
("ML_DSA_44_TEST_MESSAGE", "str", ml["message"]),
],
)
py_module(
"secp256k1_bip340_golden_vectors",
"Golden vectors from tests/fixtures/secp256k1_bip340_row0.json",
[
("SECP256K1_BIP340_ROW0_SECRET", "hex", secp["secret_hex"]),
("SECP256K1_BIP340_ROW0_EXPECTED_PK", "hex", secp["expected_pk_hex"]),
("SECP256K1_BIP340_ROW0_MESSAGE", "hex", secp["message_hex"]),
("SECP256K1_BIP340_ROW0_EXPECTED_SIG", "hex", secp["expected_sig_hex"]),
],
)
def write_js_vectors(slh: dict, ml: dict, secp: dict, target_dir: Path, dts: bool) -> None:
def js_module(
stem: str,
header: str,
hex_constants: list[tuple[str, str]],
str_constants: list[tuple[str, str]],
) -> None:
lines = [
f"// {header}",
"// Auto-generated by scripts/sync-golden-vectors.py — do not edit by hand.",
"function hexToBytes(hex) {",
" const bytes = new Uint8Array(hex.length / 2);",
" for (let i = 0; i < bytes.length; i++) {",
" bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);",
" }",
" return bytes;",
"}",
"",
]
exports = []
for name, hex_val in hex_constants:
const_name = f"{name}_HEX"
lines.append(f"const {const_name} = '{hex_val}';")
lines.append(f"const {name} = hexToBytes({const_name});")
exports.append(name)
for name, str_val in str_constants:
lines.append(f'const {name} = "{escape_js_string(str_val)}";')
exports.append(name)
lines.append("")
lines.append("module.exports = {")
lines.append(" " + ",\n ".join(exports) + ",")
lines.append("};")
lines.append("")
(target_dir / f"{stem}.js").write_text("\n".join(lines))
if dts:
dts_lines = [f"/** {header} */"]
for name, _ in hex_constants:
dts_lines.append(f"export const {name}: Uint8Array;")
for name, _ in str_constants:
dts_lines.append(f"export const {name}: string;")
dts_lines.append("")
(target_dir / f"{stem}.d.ts").write_text("\n".join(dts_lines))
js_module(
"slh_dsa_sha2_golden_vectors",
"Golden vectors from tests/fixtures/slh_dsa_sha2_golden_vectors.json",
[
("SLH_DSA_SHA2_TEST_ENTROPY", slh["entropy_hex"]),
("SLH_DSA_SHA2_EXPECTED_PK", slh["expected_pk_hex"]),
("SLH_DSA_SHA2_EXPECTED_SIG", slh["expected_sig_hex"]),
],
[("SLH_DSA_SHA2_TEST_MESSAGE", slh["message"])],
)
js_module(
"ml_dsa_44_golden_vectors",
"Golden vectors from tests/fixtures/ml_dsa_44_golden.json",
[
("ML_DSA_44_TEST_ENTROPY", ml["entropy_hex"]),
("ML_DSA_44_EXPECTED_PK", ml["expected_pk_hex"]),
("ML_DSA_44_EXPECTED_SIG", ml["expected_sig_hex"]),
],
[("ML_DSA_44_TEST_MESSAGE", ml["message"])],
)
js_module(
"secp256k1_bip340_golden_vectors",
"Golden vectors from tests/fixtures/secp256k1_bip340_row0.json",
[
("SECP256K1_BIP340_ROW0_SECRET", secp["secret_hex"]),
("SECP256K1_BIP340_ROW0_EXPECTED_PK", secp["expected_pk_hex"]),
("SECP256K1_BIP340_ROW0_MESSAGE", secp["message_hex"]),
("SECP256K1_BIP340_ROW0_EXPECTED_SIG", secp["expected_sig_hex"]),
],
[],
)
def write_c_headers(slh: dict, ml: dict, secp: dict) -> None:
lib_dir = resolve_libbitcoinpqc_src()
vectors_dir = lib_dir / "tests" / "vectors"
vectors_dir.mkdir(parents=True, exist_ok=True)
print(f"C headers -> {vectors_dir}", file=sys.stderr)
slh_entropy = hex_to_bytes(slh["entropy_hex"])
slh_pk = hex_to_bytes(slh["expected_pk_hex"])
slh_sig = hex_to_bytes(slh["expected_sig_hex"])
(vectors_dir / "slh_dsa_sha2_128s_vectors.h").write_text(
"\n".join(
[
"/*",
" * Golden test vectors for SLH-DSA-SHA2-128s.",
" * Auto-generated by libbitcoinpqc-bindings/scripts/sync-golden-vectors.py.",
" */",
"",
"#ifndef SLH_DSA_SHA2_128S_VECTORS_H",
"#define SLH_DSA_SHA2_128S_VECTORS_H",
"",
"#include <stdint.h>",
"#include <libbitcoinpqc/slh_dsa.h>",
"",
f"#define SLH_DSA_SHA2_TEST_ENTROPY_SIZE {len(slh_entropy)}",
"#define SLH_DSA_SHA2_EXPECTED_PK_SIZE SLH_DSA_SHA2_128S_PUBLIC_KEY_SIZE",
"#define SLH_DSA_SHA2_EXPECTED_SIG_SIZE SLH_DSA_SHA2_128S_SIGNATURE_SIZE",
"",
c_byte_array("SLH_DSA_SHA2_TEST_ENTROPY", slh_entropy),
"",
c_message_array("SLH_DSA_SHA2_TEST_MESSAGE", slh["message"]),
"",
c_byte_array("SLH_DSA_SHA2_EXPECTED_PK", slh_pk),
"",
c_byte_array("SLH_DSA_SHA2_EXPECTED_SIG", slh_sig),
"",
"#endif /* SLH_DSA_SHA2_128S_VECTORS_H */",
"",
]
)
)
ml_entropy = hex_to_bytes(ml["entropy_hex"])
ml_pk = hex_to_bytes(ml["expected_pk_hex"])
ml_sig = hex_to_bytes(ml["expected_sig_hex"])
(vectors_dir / "ml_dsa_44_vectors.h").write_text(
"\n".join(
[
"/*",
" * Golden test vectors for ML-DSA-44.",
" * Auto-generated by libbitcoinpqc-bindings/scripts/sync-golden-vectors.py.",
" */",
"",
"#ifndef ML_DSA_44_VECTORS_H",
"#define ML_DSA_44_VECTORS_H",
"",
"#include <stdint.h>",
"#include <libbitcoinpqc/ml_dsa.h>",
"",
f"#define ML_DSA_44_TEST_ENTROPY_SIZE {len(ml_entropy)}",
"#define ML_DSA_44_EXPECTED_PK_SIZE ML_DSA_44_PUBLIC_KEY_SIZE",
"#define ML_DSA_44_EXPECTED_SIG_SIZE ML_DSA_44_SIGNATURE_SIZE",
"",
c_byte_array("ML_DSA_44_TEST_ENTROPY", ml_entropy),
"",
c_message_array("ML_DSA_44_TEST_MESSAGE", ml["message"]),
"",
c_byte_array("ML_DSA_44_EXPECTED_PK", ml_pk),
"",
c_byte_array("ML_DSA_44_EXPECTED_SIG", ml_sig),
"",
"#endif /* ML_DSA_44_VECTORS_H */",
"",
]
)
)
secp_secret = hex_to_bytes(secp["secret_hex"])
secp_pk = hex_to_bytes(secp["expected_pk_hex"])
secp_sig = hex_to_bytes(secp["expected_sig_hex"])
secp_message = hex_to_bytes(secp["message_hex"])
(vectors_dir / "secp256k1_bip340_vectors.h").write_text(
"\n".join(
[
"/*",
" * Golden test vectors for secp256k1 Schnorr (BIP-340 row 0).",
" * Auto-generated by libbitcoinpqc-bindings/scripts/sync-golden-vectors.py.",
" */",
"",
"#ifndef SECP256K1_BIP340_VECTORS_H",
"#define SECP256K1_BIP340_VECTORS_H",
"",
"#include <stdint.h>",
"",
"#define SECP256K1_BIP340_SECRET_SIZE 32",
"#define SECP256K1_BIP340_PK_SIZE 32",
"#define SECP256K1_BIP340_MESSAGE_SIZE 32",
"#define SECP256K1_BIP340_SIG_SIZE 64",
"",
c_byte_array("SECP256K1_BIP340_ROW0_SECRET", secp_secret),
"",
c_byte_array("SECP256K1_BIP340_ROW0_EXPECTED_PK", secp_pk),
"",
c_byte_array("SECP256K1_BIP340_ROW0_MESSAGE", secp_message),
"",
c_byte_array("SECP256K1_BIP340_ROW0_EXPECTED_SIG", secp_sig),
"",
"#endif /* SECP256K1_BIP340_VECTORS_H */",
"",
]
)
)
def load_fixture(name: str) -> dict:
path = FIXTURES / name
with path.open(encoding="utf-8") as f:
return json.load(f)
def main() -> int:
slh = load_fixture("slh_dsa_sha2_golden_vectors.json")
ml = load_fixture("ml_dsa_44_golden.json")
secp = load_fixture("secp256k1_bip340_row0.json")
write_rust_vectors(slh, ml, secp)
write_python_vectors(slh, ml, secp)
write_js_vectors(slh, ml, secp, ROOT / "nodejs" / "tests", dts=True)
write_js_vectors(slh, ml, secp, ROOT / "wasm" / "test", dts=False)
write_c_headers(slh, ml, secp)
print("Golden vectors synced from tests/fixtures/")
return 0
if __name__ == "__main__":
sys.exit(main())