Skip to content

Commit e2c4107

Browse files
authored
refactor: extract DomainConstants struct (#573)
* refactor: extract DomainConstants struct from loose barycentric params Group the four precomputed barycentric interpolation values (coset points, offset^N, 1/N, 1/offset^N) into a DomainConstants struct with a from_domain constructor. This reduces get_trace_evaluations_from_lde from 10 parameters to 6 and removes the clippy::too_many_arguments suppression. * fix: improve expect message accuracy in DomainConstants The previous message ("hence non-zero in the field") overstated the guarantee — it only holds when char(F) does not divide n. Reword to state the actual precondition. * style: apply cargo fmt
1 parent 196cfac commit e2c4107

4 files changed

Lines changed: 67 additions & 72 deletions

File tree

crypto/stark/src/domain.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,44 @@ use math::{
66
},
77
};
88

9+
/// Precomputed constants for barycentric interpolation on the trace-size coset.
10+
///
11+
/// Derived from a [`Domain`]: the N evaluation points at stride `blowup_factor`
12+
/// within the LDE coset, plus the field scalars that appear in every barycentric
13+
/// evaluation. Computed once in round 3 and shared across composition-poly and
14+
/// trace OOD evaluations.
15+
pub struct DomainConstants<F: IsField> {
16+
/// The N trace-size coset points: `lde_coset[i * blowup_factor]` for `i in 0..N`.
17+
pub points: Vec<FieldElement<F>>,
18+
/// `coset_offset ^ N`.
19+
pub offset_pow_n: FieldElement<F>,
20+
/// `1 / N` in the base field.
21+
pub size_inv: FieldElement<F>,
22+
/// `(coset_offset ^ N) ^ -1`.
23+
pub offset_pow_n_inv: FieldElement<F>,
24+
}
25+
26+
impl<F: IsFFTField> DomainConstants<F> {
27+
pub fn from_domain(domain: &Domain<F>) -> Self {
28+
let n = domain.interpolation_domain_size;
29+
let bf = domain.blowup_factor;
30+
let points = (0..n)
31+
.map(|i| domain.lde_roots_of_unity_coset[i * bf].clone())
32+
.collect();
33+
let offset_pow_n = domain.coset_offset.pow(n);
34+
let size_inv = FieldElement::<F>::from(n as u64)
35+
.inv()
36+
.expect("domain size is non-zero; field characteristic must not divide n");
37+
let offset_pow_n_inv = offset_pow_n.inv().expect("coset_offset_pow_n is non-zero");
38+
Self {
39+
points,
40+
offset_pow_n,
41+
size_inv,
42+
offset_pow_n_inv,
43+
}
44+
}
45+
}
46+
947
use super::traits::AIR;
1048

1149
/// Full domain with pre-computed roots of unity. Used by the prover which needs

crypto/stark/src/prover.rs

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::trace::LDETraceTable;
3333

3434
use super::config::{BatchedMerkleTree, BatchedMerkleTreeBackend, Commitment};
3535
use super::constraints::evaluator::ConstraintEvaluator;
36-
use super::domain::Domain;
36+
use super::domain::{Domain, DomainConstants};
3737
use super::fri::fri_decommit::FriDecommitment;
3838
use super::grinding;
3939
use super::lookup::BusPublicInputs;
@@ -989,25 +989,12 @@ pub trait IsStarkProver<
989989
let domain_size = domain.interpolation_domain_size;
990990
let blowup_factor = domain.blowup_factor;
991991

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

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

1012999
let composition_poly_parts_ood_evaluation: Vec<_> = round_2_result
10131000
.lde_composition_poly_evaluations
@@ -1019,31 +1006,24 @@ pub trait IsStarkProver<
10191006
.collect();
10201007
math::polynomial::interpolate_coset_eval_ext_with_g_n_inv(
10211008
&comp_z_pow_n,
1022-
&coset_offset_pow_n,
1023-
&domain_size_inv,
1024-
&g_n_inv,
1025-
&coset_points,
1009+
&dc.offset_pow_n,
1010+
&dc.size_inv,
1011+
&dc.offset_pow_n_inv,
1012+
&dc.points,
10261013
&evals,
10271014
&comp_inv_denoms,
10281015
)
10291016
})
10301017
.collect();
10311018

