perf(stark/verifier): four micro-opts targeting the recursion guest#626
perf(stark/verifier): four micro-opts targeting the recursion guest#626diegokingston wants to merge 1 commit into
Conversation
All four changes preserve verifier semantics — `cargo test -p stark` passes 126/126; `cargo test -p lambda-vm-prover --lib` keeps the baseline 273 pass / 77 pre-existing failures (unrelated to constraints, `UnknownSyscall(5)` on `main`); `make lint` is clean. 1) step_2_verify_claimed_composition_polynomial: drop the HashMap<usize, FE> step memo in favour of a small Vec scanned linearly. Boundary constraints number in the low tens; in RISC-V the HashMap's hash function + heap allocator outweigh O(B^2) scan savings. 2) reconstruct_deep_composition_poly_evaluations_for_all_queries: hoist lde_base / lde_base_sym / denoms_scratch out of the per-query loop and `.clear()` each iteration instead of fresh `Vec::new()`. At ~64-128 queries this removes hundreds of small allocations per verify. `reconstruct_deep_composition_poly_evaluation` now takes the scratch buffer as a `&mut Vec<FE<E>>` parameter. 3) verify: drop `proof.clone()` for the single-table wrapper. Added a `multi_verify_proofs` slice-taking variant; `multi_verify` is now a one-line wrapper preserving the existing `&MultiProof` API for all current callers (~50 across stark tests and prover). 4) verify_trace_openings and verify_query_and_sym_openings: replace `result & openings_ok` (always-evaluate fold) with early `return false` on each opening failure. Cuts wasted Merkle work on tampered proofs. The FRI loop additionally drops the pre-allocated `evaluation_point_vec` in favour of stepping `evaluation_point_squared` in-place — one fewer Vec alloc per query. A length-alignment guard makes the loop hit its `i == last_layer_idx` return for any well-formed proof.
Codex Code ReviewNo issues found in the PR diff. I checked the changed verifier logic for soundness, panic paths on malformed proofs, and meaningful performance regressions. The FRI length guard and short-circuiting preserve the boolean behavior while rejecting malformed decommitment lengths earlier. Verification:
|
| // Unreachable: the length guard above ensures the loop iterates at | ||
| // least once (we passed the is_empty check) and hits the | ||
| // `i == last_layer_idx` return. | ||
| unreachable!("loop must hit the last_layer_idx return") |
There was a problem hiding this comment.
Low: unreachable!() in a verifier panics instead of rejecting
A verifier should never panic on any malformed input — a panic is a DoS vector. The invariant is sound given the guards (all three slices are equal-length and non-empty, so the loop always hits i == last_layer_idx), but return false is strictly safer for defense in depth.
| unreachable!("loop must hit the last_layer_idx return") | |
| return false; // all openings passed but loop fell through (should not happen) |
Code Review: perf(stark/verifier): four micro-opts targeting the recursion guestOverviewFour semantics-preserving performance changes to SecurityPositive — new FRI length guard (lines 514-518) Low — CorrectnessFRI
MinorO(B²)
SummaryNo critical or high issues. The one item worth addressing before merge is the |
…erify, opening short-circuit)
Follow-up to #601's preprocessed-commitment caching: targets the same recursion-guest cost surface but inside
crypto/stark/src/verifier.rs. Four independent, semantics-preserving changes that cut allocations and wasted Merkle work when the verifier runs as a RISC-V program.Changes
step_2_verify_claimed_composition_polynomial— drop theHashMap<usize, FieldElement<Field>>step memo for a smallVec<(usize, FE)>scanned linearly. Boundary constraints number in the low tens; in RISC-V theHashMap's hash function + heap allocator outweigh any O(B²) saving on a tiny B.reconstruct_deep_composition_poly_evaluations_for_all_queries— hoistlde_base,lde_base_sym, anddenoms_scratchout of the per-query loop and.clear()each iteration instead of freshVec::new(). At ~64-128 queries this removes hundreds of small allocations per verify.reconstruct_deep_composition_poly_evaluationnow takes the scratch buffer as a&mut Vec<FieldElement<FieldExtension>>parameter.verify— dropproof.clone()in the single-table wrapper. Added amulti_verify_proofsslice-taking variant;multi_verifyis now a one-line wrapper preserving the existing&MultiProofAPI for all current callers (~50 across stark tests and prover).verifynow calls the slice variant viacore::slice::from_ref(proof).verify_trace_openingsandverify_query_and_sym_openings— replace theresult & openings_okalways-evaluate fold with explicitreturn falseon each opening failure, cutting wasted Merkle work on tampered proofs. The FRI loop additionally drops the pre-allocatedevaluation_point_vecin favour of steppingevaluation_point_squaredin-place (one fewerVecalloc per query) and adds an explicit length-alignment guard so the loop hits itsi == last_layer_idxreturn for any well-formed proof.Test plan
cargo build -p stark— clean.make lint— clean across all three clippy configs.cargo test -p stark— 126/126 passing (covers single-tableverify, multi-tablemulti_verify, and all FRI / DEEP / boundary paths via the example AIRs).cargo test -p lambda-vm-prover --lib— 273 pass / 77 fail; same counts asmain(verified). All 77 failures are pre-existing on the base, unrelated to constraints (UnknownSyscall(5)etc.).Notes
multi_verifyandMultiProofare unchanged from the caller's perspective.num_queries × num_tables.Clonerequirement on the transcript) are larger and out of scope for this PR.