Repository navigation
perf(crypto): wire keccak_permute ecall into merkle/transcript/grinding - #802
Conversation
Route the STARK verifier's Keccak-256 usage (Merkle tree backends, Fiat-Shamir transcript, FRI grinding) through the keccak_permute precompile on the riscv64 guest via a new PlatformKeccak256 digest wrapper, falling back to software sha3 on host. The guest previously used sha3's software permutation exclusively, making it the dominant verifier cost. Recursion guest cycles (blowup8 preset): - single query: 5,234,002,718 -> 1,722,846,582 (3.04x) - multi query: 22,494,934,472 -> 7,687,016,693 (2.93x) Also excludes the riscv-only `syscalls` crate from the root workspace: making it reachable from crypto/crypto (a workspace member) via a target-gated path dependency caused Cargo to auto-adopt it as an implicit member, so `cargo test` tried to run its bare-metal unit tests on host and aborted. Adds an end-to-end guest test (keccak_precompile) exercising the ecall-backed sponge against known Keccak-256 vectors, covering the padding edge cases (empty, rate-1, exactly-rate, multi-block input).
|
/ai-review |
Codex Code ReviewNo actionable issues found in the PR diff. Static review only; builds and tests were not run per instructions. |
|
Review — perf(crypto): wire keccak_permute ecall into merkle/transcript/grinding Reviewed the full diff against the surrounding code. No critical/high issues found. The change is well-scoped and the key correctness property is properly guarded. Correctness (the property that matters): the critical requirement is that the guest (riscv, precompile-backed) and host (software sha3) produce IDENTICAL Keccak-256 output — otherwise a proof built one way would not verify the other. This holds:
Minor observations (non-blocking):
LGTM. |
AI ReviewPR #802 · 17 changed files
FindingsNo non-rejected structured findings were reported. Reviewer Lanes
Native Codex and Claude reviews run separately and post their own comments. They are not included in this structured provenance report. Raw lane outputs, candidates, final issues, and model metrics are uploaded as workflow artifacts. |
diegokingston
left a comment
There was a problem hiding this comment.
Statement is using still sha3
…t sponge pattern elf_digest still used sha3::Keccak256 directly, so the recursion guest hashed the entire inner ELF in software instead of through the keccak_permute ecall like every other hash site. Also add a KAT covering DefaultTranscript's real call pattern (small non-rate-aligned updates interleaved with finalize_reset), which the existing one-shot KAT never exercised, and drop a redundant clone+scratch-buffer in PlatformKeccak256's finalize_into/finalize_into_reset.
|
/bench-verify |
|
⏳ Benchmark started on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes. |
|
/bench-verify |
Verifier benchmark —
|
| Metric | main | PR | Δ |
|---|---|---|---|
| Verify time | 3.892s | 3.893s | +0.02% ⚪ |
| Proof size | 230.99 MiB | 230.99 MiB | +0.00% ⚪ |
pairs: 20 mean A (PR): 3.893s mean B (main): 3.892s
[parametric] paired-t mean +0.02% sd 0.61% se 0.14%
95% CI: [-0.26%, +0.30%] (t df=19 = 2.093)
[robust] median +0.14% Wilcoxon W+=110 W-=100 p(exact)=0.8695 (z=+0.17)
run-to-run jitter: A CV 0.44% B CV 0.37% (lower = steadier)
within-session drift: -0.31% over the run, 1st->2nd half -0.19%
⚪ INCONCLUSIVE — effect not separable from 0 at n=20 (point estimate ~+0.14%). Add pairs to resolve.
Drift-free interleaved A/B/B/A measurement. - = PR faster. Trust the verdict when paired-t and Wilcoxon agree.
Recursion guest cycles (main vs PR)
=== Recursion-guest cycle comparison — single query (blowup=2, 1 query) — deterministic to ~±100k cycles ===
REF_B (baseline) origin/main 8e504da guest=recursion-min.elf
REF_A (PR) 934ffc1 934ffc1 guest=recursion-min.elf
| Metric | REF_B (baseline) | REF_A (PR) | Δ (A-B) |
|---|---|---|---|
| Guest cycles | 126.0M | 99.1M | -26.9M (-21.35%) |
| Keccak calls | 0 | 3885 | +3885 |
note: cycles reproduce to ~±100k (build codegen + proof nondeterminism); treat sub-100k deltas as noise, not signal.
raw (exact integer counts)
ref_b_sha=8e504da5f4bc9ef77342221d7eeca2049fe7cc5a ref_b_elf=recursion-min.elf ref_b_cycles=125968543 ref_b_keccak=0 ref_b_execute_wall_s=3
ref_a_sha=934ffc1031165e05e4a37bb0353cf4ac28f50225 ref_a_elf=recursion-min.elf ref_a_cycles=99079876 ref_a_keccak=3885 ref_a_execute_wall_s=2
delta_cycles=-26888667 delta_keccak=3885
|
⏳ Benchmark started on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes. |
program_id_from_digest still used sha3::Keccak256 (software) instead of PlatformKeccak256, so the in-guest program-id fold bypassed the keccak_permute accelerator. statement.rs also imported the Digest trait via sha3 despite already using PlatformKeccak256, masking that the guest path never touches sha3 directly. Drop the sha3 dependency from prover in favor of digest, since PlatformKeccak256 supplies the concrete hasher on all targets.
MauroToscano
left a comment
There was a problem hiding this comment.
We should patch sha3 crate in a fork to use sha3, then only swap the version of sha3 on the guest, not do this
…+ cleanups (#813) * perf(crypto): route recursion program_id fold through keccak_permute + cleanups Follow-ups to #802's keccak-syscall wiring. - prover/recursion.rs: program_id_from_digest ran the in-guest program_id fold on software sha3::Keccak256 -- the one verifier-guest keccak still bypassing the keccak_permute ecall. Route it through PlatformKeccak256, as statement::elf_digest already is. Correctness-neutral (software and syscall Keccak-256 are identical, so check_attestation still matches), but it removes the last software Keccak-f from the recursion guest. - Unify prover on digest::Digest (statement.rs used sha3::Digest, while grinding.rs and default_transcript.rs already use digest::Digest). No concrete sha3 type remains in prover, so swap its sha3 dependency for digest. - platform_keccak.rs: replace the two hot-path `.try_into().unwrap()`s in finalize_into / finalize_into_reset with an infallible fixed-size copy. - Cargo.toml: correct the exclude=["syscalls"] rationale -- the crate has no unit tests; host workspace builds fail because it defines a #[global_allocator] plus #[unsafe(no_mangle)] entrypoints/syscalls (and riscv asm!) that only link for riscv64im-lambda-vm-elf. * align prover digest dep with base (drop needless default-features=false) --------- Co-authored-by: Mario Rugiero <mrugiero@gmail.com>
|
/bench-verify
After further discussion we decided to keep it like this, lgtm |
|
⏳ Benchmark started on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes. |
|
/bench |
Benchmark — ethrex 20 transfers (median of 3)Table parallelism: auto (cores / 3)
Commit: 6625390 · Baseline: cached · Runner: self-hosted bench |
Summary
keccak_permuteprecompile on the riscv64 guest, via aPlatformKeccak256digest wrapper aroundlambda_vm_syscalls::keccak::Keccak256; falls back to softwaresha3on host.syscallscrate from the root workspace (exclude = ["syscalls"]): making it reachable fromcrypto/cryptovia a target-gated path dependency caused Cargo to auto-adopt it as an implicit workspace member, socargo testtried to run its bare-metal unit tests on host and aborted.keccak_precompile) exercising the ecall-backed sponge against known Keccak-256 vectors (empty, rate-1, exactly-rate, multi-block), covering the padding edge cases.Cycle comparison (recursion guest, blowup8 preset)
Test plan
make test-profile-recursion-single/-multibefore and after (cycles above)test_recursion_execute_1query— in-VM verify accepts with the ecall-backed hashertest_keccak_precompileguest test — known-answer vectors incl. padding edge casesmake lintmake test— all pass exceptmath-cudaCUDA tests (expected: no GPU on this machine)