1414//!
1515//! ```text
1616//! shape histogram <- bound BEFORE the first root
17- //! per table: main LDE -> prep/main MMCS builders [barrier]
18- //! prep_root , main_root
17+ //! per table: main LDE -> per-table prep tree + main MMCS builder [barrier]
18+ //! per-table prep roots (from the AIR set) , main_root
1919//! LogUp challenges
2020//! per table: aux trace + aux LDE -> aux MMCS builder [barrier]
2121//! aux_root
@@ -70,7 +70,8 @@ use crate::batched::proof::{
7070 lde_bytes,
7171} ;
7272use crate :: batched:: round4:: commit_batched_fri;
73- use crate :: batched:: shape:: { EpochShape , PinnedPrep , RoundShape , ShapeError } ;
73+ use crate :: batched:: shape:: { EpochShape , RoundShape , ShapeError } ;
74+ use crypto:: merkle_tree:: merkle:: MerkleTree ;
7475use crate :: config:: StarkHash ;
7576use crate :: domain:: Domain ;
7677use crate :: fri:: batched:: HeightCombiner ;
@@ -116,20 +117,15 @@ struct LdePair<Field: IsField, FieldExtension: IsField> {
116117
117118/// Prove one epoch with batched commitments.
118119///
119- /// `expected_prep`, when supplied, is the registry's committed preprocessed root
120- /// and the widths it was committed over (M-6). The prover compares its own
121- /// against them and fails fast, preserving the property the per-table path gets
122- /// from `air.precomputed_commitment()`: a stale preprocessed constant is caught
123- /// here rather than by every future verifier.
124- ///
125- /// `None` is permissive here — it is how the root is generated in the first
126- /// place. That is the opposite of the verifier's disposition; see
127- /// [`PinnedPrep`].
120+ /// Preprocessed matrices are committed per table (the trees
121+ /// `air.precomputed_commitment()` pins), so the per-table path's stale-constant
122+ /// guard runs here unconditionally: a built prep tree that disagrees with the
123+ /// AIR's own root fails the prove with the same error the per-table prover
124+ /// raises.
128125#[ allow( clippy:: too_many_arguments) ]
129126pub fn multi_prove_batched < Field , FieldExtension , PI , H , P > (
130127 mut air_trace_pairs : Vec < BatchedAirTracePair < ' _ , Field , FieldExtension , PI > > ,
131128 transcript : & mut ( impl IsStarkTranscript < FieldExtension , Field > + Clone + Send ) ,
132- expected_prep : Option < PinnedPrep < ' _ > > ,
133129 #[ cfg( feature = "disk-spill" ) ] storage_mode : StorageMode ,
134130 residency : ResidencyMode ,
135131) -> Result <
@@ -199,14 +195,22 @@ where
199195 // Both builders are fed from the SAME expansion: a preprocessed table's
200196 // precomputed columns and its multiplicity columns are two column ranges of
201197 // one row-major main LDE, exactly as `commit_main_trace` splits them.
202- let mut prep_builder =
203- ( !shape. prep . is_empty ( ) ) . then ( || StreamingMmcsBuilder :: < Field , H > :: new ( & shape. prep . dims ) ) ;
198+ // ★ Per-table preprocessed trees — #768's arrangement, kept for the same
199+ // reason (see `BatchedQueryOpening::prep`): each preprocessed table keeps
200+ // its OWN row-pair tree, the one `air.precomputed_commitment()` pins, and
201+ // both sides absorb that root FROM THE AIR SET, never from the proof —
202+ // the per-table path's critical soundness check, verbatim. The trees are
203+ // process-cached by root, so continuation epochs stop re-committing the
204+ // execution-independent tables (DECODE, BITWISE, ...), exactly as the
205+ // per-table prover does.
206+ let mut prep_trees: Vec < Option < std:: sync:: Arc < MerkleTree < H :: Batched < Field > > > > > =
207+ ( 0 ..num_tables) . map ( |_| None ) . collect ( ) ;
204208 let mut main_builder = StreamingMmcsBuilder :: < Field , H > :: new ( & shape. main . dims ) ;
205209 let mut retained_main: Vec < Option < ( Vec < FieldElement < Field > > , usize ) > > =
206210 ( 0 ..num_tables) . map ( |_| None ) . collect ( ) ;
207211
208212 for table in 0 ..num_tables {
209- let ( _ , trace, _) = & air_trace_pairs[ table] ;
213+ let ( air , trace, _) = & air_trace_pairs[ table] ;
210214 let ( main_data, total_cols) = P :: expand_main_lde_row_major (
211215 trace,
212216 & domains[ table] ,
@@ -221,17 +225,36 @@ where
221225 let height = shape. heights [ table] ;
222226 let num_precomputed = total_cols - matrix_width ( & shape. main , table) ;
223227
224- if let Some ( builder) = prep_builder. as_mut ( )
225- && num_precomputed > 0
226- {
227- let src = vec ! [ BorrowedMatrix :: RowMajorNatural {
228- data: & main_data,
229- stride: total_cols,
230- col_start: 0 ,
231- width: num_precomputed,
232- log_height: height,
233- } ] ;
234- builder. absorb ( & src, 0 ) ;
228+ if num_precomputed > 0 {
229+ // The root every verifier will absorb is the AIR's own; building a
230+ // tree that disagrees with it is a stale constant or a wrong LDE,
231+ // and the per-table path's error is the honest name for both.
232+ let expected = air. precomputed_commitment ( ) ;
233+ let tree = match crate :: prover:: precomputed_tree_cache_get :: < H :: Batched < Field > > (
234+ & expected,
235+ ) {
236+ Some ( tree) => tree,
237+ None => {
238+ let ( tree, root) = P :: commit_rows_bit_reversed_subset :: < Field > (
239+ & main_data,
240+ total_cols,
241+ 0 ,
242+ num_precomputed,
243+ )
244+ . ok_or ( ProvingError :: PrecomputedCommitmentMismatch ) ?;
245+ if root != expected {
246+ return Err ( ProvingError :: PrecomputedCommitmentMismatch ) ;
247+ }
248+ let tree = std:: sync:: Arc :: new ( tree) ;
249+ crate :: prover:: precomputed_tree_cache_put (
250+ expected,
251+ std:: sync:: Arc :: clone ( & tree) ,
252+ ) ;
253+ tree
254+ }
255+ } ;
256+ transcript. append_bytes ( & expected) ;
257+ prep_trees[ table] = Some ( tree) ;
235258 }
236259 let src = vec ! [ BorrowedMatrix :: RowMajorNatural {
237260 data: & main_data,
@@ -253,24 +276,7 @@ where
253276 }
254277 }
255278
256- let prep_mmcs = prep_builder. map ( StreamingMmcsBuilder :: finish) ;
257279 let main_mmcs = main_builder. finish ( ) ;
258- let prep_root = prep_mmcs. as_ref ( ) . map ( MixedMmcs :: root) ;
259- // The widths are compared first because they are the more legible failure:
260- // a registry whose entry predates a change to some AIR's precomputed column
261- // count disagrees here in a way that names the cause, instead of surfacing
262- // as a root mismatch that could equally be a stale constant.
263- if let Some ( expected) = expected_prep {
264- if expected. widths != shape. prep . widths ( ) . as_slice ( ) {
265- return Err ( ProvingError :: PrecomputedCommitmentMismatch ) ;
266- }
267- if prep_root. as_ref ( ) != Some ( expected. root ) {
268- return Err ( ProvingError :: PrecomputedCommitmentMismatch ) ;
269- }
270- }
271- if let Some ( root) = prep_root {
272- transcript. append_bytes ( & root) ;
273- }
274280 let main_root = main_mmcs. root ( ) ;
275281 transcript. append_bytes ( & main_root) ;
276282
@@ -603,7 +609,10 @@ where
603609 let iotas = commit. iotas . clone ( ) ;
604610 let fri_decommitments = crate :: fri:: query_phase :: < FieldExtension , H > ( & commit. layers , & iotas) ;
605611
606- let mut prep_openings = empty_openings :: < Field > ( & iotas, shape. prep . tables . len ( ) ) ;
612+ // Per-query, per-prep-table standard openings (prep-table order =
613+ // `shape.prep.tables`, which is AIR order).
614+ let mut prep_openings: Vec < Vec < crate :: proof:: stark:: PolynomialOpenings < Field > > > =
615+ ( 0 ..iotas. len ( ) ) . map ( |_| Vec :: new ( ) ) . collect ( ) ;
607616 let mut main_openings = empty_openings :: < Field > ( & iotas, shape. main . tables . len ( ) ) ;
608617 let mut aux_openings = empty_openings :: < FieldExtension > ( & iotas, shape. aux . tables . len ( ) ) ;
609618 let mut parts_openings = empty_openings :: < FieldExtension > ( & iotas, shape. parts . tables . len ( ) ) ;
@@ -614,9 +623,6 @@ where
614623 // (the prep round's `h_max` is below the FRI's whenever the tallest
615624 // preprocessed table is not the tallest table), and a round that reduced
616625 // again on the way out would land somewhere else entirely.
617- let prep_iotas = prep_mmcs
618- . as_ref ( )
619- . map ( |mmcs| reduced_iotas ( & iotas, h_max, mmcs. h_max ( ) ) ) ;
620626 let main_iotas = reduced_iotas ( & iotas, h_max, main_mmcs. h_max ( ) ) ;
621627 let aux_iotas = aux_mmcs
622628 . as_ref ( )
@@ -644,16 +650,15 @@ where
644650 let ( main_data, total_cols) = & ldes. main ;
645651 let num_precomputed = total_cols - matrix_width ( & shape. main , table) ;
646652
647- if let ( Some ( mmcs) , Some ( m) ) = ( prep_mmcs. as_ref ( ) , matrix_index ( & shape. prep , table) ) {
648- let src = vec ! [ BorrowedMatrix :: RowMajorNatural {
649- data: main_data,
650- stride: * total_cols,
651- col_start: 0 ,
652- width: num_precomputed,
653- log_height: height,
654- } ] ;
655- let indices = prep_iotas. as_ref ( ) . expect ( "the prep MMCS exists here" ) ;
656- fill_openings ( mmcs, m, & src, indices, & mut prep_openings) ;
653+ if let Some ( tree) = prep_trees[ table] . as_ref ( ) {
654+ // The per-table tree lives in the TABLE's own index space; reduce
655+ // the shared FRI index by the height difference once, here.
656+ let table_iotas = reduced_iotas ( & iotas, h_max, height) ;
657+ for ( q, & idx) in table_iotas. iter ( ) . enumerate ( ) {
658+ prep_openings[ q] . push ( P :: open_polys_with ( & domains[ table] , tree, idx, |row| {
659+ main_data[ row * total_cols..row * total_cols + num_precomputed] . to_vec ( )
660+ } ) ) ;
661+ }
657662 }
658663 if let Some ( m) = matrix_index ( & shape. main , table) {
659664 let src = vec ! [ BorrowedMatrix :: RowMajorNatural {
@@ -697,11 +702,7 @@ where
697702
698703 let queries = ( 0 ..iotas. len ( ) )
699704 . map ( |q| BatchedQueryOpening {
700- prep : prep_mmcs. as_ref ( ) . map ( |mmcs| {
701- let indices = prep_iotas. as_ref ( ) . expect ( "the prep MMCS exists here" ) ;
702- assemble ( mmcs, indices[ q] , & mut prep_openings, q)
703- . expect ( "the prep round was opened at these very indices" )
704- } ) ,
705+ prep : std:: mem:: take ( & mut prep_openings[ q] ) ,
705706 main : assemble ( & main_mmcs, main_iotas[ q] , & mut main_openings, q)
706707 . expect ( "the main round was opened at these very indices" ) ,
707708 aux : aux_mmcs. as_ref ( ) . map ( |mmcs| {
@@ -739,7 +740,6 @@ where
739740 Ok ( (
740741 BatchedMultiProof {
741742 tables,
742- prep_root,
743743 main_root,
744744 aux_root,
745745 parts_root,
0 commit comments