Skip to content

FRI early-termination review follow-ups (2/3): deduplicate the fold schedule + terminal check#787

Merged
MauroToscano merged 2 commits into
feat/fri-early-terminationfrom
review/fri-schedule-dedup
Jul 6, 2026
Merged

FRI early-termination review follow-ups (2/3): deduplicate the fold schedule + terminal check#787
MauroToscano merged 2 commits into
feat/fri-early-terminationfrom
review/fri-schedule-dedup

Conversation

@MauroToscano

Copy link
Copy Markdown
Contributor

Follow-up to #729 (PR-A #785 already merged into this branch). Two Medium-severity refactors from the review, one commit each. No proof-format or transcript change — wire-identical.

refactor(stark): derive the FRI fold schedule in one place

The early-termination schedule (clamp, total_folds, num_committed, terminal_len, effective_k) was computed independently in three places — the CPU prover (commit_phase_from_evaluations, from LDE size), the GPU prover (try_fri_commit_gpu, from n0), and the verifier (fri_termination_params, from trace bits) — with two parameterizations whose equivalence lived only in comments. fri_termination_params' own doc claimed the arithmetic was centralized "to prevent silent drift"; it wasn't. The GPU copy is the riskiest — its parity test is #[ignore], so CI never runs it.

Introduces FriSchedule::new(lde_log, blowup_log, k) in fri/terminal.rs; all three callers derive from it. The single formulation terminal_log = min(blowup_log + k, lde_log) is overflow-safe by construction, removing the hand-rolled shift-overflow guards. Also folds the CPU prover's incremental terminal_offset squaring into the one-line coset_offset^(2^total_folds) the GPU and verifier already use.

refactor(stark): hoist the terminal-codeword check out of the fold loop

verify_query_and_sym_openings compared each query against the terminal codeword in two hand-synced places — a single-fold early return and the fold loop's last-iteration else arm (exactly where the padded-decommitment bypass the PR guards against would land). Since v/index hold the terminal-layer value/position after the loop in every regime, one hoisted check covers both, deleting the is_empty() early return and the i < len - 1 branch. Behaviour-identical because step_3_verify_fri's decommitment-length checks pin the fold count.

Testing

  • Full stark lib suite (187 tests) green, including every FRI early-termination soundness negative test (empty / padded / truncated / over-length decommitment across no-fold, single-fold, multi-fold; cross_k; oversized-k clamp; blowup4; k0) and the multi-table serialized roundtrip.
  • --features cuda type-checks (GPU prover uses the shared FriSchedule); cargo fmt --check and clippy clean on stark + math-cuda, default and cuda.

The early-termination fold layout (clamp, total_folds, num_committed,
terminal_len, effective_k) was computed independently in three places —
`commit_phase_from_evaluations` (CPU prover, from LDE size), `try_fri_commit_gpu`
(GPU prover, from n0), and `fri_termination_params` (verifier, from trace bits) —
with two different parameterizations whose equivalence lived only in comments.
The verifier's own doc comment claimed the arithmetic was centralized "to
prevent silent drift", but it was not: an edit to the clamp in one copy would
break all proofs, or (worse) only GPU-produced ones, whose parity is not
exercised in CI.

Introduce `FriFoldLayout::new(lde_log, blowup_log, k)` in fri/terminal.rs and have
all three callers derive the layout from it. The single formulation
(`terminal_log = min(blowup_log + k, lde_log)`) is also overflow-safe by
construction, removing the hand-rolled shift-overflow guards. As part of the
same cleanup, the CPU prover now derives the terminal coset offset once as
`coset_offset^(2^total_folds)` (matching the GPU and verifier) instead of
tracking it by incremental squaring through the fold loop.

Wire-identical: full stark prove/verify suite, FRI early-termination soundness
tests, and multi-table roundtrip all pass; GPU path compiles under --features cuda.
…d loop

`verify_query_and_sym_openings` checked each query against the reconstructed
terminal codeword in two places — a dedicated early return for the single-fold
(`total_folds == 1`) regime, and the last-iteration `else` arm inside the fold
loop for the multi-fold regime. Two hand-synced copies of a soundness-critical
comparison (the last-iteration arm being exactly where the padded-decommitment
bypass the PR guards against would land) is a drift risk.

After the fold loop, `v` and `index` already hold the query's terminal-layer
value and position in every regime, so a single check hoisted after the loop
covers both — deleting the `is_empty()` early return and the `i < len - 1`
last-iteration branch. The per-query decommitment length checks in
`step_3_verify_fri` (which pin the fold count) make this behaviour-identical.

Verified by the full FRI early-termination soundness suite (empty/padded/
truncated/over-length decommitment rejection across the no-fold, single-fold,
and multi-fold regimes) and the prove/verify roundtrips.
@MauroToscano MauroToscano force-pushed the review/fri-schedule-dedup branch from 29c6af7 to 7c39c7b Compare July 6, 2026 21:00
@MauroToscano MauroToscano merged commit 0a149c8 into feat/fri-early-termination Jul 6, 2026
6 checks passed
@MauroToscano MauroToscano deleted the review/fri-schedule-dedup branch July 6, 2026 21:01
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