From 820f81ffc6b5c8da5825e282a73d8ec7dc96df00 Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Sun, 20 Oct 2024 00:26:31 +0530 Subject: [PATCH 1/8] 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/8] 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/8] 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/8] 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/8] 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 c57fc90cd4e7f3766e58a092cac537e32a68e2de Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Sat, 26 Oct 2024 19:57:32 +0700 Subject: [PATCH 6/8] feat: impl trace and boundry constraints --- Cargo.toml | 2 +- air/Cargo.toml | 15 ++ air/src/boundry.rs | 271 +++++++++++++++++++++++++ air/src/boundry_periodic_column.rs | 313 +++++++++++++++++++++++++++++ air/src/lib.rs | 4 + air/src/test_utils.rs | 103 ++++++++++ air/src/trace.rs | 58 ++++++ composition_polynomial/src/air.rs | 2 +- fri/src/lib.rs | 2 +- 9 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 air/Cargo.toml create mode 100644 air/src/boundry.rs create mode 100644 air/src/boundry_periodic_column.rs create mode 100644 air/src/lib.rs create mode 100644 air/src/test_utils.rs create mode 100644 air/src/trace.rs diff --git a/Cargo.toml b/Cargo.toml index 4c9f5fd..d0a311e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["felt", "commitment_scheme", "poseidon", "randomness", "channel", "fri", "pedersen", "composition_polynomial"] +members = ["felt", "commitment_scheme", "poseidon", "randomness", "channel", "fri", "pedersen", "composition_polynomial", "air"] resolver = "2" [workspace.dependencies] diff --git a/air/Cargo.toml b/air/Cargo.toml new file mode 100644 index 0000000..e036bba --- /dev/null +++ b/air/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "air" +version = "0.1.0" +edition = "2021" + +[dependencies] +ark-ff = { workspace = true } +ark-poly = { workspace = true } +felt = { path = "../felt" } +composition_polynomial = { path = "../composition_polynomial" } +fri = { path = "../fri" } +num-bigint = { workspace = true } + +[dev-dependencies] +rand = { workspace = true } diff --git a/air/src/boundry.rs b/air/src/boundry.rs new file mode 100644 index 0000000..990d228 --- /dev/null +++ b/air/src/boundry.rs @@ -0,0 +1,271 @@ +use ark_ff::PrimeField; +use composition_polynomial::{air::Air, composition_polynomial::CompositionPolynomial}; + +#[derive(Clone)] +pub struct BoundaryAir { + trace_length: usize, + n_columns: usize, + constraints: Vec>, + mask: Vec<(usize, usize)>, +} + +#[derive(Clone)] +pub struct ConstraintData { + coeff_idx: usize, + column_index: usize, + point_x: F, + point_y: F, +} + +impl BoundaryAir { + pub fn new( + trace_length: usize, + n_columns: usize, + boundary_conditions: &[(usize, F, F)], + ) -> Self { + let mut constraints = Vec::with_capacity(boundary_conditions.len()); + + for (coeff_idx, &(column_index, ref point_x, ref point_y)) in + boundary_conditions.iter().enumerate() + { + let x = *point_x; + + let pos = constraints + .iter() + .position(|constraint: &ConstraintData| constraint.point_x == x); + + let constraint_data = ConstraintData { + coeff_idx, + column_index, + point_x: x, + point_y: *point_y, + }; + + if let Some(pos) = pos { + constraints.insert(pos, constraint_data); + } else { + constraints.push(constraint_data); + } + } + + let mut mask = Vec::with_capacity(n_columns); + for i in 0..n_columns { + mask.push((0, i)); + } + + BoundaryAir { + trace_length, + n_columns, + constraints, + mask, + } + } +} + +impl Air for BoundaryAir { + fn trace_length(&self) -> usize { + self.trace_length + } + + fn num_columns(&self) -> usize { + self.n_columns + } + + fn num_random_coefficients(&self) -> usize { + self.constraints.len() + } + + fn get_mask(&self) -> &[(usize, usize)] { + &self.mask + } + + fn constraints_eval( + &self, + neighbors: &[F], + _periodic_columns: &[F], + random_coefficients: &[F], + point: &F, + _shifts: &[F], + _precomp_domains: &[F], + ) -> F { + assert_eq!(neighbors.len(), self.n_columns); + assert_eq!(random_coefficients.len(), self.constraints.len()); + + let mut outer_sum = F::ZERO; + let mut inner_sum = F::ZERO; + let mut prev_x = self.constraints[0].point_x; + + for constraint in &self.constraints { + let constraint_value = random_coefficients[constraint.coeff_idx] + * (neighbors[constraint.column_index] - constraint.point_y); + + if prev_x == constraint.point_x { + inner_sum += constraint_value; + } else { + outer_sum += inner_sum / (*point - prev_x); + inner_sum = constraint_value; + prev_x = constraint.point_x; + } + } + + outer_sum += inner_sum / (*point - prev_x); + + outer_sum + } + + fn domain_evals_at_point(&self, _point_powers: &[F], _shifts: &[F]) -> Vec { + vec![] + } + + fn precompute_domain_evals_on_coset( + &self, + _point: &F, + _generator: &F, + _point_exponents: &[usize], + _shifts: &[F], + ) -> Vec> { + vec![] + } + + fn get_composition_polynomial_degree_bound(&self) -> usize { + self.trace_length() + } + + fn parse_dynamic_params( + &self, + _params: &std::collections::HashMap, + ) -> Vec { + vec![] + } + + fn get_interaction_params(&self) -> Option { + None + } + + fn create_composition_polynomial( + &self, + trace_generator: &F, + random_coefficients: &[F], + ) -> composition_polynomial::composition_polynomial::CompositionPolynomial { + CompositionPolynomial::new( + Box::new(self.clone()), + *trace_generator, + self.trace_length(), + vec![], + random_coefficients, + &[], + &[], + ) + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::compute_composition_degree; + use crate::trace::Trace; + use ark_ff::UniformRand; + use ark_poly::Polynomial; + use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; + use composition_polynomial::air::Air; + use felt::Felt252; + use fri::lde::MultiplicativeLDE; + use rand::Rng; + + use super::BoundaryAir; + + #[test] + fn test_boundry_correctness() { + let mut rng = rand::thread_rng(); + let n_columns = 10; + let n_conditions = 20; + let trace_length = 1024; + + let base: Radix2EvaluationDomain = + Radix2EvaluationDomain::new(trace_length).unwrap(); + let mut lde_manager = MultiplicativeLDE::new(base, false); + + let mut values = vec![]; + for _ in 0..n_columns { + let rand_col: Vec = + (0..trace_length).map(|_| Felt252::rand(&mut rng)).collect(); + lde_manager.add_eval(&rand_col); + values.push(rand_col); + } + + let mut boundary_conditions: Vec<(usize, Felt252, Felt252)> = + Vec::with_capacity(n_conditions); + + for _ in 0..n_conditions { + let col_id = rng.gen_range(0..n_columns); + let point_x = Felt252::rand(&mut rng); + let point_y = lde_manager.ldes[col_id].evaluate(&point_x); + boundary_conditions.push((col_id, point_x, point_y)); + } + + let boundry_air = BoundaryAir::new(trace_length, n_columns, &boundary_conditions); + + let random_coefficients: Vec = (0..boundry_air.num_random_coefficients()) + .map(|_| Felt252::rand(&mut rng)) + .collect(); + + let actual_degree = compute_composition_degree( + Box::new(boundry_air.clone()), + Trace::new(values), + &random_coefficients, + 2, + ); + assert_eq!(trace_length - 2, actual_degree); + assert_eq!( + boundry_air.get_composition_polynomial_degree_bound() - 2, + actual_degree + ); + } + + #[test] + fn test_boundry_soundness() { + let mut rng = rand::thread_rng(); + let n_columns = 10; + let n_conditions = 20; + let trace_length = 1024; + + let base: Radix2EvaluationDomain = + Radix2EvaluationDomain::new(trace_length).unwrap(); + let mut lde_manager = MultiplicativeLDE::new(base, false); + + let mut values = vec![]; + for _ in 0..n_columns { + let rand_col: Vec = + (0..trace_length).map(|_| Felt252::rand(&mut rng)).collect(); + lde_manager.add_eval(&rand_col); + values.push(rand_col); + } + + let mut boundary_conditions: Vec<(usize, Felt252, Felt252)> = + Vec::with_capacity(n_conditions); + + for _ in 0..n_conditions { + let col_id = rng.gen_range(0..n_columns); + let point_x = Felt252::rand(&mut rng); + let point_y = Felt252::rand(&mut rng); + boundary_conditions.push((col_id, point_x, point_y)); + } + + let boundry_air = BoundaryAir::new(trace_length, n_columns, &boundary_conditions); + + let random_coefficients: Vec = (0..boundry_air.num_random_coefficients()) + .map(|_| Felt252::rand(&mut rng)) + .collect(); + + let num_of_cosets = 2; + let actual_degree = compute_composition_degree( + Box::new(boundry_air.clone()), + Trace::new(values), + &random_coefficients, + num_of_cosets, + ); + assert_eq!( + num_of_cosets * boundry_air.get_composition_polynomial_degree_bound() - 1, + actual_degree + ); + } +} diff --git a/air/src/boundry_periodic_column.rs b/air/src/boundry_periodic_column.rs new file mode 100644 index 0000000..27cd384 --- /dev/null +++ b/air/src/boundry_periodic_column.rs @@ -0,0 +1,313 @@ +use ark_ff::PrimeField; +use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; +use composition_polynomial::periodic_columns::PeriodicColumn; +use std::collections::BTreeSet; + +fn row_indices_to_field_elements( + rows: &[usize], + trace_generator: &F, + trace_offset: &F, +) -> Vec { + let mut result = Vec::with_capacity(rows.len()); + for &row_index in rows { + let powered_generator = trace_generator.pow([row_index as u64]); + result.push(*trace_offset * powered_generator); + } + result +} + +fn create_boundary_periodic_column( + rows: &[usize], + values: &[F], + trace_length: usize, + trace_generator: &F, + trace_offset: &F, +) -> PeriodicColumn { + assert!( + rows.len() == values.len(), + "Number of rows does not match number of values" + ); + let n_values = rows.len(); + + let x_values = row_indices_to_field_elements(rows, trace_generator, trace_offset); + + let log_column_height = if n_values == 0 { + 1 + } else { + // ceil(Log_2(n_values) + (n_values as f32).log2().ceil() as usize + }; + let column_height = 1 << log_column_height; + + assert!(column_height != 0 && trace_length % column_height == 0); + + let mut reverse_cumulative_product = vec![F::ZERO; n_values]; + let mut periodic_column_values = Vec::with_capacity(column_height); + + let domain: Radix2EvaluationDomain = Radix2EvaluationDomain::new(column_height).unwrap(); + + for cur_x in domain.elements() { + let mut prod = F::ONE; + for i in 0..n_values { + let reverse_i = n_values - 1 - i; + reverse_cumulative_product[reverse_i] = prod; + prod *= cur_x - x_values[reverse_i]; + } + + prod = F::ONE; + let mut res = F::from(0u64); + + for i in 0..x_values.len() { + res += values[i] * prod * reverse_cumulative_product[i]; + prod *= cur_x - x_values[i]; + } + periodic_column_values.push(res); + } + + assert!(column_height != 0); + assert!(trace_length % column_height == 0); + let col_step = trace_length / column_height; + PeriodicColumn::new( + periodic_column_values, + *trace_generator, + F::ONE, + trace_length, + col_step, + ) +} + +pub fn create_base_boundary_periodic_column( + rows: &[usize], + trace_length: usize, + trace_generator: &F, + trace_offset: &F, +) -> PeriodicColumn { + let values = vec![F::ONE; rows.len()]; + create_boundary_periodic_column(rows, &values, trace_length, trace_generator, trace_offset) +} + +fn create_vanishing_periodic_column( + rows: &[usize], + trace_length: usize, + trace_generator: &F, + trace_offset: &F, +) -> PeriodicColumn { + let x_values = row_indices_to_field_elements(rows, trace_generator, trace_offset); + + // ceil(Log_2(x_values.len() + 1) + let log_column_height = ((x_values.len() + 1) as f32).log2().ceil() as usize; + + let column_height = 1 << log_column_height; + + let mut periodic_column_values = Vec::with_capacity(column_height); + let domain: Radix2EvaluationDomain = Radix2EvaluationDomain::new(column_height).unwrap(); + + for cur_x in domain.elements() { + let mut res = F::ONE; + for x_value in &x_values { + res *= cur_x - x_value; + } + periodic_column_values.push(res); + } + + assert!(column_height != 0); + assert!(trace_length % column_height == 0); + let col_step = trace_length / column_height; + PeriodicColumn::new( + periodic_column_values, + *trace_generator, + F::ONE, + trace_length, + col_step, + ) +} + +pub fn create_complement_vanishing_periodic_column( + rows: &[usize], + step: usize, + trace_length: usize, + trace_generator: &F, + trace_offset: &F, +) -> PeriodicColumn { + assert!(step != 0); + assert!(trace_length % step == 0); + let coset_size = trace_length / step; + + let rows_set: BTreeSet = rows.iter().cloned().collect(); + assert_eq!(rows_set.len(), rows.len(), "Rows must be distinct"); + + let mut other_rows = Vec::with_capacity(coset_size - rows.len()); + for i in (0..trace_length).step_by(step) { + if !rows_set.contains(&i) { + other_rows.push(i); + } + } + assert_eq!( + other_rows.len() + rows.len(), + coset_size, + "All rows must be in the coset." + ); + + create_vanishing_periodic_column(&other_rows, trace_length, trace_generator, trace_offset) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::get_subgroup_generator; + use ark_ff::Field; + use ark_ff::UniformRand; + use felt::Felt252; + use rand::prelude::SliceRandom; + use rand::Rng; + + fn sample_uniform_distinct_vector(min: usize, max: usize, n_values: usize) -> Vec { + let mut rng = rand::thread_rng(); + + let mut range: Vec = (min..max).collect(); + + range.shuffle(&mut rng); + + range.into_iter().take(n_values).collect() + } + + #[test] + fn test_create_boundary_periodic_column() { + let mut rng = rand::thread_rng(); + + let n_values = 37; + let trace_length = 1024; + let trace_generator = get_subgroup_generator(trace_length); + let trace_offset = Felt252::rand(&mut rng); + + let rows: Vec = (0..n_values) + .map(|_| rng.gen_range(0..trace_length)) + .collect(); + + let values: Vec = (0..n_values).map(|_| Felt252::rand(&mut rng)).collect(); + + let periodic_column = create_boundary_periodic_column( + &rows, + &values, + trace_length, + &trace_generator, + &trace_offset, + ); + let periodic_column_base = create_base_boundary_periodic_column( + &rows, + trace_length, + &trace_generator, + &trace_offset, + ); + + assert_eq!(periodic_column.get_actual_degree(), n_values - 1,); + assert_eq!(periodic_column_base.get_actual_degree(), n_values - 1,); + + for (i, &row) in rows.iter().enumerate() { + let x_value = trace_offset.clone() * trace_generator.pow([row as u64]); + let value = periodic_column.eval_at_point(x_value); + let value_base = periodic_column_base.eval_at_point(x_value); + assert_eq!(value, values[i] * value_base); + } + + // Make sure the following line doesn't throw an assertion error. + periodic_column.get_coset(&Felt252::ONE, trace_length); + } + + #[test] + fn test_create_boundary_periodic_column_empty() { + let mut rng = rand::thread_rng(); + let trace_length = 1024; + let trace_generator = get_subgroup_generator(trace_length); + let trace_offset = Felt252::rand(&mut rng); + + let periodic_column = create_boundary_periodic_column( + &[], + &[], + trace_length, + &trace_generator, + &trace_offset, + ); + let periodic_column_base = create_base_boundary_periodic_column( + &[], + trace_length, + &trace_generator, + &trace_offset, + ); + + assert_eq!(periodic_column.get_actual_degree(), 0); + assert_eq!(periodic_column_base.get_actual_degree(), 0); + + let random_point = Felt252::rand(&mut rng); + assert_eq!(periodic_column.eval_at_point(random_point), Felt252::ZERO); + assert_eq!( + periodic_column_base.eval_at_point(random_point), + Felt252::ZERO + ); + } + + #[test] + fn test_create_vanishing_periodic_column() { + let mut rng = rand::thread_rng(); + + let trace_length = 1024; + let step = 4; + let coset_size = trace_length / step; + let trace_generator = get_subgroup_generator(trace_length); + let trace_offset = Felt252::rand(&mut rng); + + for &n_values in &[0, 31, 32, 33, coset_size] { + let mut rows: Vec; + if n_values < coset_size { + rows = sample_uniform_distinct_vector(0, coset_size, n_values) + } else { + assert_eq!(n_values, coset_size); + rows = (0..coset_size).collect(); + } + + for row in &mut rows { + *row *= step; + } + + let rows_set: BTreeSet = rows.iter().copied().collect(); + + let periodic_column = create_vanishing_periodic_column( + &rows, + trace_length, + &trace_generator, + &trace_offset, + ); + let periodic_column_comp = create_complement_vanishing_periodic_column( + &rows, + step, + trace_length, + &trace_generator, + &trace_offset, + ); + + assert_eq!(periodic_column.get_actual_degree(), n_values); + assert_eq!( + periodic_column_comp.get_actual_degree(), + (coset_size - n_values) + ); + + for i in (0..trace_length).step_by(step as usize) { + let x_value = trace_offset.clone() * trace_generator.pow([i as u64]); + let value = periodic_column.eval_at_point(x_value); + let value_comp = periodic_column_comp.eval_at_point(x_value); + + if rows_set.contains(&i) { + assert_eq!(value, Felt252::ZERO); + assert_ne!(value_comp, Felt252::ZERO); + } else { + assert_ne!(value, Felt252::ZERO); + assert_eq!(value_comp, Felt252::ZERO); + } + } + + // Make sure the following line doesn't throw an assertion error. + periodic_column.get_coset(&Felt252::ONE, trace_length); + periodic_column_comp.get_coset(&Felt252::ONE, trace_length); + } + } +} diff --git a/air/src/lib.rs b/air/src/lib.rs new file mode 100644 index 0000000..32ad885 --- /dev/null +++ b/air/src/lib.rs @@ -0,0 +1,4 @@ +pub mod boundry; +pub mod boundry_periodic_column; +pub mod test_utils; +pub mod trace; diff --git a/air/src/test_utils.rs b/air/src/test_utils.rs new file mode 100644 index 0000000..f1896e4 --- /dev/null +++ b/air/src/test_utils.rs @@ -0,0 +1,103 @@ +use crate::trace::Trace; +use ark_ff::PrimeField; +use ark_poly::evaluations::univariate::Evaluations; +use ark_poly::{EvaluationDomain, Polynomial, Radix2EvaluationDomain}; +use composition_polynomial::air::Air; +use fri::lde::MultiplicativeLDE; +use fri::stone_domain::change_order_of_elements_in_domain; + +pub fn compute_composition_degree( + air: Box>, + trace: Trace, + random_coefficients: &[F], + num_of_cosets: usize, +) -> usize { + assert!(trace.width() > 0 && !trace.get_column(0).is_empty()); + + let coset_size = trace.get_column(0).len(); + + // evaluation_domain_size = Pow2(Log2Ceil(air.GetCompositionPolynomialDegreeBound() * num_of_cosets)) + let evaluation_domain_size = 1 + << ((air.get_composition_polynomial_degree_bound() * num_of_cosets) as f32) + .log2() + .ceil() as usize; + + assert!(coset_size != 0); + assert_eq!(evaluation_domain_size % coset_size, 0); + let n_cosets = evaluation_domain_size / coset_size; // 2 + + // power_of_two_cosets = Pow2(Log2Ceil(n_cosets)) + let power_of_two_cosets = 1 << (n_cosets as f32).log2().ceil() as usize; + + let coset_generator: F = get_subgroup_generator(coset_size * power_of_two_cosets); + let trace_generator = coset_generator.pow([power_of_two_cosets as u64]); + let cosets = get_cosets_offsets(n_cosets, coset_generator, F::GENERATOR); + + let bases: Radix2EvaluationDomain = Radix2EvaluationDomain::new(coset_size).unwrap(); + let mut lde_manager = MultiplicativeLDE::new(bases, false); + + for i in 0..trace.width() { + lde_manager.add_eval(trace.get_column(i)); + } + + let comp_poly = air.create_composition_polynomial(&trace_generator, random_coefficients); + + let mut evaluation = vec![]; + for i in 0..n_cosets { + let coset_offset = cosets[bit_reverse(i, n_cosets.ilog2() as usize)]; + let trace_lde = lde_manager.batch_eval(coset_offset); + evaluation.push(comp_poly.eval_on_coset_bit_reversed_output(coset_offset, trace_lde)); + } + let evaluation: Vec = evaluation.iter().flatten().cloned().collect(); + + let group = Radix2EvaluationDomain::new(evaluation_domain_size).unwrap(); + + let eval_rev = change_order_of_elements_in_domain(&evaluation); + + let evals = Evaluations::from_vec_and_domain(eval_rev, group); + + evals.interpolate().degree() +} + +fn get_cosets_offsets( + n_cosets: usize, + domain_generator: F, + common_offset: F, +) -> Vec { + let mut result = Vec::with_capacity(n_cosets); + + let mut offset = common_offset; + result.push(offset); + + for _ in 1..n_cosets { + offset *= domain_generator; + result.push(offset); + } + + result +} + +pub 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 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 +} diff --git a/air/src/trace.rs b/air/src/trace.rs new file mode 100644 index 0000000..eb5e408 --- /dev/null +++ b/air/src/trace.rs @@ -0,0 +1,58 @@ +use ark_ff::PrimeField; + +pub struct Trace { + values: Vec>, +} + +impl Trace { + pub fn new(values: Vec>) -> Self { + Trace { values } + } + + pub fn length(&self) -> usize { + self.values[0].len() + } + + pub fn width(&self) -> usize { + self.values.len() + } + + pub fn get_column(&self, column: usize) -> &Vec { + &self.values[column] + } +} + +#[cfg(test)] +mod tests { + use ark_ff::UniformRand; + use felt::Felt252; + use rand::Rng; + + use super::Trace; + + #[test] + fn test_trace() { + let mut rng = rand::thread_rng(); + + let width = rng.gen_range(1..=10); + let height = rng.gen_range(1..=10); + + let mut values = vec![]; + for _ in 0..width { + let rand_col: Vec = (0..height).map(|_| Felt252::rand(&mut rng)).collect(); + values.push(rand_col); + } + + let trace = Trace::new(values.clone()); + assert_eq!(trace.length(), height); + assert_eq!(trace.width(), width); + + for i in 0..width { + assert_eq!(trace.get_column(i).len(), height); + + for j in 0..height { + assert_eq!(trace.get_column(i)[j], values[i][j]); + } + } + } +} diff --git a/composition_polynomial/src/air.rs b/composition_polynomial/src/air.rs index f7be0a2..7e805e6 100644 --- a/composition_polynomial/src/air.rs +++ b/composition_polynomial/src/air.rs @@ -27,7 +27,7 @@ pub trait Air { periodic_columns: &[F], random_coefficients: &[F], point: &F, - gen_powers: &[F], + shifts: &[F], precomp_domains: &[F], ) -> F; diff --git a/fri/src/lib.rs b/fri/src/lib.rs index f80b4bc..434b55f 100644 --- a/fri/src/lib.rs +++ b/fri/src/lib.rs @@ -5,6 +5,6 @@ mod layers; pub mod lde; mod parameters; mod prover; -mod stone_domain; +pub mod stone_domain; mod test; mod verifier; From 467125c91e155fd8c44b8b882b1c786159eea0dd Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Mon, 28 Oct 2024 16:20:17 +0700 Subject: [PATCH 7/8] fix: add license --- air/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/air/Cargo.toml b/air/Cargo.toml index e036bba..d05d3b2 100644 --- a/air/Cargo.toml +++ b/air/Cargo.toml @@ -2,6 +2,7 @@ name = "air" version = "0.1.0" edition = "2021" +license = "MIT" [dependencies] ark-ff = { workspace = true } From c7f91eb232f6285f602fc243360888a9ad178ce4 Mon Sep 17 00:00:00 2001 From: Varun Thakore Date: Tue, 29 Oct 2024 22:22:42 +0700 Subject: [PATCH 8/8] 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,