1"""Cross-check Rust-produced (seckey, msg, sig) triples against the pure-Python references2`wp34.ecdsa` and `wp34.schnorr` (exact arithmetic). Used by run_bench.py (pass counts go into the3results JSON) and by tests/test_sig_crosscheck.py (pytest gate).45Usage: PYTHONPATH=src python experiments/signature_benchmarks/python_crosscheck.py <triples.json>6"""7from __future__ import annotations8import json, sys, time9from pathlib import Path1011ROOT = Path(__file__).resolve().parents[2]12sys.path.insert(0, str(ROOT / "src"))13from wp34 import ecdsa as D, schnorr as B # noqa: E402141516def crosscheck(path: str | Path, resign: bool = True) -> dict:17 doc = json.loads(Path(path).read_text())18 items = doc["items"]19 n = len(items)20 c = {21 "n": n, "source": str(path),22 "ecdsa_k256_sig_verified_by_python": 0, "ecdsa_libsecp_sig_verified_by_python": 0,23 "ecdsa_python_resign_identical": 0,24 "schnorr_k256_sig_verified_by_python": 0, "schnorr_libsecp_sig_verified_by_python": 0,25 "schnorr_python_resign_identical": 0,26 "tampered_sig_rejected_by_python": 0,27 }28 t0 = time.time()29 for it in items:30 d = int(it["seckey"], 16)31 msg = bytes.fromhex(it["msg"])32 aux = bytes.fromhex(it["aux_rand"])33 pub = bytes.fromhex(it["pubkey_uncompressed"])34 Q = (int.from_bytes(pub[1:33], "big"), int.from_bytes(pub[33:65], "big"))35 for key, cnt in (("ecdsa_sig_k256", "ecdsa_k256_sig_verified_by_python"),36 ("ecdsa_sig_libsecp256k1", "ecdsa_libsecp_sig_verified_by_python")):37 sig = bytes.fromhex(it[key])38 r, s = int.from_bytes(sig[:32], "big"), int.from_bytes(sig[32:], "big")39 if D.verify(Q, msg, (r, s)):40 c[cnt] += 141 if resign:42 r, s = D.sign(d, msg) # RFC 6979, low-S43 if r.to_bytes(32, "big") + s.to_bytes(32, "big") == bytes.fromhex(it["ecdsa_sig_k256"]):44 c["ecdsa_python_resign_identical"] += 145 pkx = bytes.fromhex(it["pubkey_xonly"])46 for key, cnt in (("schnorr_sig_k256", "schnorr_k256_sig_verified_by_python"),47 ("schnorr_sig_libsecp256k1", "schnorr_libsecp_sig_verified_by_python")):48 sig = bytes.fromhex(it[key])49 if B.verify(msg, pkx, sig):50 c[cnt] += 151 if resign:52 sig = B.sign(msg, d.to_bytes(32, "big"), aux)53 if sig == bytes.fromhex(it["schnorr_sig_k256"]):54 c["schnorr_python_resign_identical"] += 155 # negative control: flip one bit of s -> Python must reject56 sig = bytearray(bytes.fromhex(it["schnorr_sig_libsecp256k1"]))57 sig[63] ^= 0x0158 if not B.verify(msg, pkx, bytes(sig)):59 c["tampered_sig_rejected_by_python"] += 160 c["elapsed_s"] = round(time.time() - t0, 2)61 c["resign_checked"] = resign62 keys = ["ecdsa_k256_sig_verified_by_python", "ecdsa_libsecp_sig_verified_by_python",63 "schnorr_k256_sig_verified_by_python", "schnorr_libsecp_sig_verified_by_python",64 "tampered_sig_rejected_by_python"]65 if resign:66 keys += ["ecdsa_python_resign_identical", "schnorr_python_resign_identical"]67 c["all_pass"] = all(c[k] == n for k in keys)68 return c697071if __name__ == "__main__":72 print(json.dumps(crosscheck(sys.argv[1]), indent=1))73