diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da5270..7bfe48a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Breaking changes +- [\#86](https://github.com/arkworks-rs/r1cs-std/pull/86) Change the API for domains for coset. + ### Features - [\#79](https://github.com/arkworks-rs/r1cs-std/pull/79) Move `NonNativeFieldVar` from `ark-nonnative` to `ark-r1cs-std`. @@ -14,6 +16,7 @@ ### Bug Fixes +- [\#86](https://github.com/arkworks-rs/r1cs-std/pull/86) Make result of `query_position_to_coset` consistent with `ark-ldt`. - [\#77](https://github.com/arkworks-rs/r1cs-std/pull/77) Fix BLS12 `G2PreparedGadget`'s `AllocVar` when G2 uses a divisive twist. ## v0.3.1 diff --git a/src/poly/domain/mod.rs b/src/poly/domain/mod.rs index 389a732..93773af 100644 --- a/src/poly/domain/mod.rs +++ b/src/poly/domain/mod.rs @@ -20,7 +20,7 @@ pub struct Radix2DomainVar { pub gen: F, /// index of the quotient group (i.e. the `offset`) offset: FpVar, - /// dimension of evaluation domain + /// dimension of evaluation domain, which is log2(size of coset) pub dim: u64, } impl Radix2DomainVar { @@ -56,13 +56,13 @@ impl Radix2DomainVar { 1 << self.dim } - /// Returns g, g^2, ..., g^{dim} - fn powers_of_gen(&self, dim: usize) -> Vec { + /// Returns offset, offset*g, offset*g^2, ..., offset*g^{coset_size} + pub fn elements(&self) -> Vec> { let mut result = Vec::new(); - let mut cur = self.gen; - for _ in 0..dim { - result.push(cur); - cur = cur * cur; + result.push(self.offset.clone()); + for _ in 1..(1 << self.dim) { + let new_element = result.last().unwrap() * self.gen; + result.push(new_element); } result } @@ -73,37 +73,113 @@ impl Radix2DomainVar { } /// For domain `h` with dimension `n`, `position` represented by `query_pos` in big endian form, - /// returns `h*g^{position}` - pub fn query_position_to_coset( + /// returns all points of `h*g^{position}`. The result is the query coset at index `query_pos` + /// for the FRI protocol. + /// + /// # Panics + /// This function panics when `query_pos.len() != coset_dim` or `query_pos.len() != self.dim`. + pub fn query_position_to_coset_elements( &self, query_pos: &[Boolean], coset_dim: u64, ) -> Result>, SynthesisError> { - let mut coset_index = query_pos; - assert!( - query_pos.len() == self.dim as usize - || query_pos.len() == (self.dim - coset_dim) as usize - ); - if query_pos.len() == self.dim as usize { - coset_index = &coset_index[0..(coset_index.len() - coset_dim as usize)]; - } - let mut coset = Vec::new(); - let powers_of_g = &self.powers_of_gen(self.dim as usize)[(coset_dim as usize)..]; + Ok(self + .query_position_to_coset(query_pos, coset_dim)? + .elements()) + } - let mut first_point_in_coset: FpVar = FpVar::zero(); - for i in 0..coset_index.len() { - let term = coset_index[i].select(&FpVar::constant(powers_of_g[i]), &FpVar::zero())?; - first_point_in_coset += &term; - } + /// For domain `h` with dimension `n`, `position` represented by `query_pos` in big endian form, + /// returns all points of `h*g^{position}` + /// + /// # Panics + /// This function panics when `query_pos.len() != coset_dim` or `query_pos.len() != self.dim`. + pub fn query_position_to_coset( + &self, + query_pos: &[Boolean], + coset_dim: u64, + ) -> Result { + let coset_index = truncate_to_coset_index(query_pos, self.dim, coset_dim); + let offset_var = &self.offset * FpVar::Constant(self.gen).pow_le(&coset_index)?; + Ok(Self { + gen: self.gen.pow(&[1 << (self.dim - coset_dim)]), // distance between coset + offset: offset_var, + dim: coset_dim, + }) + } +} - first_point_in_coset *= &self.offset; +fn truncate_to_coset_index( + query_pos: &[Boolean], + codeword_dim: u64, + coset_dim: u64, +) -> Vec> { + assert!(query_pos.len() == codeword_dim as usize || query_pos.len() == coset_dim as usize); + if query_pos.len() == codeword_dim as usize { + query_pos[0..(query_pos.len() - coset_dim as usize)].to_vec() + } else { + query_pos.to_vec() + } +} - coset.push(first_point_in_coset); - for i in 1..(1 << (coset_dim as usize)) { - let new_elem = &coset[i - 1] * &FpVar::Constant(self.gen); - coset.push(new_elem); - } +#[cfg(test)] +mod tests { + use crate::prelude::*; + use ark_ff::PrimeField; + use ark_relations::r1cs::ConstraintSystem; + use ark_std::{rand::Rng, test_rng}; + + use crate::{alloc::AllocVar, fields::fp::FpVar, poly::domain::Radix2DomainVar, R1CSVar}; + + fn test_query_coset_template() { + const COSET_DIM: u64 = 7; + const COSET_SIZE: usize = 1 << COSET_DIM; + const LOCALIZATION: u64 = 3; + let cs = ConstraintSystem::new_ref(); + let mut rng = test_rng(); + let gen = F::get_root_of_unity(COSET_SIZE).unwrap(); + let offset = F::rand(&mut rng); + let offset_var = FpVar::new_witness(cs.clone(), || Ok(offset)).unwrap(); + let domain = Radix2DomainVar::new(gen, COSET_DIM, offset_var).unwrap(); + + let num_cosets = 1 << (COSET_DIM - LOCALIZATION); + + let coset_index = rng.gen_range(0..num_cosets); + let coset_index_var = UInt32::new_witness(cs.clone(), || Ok(coset_index)) + .unwrap() + .to_bits_le() + .into_iter() + .take(COSET_DIM as usize) + .collect::>(); + + let elements_actual = domain + .query_position_to_coset(&coset_index_var, LOCALIZATION) + .unwrap() + .elements(); + + let elements_expected = domain + .elements() + .into_iter() + .skip(coset_index as usize) + .step_by(1 << (COSET_DIM - LOCALIZATION)) + .collect::>(); + + assert_eq!(elements_expected.len(), elements_actual.len()); + + elements_expected + .into_iter() + .zip(elements_actual.into_iter()) + .for_each(|(left, right)| { + assert_eq!(left.value().unwrap(), right.value().unwrap()); + }); + } + + #[test] + fn test_on_bls12_381() { + test_query_coset_template::(); + } - Ok(coset) + #[test] + fn test_on_bls12_377() { + test_query_coset_template::(); } }