From 820f81ffc6b5c8da5825e282a73d8ec7dc96df00 Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Sun, 20 Oct 2024 00:26:31 +0530 Subject: [PATCH 1/6] fix: remove prints --- fri/src/stone_domain.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/fri/src/stone_domain.rs b/fri/src/stone_domain.rs index b097dcd..f09d78e 100644 --- a/fri/src/stone_domain.rs +++ b/fri/src/stone_domain.rs @@ -20,11 +20,8 @@ pub fn change_order_of_elements_in_domain(elements: &[F]) -> Vec // byte size of usize - log_len let log_len = size.trailing_zeros() as usize; // byte size of usize - log_len - println!("log_len: {}", log_len); let mut new_elements = Vec::with_capacity(size); for i in 0..size { - println!("i: {}", i); - println!("translate_index(i): {}", translate_index(i, log_len)); new_elements.push(elements[translate_index(i, log_len)]) } From 028e1d26e73cdd77097ea55aa4929404ef4833a9 Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Sun, 20 Oct 2024 00:28:18 +0530 Subject: [PATCH 2/6] refactor: minor changes --- fri/src/lde.rs | 1 + fri/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/fri/src/lde.rs b/fri/src/lde.rs index 0c5a812..a525228 100644 --- a/fri/src/lde.rs +++ b/fri/src/lde.rs @@ -6,6 +6,7 @@ use ark_poly::{ use crate::stone_domain::change_order_of_elements_in_domain; #[allow(dead_code)] +#[derive(Debug, Clone)] pub struct MultiplicativeLDE { pub ldes: Vec>, pub base: Radix2EvaluationDomain, diff --git a/fri/src/lib.rs b/fri/src/lib.rs index da6e3ba..cd9caa4 100644 --- a/fri/src/lib.rs +++ b/fri/src/lib.rs @@ -2,7 +2,7 @@ mod committed_layers; mod details; mod folder; mod layers; -mod lde; +pub mod lde; mod parameters; mod prover; mod stone_domain; From 77442ddcc49a57a2242b875c567dc21a3ec04e40 Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Sun, 20 Oct 2024 00:42:07 +0530 Subject: [PATCH 3/6] feat: impl multiplicative_neighborsand periodic_columns --- composition_polynomial/Cargo.toml | 15 ++ composition_polynomial/src/lib.rs | 2 + .../src/multiplicative_neighbors.rs | 160 ++++++++++++++ .../src/periodic_columns.rs | 199 ++++++++++++++++++ 4 files changed, 376 insertions(+) create mode 100644 composition_polynomial/Cargo.toml create mode 100644 composition_polynomial/src/lib.rs create mode 100644 composition_polynomial/src/multiplicative_neighbors.rs create mode 100644 composition_polynomial/src/periodic_columns.rs diff --git a/composition_polynomial/Cargo.toml b/composition_polynomial/Cargo.toml new file mode 100644 index 0000000..8464286 --- /dev/null +++ b/composition_polynomial/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "composition_polynomial" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +ark-ff = { workspace = true } +ark-poly ={ workspace = true } +fri = { path = "../fri" } + +[dev-dependencies] +rand.workspace = true +num-bigint.workspace = true +felt = { path = "../felt" } diff --git a/composition_polynomial/src/lib.rs b/composition_polynomial/src/lib.rs new file mode 100644 index 0000000..6e68e77 --- /dev/null +++ b/composition_polynomial/src/lib.rs @@ -0,0 +1,2 @@ +pub mod multiplicative_neighbors; +pub mod periodic_columns; diff --git a/composition_polynomial/src/multiplicative_neighbors.rs b/composition_polynomial/src/multiplicative_neighbors.rs new file mode 100644 index 0000000..535239b --- /dev/null +++ b/composition_polynomial/src/multiplicative_neighbors.rs @@ -0,0 +1,160 @@ +use ark_ff::PrimeField; + +pub struct MultiplicativeNeighbors { + mask: Vec<(usize, usize)>, + coset_size: usize, + neighbor_wraparound_mask: usize, + trace_lde: Vec>, +} + +impl MultiplicativeNeighbors { + pub fn new(mask: &[(usize, usize)], trace_lde: &[Vec]) -> Self { + let coset_size = get_coset_size(trace_lde); + let neighbor_wraparound_mask = coset_size - 1; + + assert!( + coset_size.is_power_of_two(), + "Coset size must be a power of 2" + ); + + for mask_item in mask.iter() { + assert!( + mask_item.1 < trace_lde.len(), + "Too few trace LDE columns provided." + ); + } + + MultiplicativeNeighbors { + mask: mask.to_vec(), + coset_size, + neighbor_wraparound_mask, + trace_lde: trace_lde.to_vec(), + } + } + + pub fn get_neighbors(&self, idx: usize) -> Vec { + let mask = &self.mask; + let trace_lde = &self.trace_lde; + let neighbor_wraparound_mask = self.neighbor_wraparound_mask; + + let mut neighbors = Vec::with_capacity(mask.len()); + + for (relative_row, col) in mask.iter() { + let pos = (idx + relative_row) & neighbor_wraparound_mask; + neighbors.push(trace_lde[*col][pos]); + } + + neighbors + } + + pub fn coset_size(&self) -> usize { + self.coset_size + } +} + +fn get_coset_size(trace_lde: &[Vec]) -> usize { + assert!( + !trace_lde.is_empty(), + "Trace must contain at least one column." + ); + let coset_size = trace_lde[0].len(); + for column in trace_lde.iter() { + assert!( + column.len() == coset_size, + "All columns must have the same size." + ); + } + coset_size +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_ff::UniformRand; + use felt::Felt252; + + #[test] + fn test_multiplicative_neighbors() { + let mut rng = rand::thread_rng(); + + let trace_length = 8; + let n_columns = 4; + let mask: [(usize, usize); 5] = [(0, 0), (0, 1), (1, 2), (2, 0), (2, 3)]; + + let mut trace: Vec> = Vec::with_capacity(n_columns); + + for _ in 0..n_columns { + let rand_vec: Vec = + (0..trace_length).map(|_| Felt252::rand(&mut rng)).collect(); + trace.push(rand_vec); + } + + let neighbors = MultiplicativeNeighbors::new(&mask, &trace); + + let mut result: Vec> = Vec::new(); + for i in 0..trace_length { + result.push(neighbors.get_neighbors(i)); + } + + let expected = vec![ + vec![ + trace[0][0], + trace[1][0], + trace[2][1], + trace[0][2], + trace[3][2], + ], + vec![ + trace[0][1], + trace[1][1], + trace[2][2], + trace[0][3], + trace[3][3], + ], + vec![ + trace[0][2], + trace[1][2], + trace[2][3], + trace[0][4], + trace[3][4], + ], + vec![ + trace[0][3], + trace[1][3], + trace[2][4], + trace[0][5], + trace[3][5], + ], + vec![ + trace[0][4], + trace[1][4], + trace[2][5], + trace[0][6], + trace[3][6], + ], + vec![ + trace[0][5], + trace[1][5], + trace[2][6], + trace[0][7], + trace[3][7], + ], + vec![ + trace[0][6], + trace[1][6], + trace[2][7], + trace[0][0], + trace[3][0], + ], + vec![ + trace[0][7], + trace[1][7], + trace[2][0], + trace[0][1], + trace[3][1], + ], + ]; + + assert_eq!(result, expected); + } +} diff --git a/composition_polynomial/src/periodic_columns.rs b/composition_polynomial/src/periodic_columns.rs new file mode 100644 index 0000000..baf0103 --- /dev/null +++ b/composition_polynomial/src/periodic_columns.rs @@ -0,0 +1,199 @@ +use ark_ff::PrimeField; +use ark_poly::{EvaluationDomain, Polynomial, Radix2EvaluationDomain}; +use fri::lde::MultiplicativeLDE; + +#[derive(Debug, Clone)] +pub struct PeriodicColumn { + group_generator: F, + column_step: usize, + period_in_trace: usize, + n_copies: usize, + offset_compensation: F, + lde_manager: MultiplicativeLDE, +} + +impl PeriodicColumn { + pub fn new( + values: Vec, + group_generator: F, + offset: F, + coset_size: usize, + column_step: usize, + ) -> Self { + let period_in_trace = values.len() * column_step; + + assert!(period_in_trace != 0); + assert!(coset_size % period_in_trace == 0); + let n_copies = coset_size / period_in_trace; + + let offset_compensation = offset.pow([n_copies as u64]).inverse().unwrap(); + + let base = Radix2EvaluationDomain::new_coset(values.len(), F::ONE).unwrap(); + let mut lde_manager = MultiplicativeLDE::new(base, false); + lde_manager.add_eval(&values); + + Self { + group_generator, + column_step, + period_in_trace, + n_copies, + offset_compensation, + lde_manager, + } + } + + pub fn eval_at_point(&self, x: F) -> F { + let point = x.pow([self.n_copies as u64]) * self.offset_compensation; + self.lde_manager.ldes[0].evaluate(&point) + } + + pub fn get_actual_degree(&self) -> usize { + self.lde_manager.ldes[0].degree() + } + + pub fn get_coset(&self, start_point: &F, coset_size: usize) -> CosetEvaluation { + let offset = start_point.pow([self.n_copies as u64]); + let n_values = self.lde_manager.base.size as usize; + + assert!( + coset_size == self.n_copies * self.column_step * n_values, + "Currently coset_size must be the same as the size of the coset that was used to create the PeriodicColumn." + ); + + let mut period_on_coset = vec![F::zero(); self.period_in_trace]; + + let offset_multiplier = self.group_generator.pow([self.n_copies as u64]); + let start_offset = offset; + + for i in 0..self.column_step { + let offset = start_offset * offset_multiplier.pow([i as u64]); + + let lde = self + .lde_manager + .batch_eval(offset * self.offset_compensation); + assert_eq!(lde.len(), 1); + assert_eq!(lde[0].len(), n_values); + + for (j, lde_value) in lde[0].iter().enumerate() { + period_on_coset[i + j * self.column_step] = *lde_value; + } + } + + CosetEvaluation::new(period_on_coset) + } +} + +pub struct CosetEvaluation { + index_mask: usize, + values: Vec, +} + +impl CosetEvaluation { + pub fn new(values: Vec) -> Self { + Self { + index_mask: values.len() - 1, + values, + } + } + + pub fn get_value(&self, idx: usize) -> F { + let i = idx & self.index_mask; + self.values[i] + } +} + +#[cfg(test)] +mod tests { + use crate::periodic_columns::PeriodicColumn; + use ark_ff::{PrimeField, UniformRand}; + use ark_poly::evaluations::univariate::Evaluations; + use ark_poly::{EvaluationDomain, Polynomial, Radix2EvaluationDomain}; + use felt::Felt252; + use rand::Rng; + + fn get_subgroup_generator(n: usize) -> F { + let q_minus_1: num_bigint::BigUint = F::ONE.neg().into(); + + // Calculate (q - 1) / n + assert!( + q_minus_1.clone() % n == num_bigint::BigUint::from(0u64), + "No subgroup of required size exists" + ); + let quotient = q_minus_1 / n; + + F::GENERATOR.pow(quotient.to_u64_digits()) + } + + fn test_periodic_column_with(n_values: usize, coset_size: usize, column_step: usize) { + let mut rng = rand::thread_rng(); + + let group_generator: Felt252 = get_subgroup_generator(coset_size); + let offset = Felt252::rand(&mut rng); + let values: Vec = (0..n_values).map(|_| Felt252::rand(&mut rng)).collect(); + + let column = PeriodicColumn::new( + values.clone(), + group_generator, + offset, + coset_size, + column_step, + ); + + let mut point = offset; + let mut domain = Vec::with_capacity(coset_size); + let mut values_ext = Vec::with_capacity(coset_size); + let periodic_column_coset = column.get_coset(&offset, coset_size); + + for i in 0..coset_size { + let iterator_value = periodic_column_coset.get_value(i); + let col_eval = column.eval_at_point(point); + assert_eq!(iterator_value, col_eval); + + if i % column_step == 0 { + let expected_val = values[(i / column_step) % n_values]; + assert_eq!(expected_val, iterator_value); + } + + domain.push(point); + values_ext.push(iterator_value); + point *= group_generator; + } + + let domain_calc = Radix2EvaluationDomain::new_coset(coset_size, offset).unwrap(); + for (e, d) in domain_calc.elements().zip(domain) { + assert_eq!(e, d); + } + let evals = Evaluations::from_vec_and_domain(values_ext, domain_calc); + let interpolant = evals.interpolate(); + let random_point = Felt252::rand(&mut rng); + assert_eq!( + interpolant.evaluate(&random_point), + column.eval_at_point(random_point) + ); + + let mut point = random_point; + let periodic_column_coset2 = column.get_coset(&random_point, coset_size); + for i in 0..coset_size { + let iterator_value = periodic_column_coset2.get_value(i); + assert_eq!(interpolant.evaluate(&point), iterator_value); + point *= group_generator; + } + } + + #[test] + fn test_periodic_column() { + test_periodic_column_with(8, 32, 1); + test_periodic_column_with(8, 32, 2); + test_periodic_column_with(8, 32, 4); + test_periodic_column_with(8, 8, 1); + test_periodic_column_with(1, 8, 1); + test_periodic_column_with(1, 8, 8); + + // Random sizes. + let mut rng = rand::thread_rng(); + let log_coset_size = rng.gen_range(0..=5) as usize; + let log_n_values = rng.gen_range(0..=log_coset_size) as usize; + let log_column_step = rng.gen_range(0..=(log_coset_size - log_n_values)) as usize; + test_periodic_column_with(1 << log_n_values, 1 << log_coset_size, 1 << log_column_step); + } +} From 719f7bc8f2204de39662e136739564c2c3bd0b89 Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Sun, 20 Oct 2024 00:44:15 +0530 Subject: [PATCH 4/6] feat: impl air trait and composition_polynomial --- composition_polynomial/src/air.rs | 218 +++++++++++++ .../src/composition_polynomial.rs | 295 ++++++++++++++++++ composition_polynomial/src/lib.rs | 2 + 3 files changed, 515 insertions(+) create mode 100644 composition_polynomial/src/air.rs create mode 100644 composition_polynomial/src/composition_polynomial.rs diff --git a/composition_polynomial/src/air.rs b/composition_polynomial/src/air.rs new file mode 100644 index 0000000..f7be0a2 --- /dev/null +++ b/composition_polynomial/src/air.rs @@ -0,0 +1,218 @@ +use crate::composition_polynomial::{batch_pow, CompositionPolynomial}; +use crate::periodic_columns::PeriodicColumn; +use ark_ff::PrimeField; +use std::collections::HashMap; +use std::sync::Arc; + +pub trait Air { + fn create_composition_polynomial( + &self, + trace_generator: &F, + random_coefficients: &[F], + ) -> CompositionPolynomial; + + fn trace_length(&self) -> usize; + + fn get_composition_polynomial_degree_bound(&self) -> usize; + + fn num_random_coefficients(&self) -> usize; + + fn get_num_constraints(&self) -> usize { + self.num_random_coefficients() + } + + fn constraints_eval( + &self, + neighbors: &[F], + periodic_columns: &[F], + random_coefficients: &[F], + point: &F, + gen_powers: &[F], + precomp_domains: &[F], + ) -> F; + + fn get_mask(&self) -> &[(usize, usize)]; + + fn num_columns(&self) -> usize; + + fn domain_evals_at_point(&self, point_powers: &[F], shifts: &[F]) -> Vec; + + fn parse_dynamic_params(&self, params: &HashMap) -> Vec; + + fn with_interaction_elements(&self, _interaction_elms: &[F]) -> Box> { + panic!("Calling with_interaction_elements in an air with no interaction."); + } + + fn get_interaction_params(&self) -> Option; + + fn get_n_columns_first(&self) -> usize { + match self.get_interaction_params() { + Some(params) => params.n_columns_first, + None => self.num_columns(), + } + } + + fn precompute_domain_evals_on_coset( + &self, + point: &F, + generator: &F, + point_exponents: &[usize], + shifts: &[F], + ) -> Vec>; +} + +pub struct InteractionParams { + pub n_columns_first: usize, + pub n_columns_second: usize, + pub n_interaction_elements: usize, +} + +type ConstraintFunction = Arc F>; + +// DummyAir struct definition +#[derive(Clone)] +pub struct DummyAir { + pub trace_length: usize, + pub n_constraints: usize, + pub n_columns: usize, + pub mask: Vec<(usize, usize)>, + pub periodic_columns: Vec>, + pub point_exponents: Vec, + pub gen_exponents: Vec, + pub constraints: Vec>, + pub composition_polynomial_degree_bound: Option, +} + +impl DummyAir { + pub fn new(trace_length: usize) -> Self { + assert!(trace_length.is_power_of_two()); + Self { + trace_length, + n_constraints: 0, + n_columns: 0, + mask: vec![], + periodic_columns: vec![], + point_exponents: vec![], + gen_exponents: vec![], + constraints: vec![], + composition_polynomial_degree_bound: None, + } + } +} + +impl Air for DummyAir { + fn trace_length(&self) -> usize { + self.trace_length + } + + fn get_composition_polynomial_degree_bound(&self) -> usize { + assert!( + self.composition_polynomial_degree_bound.is_some(), + "composition_polynomial_degree_bound wasn't initialized." + ); + self.composition_polynomial_degree_bound.unwrap() + } + + fn num_random_coefficients(&self) -> usize { + self.n_constraints + } + + fn num_columns(&self) -> usize { + self.n_columns + } + + fn get_interaction_params(&self) -> Option { + None + } + + fn constraints_eval( + &self, + neighbors: &[F], + periodic_columns: &[F], + random_coefficients: &[F], + point: &F, + gen_powers: &[F], + precomp_domains: &[F], + ) -> F { + assert!( + random_coefficients.len() == self.constraints.len(), + "This is a bug in the test." + ); + + let mut res = F::ZERO; + for constraint in self.constraints.iter() { + res += constraint( + neighbors, + periodic_columns, + random_coefficients, + point, + gen_powers, + precomp_domains, + ); + } + res + } + + fn domain_evals_at_point(&self, point_powers: &[F], _shifts: &[F]) -> Vec { + if point_powers.len() <= 1 { + return vec![]; + } + vec![point_powers[1] - F::ONE] + } + + fn create_composition_polynomial( + &self, + trace_generator: &F, + random_coefficients: &[F], + ) -> CompositionPolynomial { + let shifts = batch_pow(trace_generator, &self.gen_exponents); + CompositionPolynomial::new( + Box::new(self.clone()), + *trace_generator, + self.trace_length(), + self.periodic_columns.clone(), + random_coefficients, + &self.point_exponents, + &shifts, + ) + } + + fn get_mask(&self) -> &[(usize, usize)] { + &self.mask + } + + fn parse_dynamic_params( + &self, + _params: &std::collections::HashMap, + ) -> Vec { + vec![] + } + + fn precompute_domain_evals_on_coset( + &self, + point: &F, + generator: &F, + point_exponents: &[usize], + _shifts: &[F], + ) -> Vec> { + assert!(point_exponents[0] != 0); + assert!(self.trace_length % point_exponents[0] == 0); + let size = self.trace_length / point_exponents[0]; + + let mut point_powers = Vec::with_capacity(size); + let mut power = point.pow([point_exponents[0] as u64]); + let gen_power = generator.pow([point_exponents[0] as u64]); + + point_powers.push(power); + for _ in 1..size { + power *= gen_power; + point_powers.push(power); + } + + let mut precomp_domains = vec![Vec::with_capacity(size)]; + for p in point_powers.iter() { + precomp_domains[0].push(*p - F::ONE); + } + precomp_domains + } +} diff --git a/composition_polynomial/src/composition_polynomial.rs b/composition_polynomial/src/composition_polynomial.rs new file mode 100644 index 0000000..a262ad9 --- /dev/null +++ b/composition_polynomial/src/composition_polynomial.rs @@ -0,0 +1,295 @@ +use crate::air::Air; +use crate::multiplicative_neighbors::MultiplicativeNeighbors; +use crate::periodic_columns::PeriodicColumn; +use ark_ff::PrimeField; + +pub struct CompositionPolynomial { + air: Box>, + trace_generator: F, + coset_size: usize, + periodic_columns: Vec>, + coefficients: Vec, + point_exponents: Vec, + shifts: Vec, +} + +impl CompositionPolynomial { + pub fn new( + air: Box>, + trace_generator: F, + coset_size: usize, + periodic_columns: Vec>, + coefficients: &[F], + point_exponents: &[usize], + shifts: &[F], + ) -> Self { + assert_eq!( + coefficients.len(), + air.num_random_coefficients(), + "Wrong number of coefficients." + ); + + assert!( + coset_size.is_power_of_two(), + "Only cosets of size which is a power of two are supported." + ); + + assert_eq!( + trace_generator.pow([coset_size as u64]), + F::ONE, + "Provided generator does not generate a group of expected size." + ); + + Self { + air, + trace_generator, + coset_size, + periodic_columns, + coefficients: coefficients.to_vec(), + point_exponents: point_exponents.to_vec(), + shifts: shifts.to_vec(), + } + } + + pub fn eval_at_point(&self, point: &F, neighbors: &[F]) -> F { + let mut periodic_column_vals = Vec::with_capacity(self.periodic_columns.len()); + for column in &self.periodic_columns { + periodic_column_vals.push(column.eval_at_point(*point)); + } + + let point_powers = batch_pow(point, &self.point_exponents); + + let domain_evals = self.air.domain_evals_at_point(&point_powers, &self.shifts); + + self.air.constraints_eval( + neighbors, + &periodic_column_vals, + &self.coefficients, + point, + &self.shifts, + &domain_evals, + ) + } + + pub fn eval_on_coset_bit_reversed_output( + &self, + coset_offset: F, + trace_lde: Vec>, + ) -> Vec { + let multiplicative_neighbors = + MultiplicativeNeighbors::new(self.air.get_mask(), &trace_lde); + assert_eq!(multiplicative_neighbors.coset_size(), self.coset_size); + + assert!(self.coset_size.is_power_of_two()); + let log_coset_size = self.coset_size.ilog2() as usize; + + let mut point = coset_offset; + + let all_precomp_domain_evals = self.air.precompute_domain_evals_on_coset( + &coset_offset, + &self.trace_generator, + &self.point_exponents, + &self.shifts, + ); + let precomp_domain_masks: Vec = all_precomp_domain_evals + .iter() + .map(|vec| vec.len() - 1) + .collect(); + + let periodic_column_cosets: Vec<_> = self + .periodic_columns + .iter() + .map(|column| column.get_coset(&coset_offset, self.coset_size)) + .collect(); + + let mut out_evaluation = vec![F::zero(); self.coset_size]; + let mut periodic_column_vals = vec![F::ZERO; self.periodic_columns.len()]; + let mut precomp_domain_evals = vec![F::ZERO; all_precomp_domain_evals.len()]; + let mut batch_inverse = vec![F::ZERO; self.coset_size]; + + for point_idx in 0..self.coset_size { + for (i, column_coset) in periodic_column_cosets.iter().enumerate() { + periodic_column_vals[i] = column_coset.get_value(point_idx); + } + + for (i, eval) in all_precomp_domain_evals.iter().enumerate() { + precomp_domain_evals[i] = if !eval.is_empty() { + eval[point_idx & precomp_domain_masks[i]] + } else { + F::zero() + }; + } + + let neighbors = multiplicative_neighbors.get_neighbors(point_idx); + + batch_inverse[point_idx] = self.air.constraints_eval( + &neighbors, + &periodic_column_vals, + &self.coefficients, + &point, + &self.shifts, + &precomp_domain_evals, + ); + + point *= self.trace_generator; + } + + for point_idx in 0..self.coset_size { + out_evaluation[bit_reverse(point_idx, log_coset_size)] = batch_inverse[point_idx]; + } + + out_evaluation + } +} + +pub fn batch_pow(base: &F, exponents: &[usize]) -> Vec { + let mut output = Vec::with_capacity(exponents.len() + 1); + output.push(*base); + + for e in exponents { + output.push(base.pow([*e as u64])); + } + output +} + +fn bit_reverse(n: usize, number_of_bits: usize) -> usize { + let mut reversed = 0; + let mut num = n; + + for _ in 0..number_of_bits { + reversed = (reversed << 1) | (num & 1); + num >>= 1; + } + + reversed +} + +#[cfg(test)] +mod tests { + use crate::{ + air::{Air, DummyAir}, + composition_polynomial::bit_reverse, + periodic_columns::PeriodicColumn, + }; + use ark_ff::{Field, PrimeField, UniformRand}; + use felt::Felt252; + use rand::Rng; + use std::sync::Arc; + + fn get_subgroup_generator(n: usize) -> F { + let q_minus_1: num_bigint::BigUint = F::ONE.neg().into(); + + // Calculate (q - 1) / n + assert!( + q_minus_1.clone() % n == num_bigint::BigUint::from(0u64), + "No subgroup of required size exists" + ); + let quotient = q_minus_1 / n; + + F::GENERATOR.pow(quotient.to_u64_digits()) + } + + #[test] + fn test_zero_constraints() { + let mut rng = rand::thread_rng(); + let mut air: DummyAir = DummyAir::new(4); + air.composition_polynomial_degree_bound = Some(1000); + air.n_constraints = 0; + let poly = air.create_composition_polynomial(&Felt252::ONE, &vec![]); + let evaluation_point = Felt252::rand(&mut rng); + + assert_eq!( + Felt252::ZERO, + poly.eval_at_point(&evaluation_point, &vec![]) + ) + } + + fn get_random_periodic_col(log_coset_size: usize) -> PeriodicColumn { + let mut rng = rand::thread_rng(); + let log_n_values = rng.gen_range(0..=log_coset_size); + let group_generator = get_subgroup_generator(1 << log_coset_size); + let offset = Felt252::rand(&mut rng); + + let values = (0..(1 << log_n_values)) + .map(|_| Felt252::rand(&mut rng)) + .collect(); + PeriodicColumn::new(values, group_generator, offset, 1 << log_coset_size, 1) + } + + fn test_eval_composition_on_coset_with(log_coset_size: usize) { + let mut rng = rand::thread_rng(); + + let n_columns = rng.gen_range(1..=20); + let trace_length = 1 << log_coset_size; + + let mut air: DummyAir = DummyAir::new(trace_length); + + air.n_constraints = 1; + air.periodic_columns + .push(get_random_periodic_col(log_coset_size)); + air.n_columns = n_columns; + air.mask = vec![(0, 0), (1, 0)]; + + air.composition_polynomial_degree_bound = Some(2 * trace_length); + + air.point_exponents = vec![trace_length]; + air.constraints = vec![Arc::new( + |neighbors, + periodic_columns, + random_coefficients, + _point, + _gen_power, + precomp_evals| { + let constraint = neighbors[0] * periodic_columns[0] - neighbors[1]; + + let numerator = Felt252::ONE; + + let denominator = precomp_evals[0]; + + constraint * random_coefficients[0] * numerator / denominator + }, + )]; + + let coset_group_generator: Felt252 = get_subgroup_generator(trace_length); + + let coeff: Vec = (0..air.num_random_coefficients()) + .map(|_| Felt252::rand(&mut rng)) + .collect(); + + let poly = air.create_composition_polynomial(&coset_group_generator, &coeff); + + let coset_offset = Felt252::rand(&mut rng); + + let mut trace_lde = vec![]; + for _i in 0..n_columns { + let rand_col: Vec = + (0..trace_length).map(|_| Felt252::rand(&mut rng)).collect(); + trace_lde.push(rand_col); + } + + let evaluation = poly.eval_on_coset_bit_reversed_output(coset_offset, trace_lde.clone()); + + for i in 0..trace_length { + let mut neighbors = Vec::with_capacity(air.mask.len()); + for mask_item in &air.mask { + neighbors.push(trace_lde[mask_item.1][(i + mask_item.0) % trace_length]); + } + + let curr_point = coset_offset * coset_group_generator.pow([i as u64]); + + assert_eq!( + poly.eval_at_point(&curr_point, &neighbors), + evaluation[bit_reverse(i, log_coset_size)] + ) + } + } + + #[test] + fn test_eval_composition_on_coset() { + let mut rng = rand::thread_rng(); + + test_eval_composition_on_coset_with(rng.gen_range(4..9)); + test_eval_composition_on_coset_with(rng.gen_range(4..9)); + test_eval_composition_on_coset_with(rng.gen_range(4..9)); + } +} diff --git a/composition_polynomial/src/lib.rs b/composition_polynomial/src/lib.rs index 6e68e77..dcc421a 100644 --- a/composition_polynomial/src/lib.rs +++ b/composition_polynomial/src/lib.rs @@ -1,2 +1,4 @@ +pub mod air; +pub mod composition_polynomial; pub mod multiplicative_neighbors; pub mod periodic_columns; From e4406698a5a9e46c72b52b14db922aeff1511125 Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Sun, 20 Oct 2024 00:45:12 +0530 Subject: [PATCH 5/6] feat: add composition_polynomial --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c40eca3..27e51a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["felt", "commitment_scheme", "poseidon", "randomness", "channel", "fri"] +members = ["felt", "commitment_scheme", "poseidon", "randomness", "channel", "fri", "composition_polynomial"] resolver = "2" [workspace.dependencies] From c7f91eb232f6285f602fc243360888a9ad178ce4 Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Tue, 29 Oct 2024 22:22:42 +0700 Subject: [PATCH 6/6] refactor: apply code review --- composition_polynomial/src/air.rs | 51 ++++++++++++++----- .../src/composition_polynomial.rs | 44 ++++++---------- .../src/periodic_columns.rs | 9 ++-- 3 files changed, 59 insertions(+), 45 deletions(-) diff --git a/composition_polynomial/src/air.rs b/composition_polynomial/src/air.rs index f7be0a2..5472666 100644 --- a/composition_polynomial/src/air.rs +++ b/composition_polynomial/src/air.rs @@ -67,13 +67,41 @@ pub struct InteractionParams { pub n_interaction_elements: usize, } -type ConstraintFunction = Arc F>; +pub struct ConstraintEval { + pub neighbors: Vec, + pub periodic_columns: Vec, + pub random_coefficients: Vec, + pub point: F, + pub gen_powers: Vec, + pub precomp_domains: Vec, +} + +impl ConstraintEval { + pub fn new( + neighbors: Vec, + periodic_columns: Vec, + random_coefficients: Vec, + point: F, + gen_powers: Vec, + precomp_domains: Vec, + ) -> Self { + Self { + neighbors, + periodic_columns, + random_coefficients, + point, + gen_powers, + precomp_domains, + } + } +} + +type ConstraintFunction = Arc) -> F>; // DummyAir struct definition #[derive(Clone)] pub struct DummyAir { pub trace_length: usize, - pub n_constraints: usize, pub n_columns: usize, pub mask: Vec<(usize, usize)>, pub periodic_columns: Vec>, @@ -88,7 +116,6 @@ impl DummyAir { assert!(trace_length.is_power_of_two()); Self { trace_length, - n_constraints: 0, n_columns: 0, mask: vec![], periodic_columns: vec![], @@ -114,7 +141,7 @@ impl Air for DummyAir { } fn num_random_coefficients(&self) -> usize { - self.n_constraints + self.constraints.len() } fn num_columns(&self) -> usize { @@ -141,14 +168,14 @@ impl Air for DummyAir { let mut res = F::ZERO; for constraint in self.constraints.iter() { - res += constraint( - neighbors, - periodic_columns, - random_coefficients, - point, - gen_powers, - precomp_domains, - ); + res += constraint(ConstraintEval::new( + neighbors.to_vec(), + periodic_columns.to_vec(), + random_coefficients.to_vec(), + *point, + gen_powers.to_vec(), + precomp_domains.to_vec(), + )); } res } diff --git a/composition_polynomial/src/composition_polynomial.rs b/composition_polynomial/src/composition_polynomial.rs index a262ad9..44e3046 100644 --- a/composition_polynomial/src/composition_polynomial.rs +++ b/composition_polynomial/src/composition_polynomial.rs @@ -153,15 +153,8 @@ pub fn batch_pow(base: &F, exponents: &[usize]) -> Vec { } fn bit_reverse(n: usize, number_of_bits: usize) -> usize { - let mut reversed = 0; - let mut num = n; - - for _ in 0..number_of_bits { - reversed = (reversed << 1) | (num & 1); - num >>= 1; - } - - reversed + debug_assert!(n < (1 << number_of_bits)); + ((n as u64).reverse_bits() >> (64 - number_of_bits)) as usize } #[cfg(test)] @@ -180,8 +173,9 @@ mod tests { let q_minus_1: num_bigint::BigUint = F::ONE.neg().into(); // Calculate (q - 1) / n - assert!( - q_minus_1.clone() % n == num_bigint::BigUint::from(0u64), + assert_eq!( + q_minus_1.clone() % n, + num_bigint::BigUint::from(0u64), "No subgroup of required size exists" ); let quotient = q_minus_1 / n; @@ -194,7 +188,6 @@ mod tests { let mut rng = rand::thread_rng(); let mut air: DummyAir = DummyAir::new(4); air.composition_polynomial_degree_bound = Some(1000); - air.n_constraints = 0; let poly = air.create_composition_polynomial(&Felt252::ONE, &vec![]); let evaluation_point = Felt252::rand(&mut rng); @@ -224,7 +217,6 @@ mod tests { let mut air: DummyAir = DummyAir::new(trace_length); - air.n_constraints = 1; air.periodic_columns .push(get_random_periodic_col(log_coset_size)); air.n_columns = n_columns; @@ -233,22 +225,16 @@ mod tests { air.composition_polynomial_degree_bound = Some(2 * trace_length); air.point_exponents = vec![trace_length]; - air.constraints = vec![Arc::new( - |neighbors, - periodic_columns, - random_coefficients, - _point, - _gen_power, - precomp_evals| { - let constraint = neighbors[0] * periodic_columns[0] - neighbors[1]; - - let numerator = Felt252::ONE; - - let denominator = precomp_evals[0]; - - constraint * random_coefficients[0] * numerator / denominator - }, - )]; + air.constraints = vec![Arc::new(|constraint_eval| { + let constraint = constraint_eval.neighbors[0] * constraint_eval.periodic_columns[0] + - constraint_eval.neighbors[1]; + + let numerator = Felt252::ONE; + + let denominator = constraint_eval.precomp_domains[0]; + + constraint * constraint_eval.random_coefficients[0] * numerator / denominator + })]; let coset_group_generator: Felt252 = get_subgroup_generator(trace_length); diff --git a/composition_polynomial/src/periodic_columns.rs b/composition_polynomial/src/periodic_columns.rs index baf0103..6b75070 100644 --- a/composition_polynomial/src/periodic_columns.rs +++ b/composition_polynomial/src/periodic_columns.rs @@ -22,8 +22,8 @@ impl PeriodicColumn { ) -> Self { let period_in_trace = values.len() * column_step; - assert!(period_in_trace != 0); - assert!(coset_size % period_in_trace == 0); + assert_ne!(period_in_trace, 0); + assert_eq!(coset_size % period_in_trace, 0); let n_copies = coset_size / period_in_trace; let offset_compensation = offset.pow([n_copies as u64]).inverse().unwrap(); @@ -55,8 +55,8 @@ impl PeriodicColumn { let offset = start_point.pow([self.n_copies as u64]); let n_values = self.lde_manager.base.size as usize; - assert!( - coset_size == self.n_copies * self.column_step * n_values, + assert_eq!( + coset_size, self.n_copies * self.column_step * n_values, "Currently coset_size must be the same as the size of the coset that was used to create the PeriodicColumn." ); @@ -90,6 +90,7 @@ pub struct CosetEvaluation { impl CosetEvaluation { pub fn new(values: Vec) -> Self { + assert!(values.len().is_power_of_two()); Self { index_mask: values.len() - 1, values,