Skip to content

feat(recursion): return audited lookup contexts on reconstruct_batch_tables - #458

Open
Raphsolt wants to merge 3 commits into
Plonky3:mainfrom
Raphsolt:feat/reconstruct-batch-lookups
Open

Raphsolt wants to merge 3 commits into
Plonky3:mainfrom
Raphsolt:feat/reconstruct-batch-lookups

Conversation

@Raphsolt

@Raphsolt Raphsolt commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Exposes a lookups: Vec<Lookups<Val<SC>>> field on ReconstructedBatchTables, derived inside reconstruct_batch_tables directly from the reconstructed AIRs, and consumed by verify_p3_batch_proof_circuit instead of being derived a second time there.

Why

BatchStarkProof intentionally omits lookup contexts from its serialized stark_common, because the verifier rebuilds them from the AIRs reconstructed from proof metadata. verify_p3_batch_proof_circuit's own comment gives the reason to prefer the rebuild: a malformed or malicious lookup set is ignored rather than believed.

That rebuild was reachable only from inside the crate:

  • reconstruct_batch_tables returned airs, trace_lens and public_values, but not the lookups.
  • CircuitTablesAir::to_table_air is crate-private, so an external caller cannot pass those AIRs into lookups_for_circuit_table_air.

So an external caller had no way to rebuild CommonData for a proof deserialized from bytes, and every entry point that takes one was closed to them. replay_batch_stark_transcript is the one that prompted this: it recovers the challenger state and the commitment/opening-point list a proof was produced under, which a recursive verifier needs before it can restore query paths.

Returning Lookups<Val<SC>> rather than a flattened Vec<Lookup<_>> preserves the newtype CommonData::new accepts. Lookups has private fields and no From/FromIterator, so a flattened field could not be converted back and would be usable only by the one consumer whose target type happens to want that shape.

Changes

  • Add pub lookups: Vec<Lookups<Val<SC>>> to ReconstructedBatchTables, documented with rustdoc.
  • Derive it in reconstruct_batch_tables via lookups_for_circuit_table_air, moving the existing comment about not trusting the proof-supplied set to where the decision is now made.
  • Update verify_p3_batch_proof_circuit to consume tables.lookups, flattening with to_vec() only at that one target site.
  • Update the destructure in backend/transcript.rs for the new field.
  • Propagate the required SymbolicExpressionExt algebra where-clause to reconstruct_batch_tables and its callers in prepared/input.rs.

Testing

cargo check -p p3-recursion passes.

cargo test -p p3-recursion passes.

Exercised downstream by a recursive verifier that rebuilds CommonData for deserialised proofs, which is what surfaced the gap.

Risk

Low, and additive. Verification behaviour is unchanged: the lookups the verifier uses are identical to the ones it derived before, computed once rather than twice.

The API additions are a public field on ReconstructedBatchTables, so anyone constructing one literally needs the extra initialiser, and a where-clause on reconstruct_batch_tables — SymbolicExpressionExt<Val<SC>, SC::Challenge>: Algebra<SymbolicExpression<Val<SC>>> + Algebra<SC::Challenge>. That bound was always required by the derivation; moving the derivation moves it. Callers reaching this through verify_p3_batch_proof_circuit already satisfy it.

No performance work. The change removes one duplicated derivation and is otherwise identical; nothing was measured because nothing was expected to move.

Review notes

Why native Lookups and not Vec<Lookup<_>>: covered above — the flattened form is a one-way door, since Lookups' only public constructor is from_air.

Alternative considered: making to_table_air public instead. Returning the lookups is preferred because the audited derivation then stays in one place rather than becoming something each external caller reimplements — and reimplementing it wrongly means trusting a lookup set the proof supplied.

Sibling: same shape as Plonky3#2119 — a symbol needed by downstream consumers that was reachable only inside the crate.


Update: replay_batch_layer_transcript (added to main after this was opened) and the FRI and WHIR backends now also read ReconstructedBatchTables::lookups rather than re-deriving it, so the derivation lives only in reconstruct_batch_tables / trusted_batch_tables.

@Raphsolt
Raphsolt requested a review from Nashtare as a code owner September 10, 2026 07:33
@Raphsolt
Raphsolt force-pushed the feat/reconstruct-batch-lookups branch from 51831a3 to 49f744a Compare September 15, 2026 23:31
Raphsolt and others added 2 commits September 26, 2026 11:23
…where

replay_batch_layer_transcript and the FRI and WHIR backends each derived
every table's lookups again straight after reconstructing or trusting its
tables. They now read the lookups the reconstruction returns, so the
derivation lives only in reconstruct_batch_tables and trusted_batch_tables.
No behaviour change: each removed derivation called the same function with
the same arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Raphsolt

Copy link
Copy Markdown
Contributor Author

Since opening this, replay_batch_layer_transcript landed on main and derives the lookups itself, as do the FRI and WHIR backends right after reconstructing or trusting their tables. I've updated the PR so all of those read the new field, and the derivation now lives only in reconstruct_batch_tables / trusted_batch_tables. No behaviour change: each removed derivation called lookups_for_circuit_table_air with the same arguments.

(If CI's clippy job fails, it's clippy::chunks_exact_to_as_chunks in circuit/src/ops/, which is on main and untouched here.)

Happy to close this if you'd rather keep the derivations where they are.

This branch has not been deployed

No deployments
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