Clone this repository
For self-hosted knots, clone URLs may differ based on your setup.
Download tar.gz
root cause: every call to verify() was doing
`AffinePoint.fromStdlib(public_key.affineCoordinates())`, and stdlib's
Secp256k1.affineCoordinates() unconditionally inverts Z — even when the
point was created from SEC1 (where Z is always 1). that field inversion
was ~12 µs per call, which Tracy instrumentation showed was 19% of the
verify budget. totally wasted work since the result is deterministic
per PublicKey.
fix: PublicKey now caches an AffinePoint at fromSec1 time. one-time
field inversion cost at key construction, zero cost per verify. adds
~80 bytes per PublicKey (2 × Fe = 10 × u64), negligible.
signature change: verify_mod.verify() now takes AffinePoint instead of
Secp256k1. soft-breaking only for direct callers of the low-level
verify function — the public high-level APIs (Signature.verifyMsg,
Signature.verifyPrehashed, PublicKey.fromSec1) are unchanged.
measured (ReleaseFast, M1, 200k iterations × 8 warm runs):
before: mean 18,630 v/s, ~52.7 µs/op
after: mean 23,299 v/s, ~42.9 µs/op
delta: +25% mean, +11.6% worst-case (worst-after vs best-before)
added correctness safety net:
- tests/verify_test.zig: 2 new stress tests that run under standard
`zig build test`:
- "stress: 2000 random verify cases match stdlib exactly" —
randomized (keypair, msg, signature) triples, bit-exact agreement
with stdlib verify required
- "stress: 500 corrupted signature cases match stdlib exactly" —
random bit-flip corruptions, rejection parity with stdlib
these catch regressions in scalar reduction, field arithmetic, table
indexing, sign handling, and edge cases before a benchmark would.
all 42 tests pass pre- and post-change.
added scripts/bench_verify.zig + `zig build bench-verify` target for
reproducible throughput measurement on future optimization work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>