1"""Generate paper/tables/numbers.tex (LaTeX macros) and result tables from results/*.json. Never type numbers by hand."""2import json, math, pathlib, statistics as st3ROOT = pathlib.Path(__file__).resolve().parents[1]4R = ROOT / "results"; T = ROOT / "paper/tables"; T.mkdir(exist_ok=True, parents=True)5macros = {}678def J(p):9 return json.loads((R / p).read_text())101112def m(name, value, fmt=None):13 if isinstance(value, float) and fmt:14 value = format(value, fmt)15 macros[name] = str(value)161718def fmt_int(x):19 return f"{int(x):,}".replace(",", r"\,")202122# ---- secp256k1 facts ------------------------------------------------------------------------------------------23f = J("baselines/secp256k1_facts.json")["measurements"]24m("secpAllAgree", "yes" if f["all_agree"] else "NO")25m("secpNumChecks", len(f["agreement"]))26m("secpTwistCofactorBits", f["python"]["twist_cofactor_bits"])27m("secpEmbeddingDegreeBits", int(f["pari_gp"]["embedding_degree"]).bit_length())2829# ---- scalar multiplication op counts -------------------------------------------------------------------------30s = J("baselines/scalar_mult_opcount.json")["measurements"]31for k, v in s.items():32 if k == "glv_subscalar_bits":33 m("glvKoneMax", v["k1_max"]); m("glvKtwoMax", v["k2_max"]); m("glvSamples", v["samples"]); continue34 key = {"double_and_add": "Dbladd", "wnaf_w4": "WnafFour", "wnaf_w5": "WnafFive", "glv_wnaf_w4": "GlvFour", "glv_wnaf_w5": "GlvFive"}[k]35 m("ops" + key + "Total", v["total_mean"], ".1f"); m("ops" + key + "Add", v["add_mean"], ".1f"); m("ops" + key + "Dbl", v["dbl_mean"], ".1f")36 m("ops" + key + "Speedup", v["speedup_vs_double_and_add_ops"], ".2f")37m("opsGlvVsWnafSpeedup", s["wnaf_w5"]["total_mean"] / s["glv_wnaf_w5"]["total_mean"], ".2f")38rows = [r"\begin{tabular}{@{}lrrrr@{}}", r"\toprule Method & Additions & Doublings & Total & Speed-up vs.\ double-and-add \\ \midrule"]39for k, lab in [("double_and_add", "Double-and-add"), ("wnaf_w4", "wNAF, $w=4$"), ("wnaf_w5", "wNAF, $w=5$"), ("glv_wnaf_w4", "GLV + interleaved wNAF, $w=4$"), ("glv_wnaf_w5", "GLV + interleaved wNAF, $w=5$")]:40 v = s[k]; rows.append(f"{lab} & {v['add_mean']:.1f} & {v['dbl_mean']:.1f} & {v['total_mean']:.1f} $\\pm$ {v['total_sd']:.1f} & {v['speedup_vs_double_and_add_ops']:.2f}$\\times$ \\\\")41rows += [r"\bottomrule", r"\end{tabular}"]42(T / "scalar_mult_opcount.tex").write_text("\\resizebox{\\textwidth}{!}{%\n" + "\n".join(rows) + "}\n")4344# ---- index calculus probe --------------------------------------------------------------------------------------45ic = J("synthetic_ecdlp/index_calculus_probe.json")["measurements"]["rows"]46rows = [r"\begin{tabular}{@{}rrrrrrrr@{}}", r"\toprule Bits & $|F|$ & $|F|/\sqrt n$ & Trials & Rate (meas.) & Rate (pred.) & Oracle relations$/\sqrt n$ & Lin.\ alg.$/\sqrt n$ \\ \midrule"]47for r in ic:48 rows.append(f"{r['bits']} & {fmt_int(r['factor_base_size'])} & {r['factor_base_size']/r['sqrt_n']:.2f} & {r['trials']} & {r['decomposition_rate']:.3f} & {r['predicted_rate']:.3f} & {r['oracle_relation_cost_over_sqrt_n']:.2f} & {r['linear_algebra_cost_over_sqrt_n']:.0f} \\\\")49rows += [r"\bottomrule", r"\end{tabular}"]50(T / "index_calculus_probe.tex").write_text("\\resizebox{\\textwidth}{!}{%\n" + "\n".join(rows) + "}\n")51m("icMinOracleRatio", min(r["oracle_relation_cost_over_sqrt_n"] for r in ic), ".2f")52m("icRateMin", min(r["decomposition_rate"] for r in ic if r["frac_exp"] == 0.5), ".2f")53m("icRateMax", max(r["decomposition_rate"] for r in ic if r["frac_exp"] == 0.5), ".2f")5455# ---- preprocessing ----------------------------------------------------------------------------------------------56pp = J("synthetic_ecdlp/preprocessing_python.json")["measurements"]["rows"]57rows = [r"\begin{tabular}{@{}rrrrrrrr@{}}", r"\toprule Bits & $T$ & $W$ & Offline ops & Online ops (mean $\pm$ 95\% CI) & $\sqrt{n/T}$ & Ratio & Speed-up per instance \\ \midrule"]58for r in pp:59 rows.append(f"{r['bits']} & {r['T']} & {r['W']} & {fmt_int(r['offline_group_ops'])} & {r['online_mean']:.0f} $\\pm$ {r['online_ci95_halfwidth']:.0f} & {r['pred_sqrt_n_over_T']:.0f} & {r['ratio_online_to_pred']:.2f} & {r['per_instance_speedup_vs_sqrt_n']:.1f}$\\times$ \\\\")60rows += [r"\bottomrule", r"\end{tabular}"]61(T / "preprocessing.tex").write_text("\\resizebox{\\textwidth}{!}{%\n" + "\n".join(rows) + "}\n")62ratios = [r["ratio_online_to_pred"] for r in pp]63m("ppRatioMin", min(ratios), ".2f"); m("ppRatioMax", max(ratios), ".2f"); m("ppRatioMean", st.mean(ratios), ".2f")64m("ppSpeedupMax", max(r["per_instance_speedup_vs_sqrt_n"] for r in pp), ".0f"); m("ppInstances", pp[0]["instances"])6566# ---- identity search -------------------------------------------------------------------------------------------67idn = J("automated_discovery/identity_search.json")["measurements"]68m("idTested", fmt_int(idn["candidates_tested"])); m("idFound", idn["identities_found"])69m("idUnexplained", len(idn["unexplained"])); m("idQfour", idn["negative_controls"]["Q4_cross_modulus_hits"]); m("idQfive", idn["negative_controls"]["Q5_small_linear_hits"])7071# ---- orbit counterexample ---------------------------------------------------------------------------------------72oc = J("theory/orbit_quotient_counterexample.json")["measurements"]["rows"]73m("orbitFails", sum(r["compatibility_failures"] for r in oc)); m("orbitTrials", sum(r["trials"] for r in oc))7475# ---- signatures ---------------------------------------------------------------------------------------------------76sg = J("signature_benchmarks/summary.json")77hosts = sg["hosts"]; lap = [h for h in hosts if "Laptop" in h][0]; stu = [h for h in hosts if "M3U96a" in h or "M3 Ultra" in h][0]78L, S_ = hosts[lap], hosts[stu]79sv = L["sign_verify_median_ns"]80def _mn(k):81 return "".join(w.capitalize() for w in k.replace("libsecp256k1", "libsecp").replace("k256", "kk").split("_"))82for k, v in sv.items():83 m("sig" + _mn(k) + "Us", v / 1000, ".1f")84svs = S_["sign_verify_median_ns"]85for k, v in svs.items():86 m("sigStudio" + _mn(k) + "Us", v / 1000, ".1f")87bv = L["batch_verification"]88best_b = max(int(b) for b in bv["pippenger"].keys()) if "pippenger" in bv else None89pip = bv.get("pippenger", {})90if pip:91 bb = str(best_b); e = pip[bb]92 m("batchBestSize", bb); m("batchBestUs", e["ns_per_sig"] / 1000, ".1f")93 m("batchSpeedupK", e["speedup_vs_k256"][0] if isinstance(e["speedup_vs_k256"], list) else e["speedup_vs_k256"], ".2f")94 if isinstance(e["speedup_vs_k256"], list) and len(e["speedup_vs_k256"]) >= 3:95 m("batchSpeedupKLo", e["speedup_vs_k256"][1], ".2f"); m("batchSpeedupKHi", e["speedup_vs_k256"][2], ".2f")96 m("batchSpeedupLib", e["speedup_vs_libsecp256k1"][0] if isinstance(e["speedup_vs_libsecp256k1"], list) else e["speedup_vs_libsecp256k1"], ".2f")97py = L.get("python_baselines_median_ns", {})98# implementation table99rows = [r"\begin{tabular}{@{}lrrrr@{}}", r"\toprule Implementation & ECDSA sign & ECDSA verify & Schnorr sign & Schnorr verify \\ \midrule"]100for lib, lab in [("libsecp256k1", r"\libsecp{} (C, via rust-secp256k1)"), ("k256", r"\kk{} (Rust, RustCrypto)")]:101 rows.append(f"{lab} & {sv['ecdsa_sign_'+lib]/1000:.1f} & {sv['ecdsa_verify_'+lib]/1000:.1f} & {sv['schnorr_sign_'+lib]/1000:.1f} & {sv['schnorr_verify_'+lib]/1000:.1f} \\\\")102for lib, lab in [("libsecp256k1", r"\libsecp{}, Mac Studio M3 Ultra"), ("k256", r"\kk{}, Mac Studio M3 Ultra")]:103 rows.append(f"{lab} & {svs['ecdsa_sign_'+lib]/1000:.1f} & {svs['ecdsa_verify_'+lib]/1000:.1f} & {svs['schnorr_sign_'+lib]/1000:.1f} & {svs['schnorr_verify_'+lib]/1000:.1f} \\\\")104if py:105 def g(k):106 v = py.get(k); return f"{v/1000:.1f}" if isinstance(v, (int, float)) else "---"107 rows.append(r"\midrule")108 rows.append(f"pure Python reference (\\code{{src/wp34}}, affine) & {g('wp34_pure_python_ecdsa_sign')} & {g('wp34_pure_python_ecdsa_verify')} & {g('wp34_pure_python_schnorr_sign')} & {g('wp34_pure_python_schnorr_verify')} \\\\")109 rows.append(f"python-ecdsa (pure Python, ECDSA only) & {g('python_ecdsa_pkg_ecdsa_sign')} & {g('python_ecdsa_pkg_ecdsa_verify')} & --- & --- \\\\")110 rows.append(f"coincurve (\\libsecp{{}} via Python/CFFI) & {g('coincurve_ecdsa_sign_der')} & {g('coincurve_ecdsa_verify_der')} & {g('coincurve_schnorr_sign')} & {g('coincurve_schnorr_verify')} \\\\")111 m("sigPurePythonSchnorrVerifyMs", py['wp34_pure_python_schnorr_verify'] / 1e6, ".1f")112 m("sigPurePythonVsLibsecp", py['wp34_pure_python_schnorr_verify'] / sv['schnorr_verify_libsecp256k1'], ".0f")113rows += [r"\bottomrule", r"\end{tabular}"]114(T / "sig_implementations.tex").write_text("\\resizebox{\\textwidth}{!}{%\n" + "\n".join(rows) + "}\n")115116# ---- write macros -----------------------------------------------------------------------------------------------117out = ["% generated by scripts/make_tables.py — do not edit"]118for k, v in macros.items():119 out.append(f"\\newcommand{{\\{k}}}{{{v}}}")120(T / "numbers.tex").write_text("\n".join(out) + "\n")121print(len(macros), "macros;", "python baseline keys:", list(py.keys())[:12])122