SPB Git

spb/wp34_uqo

Public
6.1 KB · 150 lines python
1"""End-to-end tests of the native ECDLP engine (experiments/synthetic_ecdlp/rho_engine).23Runs the release binary on every instance of tests/fixtures/ecdlp_instances.json (generated by4experiments/synthetic_ecdlp/rho_engine/scripts/gen_fixtures.py from the pure-Python reference5src/wp34/ec.py) with every algorithm / option combination and checks d against the fixture,6plus d·G == Q recomputed in Python. Build first:  cargo build --release --manifest-path7experiments/synthetic_ecdlp/rho_engine/Cargo.toml8"""9import json, pathlib, subprocess, sys10import pytest1112ROOT = pathlib.Path(__file__).resolve().parents[1]13sys.path.insert(0, str(ROOT / "src"))14from wp34.ec import Curve  # noqa: E4021516CRATE = ROOT / "experiments" / "synthetic_ecdlp" / "rho_engine"17BIN = CRATE / "target" / "release" / "rho_engine"18FIXTURE = ROOT / "tests" / "fixtures" / "ecdlp_instances.json"1920pytestmark = pytest.mark.skipif(not BIN.exists(), reason="rho_engine release binary not built")212223def _instances():24    if not FIXTURE.exists():25        return []26    return json.loads(FIXTURE.read_text())["instances"]272829INSTANCES = _instances()30GENERIC = [i for i in INSTANCES if not i["j0"]]31J0 = [i for i in INSTANCES if i["j0"]]3233COMBOS = {34    "bsgs": ["--alg", "bsgs"],35    "rho-floyd": ["--alg", "rho", "--cycle", "floyd"],36    "rho-brent": ["--alg", "rho", "--cycle", "brent"],37    "rho-dp-1t": ["--alg", "rho", "--threads", "1"],38    "rho-dp-4t": ["--alg", "rho", "--threads", "4"],39    "rho-neg": ["--alg", "rho", "--neg"],40    "rho-mixed": ["--alg", "rho", "--mixed", "4"],41    "kangaroo": ["--alg", "kangaroo", "--lo", "0"],  # --hi defaults to n-142}43J0_COMBOS = {44    "rho-aut3": ["--alg", "rho", "--aut3"],45    "rho-neg-aut3": ["--alg", "rho", "--neg", "--aut3"],46    "rho-neg-aut3-brent": ["--alg", "rho", "--neg", "--aut3", "--cycle", "brent"],47}484950def solve(inst, extra, seed=1, expect_ok=True):51    args = [str(BIN), "solve"]52    for k in ("p", "a", "b", "n", "gx", "gy", "qx", "qy"):53        args += [f"--{k}", inst[k]]54    if "beta" in inst:55        args += ["--beta", inst["beta"], "--lambda", inst["lambda"]]56    args += ["--seed", str(seed), "--json", "--expected-d", inst["d"]] + extra57    proc = subprocess.run(args, capture_output=True, text=True, timeout=600)58    out = json.loads(proc.stdout)59    if expect_ok:60        assert proc.returncode == 0, (proc.stdout, proc.stderr)61    return out626364def check_output(inst, out):65    assert out["ok"] is True66    assert out["match"] is True67    assert out["d"] == inst["d"]68    # independent verification in pure Python69    E = Curve(int(inst["p"]), int(inst["a"]), int(inst["b"]), n=int(inst["n"]))70    G = (int(inst["gx"]), int(inst["gy"]))71    assert E.mul(int(out["d"]), G) == (int(inst["qx"]), int(inst["qy"]))72    for key in ("ops", "iterations", "dp_count", "dp_bytes", "fruitless_cycles", "threads", "wall_s", "cpu_s",73                "table_entries", "peak_rss_bytes", "params"):74        assert key in out, key75    assert set(out["ops"]) == {"add", "dbl", "inv", "mul"}76    assert out["peak_rss_bytes"] > 0777879@pytest.mark.parametrize("combo", sorted(COMBOS))80@pytest.mark.parametrize("inst", INSTANCES, ids=[i["label"] for i in INSTANCES])81def test_all_instances_all_algorithms(inst, combo):82    out = solve(inst, COMBOS[combo])83    check_output(inst, out)84    if combo == "bsgs":85        assert out["table_entries"] > 086    if combo == "rho-dp-4t":87        assert out["threads"] == 488    if combo == "rho-neg":89        assert out["params"]["neg"] is True909192@pytest.mark.parametrize("combo", sorted(J0_COMBOS))93@pytest.mark.parametrize("inst", J0, ids=[i["label"] for i in J0])94def test_j0_instances_automorphism_classes(inst, combo):95    out = solve(inst, J0_COMBOS[combo])96    check_output(inst, out)97    assert out["params"]["aut3"] is True9899100@pytest.mark.parametrize("inst", GENERIC[::7] + J0[::5], ids=lambda i: i["label"])101def test_single_thread_runs_are_deterministic(inst):102    for extra in (["--alg", "rho"], ["--alg", "rho", "--neg"], ["--alg", "kangaroo"], ["--alg", "rho", "--cycle", "brent"]):103        a = solve(inst, extra, seed=42)104        b = solve(inst, extra, seed=42)105        for key in ("d", "iterations", "ops", "dp_count", "fruitless_cycles"):106            assert a[key] == b[key], key107108109def test_wrong_beta_aborts():110    inst = J0[0]111    bad = dict(inst, beta=str(int(inst["beta"]) + 1))112    out = solve(bad, ["--alg", "rho", "--aut3"], expect_ok=False)113    assert out["ok"] is False and "error" in out114115116def test_kangaroo_interval_and_memory_override():117    inst = GENERIC[-1]118    d, n = int(inst["d"]), int(inst["n"])119    out = solve(inst, ["--alg", "kangaroo", "--lo", str(max(0, d - 10**5)), "--hi", str(min(n - 1, d + 10**5))])120    check_output(inst, out)121    assert out["iterations"] < 20_000  # ~2·sqrt(2e5) ≈ 900 expected steps, plus DP overshoot122    out = solve(inst, ["--alg", "bsgs", "--memory", "4096"])123    check_output(inst, out)124    assert out["table_entries"] <= 4096125126127def test_batch_mode(tmp_path):128    jobs = []129    for inst in GENERIC[:3] + J0[:1]:130        base = {k: inst[k] for k in ("p", "a", "b", "n", "gx", "gy", "qx", "qy")}131        base["expected_d"] = inst["d"]132        jobs.append(dict(base, alg="bsgs", seed=1, label=inst["label"] + "/bsgs"))133        jobs.append(dict(base, alg="rho", seed=2, neg=True, threads=2, label=inst["label"] + "/rho"))134        jobs.append(dict(base, alg="kangaroo", seed=3, label=inst["label"] + "/kangaroo"))135        if "beta" in inst:136            jobs.append(dict(base, alg="rho", seed=4, neg=True, aut3=True, beta=inst["beta"], lambda_=None,137                             **{"lambda": inst["lambda"]}, label=inst["label"] + "/rho-neg-aut3"))138    for j in jobs:139        j.pop("lambda_", None)140    f = tmp_path / "jobs.json"141    f.write_text(json.dumps(jobs))142    outf = tmp_path / "results.json"143    proc = subprocess.run([str(BIN), "batch", "--file", str(f), "--out", str(outf)], capture_output=True, text=True, timeout=600)144    assert proc.returncode == 0, proc.stderr145    results = json.loads(outf.read_text())146    assert len(results) == len(jobs)147    for job, res in zip(jobs, results):148        assert res["label"] == job["label"]149        assert res["ok"] and res["match"] and res["d"] == job["expected_d"]150
Files · c732f7e320257301eb619a8227b914374a41aac4
Menu

Theme