10321019
// === Trace polynomials: barycentric evaluation via LDE ===
1033-
// Uses get_trace_evaluations_from_lde which performs barycentric interpolation
1034-
// on the LDE trace data, avoiding the need for coefficient-form trace_polys.
1035-
// Reuses coset_points, coset_offset_pow_n, domain_size_inv, g_n_inv already
1036-
// computed above for composition poly evaluation — avoids redundant work.
10371020
let trace_ood_evaluations = crate::trace::get_trace_evaluations_from_lde(
10381021
&round_1_result.lde_trace,
10391022
domain,
10401023
z,
10411024
&air.context().transition_offsets,
10421025
air.step_size(),
1043-
&coset_points,
1044-
&coset_offset_pow_n,
1045-
&domain_size_inv,
1046-
&g_n_inv,
1026+
&dc,
10471027
);
10481028

10491029
Round3 {

crypto/stark/src/tests/prover_tests.rs

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crypto::fiat_shamir::default_transcript::DefaultTranscript;
22

33
use crate::{
4-
domain::Domain,
4+
domain::{Domain, DomainConstants},
55
examples::{
66
quadratic_air::QuadraticAIR,
77
simple_fibonacci::{self, FibonacciAIR, FibonacciPublicInputs},
@@ -169,28 +169,11 @@ fn barycentric_trace_eval_matches_horner_trace_eval() {
169169
step_size,
170170
);
171171

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

182174
// Barycentric evaluation (new path)
183-
let result = get_trace_evaluations_from_lde(
184-
&lde_trace,
185-
&domain,
186-
&z,
187-
&frame_offsets,
188-
step_size,
189-
&coset_points,
190-
&coset_offset_pow_n,
191-
&n_inv,
192-
&g_n_inv,
193-
);
175+
let result =
176+
get_trace_evaluations_from_lde(&lde_trace, &domain, &z, &frame_offsets, step_size, &dc);
194177

195178
assert_eq!(result.width, expected.width);
196179
assert_eq!(result.height, expected.height);

crypto/stark/src/trace.rs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::domain::Domain;
1+
use crate::domain::{Domain, DomainConstants};
22
use crate::table::Table;
33
use itertools::Itertools;
44
use math::fft::errors::FFTError;
@@ -362,20 +362,16 @@ where
362362
/// Taking every blowup_factor-th point gives N evaluations on the trace-size coset
363363
/// {g * w_trace^i}, which is sufficient to interpolate a degree < N polynomial.
364364
///
365-
/// Accepts precomputed `coset_points`, `coset_offset_pow_n`, `n_inv`, and `g_n_inv`
366-
/// to avoid redundant computation when these are already available from the caller
367-
/// (e.g., round_3 in prover.rs computes identical values for composition poly eval).
368-
#[allow(clippy::too_many_arguments)]
365+
/// Accepts a [`DomainConstants`] to avoid redundant computation when the caller
366+
/// has already derived these values (e.g., round_3 shares them with composition
367+
/// poly evaluation).
369368
pub fn get_trace_evaluations_from_lde<F, E>(
370369
lde_trace: &LDETraceTable<F, E>,
371370
domain: &Domain<F>,
372371
z: &FieldElement<E>,
373372
frame_offsets: &[usize],
374373
step_size: usize,
375-
coset_points: &[FieldElement<F>],
376-
coset_offset_pow_n: &FieldElement<F>,
377-
n_inv: &FieldElement<F>,
378-
g_n_inv: &FieldElement<F>,
374+
dc: &DomainConstants<F>,
379375
) -> Table<E>
380376
where
381377
F: IsSubFieldOf<E> + IsFFTField,
@@ -387,38 +383,36 @@ where
387383
let num_aux_cols = lde_trace.num_aux_cols();
388384
let table_width = num_main_cols + num_aux_cols;
389385

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

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

403-
// Precompute n_inv * g_n_inv once — shared across all eval points and columns.
404-
let n_inv_g_n_inv: FieldElement<F> = n_inv * g_n_inv;
396+
// Precompute size_inv * offset_pow_n_inv once — shared across all eval points and columns.
397+
let n_inv_g_n_inv: FieldElement<F> = &dc.size_inv * &dc.offset_pow_n_inv;
405398

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

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

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

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

419-
// Precompute col_scale[i] = coset_point[i] * inv_denom[i] — shared across ALL columns.
412+
// Precompute col_scale[i] = point[i] * inv_denom[i] — shared across ALL columns.
420413
// This eliminates N redundant F×E multiplies per column.
421-
let col_scale: Vec<FieldElement<E>> = coset_points
414+
let col_scale: Vec<FieldElement<E>> = dc
415+
.points
422416
.iter()
423417
.zip(inv_denoms.iter())
424418
.map(|(point, inv_d)| point * inv_d)

0 commit comments

Comments
 (0)