Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions crypto/stark/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,44 @@ use math::{
},
};

/// Precomputed constants for barycentric interpolation on the trace-size coset.
///
/// Derived from a [`Domain`]: the N evaluation points at stride `blowup_factor`
/// within the LDE coset, plus the field scalars that appear in every barycentric
/// evaluation. Computed once in round 3 and shared across composition-poly and
/// trace OOD evaluations.
pub struct DomainConstants<F: IsField> {
/// The N trace-size coset points: `lde_coset[i * blowup_factor]` for `i in 0..N`.
pub points: Vec<FieldElement<F>>,
/// `coset_offset ^ N`.
pub offset_pow_n: FieldElement<F>,
/// `1 / N` in the base field.
pub size_inv: FieldElement<F>,
/// `(coset_offset ^ N) ^ -1`.
pub offset_pow_n_inv: FieldElement<F>,
}

impl<F: IsFFTField> DomainConstants<F> {
pub fn from_domain(domain: &Domain<F>) -> Self {
let n = domain.interpolation_domain_size;
let bf = domain.blowup_factor;
let points = (0..n)
.map(|i| domain.lde_roots_of_unity_coset[i * bf].clone())
.collect();
let offset_pow_n = domain.coset_offset.pow(n);
let size_inv = FieldElement::<F>::from(n as u64)
Comment thread
MauroToscano marked this conversation as resolved.
.inv()
.expect("domain size is non-zero; field characteristic must not divide n");
let offset_pow_n_inv = offset_pow_n.inv().expect("coset_offset_pow_n is non-zero");
Self {
points,
offset_pow_n,
size_inv,
offset_pow_n_inv,
}
}
}

use super::traits::AIR;

/// Full domain with pre-computed roots of unity. Used by the prover which needs
Expand Down
40 changes: 10 additions & 30 deletions crypto/stark/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::trace::LDETraceTable;

use super::config::{BatchedMerkleTree, BatchedMerkleTreeBackend, Commitment};
use super::constraints::evaluator::ConstraintEvaluator;
use super::domain::Domain;
use super::domain::{Domain, DomainConstants};
use super::fri::fri_decommit::FriDecommitment;
use super::grinding;
use super::lookup::BusPublicInputs;
Expand Down Expand Up @@ -989,25 +989,12 @@ pub trait IsStarkProver<
let domain_size = domain.interpolation_domain_size;
let blowup_factor = domain.blowup_factor;

// === Composition poly parts: barycentric evaluation at z^num_parts ===
// Extract trace-size coset points from the LDE coset (stride = blowup_factor)
// Keep coset points in base field — mixed F×E arithmetic is cheaper than E×E.
let coset_points: Vec<FieldElement<Field>> = (0..domain_size)
.map(|i| domain.lde_roots_of_unity_coset[i * blowup_factor].clone())
.collect();
// Keep coset_offset_pow_n and g_n_inv in base field F — the barycentric
// functions use F×E→E mixed arithmetic, avoiding field conversions.
let coset_offset_pow_n: FieldElement<Field> = domain.coset_offset.pow(domain_size);
let domain_size_inv: FieldElement<Field> = FieldElement::<Field>::from(domain_size as u64)
.inv()
.expect("domain_size is a power of two, hence non-zero in the field");
let g_n_inv: FieldElement<Field> = coset_offset_pow_n
.inv()
.expect("coset_offset_pow_n is non-zero");
// === Shared domain constants for barycentric evaluation ===
let dc = DomainConstants::from_domain(domain);

// Precompute inv_denoms for z^num_parts (shared across all composition poly parts)
// === Composition poly parts: barycentric evaluation at z^num_parts ===
let comp_z_pow_n = z_power.pow(domain_size);
let comp_inv_denoms = math::polynomial::barycentric_inv_denoms(&z_power, &coset_points);
let comp_inv_denoms = math::polynomial::barycentric_inv_denoms(&z_power, &dc.points);

let composition_poly_parts_ood_evaluation: Vec<_> = round_2_result
.lde_composition_poly_evaluations
Expand All @@ -1019,31 +1006,24 @@ pub trait IsStarkProver<
.collect();
math::polynomial::interpolate_coset_eval_ext_with_g_n_inv(
&comp_z_pow_n,
&coset_offset_pow_n,
&domain_size_inv,
&g_n_inv,
&coset_points,
&dc.offset_pow_n,
&dc.size_inv,
&dc.offset_pow_n_inv,
&dc.points,
&evals,
&comp_inv_denoms,
)
})
.collect();

// === Trace polynomials: barycentric evaluation via LDE ===
// Uses get_trace_evaluations_from_lde which performs barycentric interpolation
// on the LDE trace data, avoiding the need for coefficient-form trace_polys.
// Reuses coset_points, coset_offset_pow_n, domain_size_inv, g_n_inv already
// computed above for composition poly evaluation — avoids redundant work.
let trace_ood_evaluations = crate::trace::get_trace_evaluations_from_lde(
&round_1_result.lde_trace,
domain,
z,
&air.context().transition_offsets,
air.step_size(),
&coset_points,
&coset_offset_pow_n,
&domain_size_inv,
&g_n_inv,
&dc,
);

