From 1ea787697917bf6e4b85620c8913907b5b5a3683 Mon Sep 17 00:00:00 2001 From: diegokingston Date: Thu, 16 Jul 2026 17:23:22 -0300 Subject: [PATCH] perf(verifier): reuse precomputed trace root + share Q FRI points Two recursion-cycle savings in the verifier (= serial recursion guest): 1. Drop VerifierDomain::root_order. The DEEP driver recomputed the trace primitive root via get_primitive_root_of_unity (repeated squarings) every proof; reuse the precomputed domain.trace_primitive_root instead. 2. Compute the Q primary FRI evaluation points once in step_3 and share them between the DEEP reconstruction and the FRI fold. The DEEP driver previously recomputed all 2Q (primary+symmetric) points and the fold recomputed Q more. Now: Q pow() total; each symmetric point is -primary (bit-reversed index primary+N/2, lde_root^(N/2)=-1), so negate instead of a second pow(); the fold moves the same Q points into its inverses. Byte-identical. 204 stark tests pass, clippy clean. --- crypto/stark/src/domain.rs | 2 -- crypto/stark/src/verifier.rs | 45 +++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/crypto/stark/src/domain.rs b/crypto/stark/src/domain.rs index 9b9be3af2..afa12bbd4 100644 --- a/crypto/stark/src/domain.rs +++ b/crypto/stark/src/domain.rs @@ -101,7 +101,6 @@ impl Domain { /// which only needs to compute specific elements on-demand. /// This avoids allocating O(n) memory for domains that would be O(millions) of elements. pub struct VerifierDomain { - pub(crate) root_order: u32, pub(crate) trace_length: usize, pub(crate) lde_length: usize, pub(crate) trace_primitive_root: FieldElement, @@ -139,7 +138,6 @@ where let lde_primitive_root = Field::get_primitive_root_of_unity(lde_root_order as u64).unwrap(); VerifierDomain { - root_order, trace_length, lde_length, trace_primitive_root, diff --git a/crypto/stark/src/verifier.rs b/crypto/stark/src/verifier.rs index 775120032..361df4636 100644 --- a/crypto/stark/src/verifier.rs +++ b/crypto/stark/src/verifier.rs @@ -428,6 +428,18 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, { crate::profile_markers::step_marker::<{ crate::profile_markers::STEP_VERIFY_FRI }>(); + + // The Q primary FRI evaluation points 𝜐 = offsetΒ·lde_root^iota, computed once + // (one `pow` each) and shared between the DEEP reconstruction β€” whose symmetric + // point is βˆ’πœ, so it needs no second `pow` β€” and the FRI fold's 𝜐⁻¹ below. + // Previously the driver recomputed all 2Q (primary + symmetric) and the fold + // recomputed Q more. + let query_points: Vec> = challenges + .iotas + .iter() + .map(|iota| Self::query_challenge_to_evaluation_point(*iota, false, domain)) + .collect(); + let (deep_poly_evaluations, deep_poly_evaluations_sym) = match Self::reconstruct_deep_composition_poly_evaluations_for_all_queries( challenges, @@ -436,6 +448,7 @@ pub trait IsStarkVerifier< ood_full, next_row_cols, step_size, + &query_points, ) { Some(pair) => pair, None => return false, @@ -482,12 +495,9 @@ pub trait IsStarkVerifier< layout.terminal_len, ); - // verify FRI - let mut evaluation_point_inverse = challenges - .iotas - .iter() - .map(|iota| Self::query_challenge_to_evaluation_point(*iota, false, domain)) - .collect::>>(); + // verify FRI: the fold needs 𝜐⁻¹ per query β€” reuse the primary points computed + // above (moved in) instead of recomputing them. + let mut evaluation_point_inverse = query_points; // Any zero evaluation point means a malformed query index, reject. if FieldElement::inplace_batch_inverse(&mut evaluation_point_inverse).is_err() { return false; @@ -852,6 +862,9 @@ pub trait IsStarkVerifier< ood_full: &Table, next_row_cols: &[usize], step_size: usize, + // The Q primary FRI evaluation points 𝜐, computed once by the caller and shared + // with the FRI fold. Each query's symmetric point is βˆ’πœ (no extra `pow`). + query_points: &[FieldElement], ) -> Option> { let num_queries = challenges.iotas.len(); @@ -866,8 +879,10 @@ pub trait IsStarkVerifier< return None; } - let primitive_root = &Field::get_primitive_root_of_unity(domain.root_order as u64) - .expect("verifier domain root_order is a valid power of two"); + // The trace-size primitive root is precomputed once when the verifier domain + // is built; reuse it instead of recomputing (which needed `root_order` and a + // `get_primitive_root_of_unity` β€” repeated squarings β€” every proof). + let primitive_root = &domain.trace_primitive_root; let query_invariant_terms = Self::compute_query_invariant_deep_terms( challenges, @@ -897,14 +912,16 @@ pub trait IsStarkVerifier< // denominator; points ordered [q0 primary, q0 sym, q1 primary, q1 sym, …]. let stride = ood_height + 1; let mut denominators = Vec::with_capacity(2 * num_queries * stride); - for iota in challenges.iotas.iter() { - for is_sym in [false, true] { - let evaluation_point = - Self::query_challenge_to_evaluation_point(*iota, is_sym, domain); + // The symmetric point of each primary FRI point 𝜐 is βˆ’πœ (its bit-reversed LDE + // index is `primary + N/2` and `lde_root^(N/2) = βˆ’1`), so negate instead of a + // second `pow`. + for primary in query_points { + let sym = -primary; + for evaluation_point in [primary, &sym] { for z_row_point in &z_row_points { - denominators.push(&evaluation_point - z_row_point); + denominators.push(evaluation_point - z_row_point); } - denominators.push(&evaluation_point - z_pow_parts); + denominators.push(evaluation_point - z_pow_parts); } } // One inversion for the whole proof's DEEP denominators. Fails closed if any