use std::iter::zip;
use itertools::Itertools;
use tracing::{span, Level};
use crate::core::circle::CirclePoint;
use crate::core::fields::m31::BaseField;
use crate::core::fields::qm31::SecureField;
use crate::core::pcs::quotients::{
build_samples_with_randomness_and_periodicity, ColumnSampleBatch, PointSample,
};
use crate::core::pcs::TreeVec;
use crate::prover::backend::ColumnOps;
use crate::prover::poly::circle::{CircleEvaluation, PolyOps, SecureEvaluation};
use crate::prover::poly::twiddles::TwiddleTree;
use crate::prover::poly::BitReversedOrder;
use crate::prover::secure_column::SecureColumnByCoords;
use crate::prover::AccumulationOps;
pub trait QuotientOps: PolyOps {
fn accumulate_numerators(
columns: &[&CircleEvaluation<Self, BaseField, BitReversedOrder>],
sample_batches: &[ColumnSampleBatch],
accumulated_numerators_vec: &mut Vec<AccumulatedNumerators<Self>>,
log_blowup_factor: u32,
);
fn compute_quotients_and_combine(
accs: Vec<AccumulatedNumerators<Self>>,
lifting_log_size: u32,
log_blowup_factor: u32,
twiddles: &TwiddleTree<Self>,
) -> SecureEvaluation<Self, BitReversedOrder>;
}
#[derive(Clone)]
pub struct AccumulatedNumerators<B: ColumnOps<BaseField>> {
pub sample_point: CirclePoint<SecureField>,
pub partial_numerators_acc: SecureColumnByCoords<B>,
pub first_linear_term_acc: SecureField,
}
pub fn compute_fri_quotients<B: QuotientOps + AccumulationOps>(
columns: &TreeVec<Vec<&CircleEvaluation<B, BaseField, BitReversedOrder>>>,
samples: &TreeVec<Vec<Vec<PointSample>>>,
random_coeff: SecureField,
lifting_log_size: u32,
twiddles: &TwiddleTree<B>,
log_blowup_factor: u32,
) -> SecureEvaluation<B, BitReversedOrder> {
let _span = span!(Level::INFO, "Compute FRI quotients", class = "FRIQuotients").entered();
let mut accumulated_numerators_vec: Vec<AccumulatedNumerators<B>> = vec![];
let samples_with_randomness = build_samples_with_randomness_and_periodicity(
samples,
columns
.0
.iter()
.map(|x| x.iter().map(|c| c.domain.log_size()))
.collect(),
lifting_log_size,
random_coeff,
);
zip(
columns.iter().flatten(),
samples_with_randomness.iter().flatten(),
)
.sorted_by_key(|(c, _)| c.domain.log_size())
.group_by(|(c, _)| c.domain.log_size())
.into_iter()
.for_each(|(_, tuples)| {
let (columns, samples_with_randomness): (Vec<_>, Vec<_>) = tuples.unzip();
let sample_batches = ColumnSampleBatch::new_vec(&samples_with_randomness);
B::accumulate_numerators(
&columns,
&sample_batches,
&mut accumulated_numerators_vec,
log_blowup_factor,
)
});
let accumulations_per_sample_point = accumulated_numerators_vec
.into_iter()
.sorted_by_key(|c| (c.sample_point.x, c.sample_point.y))
.group_by(|c| c.sample_point)
.into_iter()
.map(|(sample_point, accumulations_per_log_size)| {
let accumulations_per_log_size = accumulations_per_log_size.collect_vec();
let first_linear_term_acc: SecureField = accumulations_per_log_size
.iter()
.map(|x| x.first_linear_term_acc)
.sum();
let partial_numerators_acc = accumulations_per_log_size
.into_iter()
.map(|x| x.partial_numerators_acc)
.collect_vec();
let res = B::lift_and_accumulate(partial_numerators_acc).unwrap();
AccumulatedNumerators {
sample_point,
partial_numerators_acc: res,
first_linear_term_acc,
}
})
.collect_vec();
B::compute_quotients_and_combine(
accumulations_per_sample_point,
lifting_log_size,
log_blowup_factor,
twiddles,
)
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
use num_traits::Zero;
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use crate::core::channel::Blake2sChannel;
use crate::core::circle::SECURE_FIELD_CIRCLE_GEN;
use crate::core::fields::m31::M31;
use crate::core::pcs::quotients::PointSample;
use crate::core::pcs::{CommitmentSchemeVerifier, PcsConfig, TreeVec};
use crate::core::poly::circle::CanonicCoset;
use crate::core::vcs_lifted::blake2_merkle::Blake2sMerkleChannel;
use crate::core::verifier::VerificationError;
use crate::prover::backend::cpu::{CpuCircleEvaluation, CpuCirclePoly};
use crate::prover::backend::simd::column::BaseColumn;
use crate::prover::backend::simd::SimdBackend;
use crate::prover::backend::{Backend, BackendForChannel, Column, CpuBackend};
use crate::prover::pcs::quotient_ops::compute_fri_quotients;
use crate::prover::poly::circle::{CircleCoefficients, CircleEvaluation, PolyOps};
use crate::prover::{CommitmentSchemeProver, SecureField};
#[test]
fn test_quotients_are_low_degree() {
let mut rng = SmallRng::seed_from_u64(0);
const LOG_SIZE: u32 = 3;
const LOG_BLOWUP_FACTOR: u32 = 4;
let polynomial = CpuCirclePoly::new((0..1 << LOG_SIZE).map(M31::from).collect());
let eval_domain = CanonicCoset::new(LOG_SIZE + LOG_BLOWUP_FACTOR).circle_domain();
let eval = polynomial.evaluate(eval_domain);
let sample_points = [
SECURE_FIELD_CIRCLE_GEN.mul(rng.gen::<u128>()),
SECURE_FIELD_CIRCLE_GEN.mul(rng.gen::<u128>()),
];
let samples = sample_points
.into_iter()
.map(|x| PointSample {
point: x,
value: polynomial.eval_at_point(x),
})
.collect_vec();
let rand_coeff =
SecureField::from_m31_array(std::array::from_fn(|_| M31::from(rng.gen::<u32>())));
let quot_eval = compute_fri_quotients(
&TreeVec(vec![vec![&eval]]),
&TreeVec(vec![vec![samples]]),
rand_coeff,
LOG_SIZE + LOG_BLOWUP_FACTOR,
&CpuBackend::precompute_twiddles(eval_domain.half_coset),
LOG_BLOWUP_FACTOR,
);
let mut coeffs = quot_eval
.values
.columns
.iter()
.map(|c| CpuCircleEvaluation::new(eval_domain, c.clone()).interpolate())
.collect_vec();
let zeros = coeffs[0].coeffs.split_off((1 << LOG_SIZE) - 1);
assert!(zeros.iter().all(|c| c.is_zero()));
}
fn prepare_polys<B: Backend, const N_COLS: usize, const LIFTING_LOG_SIZE: u32>(
) -> Vec<CircleCoefficients<B>> {
let mut rng = SmallRng::seed_from_u64(0);
let mut polys: Vec<CircleCoefficients<B>> = (0..N_COLS - 1)
.map(|_| {
CircleCoefficients::new(
(0..1 << rng.gen_range(4..LIFTING_LOG_SIZE - 1))
.map(M31::from)
.collect(),
)
})
.collect();
polys.push(CircleCoefficients::new(
(0..1 << LIFTING_LOG_SIZE).map(M31::from).collect(),
));
polys
}
fn prove_and_verify_pcs<
B: BackendForChannel<Blake2sMerkleChannel>,
const STORE_COEFFS: bool,
>() -> Result<(), VerificationError> {
const N_COLS: usize = 10;
const LIFTING_LOG_SIZE: u32 = 8;
let mut channel = Blake2sChannel::default();
let config = PcsConfig::default();
let twiddles = B::precompute_twiddles(
CanonicCoset::new(LIFTING_LOG_SIZE + config.fri_config.log_blowup_factor).half_coset(),
);
let mut commitment_scheme =
CommitmentSchemeProver::<B, Blake2sMerkleChannel>::new(config, &twiddles);
if STORE_COEFFS {
commitment_scheme.set_store_polynomials_coefficients();
}
let polys = prepare_polys::<B, N_COLS, LIFTING_LOG_SIZE>();
let sizes = polys.iter().map(|poly| poly.log_size()).collect_vec();
let mut tree_builder = commitment_scheme.tree_builder();
tree_builder.extend_polys(polys);
tree_builder.commit(&mut channel);
let mut rng = SmallRng::seed_from_u64(0);
let mask_structure = (0..N_COLS).map(|_| rng.gen_range(1..=2)).collect_vec();
let samples = [
SECURE_FIELD_CIRCLE_GEN.mul(rng.gen::<u128>()),
SECURE_FIELD_CIRCLE_GEN.mul(rng.gen::<u128>()),
];
let sampled_points = vec![(0..N_COLS)
.zip(mask_structure.iter())
.map(|(_, i)| samples.into_iter().take(*i).collect_vec())
.collect_vec()];
let proof = commitment_scheme.prove_values(TreeVec(sampled_points.clone()), &mut channel);
let mut channel = Blake2sChannel::default();
let mut verifier = CommitmentSchemeVerifier::<Blake2sMerkleChannel>::new(config);
verifier.commit(proof.proof.commitments[0], &sizes, &mut channel);
verifier.verify_values(TreeVec(sampled_points), proof.proof, &mut channel)
}
#[test]
fn test_pcs_prove_and_verify_cpu() {
assert!(prove_and_verify_pcs::<CpuBackend, true>().is_ok());
}
#[test]
fn test_pcs_prove_and_verify_simd() {
assert!(prove_and_verify_pcs::<SimdBackend, true>().is_ok());
}
#[test]
fn test_pcs_prove_and_verify_simd_with_barycentric() {
assert!(prove_and_verify_pcs::<SimdBackend, false>().is_ok());
}
#[test]
fn test_simd_quotients_are_low_degree_small_trace() {
let mut rng = SmallRng::seed_from_u64(0);
const LOG_SIZE: u32 = 3;
const LOG_BLOWUP_FACTOR: u32 = 4;
let polynomial = CpuCirclePoly::new((0..1 << LOG_SIZE).map(M31::from).collect());
let eval_domain = CanonicCoset::new(LOG_SIZE + LOG_BLOWUP_FACTOR).circle_domain();
let cpu_eval = polynomial.evaluate(eval_domain);
let simd_eval = CircleEvaluation::new(eval_domain, BaseColumn::from_cpu(&cpu_eval.values));
let sample_points = [
SECURE_FIELD_CIRCLE_GEN.mul(rng.gen::<u128>()),
SECURE_FIELD_CIRCLE_GEN.mul(rng.gen::<u128>()),
];
let samples = sample_points
.into_iter()
.map(|x| PointSample {
point: x,
value: polynomial.eval_at_point(x),
})
.collect_vec();
let rand_coeff =
SecureField::from_m31_array(std::array::from_fn(|_| M31::from(rng.gen::<u32>())));
let twiddles = SimdBackend::precompute_twiddles(eval_domain.half_coset);
let quot_eval = compute_fri_quotients(
&TreeVec(vec![vec![&simd_eval]]),
&TreeVec(vec![vec![samples]]),
rand_coeff,
LOG_SIZE + LOG_BLOWUP_FACTOR,
&twiddles,
LOG_BLOWUP_FACTOR,
);
let mut coeffs = quot_eval
.values
.columns
.iter()
.map(|c| CpuCircleEvaluation::new(eval_domain, c.to_cpu()).interpolate())
.collect_vec();
let zeros = coeffs[0].coeffs.split_off((1 << LOG_SIZE) - 1);
assert!(zeros.iter().all(|c| c.is_zero()));
}
}