Skip to content

perf(ecsm): 7.7x faster witness generation#866

Open
diegokingston wants to merge 2 commits into
mainfrom
perf/ecsm-witness-biguint
Open

perf(ecsm): 7.7x faster witness generation#866
diegokingston wants to merge 2 commits into
mainfrom
perf/ecsm-witness-biguint

Conversation

@diegokingston

Copy link
Copy Markdown
Collaborator

Summary

compute_witness (one ECSM ecall's witness): 20.8ms → 2.7ms measured (high-popcount scalar, ~380 steps). At 4 ecalls per ecrecover that's ~83ms → ~11ms host per recovery; ~8.3s → ~1.1s for a 100-signature block.

Three changes:

  1. Hand-rolled Jacobian replay (dbl-2009-l / madd-2007-bl) over k256's public field arithmetic, using the crate's own Montgomery batch-inversion for z-normalization and slope denominators. Replaces k256's ProjectivePoint::batch_normalize, which measured ~5-6ms for the ~760 points of one witness — no better than 760 individual to_affine() calls (its inner BatchInvert is fine at ~60µs; the total never gets the batching benefit).
  2. shifted_quotient: one div_rem instead of separate % (divisibility assert) and / — halves the 512/256-bit BigInt divisions (6 → 3 per step).
  3. build_step loop on rayon — steps are independent witnesses.

The subtle part (caught by the parity tests)

The first Jacobian version produced wrong points: k256's lazy-magnitude negate(1) is only correct below ~2p, so subtraction chains over unnormalized intermediates miscompute (this is why k256's own formulas call normalize_weak after every add-chain). Fixed with strategic normalize() on intermediates feeding subtractions, documented in the code. Parity with the BigUint reference replay is covered by tests::curve_tests.

Verification

  • 15/15 ecsm crate tests (parity vs the reference replay).
  • 8/8 prover ECSM tests: full prove+verify of the ecsm guests + the forged-witness rejection tests.
  • Timing harness included: cargo run --release --example bench_witness -p ecsm.

compute_witness: 20.8ms -> 2.7ms per ECSM ecall (measured, this machine,
high-popcount scalar ~380 steps).

Three changes:
- Replay in hand-rolled Jacobian coordinates (dbl-2009-l / madd-2007-bl)
  over k256's public field arithmetic, with the crate's own Montgomery
  batch-inversion for z-normalization and slope denominators. Replaces
  k256's ProjectivePoint::batch_normalize, which measured ~5-6ms for the
  ~760 points of one witness -- no better than per-point to_affine.
  Intermediates are normalized before subtractions: k256's lazy-magnitude
  negate(1) is only correct below ~2p (same reason k256's own formulas
  call normalize_weak). Parity with the BigUint reference replay is
  covered by tests::curve_tests.
- shifted_quotient: one div_rem instead of separate % and / (halves the
  512/256-bit BigInt divisions, 6 -> 3 per step).
- build_step loop runs on rayon (steps are independent witnesses).

Verification: 15/15 ecsm tests, 8/8 prover ecsm tests (incl. full
prove+verify of the ecsm guests and the forged-witness rejection tests).
Adds examples/bench_witness.rs as the timing harness.
@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench

@github-actions

Copy link
Copy Markdown

Benchmark — ethrex 20 transfers (median of 3)

Table parallelism: auto (cores / 3)

Metric main PR Δ
Peak heap 57086 MB 57319 MB +233 MB (+0.4%) ⚪
Prove time 30.840s 29.506s -1.334s (-4.3%) ⚪

✅ No significant change.

✅ Low variance (time: 1.3%, heap: 1.8%)

Commit: 59acfe8 · Baseline: cached · Runner: self-hosted bench

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant