From a4b3aaacf09c154bf4a20995d599c537b2c83153 Mon Sep 17 00:00:00 2001 From: diegokingston Date: Thu, 16 Jul 2026 17:48:39 -0300 Subject: [PATCH] =?UTF-8?q?perf(verifier):=20compute=20shared=20LogUp=20?= =?UTF-8?q?=CE=B1-powers=20once,=20slice=20per=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit step_2 rebuilt [1, α, α², …] (α-powers up to the table's bus width) once per table via compute_alpha_powers, even though the LogUp α challenge is a single value sampled once in multi_verify_views and shared across every table. Each table's power vector is just a prefix of the same geometric sequence. Compute the sequence ONCE after sampling the shared challenges — to the global-max bus width across all AIRs — and slice each table's prefix in step_2. Saves (Σ_tables max_bus_elements − max_bus_elements) ext-field multiplications and N−1 heap allocations per proof (allocations = proven memory-table writes in the recursion guest). Byte-identical: the sliced prefix equals what each table computed before. 204 stark tests pass, clippy clean, prover builds. --- crypto/stark/src/verifier.rs | 43 ++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/crypto/stark/src/verifier.rs b/crypto/stark/src/verifier.rs index 361df4636..ac0546e44 100644 --- a/crypto/stark/src/verifier.rs +++ b/crypto/stark/src/verifier.rs @@ -196,6 +196,7 @@ pub trait IsStarkVerifier< && next.height() == expected_next_height } + #[allow(clippy::too_many_arguments)] fn step_2_verify_claimed_composition_polynomial( air: &dyn AIR, proof: StarkProofView<'_, Field, FieldExtension, PI>, @@ -209,6 +210,10 @@ pub trait IsStarkVerifier< // split below. ood_full: &Table, step_size: usize, + // Powers of the shared LogUp α challenge, precomputed once by the caller to + // the global-max bus width across all tables (α is sampled once and shared), + // then sliced here to this table's width — no per-table recompute or alloc. + shared_alpha_powers: &[FieldElement], ) -> bool { crate::profile_markers::step_marker::< { crate::profile_markers::STEP_VERIFY_CLAIMED_COMPOSITION_POLYNOMIAL }, @@ -293,14 +298,14 @@ pub trait IsStarkVerifier< None => return false, }; - let logup_alpha_powers: Vec> = + // Slice this table's prefix out of the caller's shared α-power vector. When + // the proof carries LogUp challenges, `shared_alpha_powers.len()` is the + // global-max bus width, so `air.max_bus_elements()` is always in range. + let logup_alpha_powers: &[FieldElement] = if challenges.rap_challenges.len() > LOGUP_CHALLENGE_ALPHA { - compute_alpha_powers( - &challenges.rap_challenges[LOGUP_CHALLENGE_ALPHA], - air.max_bus_elements(), - ) + &shared_alpha_powers[..air.max_bus_elements()] } else { - Vec::new() + &[] }; let logup_table_offset = match proof.bus_table_contribution() { @@ -322,7 +327,7 @@ pub trait IsStarkVerifier< let transition_evaluation_context = TransitionEvaluationContext::new_verifier( &ood_frame, &challenges.rap_challenges, - &logup_alpha_powers, + logup_alpha_powers, &logup_table_offset, ); let transition_ood_frame_evaluations = @@ -1287,6 +1292,26 @@ pub trait IsStarkVerifier< Vec::new() }; + // The LogUp α challenge is shared by every table (sampled once, above), and + // the α-powers each table needs are just `[1, α, α², …]` truncated to its bus + // width. Compute the sequence ONCE to the global-max width here; each table's + // step_2 slices the prefix it needs instead of recomputing the powers and + // reallocating a vector per table. + let shared_alpha_powers: Vec> = + if lookup_challenges.len() > LOGUP_CHALLENGE_ALPHA { + let global_max_bus_elements = airs + .iter() + .map(|air| air.max_bus_elements()) + .max() + .unwrap_or(0); + compute_alpha_powers( + &lookup_challenges[LOGUP_CHALLENGE_ALPHA], + global_max_bus_elements, + ) + } else { + Vec::new() + }; + // ===================================================================== // Validate bus_public_inputs presence against AIR layout // ===================================================================== @@ -1352,6 +1377,7 @@ pub trait IsStarkVerifier< &public_inputs, &mut table_transcript, lookup_challenges.clone(), + &shared_alpha_powers, ) { error!( "Table {} failed verify_rounds_2_to_4 (num_constraints={}, trace_cols={})", @@ -1586,6 +1612,8 @@ pub trait IsStarkVerifier< public_inputs: &PI, transcript: &mut impl IsStarkTranscript, rap_challenges: Vec>, + // Shared LogUp α-powers (global-max bus width), passed through to step_2. + shared_alpha_powers: &[FieldElement], ) -> bool where FieldElement: AsBytes + Sync + Send, @@ -1674,6 +1702,7 @@ pub trait IsStarkVerifier< &challenges, &ood_full, layout.step_size(), + shared_alpha_powers, ) { #[cfg(not(feature = "test_fiat_shamir"))] error!("Composition Polynomial verification failed");