diff --git a/src/core/backend/cpu/quotients.rs b/src/core/backend/cpu/quotients.rs index af650595c..c2c550cba 100644 --- a/src/core/backend/cpu/quotients.rs +++ b/src/core/backend/cpu/quotients.rs @@ -1,9 +1,10 @@ -use num_traits::Zero; +use itertools::zip_eq; +use num_traits::{One, Zero}; use super::CPUBackend; use crate::core::circle::CirclePoint; -use crate::core::commitment_scheme::quotients::{ColumnSampleBatch, QuotientOps}; -use crate::core::constraints::{complex_conjugate_line, pair_vanishing}; +use crate::core::commitment_scheme::quotients::{ColumnSampleBatch, PointSample, QuotientOps}; +use crate::core::constraints::{complex_conjugate_line_coefficients, pair_vanishing}; use crate::core::fields::m31::BaseField; use crate::core::fields::qm31::SecureField; use crate::core::fields::secure_column::SecureColumn; @@ -20,11 +21,19 @@ impl QuotientOps for CPUBackend { sample_batches: &[ColumnSampleBatch], ) -> SecureColumn { let mut res = SecureColumn::zeros(domain.size()); + let column_constants = column_constants(sample_batches, random_coeff); + for row in 0..domain.size() { // TODO(alonh): Make an efficient bit reverse domain iterator, possibly for AVX backend. let domain_point = domain.at(bit_reverse_index(row, domain.log_size())); - let row_value = - accumulate_row_quotients(sample_batches, columns, row, random_coeff, domain_point); + let row_value = accumulate_row_quotients( + sample_batches, + columns, + &column_constants, + row, + random_coeff, + domain_point, + ); res.set(row, row_value); } res @@ -34,19 +43,21 @@ impl QuotientOps for CPUBackend { pub fn accumulate_row_quotients( sample_batches: &[ColumnSampleBatch], columns: &[&CircleEvaluation], + column_constants: &[Vec<(SecureField, SecureField, SecureField)>], row: usize, random_coeff: SecureField, domain_point: CirclePoint, ) -> SecureField { let mut row_accumulator = SecureField::zero(); - for sample_batch in sample_batches { + for (sample_batch, sample_constants) in zip_eq(sample_batches, column_constants) { let mut numerator = SecureField::zero(); - for (column_index, sampled_value) in &sample_batch.columns_and_values { + for ((column_index, _sampled_value), (a, b, c)) in + zip_eq(&sample_batch.columns_and_values, sample_constants) + { let column = &columns[*column_index]; - let value = column[row]; - let linear_term = - complex_conjugate_line(sample_batch.point, *sampled_value, domain_point); - numerator = numerator * random_coeff + value - linear_term; + let value = column[row] * *c; + let linear_term = *a * domain_point.y + *b; + numerator += value - linear_term; } let denominator = pair_vanishing( @@ -62,6 +73,33 @@ pub fn accumulate_row_quotients( row_accumulator } +/// Precompute the complex conjugate line constants for each column in each sample batch. +/// Specifically, for the i-th (in a sample batch) column's numerator term +/// `alpha^i * (F(p) - (a * p.y + b))`, we precompute the constants `alpha^i * a` and alpha^i * `b`. +pub fn column_constants( + sample_batches: &[ColumnSampleBatch], + random_coeff: SecureField, +) -> Vec> { + sample_batches + .iter() + .map(|sample_batch| { + let mut alpha = SecureField::one(); + sample_batch + .columns_and_values + .iter() + .map(|(_, sampled_value)| { + alpha *= random_coeff; + let sample = PointSample { + point: sample_batch.point, + value: *sampled_value, + }; + complex_conjugate_line_coefficients(&sample, alpha) + }) + .collect() + }) + .collect() +} + #[cfg(test)] mod tests { use crate::core::backend::cpu::{CPUCircleEvaluation, CPUCirclePoly}; diff --git a/src/core/commitment_scheme/quotients.rs b/src/core/commitment_scheme/quotients.rs index 513961ca8..d30c2dee5 100644 --- a/src/core/commitment_scheme/quotients.rs +++ b/src/core/commitment_scheme/quotients.rs @@ -4,7 +4,7 @@ use std::iter::zip; use itertools::{izip, multiunzip, Itertools}; -use crate::core::backend::cpu::quotients::accumulate_row_quotients; +use crate::core::backend::cpu::quotients::{accumulate_row_quotients, column_constants}; use crate::core::backend::Backend; use crate::core::circle::CirclePoint; use crate::core::fields::m31::BaseField; @@ -39,6 +39,7 @@ pub struct ColumnSampleBatch { /// The sampled column indices and their values at the point. pub columns_and_values: Vec<(usize, SecureField)>, } + impl ColumnSampleBatch { /// Groups column samples by sampled point. /// # Arguments @@ -124,6 +125,7 @@ pub fn fri_answers_for_log_size( ) -> Result, VerificationError> { let commitment_domain = CanonicCoset::new(log_size).circle_domain(); let sample_batches = ColumnSampleBatch::new_vec(samples); + let column_constants = column_constants(&sample_batches, random_coeff); for queried_values in queried_values_per_column { if queried_values.len() != query_domain.flatten().len() { return Err(VerificationError::InvalidStructure( @@ -154,6 +156,7 @@ pub fn fri_answers_for_log_size( let value = accumulate_row_quotients( &sample_batches, &column_evals.iter().collect_vec(), + &column_constants, row, random_coeff, domain_point,