SPB Git

spb/wp34_uqo

Public

# experiments/signature_benchmarks — Research Axis B (ECDSA / BIP340 Schnorr)

Benchmarks of legitimate secp256k1 signature implementations on synthetic keys (mandate §5, §9; research questions B2 and B5). Nothing here touches real keys: every secret key, message, aux-rand value and batch weight is drawn from a ChaCha20 RNG seeded with --seed (default 34).

# Layout

Path Role
sigbench/ Rust crate (binary). Dependencies: secp256k1 0.33 (rust-secp256k1 → vendored libsecp256k1 C, features rand, global-context, std), k256 0.14 (RustCrypto, features ecdsa, schnorr, arithmetic, precomputed-tables, std, alloc), sha2, rand/rand_chacha 0.9, serde(_json), clap. Release profile: opt-level 3, fat LTO, 1 codegen unit.
sigbench/src/msm.rs Own multi-scalar multiplication on k256::{AffinePoint, ProjectivePoint, Scalar}: Straus (interleaved width-5 wNAF, per-term odd-multiple tables) and Pippenger (bucket method, c-bit windows, mixed additions, running-sum trick), both instrumented with counters (projective adds, mixed adds, doublings, identity adds). Also lift_x, the BIP340 challenge hash and batch_verify.
sigbench/src/vectors.rs Parser + checker for data/vectors/bip340_test_vectors.csv against both libraries (sign where a secret key is given, verify everywhere, compare with the expected column).
sigbench/src/stats.rs Median / quartiles / IQR / mean / sd and a bootstrap (percentile) 95 % CI for a ratio of medians.
run_bench.py Thin wrapper: cargo build --release, run the binary, run python_crosscheck.py on the dumped triples, run python_baselines.py, add the wp34.provenance.record() block plus rustc/cargo/crate versions (cargo tree), write results/signature_benchmarks/bench_<hostname>.json. Standard library only (runs with a bare python3 on cluster nodes).
python_crosscheck.py Verifies Rust-produced (seckey, msg, sig) triples with the exact-arithmetic references src/wp34/ecdsa.py and src/wp34/schnorr.py, re-signs in Python and compares bytes, and rejects a tampered signature. Used by tests/test_sig_crosscheck.py.
python_baselines.py Pure-Python src/wp34, pure-Python ecdsa package, and coincurve (CFFI → libsecp256k1) timings, 20 ops × 7 reps.
summarize.py Collects every bench_*.json into results/signature_benchmarks/summary.json and prints a Markdown digest.
../../figures/make_sig_figures.py Regenerates figures/sig_batch_verification.pdf and figures/sig_implementations.pdf from the JSON files.

# Running

bash
# laptop (repository root)
PYTHONPATH=src .venv/bin/python experiments/signature_benchmarks/run_bench.py --seed 34 --label "Laptop (Apple M5 Max)"

# cluster node (crate + src/wp34 + data/vectors rsync'ed to ~/wp34-sigbench, cargo in PATH)
ssh M3U96a 'cd ~/wp34-sigbench && PYTHONPATH=src python3 experiments/signature_benchmarks/run_bench.py --seed 34 --label "M3U96a (Apple M3 Ultra)"'
rsync -a M3U96a:~/wp34-sigbench/results/signature_benchmarks/ results/signature_benchmarks/

.venv/bin/python experiments/signature_benchmarks/summarize.py
.venv/bin/python figures/make_sig_figures.py
.venv/bin/python -m pytest tests/test_sig_crosscheck.py -q

Binary options (sigbench --help): --seed, --reps (7), --batch-ops (2000 operations per repetition), --batch-min-sigs (≥ 2000 signatures per repetition in the batch benchmark), --batch-sizes, --crosscheck-n (1000), --python-crosscheck-n (200), --bootstrap (2000 resamples), --pippenger-c (0 = size heuristic), --ct-lincomb-max-batch (256). Everything runs on the main thread; macOS has no hard CPU pinning from user space, so "pinned single thread" means single-threaded with no parallelism.

# What is measured

  1. Correctness gates (all recorded in the JSON under measurements.correctness_gates): (a) 19/19 BIP340 vectors with both libraries (verification column + byte-identical signing for the 8 vectors with a secret key); (b) 1000 cross-library checks in each direction for ECDSA (RFC 6979, SHA-256 prehash, low-S) and Schnorr (with aux_rand) — signatures are also byte-identical across libraries because both nonce derivations are deterministic; (c) 200 triples verified and re-signed by the pure-Python references (python_reference_crosscheck); (d) own wNAF scalar multiplication agrees with k256 on 64 cases; (e) batch negative controls (one signature with s+1, a flipped message bit, or r+1 inside a batch of 64) rejected by all four MSM back-ends.
  2. Sign / verify ns per op (median, IQR over 7 reps × 2000 ops): ECDSA and Schnorr, libsecp256k1 and k256.
  3. Batch verification Σ a_i s_i G = Σ a_i R_i + Σ a_i e_i P_i (a_1 = 1, a_i random 128-bit), merged into one MSM with 2b + 1 terms (points negated instead of scalars so the weights stay 128-bit), b ∈ {1, …, 2048}, with four back-ends: own Straus, own Pippenger, k256 lincomb_vartime (GLV + wNAF), k256 lincomb (constant time, up to b = 256). Reported per signature: wall-clock, MSM-only time, operation counts, and speed-up S(b) relative to single verification (k256 verify_raw and libsecp256k1) with bootstrap 95 % CI.
  4. Scalar multiplication: k256 constant-time (GLV, generic point and generator), k256 mul_by_generator (precomputed table), k256 vartime (GLV + wNAF), own wNAF-5 double-and-add without GLV (with op counts), libsecp256k1 ecmult_gen (PublicKey::from_secret_key) and ecmult_const (PublicKey::mul_tweak). GLV cannot be toggled from either library's public API (see scalar_mult.glv_note in the JSON); the only non-GLV data point is our own implementation.
  5. Python baselines for the implementation spread (pure Python ≈ milliseconds, CFFI ≈ C).

# Headline numbers (seed 34, 2026-11-01)

Laptop, Apple M5 Max M3U96a, Apple M3 Ultra
ECDSA sign / verify, libsecp256k1 23.6 / 12.6 µs 29.2 / 16.7 µs
ECDSA sign / verify, k256 15.3 / 25.0 µs 18.8 / 33.5 µs
Schnorr sign / verify, libsecp256k1 20.5 / 13.2 µs 24.7 / 16.9 µs
Schnorr sign / verify, k256 11.6 / 24.5 µs 14.8 / 32.5 µs
Batch verify, own Pippenger, b = 2048 7.9 µs/sig, S = 3.10 [3.05, 3.16] vs k256, 1.67 vs libsecp256k1 10.7 µs/sig, S = 3.03 [3.00, 3.04] vs k256, 1.58 vs libsecp256k1
Batch verify, own Straus (plateau b ≥ 32) ≈ 12.1–12.7 µs/sig, S ≈ 2.0 ≈ 16.1 µs/sig, S ≈ 2.0
Pure Python src/wp34 Schnorr verify 7.4 ms 9.2 ms

Full digest: results/signature_benchmarks/summary.json; interpretation and classification: research_log/2026-11-01-signatures.md.

Files · main
Menu

Theme