decider
superseded: this page describes the decider of the legacy fold (feature
legacy, unsound, removed in phase 5); the decider of the shipped design is one WHIR opening of the accumulator — see accumulation (envelope profile 4; under IVC, profiles 5 and 6) and soundness.
what replaces it
the legacy chain was "fold every step into a CCS accumulator, then run one decider". its fold was never checked by the verifier (hemera is a hash, not a homomorphic commitment, so no cross-term commitment chain exists), so the decider closed a claim nobody had verified — see §soundness below.
the shipped design replaces it with hash-based accumulation of Reed–Solomon evaluation claims, ARC/WARP-style (ARC eprint 2024/1731, WARP 2025/753), specified in accumulation and machine: each step batches its claims with a sumcheck, commits one combined word, samples out-of-domain points and opens a few query positions; the accumulator is one root plus a fixed number of claims, independent of the number of steps; the decider is one WHIR opening of the final accumulator. accumulation alone does not make the proof constant — every step leaves openings for the verifier (profile 4 grows with the steps); IVC (profile 5) checks them inside the next step and the wrap chain (profile 6) compresses the result.
| quantity | status |
|---|---|
| decider proof size | decider 44–93 KB measured (one WHIR opening of an Fp3 word, ℓ = 14…21; audit/accumulation-2026-10.md) |
| decider verify time | goal ≤ 1 ms; decider verify 3.1–4.6 ms measured (hash.tri, merkle-32; whole machine proof 5–40 ms, linear in segments) |
| constant-size proof of a run | profile 6: final proof ≤ 64,213 B over every transcript (audit/proof-stack-review-2026-10.md), verify 1.9–2.2 ms with keys loaded (audit/ivc-speed-2026-10.md); goal ≤ 64 KB, ≤ 1 ms |
when to decide is unchanged in spirit: the accumulator keeps absorbing steps (signal, block, epoch) and is decided once where an outside party needs a proof.
the legacy decider (historical, 0.3.1)
decide(acc) ran one SuperSpartan + sumcheck + Brakedown opening on the folded universal-step instance (m = 64 rows, 15 matrices, degree ≤ 4, witness padded to n = 128). measured wire sizes of that unsound construction, kept as a record only: universal group 1.1–1.5 KiB, binding group about 1.0 KiB; joy programs (a+b)*a 1127 B, two divine() 1179 B, one hash 2154 B, a depth-32 Merkle path 2161 B. these figures say nothing about a sound proof: the opening did not bind the evaluation (§lens residual) and the fold was unchecked. the earlier "constant cost in constraints", fixed verify-time and recursion figures for this decider were never measured and are withdrawn.
soundness
in a verifier-checked fold, folding preserves CCS satisfiability: if any step produced a dishonest trace row, the accumulated error term e will be non-zero with overwhelming probability over the fold challenge β. the decider checks satisfiability of the folded instance — a non-zero e causes it to reject. the legacy fold is not verifier-checked, which is what the residuals below record.
current implementation residual. the relaxed fold as implemented recomputes e from the folded witness and the verifier receives e as public accumulator data without a verifier-side fold check (no cross-term commitment chain — Brakedown is not additively homomorphic). for a degree-1 instance this is closed by the zero-error rule: satisfied steps fold to e = 0 exactly, so the binding group must carry a zero error vector. for the universal instance (degree ≤ 4) the honest error vector is non-zero, so a violated Layer-1 row is not visible to the verifier through e; commit() refuses to fold one (CommitError::StepUnsatisfied), which makes the prover honest but leaves the verifier trusting the fold. closing this needs a verifier-checked fold; phase 3 provides it with hash-based accumulation (accumulation.md).
empirically confirmed, 0.3.2. three attacks were built directly against decide()/verify(), bypassing commit()'s honesty gates entirely (constructing witnesses and, in earlier drafts of these tests, accumulators by hand):
- a witness with the right shape but a wrong register (e.g. an add row claiming
5 + 3 = 9) folded through the publicfold_step/foldentry point used to be accepted unconditionally —fold_steptrusted the caller (in practice, onlycommit()'s own separate, caller-side gate,CommitError::StepUnsatisfied) to have screened the witness first. closed in 0.3.2: the per-witness satisfiability check moved intofold_stepitself (FoldError::UnsatisfyingWitness), so no caller —commit(), a downstream crate callingcrate::folddirectly, or a future bug in some other caller — can skip it.Accumulator's fields are alsopub(crate)now (werepub): the type can only be produced by folding through this gate, not by a bare struct literal from outside the crate. Regression:fold_step_rejects_witness_with_wrong_register(rs/src/folding/fold.rs). - a SHARPER bug in the same family: the "constant" witness column (
z[32]in the universal instance'sZ_LEN = 96layout, meant to always hold the field element 1) is never independently pinned by any CCS row — every row that references it to mean "the literal 1" (the selector-sum rowΣ s_p − z[32] = 0, the axis budget rowr9 − r8 + z[32] = 0, …) is itself gated by a pattern selector that is also a free witness column. The all-zero vector —z[32]included — therefore satisfies the ENTIRE universal instance exactly (error_evalscomes back all-zero, not merely undetected): every gated row is0 × (anything) = 0, and every ungated row is a homogeneous linear relation among witness columns with no independent additive constant, so it is0 = 0too. NOT closed in 0.3.2 — finding 1's gate cannot reject this witness because it is not lying about its own error; the CCS relation itself does not notice. Closing it needs the constant wire to be independently, verifier-checked pinned to 1 — a public/private witness split (the standard R1CS/CCS construction, where the verifier supplies the "1" and any public inputs directly rather than trusting the prover's commitment for them), or, short of that architecture change, an extra fixed-point PCS opening of the committed witness at column 32 checked to equal 1.commit()'s honest path always setsz[32] = 1(ccs::universal::universal_witness), so real proofs are unaffected; recorded here as follow-up work comparable in size to the universal-step-CCS milestone. Regression:retired_path_hole_zeroed_constant_wire_satisfies_universal_instance(rs/src/folding/fold.rs,legacyfeature only) — a passing test that demonstrates the hole of the retired path. The production profiles pin the constant on the verifier side (execution::certificate,fixture_zeroed_constant_is_rejectedinrs/tests/attack_fixtures.rs). - the deepest residual, unchanged from 0.3.0 and NOT closed by 0.3.2: a witness that is honestly, genuinely satisfying — a real solution of the CCS,
z[32] = 1included — but corresponds to no real nox execution and bears no relationship to theStatement, still decides and verifies for ANYStatementthe caller supplies.decide()'s SuperSpartan sumcheck proves "eis consistent with the committedz" (and, if the PCS opening is sound, tieseto that specificz) — it says nothing about whetherzreflects a real execution, or has anything to do withprogram_hash/input_hash/output_hash/focus_bound/bbg_root.Statementis absorbed into the Fiat-Shamir transcript, so a proof is bound to the exact statement bytesdecide()used, but nothing stops a caller from choosing the witness AND the statement independently and honestly with respect to each other. Closing this needs a verifier-checked accumulation over the N real steps that produced the witness and a statement-derived relation — phase 3 accumulation over the machine relation (machine.md); no per-witness gate closes it, because there is nothing internally inconsistent to catch. Regression:retired_path_hole_meaningless_witness_passes_for_any_statement(rs/src/folding/fold.rs,legacyfeature only) — a passing test that demonstrates the hole of the retired path. The production profiles derive the relation and its public prefix from the statement (fixture_meaningless_witness_is_rejectedinrs/tests/attack_fixtures.rs).
what this means for shipped 0.3.0/0.3.1 proofs. none of the three findings retroactively invalidate a proof HONESTLY produced by commit() from a real nox trace: commit()'s own gates (StepUnsatisfied, the axis/hash/look *Binding gates, StatementMismatch) already ensured the witness was a genuine, satisfying encoding of that trace, with z[32] = 1 by construction — those proofs remain valid and verify() accepts them exactly as before. 0.3.2 closes what a caller who bypasses commit() and calls the folding API directly could get away with going forward for finding 1; findings 2 and 3 were already true of every prior version back to 0.3.0 (finding 2 further back, to whenever the universal instance landed) and remain true after 0.3.2 — they describe what the accumulator/decider layer alone can and cannot prove about a witness's provenance, independent of which shipped version produced the wire bytes.
lens residual. the Brakedown opening as implemented in lens checks that round_commitments[0] equals the commitment, that each proximity query index equals the transcript-derived one, and that final_poly equals the claimed value. it does not check the queried codeword symbols (a flat hash of the codeword cannot authenticate one symbol) and does not tie consecutive round commitments to each other through the tensor reduction — so the opening does not yet bind the claimed evaluation to the committed polynomial. the proximity test is owed by lens; zheng's wire form carries only what the verifier reads today, and grows when the verifier does.
see accumulation.md for the accumulation scheme that replaces the legacy fold, recursion and accumulator for the superseded legacy pages, verifier for the shipped verifiers.