From 5ec422ffd79a5b9a1551dab89090603a955463fc Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Fri, 6 Feb 2026 19:17:37 -0300 Subject: [PATCH 1/3] Process tables sequentially to reduce peak memory in multi-table proving --- crypto/stark/src/prover.rs | 66 ++++++++++++++---------------------- crypto/stark/src/verifier.rs | 13 +++---- 2 files changed, 30 insertions(+), 49 deletions(-) diff --git a/crypto/stark/src/prover.rs b/crypto/stark/src/prover.rs index 059b91378..db0b042d7 100644 --- a/crypto/stark/src/prover.rs +++ b/crypto/stark/src/prover.rs @@ -43,8 +43,6 @@ type AirTracePair<'a, Field, FieldExtension, PI> = ( &'a PI, ); -type MainCommitment = (Round1CommitmentData, Vec>>); - /// A default STARK prover implementing `IsStarkProver`. pub struct Prover< Field: IsSubFieldOf + IsFFTField + Send + Sync, @@ -458,32 +456,30 @@ pub trait IsStarkProver< /// Phase 1a of Round 1: Commit only the main trace to the transcript. /// Returns the main trace commitment data and LDE evaluations. /// Does NOT sample RAP challenges or build auxiliary trace. - #[allow(clippy::type_complexity)] fn round_1_commit_main_trace( trace: &TraceTable, domain: &Domain, transcript: &mut impl IsStarkTranscript, - ) -> Result<(Round1CommitmentData, Vec>>), ProvingError> + ) -> Result, ProvingError> where FieldElement: AsBytes, FieldElement: AsBytes, { - let Some((trace_polys, evaluations, main_merkle_tree, main_merkle_root)) = + let Some((trace_polys, _evaluations, main_merkle_tree, main_merkle_root)) = Self::interpolate_and_commit_main(trace, domain, transcript) else { return Err(ProvingError::EmptyCommitment); }; + // _evaluations dropped here — will be recomputed from trace_polys later - let main = Round1CommitmentData:: { + Ok(Round1CommitmentData:: { trace_polys, lde_trace_merkle_tree: main_merkle_tree, lde_trace_merkle_root: main_merkle_root, precomputed_merkle_tree: None, precomputed_merkle_root: None, num_precomputed_cols: 0, - }; - - Ok((main, evaluations)) + }) } /// Phase 1a variant for preprocessed tables: commits precomputed and multiplicities separately. @@ -493,14 +489,13 @@ pub trait IsStarkProver< /// - Multiplicity columns (num_precomputed_cols..): separate tree, root in proof /// /// Both commitments are added to the transcript for Fiat-Shamir binding. - #[allow(clippy::type_complexity)] fn round_1_commit_preprocessed_trace( trace: &TraceTable, domain: &Domain, transcript: &mut impl IsStarkTranscript, precomputed_commitment: Commitment, num_precomputed_cols: usize, - ) -> Result<(Round1CommitmentData, Vec>>), ProvingError> + ) -> Result, ProvingError> where FieldElement: AsBytes, FieldElement: AsBytes, @@ -533,30 +528,25 @@ pub trait IsStarkProver< Self::batch_commit_main(&mult_rows).ok_or(ProvingError::EmptyCommitment)?; // Verify that our computed precomputed root matches the hardcoded commitment. - // This is a sanity check - if they don't match, something is wrong with the trace. debug_assert_eq!( precomputed_root, precomputed_commitment, "Prover's precomputed commitment doesn't match hardcoded AIR commitment" ); // Add BOTH commitments to transcript for Fiat-Shamir binding. - // The precomputed commitment binds challenges to the correct precomputed values. - // The multiplicities commitment binds challenges to the actual lookups made. transcript.append_bytes(&precomputed_commitment); transcript.append_bytes(&mult_root); - // Store multiplicities tree as main (for FRI openings), precomputed tree separately - let main = Round1CommitmentData:: { + // evaluations dropped here — will be recomputed from trace_polys later + + Ok(Round1CommitmentData:: { trace_polys, lde_trace_merkle_tree: mult_tree, lde_trace_merkle_root: mult_root, precomputed_merkle_tree: Some(precomputed_tree), precomputed_merkle_root: Some(precomputed_root), num_precomputed_cols, - }; - - // Return full evaluations (all columns) for constraint evaluation - Ok((main, evaluations)) + }) } /// Phase 1c of Round 1: Build and commit auxiliary trace using pre-sampled challenges. @@ -1211,14 +1201,13 @@ pub trait IsStarkProver< // same hardcoded value in the transcript. let mut domains = Vec::with_capacity(num_airs); - let mut main_commitments: Vec> = Vec::with_capacity(num_airs); + let mut main_commitments: Vec> = Vec::with_capacity(num_airs); for (air, trace, _pub_inputs) in &*air_trace_pairs { let trace_length = trace.num_rows(); let domain = new_domain(*air, trace_length); - let (main, evaluations) = if air.is_preprocessed() { - // Preprocessed table: use hardcoded commitment for precomputed columns + let main = if air.is_preprocessed() { Self::round_1_commit_preprocessed_trace( *trace, &domain, @@ -1227,11 +1216,10 @@ pub trait IsStarkProver< air.num_precomputed_columns(), )? } else { - // Normal table: compute commitment as usual Self::round_1_commit_main_trace(*trace, &domain, transcript)? }; - main_commitments.push((main, evaluations)); + main_commitments.push(main); domains.push(domain); } @@ -1250,16 +1238,22 @@ pub trait IsStarkProver< }; // ===================================================================== - // Round 1, Phase C: Build and commit auxiliary traces + // Phase C + Rounds 2-4: Process each table sequentially // ===================================================================== - // Each AIR builds its LogUp running-sum columns using the shared challenges. + // Only one table's full Round1 data exists at a time. + // LDE evaluations are recomputed from polynomials per table. - let mut round_1_results: Vec> = Vec::with_capacity(num_airs); - for (((air, trace, _pub_inputs), (main, main_evaluations)), domain) in air_trace_pairs + let mut proofs = Vec::with_capacity(num_airs); + for (((air, trace, pub_inputs), main), domain) in air_trace_pairs .iter_mut() .zip(main_commitments) .zip(domains.iter()) { + // Recompute main LDE evaluations from polynomials + let main_evaluations = + Self::compute_lde_trace_evaluations::(&main.trace_polys, domain); + + // Phase C: Build and commit auxiliary trace let round_1_result = Self::round_1_build_auxiliary_trace( *air, *trace, @@ -1269,20 +1263,12 @@ pub trait IsStarkProver< main_evaluations, logup_challenges.clone(), )?; - round_1_results.push(round_1_result); - } - // ===================================================================== - // Rounds 2-4: Standard STARK protocol for each AIR - // ===================================================================== - - let mut proofs = Vec::with_capacity(num_airs); - for (((air, _, pub_inputs), round_1_result), domain) in - air_trace_pairs.iter().zip(round_1_results).zip(domains) - { + // Rounds 2-4 let proof = - Self::prove_rounds_2_to_4(*air, *pub_inputs, &round_1_result, transcript, &domain)?; + Self::prove_rounds_2_to_4(*air, *pub_inputs, &round_1_result, transcript, domain)?; proofs.push(proof); + // round_1_result dropped here — frees this table's LDE + trees } Ok(MultiProof::new(proofs)) diff --git a/crypto/stark/src/verifier.rs b/crypto/stark/src/verifier.rs index 8e63f166d..b1260801e 100644 --- a/crypto/stark/src/verifier.rs +++ b/crypto/stark/src/verifier.rs @@ -887,20 +887,15 @@ pub trait IsStarkVerifier< }; // ===================================================================== - // Round 1, Phase C: Replay auxiliary trace commitments + // Phase C + Rounds 2-4: Interleaved per table (matches prover ordering) // ===================================================================== - for proof in &multi_proof.proofs { + for (air, proof) in airs.iter().zip(&multi_proof.proofs) { + // Phase C: replay this table's auxiliary commitment if let Some(root) = proof.lde_trace_aux_merkle_root { transcript.append_bytes(&root); } - } - - // ===================================================================== - // Rounds 2-4: Verify each proof - // ===================================================================== - - for (air, proof) in airs.iter().zip(&multi_proof.proofs) { + // Rounds 2-4: verify this table's proof if !Self::verify_rounds_2_to_4(*air, proof, transcript, logup_challenges.clone()) { return false; } From 159201e4f6277b6416e3776e481ca7af3de6c70c Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Fri, 6 Feb 2026 20:31:08 -0300 Subject: [PATCH 2/3] Fix missing idx variable in verifier loop and trailing whitespace --- crypto/stark/src/prover.rs | 2 +- crypto/stark/src/verifier.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/stark/src/prover.rs b/crypto/stark/src/prover.rs index 357745d9c..c4d3a85b2 100644 --- a/crypto/stark/src/prover.rs +++ b/crypto/stark/src/prover.rs @@ -1263,7 +1263,7 @@ pub trait IsStarkProver< main_evaluations, logup_challenges.clone(), )?; - + #[cfg(feature = "debug-checks")] print_bus_balance_report(&round_1_results); diff --git a/crypto/stark/src/verifier.rs b/crypto/stark/src/verifier.rs index 8e704f46d..71c84150b 100644 --- a/crypto/stark/src/verifier.rs +++ b/crypto/stark/src/verifier.rs @@ -892,12 +892,12 @@ pub trait IsStarkVerifier< // Phase C + Rounds 2-4: Interleaved per table (matches prover ordering) // ===================================================================== - for (air, proof) in airs.iter().zip(&multi_proof.proofs) { + for (idx, (air, proof)) in airs.iter().zip(&multi_proof.proofs).enumerate() { // Phase C: replay this table's auxiliary commitment if let Some(root) = proof.lde_trace_aux_merkle_root { transcript.append_bytes(&root); } - + // ===================================================================== // Rounds 2-4: Verify each proof // ===================================================================== From 64648da37368e81578b8cab8876a775d217818a5 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 11 Feb 2026 15:21:13 -0300 Subject: [PATCH 3/3] Fix debug-checks build: use singular round_1_result in sequential loop --- crypto/stark/src/prover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/stark/src/prover.rs b/crypto/stark/src/prover.rs index fdb02a66a..10a5ba843 100644 --- a/crypto/stark/src/prover.rs +++ b/crypto/stark/src/prover.rs @@ -1265,7 +1265,7 @@ pub trait IsStarkProver< )?; #[cfg(feature = "debug-checks")] - print_bus_balance_report(&round_1_results); + print_bus_balance_report(std::slice::from_ref(&round_1_result)); // ===================================================================== // Rounds 2-4: Standard STARK protocol for each AIR