Round3 {
Expand Down
25 changes: 4 additions & 21 deletions crypto/stark/src/tests/prover_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crypto::fiat_shamir::default_transcript::DefaultTranscript;

use crate::{
domain::Domain,
domain::{Domain, DomainConstants},
examples::{
quadratic_air::QuadraticAIR,
simple_fibonacci::{self, FibonacciAIR, FibonacciPublicInputs},
Expand Down Expand Up @@ -169,28 +169,11 @@ fn barycentric_trace_eval_matches_horner_trace_eval() {
step_size,
);

// Precompute shared barycentric scalars
let n = domain.interpolation_domain_size;
let bf = domain.blowup_factor;
let coset_points: Vec<Felt> = (0..n)
.map(|i| domain.lde_roots_of_unity_coset[i * bf])
.collect();
let coset_offset_pow_n: Felt = domain.coset_offset.pow(n);
let n_inv: Felt = Felt::from(n as u64).inv().expect("n is a power of two");
let g_n_inv: Felt = coset_offset_pow_n.inv().expect("non-zero");
let dc = DomainConstants::from_domain(&domain);

// Barycentric evaluation (new path)
let result = get_trace_evaluations_from_lde(
&lde_trace,
&domain,
&z,
&frame_offsets,
step_size,
&coset_points,
&coset_offset_pow_n,
&n_inv,
&g_n_inv,
);
let result =
get_trace_evaluations_from_lde(&lde_trace, &domain, &z, &frame_offsets, step_size, &dc);

assert_eq!(result.width, expected.width);
assert_eq!(result.height, expected.height);
Expand Down
36 changes: 15 additions & 21 deletions crypto/stark/src/trace.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::domain::Domain;
use crate::domain::{Domain, DomainConstants};
use crate::table::Table;
use itertools::Itertools;
use math::fft::errors::FFTError;
Expand Down Expand Up @@ -362,20 +362,16 @@ where
/// Taking every blowup_factor-th point gives N evaluations on the trace-size coset
/// {g * w_trace^i}, which is sufficient to interpolate a degree < N polynomial.
///
/// Accepts precomputed `coset_points`, `coset_offset_pow_n`, `n_inv`, and `g_n_inv`
/// to avoid redundant computation when these are already available from the caller
/// (e.g., round_3 in prover.rs computes identical values for composition poly eval).
#[allow(clippy::too_many_arguments)]
/// Accepts a [`DomainConstants`] to avoid redundant computation when the caller
/// has already derived these values (e.g., round_3 shares them with composition
/// poly evaluation).
pub fn get_trace_evaluations_from_lde<F, E>(
lde_trace: &LDETraceTable<F, E>,
domain: &Domain<F>,
z: &FieldElement<E>,
frame_offsets: &[usize],
step_size: usize,
coset_points: &[FieldElement<F>],
coset_offset_pow_n: &FieldElement<F>,
n_inv: &FieldElement<F>,
g_n_inv: &FieldElement<F>,
dc: &DomainConstants<F>,
) -> Table<E>
where
F: IsSubFieldOf<E> + IsFFTField,
Expand All @@ -387,38 +383,36 @@ where
let num_aux_cols = lde_trace.num_aux_cols();
let table_width = num_main_cols + num_aux_cols;

// Caller-supplied barycentric scalars must match the domain they describe.
// A mismatch would silently produce wrong evaluations (if too short) or panic
// with an opaque index-out-of-bounds in the LDE stride access below.
debug_assert_eq!(
coset_points.len(),
dc.points.len(),
n,
"coset_points length must equal domain.interpolation_domain_size"
"DomainConstants.points length must equal domain.interpolation_domain_size"
);

// Build evaluation points: for each frame offset and step within, z * w_trace^exponent
let evaluation_points =
compute_frame_evaluation_points(z, frame_offsets, &domain.trace_primitive_root, step_size);

// Precompute n_inv * g_n_inv once — shared across all eval points and columns.
let n_inv_g_n_inv: FieldElement<F> = n_inv * g_n_inv;
// Precompute size_inv * offset_pow_n_inv once — shared across all eval points and columns.
let n_inv_g_n_inv: FieldElement<F> = &dc.size_inv * &dc.offset_pow_n_inv;
Comment thread
MauroToscano marked this conversation as resolved.

let mut table_data = Vec::with_capacity(evaluation_points.len() * table_width);

for eval_point in &evaluation_points {
// z_pow_n for this evaluation point
let z_pow_n = eval_point.pow(n);

// vanishing_factor = (z^N - g^N) * n_inv * g_n_inv — shared across all columns
let vanishing = z_pow_n.sub_subfield(coset_offset_pow_n);
// vanishing_factor = (z^N - offset^N) * size_inv * offset_pow_n_inv
let vanishing = z_pow_n.sub_subfield(&dc.offset_pow_n);
let vanishing_factor = &n_inv_g_n_inv * &vanishing;

// Precompute inv_denoms = 1/(eval_point - coset_point_i) — shared across all columns
let inv_denoms = barycentric_inv_denoms(eval_point, coset_points);
let inv_denoms = barycentric_inv_denoms(eval_point, &dc.points);

// Precompute col_scale[i] = coset_point[i] * inv_denom[i] — shared across ALL columns.
// Precompute col_scale[i] = point[i] * inv_denom[i] — shared across ALL columns.
// This eliminates N redundant F×E multiplies per column.
let col_scale: Vec<FieldElement<E>> = coset_points
let col_scale: Vec<FieldElement<E>> = dc
.points
.iter()
.zip(inv_denoms.iter())
.map(|(point, inv_d)| point * inv_d)
Expand Down
Loading