SPB Git

spb/wp34_uqo

Public
6.3 KB · 120 lines python
1"""Python baselines for the implementation-choice spread (WP34 Axis B, research question B5):2pure-Python `src/wp34` (exact integer arithmetic, no tables), the pure-Python `ecdsa` package,3and `coincurve` (CFFI bindings to libsecp256k1). Small samples (default 20 ops x 7 reps),4median / IQR reported in ns per op. Synthetic keys only (random.Random(seed)).56Usage: PYTHONPATH=src python experiments/signature_benchmarks/python_baselines.py [--seed 34] [--ops 20] [--reps 7]7Prints a JSON document on stdout. Libraries that are not importable are reported as null.8"""9from __future__ import annotations10import argparse, hashlib, json, random, statistics, sys, time11from pathlib import Path1213ROOT = Path(__file__).resolve().parents[2]14sys.path.insert(0, str(ROOT / "src"))151617def quantile(sorted_xs, q):18    pos = q * (len(sorted_xs) - 1)19    lo, hi = int(pos // 1), min(int(pos // 1) + 1, len(sorted_xs) - 1)20    return sorted_xs[lo] + (sorted_xs[hi] - sorted_xs[lo]) * (pos - lo)212223def timing(reps, ops, fn):24    fn()  # warm-up25    samples = []26    for _ in range(reps):27        t = time.perf_counter_ns()28        fn()29        samples.append((time.perf_counter_ns() - t) / ops)30    v = sorted(samples)31    med = quantile(v, 0.5)32    return {33        "reps": reps, "ops_per_rep": ops, "ns_per_op_samples": samples,34        "median_ns": med, "q1_ns": quantile(v, 0.25), "q3_ns": quantile(v, 0.75),35        "iqr_ns": quantile(v, 0.75) - quantile(v, 0.25), "min_ns": v[0],36        "mean_ns": statistics.fmean(v), "sd_ns": statistics.stdev(v) if len(v) > 1 else 0.0,37        "ops_per_s": 1e9 / med,38    }394041def main():42    ap = argparse.ArgumentParser()43    ap.add_argument("--seed", type=int, default=34)44    ap.add_argument("--ops", type=int, default=20)45    ap.add_argument("--reps", type=int, default=7)46    ap.add_argument("--batch", type=int, default=16, help="batch size for the pure-Python batch verification")47    a = ap.parse_args()48    rng = random.Random(a.seed)49    from wp34 import secp256k1 as k150    n_ord = k1.n51    keys = [rng.randrange(1, n_ord) for _ in range(a.ops)]52    msgs = [bytes(rng.getrandbits(8) for _ in range(32)) for _ in range(a.ops)]53    auxs = [bytes(rng.getrandbits(8) for _ in range(32)) for _ in range(a.ops)]54    out = {"seed": a.seed, "ops": a.ops, "reps": a.reps, "python": sys.version.split()[0], "results": {}, "versions": {}}55    R = out["results"]5657    # ---- pure Python src/wp34 (exact arithmetic, affine double-and-add, no precomputation)58    from wp34 import ecdsa as D, schnorr as B59    Qs = [D.pubkey(d) for d in keys]60    sigs = [D.sign(d, m) for d, m in zip(keys, msgs)]61    R["wp34_pure_python_ecdsa_sign"] = timing(a.reps, a.ops, lambda: [D.sign(d, m) for d, m in zip(keys, msgs)])62    R["wp34_pure_python_ecdsa_verify"] = timing(a.reps, a.ops, lambda: [D.verify(Q, m, s) for Q, m, s in zip(Qs, msgs, sigs)])63    assert all(D.verify(Q, m, s) for Q, m, s in zip(Qs, msgs, sigs))64    R["wp34_pure_python_ecdsa_verify_shamir"] = timing(a.reps, a.ops, lambda: [D.verify_shamir(Q, m, s) for Q, m, s in zip(Qs, msgs, sigs)])65    skb = [d.to_bytes(32, "big") for d in keys]66    pks = [B.pubkey_gen(s) for s in skb]67    ssigs = [B.sign(m, s, x) for m, s, x in zip(msgs, skb, auxs)]68    R["wp34_pure_python_schnorr_sign"] = timing(a.reps, a.ops, lambda: [B.sign(m, s, x) for m, s, x in zip(msgs, skb, auxs)])69    R["wp34_pure_python_schnorr_verify"] = timing(a.reps, a.ops, lambda: [B.verify(m, p, s) for m, p, s in zip(msgs, pks, ssigs)])70    assert all(B.verify(m, p, s) for m, p, s in zip(msgs, pks, ssigs))71    b = min(a.batch, a.ops)72    items = list(zip(msgs[:b], pks[:b], ssigs[:b]))73    brng = random.Random(a.seed + 1)74    R["wp34_pure_python_schnorr_batch_verify_per_sig"] = timing(a.reps, b, lambda: B.batch_verify(items, brng))75    R["wp34_pure_python_schnorr_batch_verify_per_sig"]["batch_size"] = b76    assert B.batch_verify(items, brng)77    bad = list(items)78    bad[1] = (bad[1][0], bad[1][1], bad[1][2][:63] + bytes([bad[1][2][63] ^ 1]))79    out["pure_python_batch_negative_control_rejected"] = not B.batch_verify(bad, brng)8081    # ---- python-ecdsa package (pure Python too, but with its own arithmetic)82    try:83        import ecdsa as E84        out["versions"]["ecdsa"] = E.__version__85        sk = [E.SigningKey.from_string(s, curve=E.SECP256k1, hashfunc=hashlib.sha256) for s in skb]86        vk = [k.get_verifying_key() for k in sk]87        es = [k.sign_digest_deterministic(m, hashfunc=hashlib.sha256) for k, m in zip(sk, msgs)]88        R["python_ecdsa_pkg_ecdsa_sign"] = timing(a.reps, a.ops, lambda: [k.sign_digest_deterministic(m, hashfunc=hashlib.sha256) for k, m in zip(sk, msgs)])89        R["python_ecdsa_pkg_ecdsa_verify"] = timing(a.reps, a.ops, lambda: [v.verify_digest(s, m) for v, s, m in zip(vk, es, msgs)])90    except Exception as e:  # pragma: no cover91        out["versions"]["ecdsa"] = None92        out["python_ecdsa_pkg_error"] = repr(e)9394    # ---- coincurve (libsecp256k1 through CFFI)95    try:96        import coincurve as C97        out["versions"]["coincurve"] = C.__version__98        ck = [C.PrivateKey(s) for s in skb]99        cpk = [k.public_key for k in ck]100        cs = [k.sign(m, hasher=None) for k, m in zip(ck, msgs)]  # DER, low-S, RFC 6979101        R["coincurve_ecdsa_sign_der"] = timing(a.reps, a.ops, lambda: [k.sign(m, hasher=None) for k, m in zip(ck, msgs)])102        R["coincurve_ecdsa_verify_der"] = timing(a.reps, a.ops, lambda: [p.verify(s, m, hasher=None) for p, s, m in zip(cpk, cs, msgs)])103        assert all(p.verify(s, m, hasher=None) for p, s, m in zip(cpk, cs, msgs))104        xo = [C.PublicKeyXOnly.from_secret(s) for s in skb]105        css = [k.sign_schnorr(m, x) for k, m, x in zip(ck, msgs, auxs)]106        R["coincurve_schnorr_sign"] = timing(a.reps, a.ops, lambda: [k.sign_schnorr(m, x) for k, m, x in zip(ck, msgs, auxs)])107        R["coincurve_schnorr_verify"] = timing(a.reps, a.ops, lambda: [p.verify(s, m) for p, s, m in zip(xo, css, msgs)])108        assert all(p.verify(s, m) for p, s, m in zip(xo, css, msgs))109        # agreement with the pure-Python reference (bytes must be identical: same aux_rand)110        out["coincurve_schnorr_identical_to_wp34"] = sum(x == y for x, y in zip(css, ssigs))111    except Exception as e:  # pragma: no cover112        out["versions"]["coincurve"] = None113        out["coincurve_error"] = repr(e)114115    print(json.dumps(out))116117118if __name__ == "__main__":119    main()120
Files · c732f7e320257301eb619a8227b914374a41aac4
Menu

Theme