diff --git a/Cargo.toml b/Cargo.toml index 88e4433..38b02a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["felt", "commitment_scheme", "poseidon", "randomness", "channel"] +members = ["felt", "commitment_scheme", "poseidon", "randomness", "channel", "fri"] resolver = "2" [workspace.dependencies] diff --git a/channel/src/lib.rs b/channel/src/lib.rs index 5c2b887..a0dfff9 100644 --- a/channel/src/lib.rs +++ b/channel/src/lib.rs @@ -18,7 +18,7 @@ pub trait Channel { } #[allow(dead_code)] -trait FSChannel: Channel { +pub trait FSChannel: Channel { type PowHash; fn apply_proof_of_work(&mut self, security_bits: usize) -> Result<(), anyhow::Error>; diff --git a/fri/Cargo.toml b/fri/Cargo.toml new file mode 100644 index 0000000..af91045 --- /dev/null +++ b/fri/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "fri" +version = "0.1.0" +edition = "2021" + +[dependencies] +ark-ff.workspace = true +ark-poly.workspace = true +felt = { path = "../felt" } +randomness = { path = "../randomness" } +channel = { path = "../channel" } +sha3.workspace = true diff --git a/fri/src/lde.rs b/fri/src/lde.rs new file mode 100644 index 0000000..1391640 --- /dev/null +++ b/fri/src/lde.rs @@ -0,0 +1,154 @@ +use ark_ff::PrimeField; +use ark_poly::{ + univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain, Radix2EvaluationDomain, +}; + +use crate::stone_domain::change_order_of_elements_in_domain; + +#[allow(dead_code)] +pub struct MultiplicativeLDE { + pub ldes: Vec>, + pub base: Radix2EvaluationDomain, + pub reversed_order: bool, +} + +#[allow(dead_code)] +impl MultiplicativeLDE { + pub fn new(base: Radix2EvaluationDomain, reversed_order: bool) -> Self { + Self { + ldes: vec![], + base, + reversed_order, + } + } + + // Adds an evaluation on coset that was used to build the LDE. + // Future eval invocations will add the lde of that evaluation to the results. + pub fn add_eval(&mut self, evaluation: &[F]) { + assert_eq!( + evaluation.len(), + self.base.size(), + "length of evaluation must be equal to base size" + ); + + let new_lde = if self.reversed_order { + let evaluation_order_changed = change_order_of_elements_in_domain(evaluation); + self.base.ifft(&evaluation_order_changed) + } else { + self.base.ifft(evaluation) + }; + + self.ldes + .push(DensePolynomial::from_coefficients_slice(&new_lde)); + } + + pub fn add_coeff(&mut self, coeffs: &[F]) { + assert!( + coeffs.len() == self.base.size(), + "length of coeffs must be equal to base size" + ); + self.ldes + .push(DensePolynomial::from_coefficients_slice(coeffs)); + } + + // Evaluates the low degree extension of the evaluation that were previously added on a given coset. + // The results are ordered according to the order that the LDEs were added. + pub fn eval(&self, offset: F) -> Vec> { + let eval_domain = self.base.get_coset(offset).unwrap(); + let mut evals: Vec> = vec![]; + for lde_poly in self.ldes.iter() { + let evals_lde = lde_poly.evaluate_over_domain_by_ref(eval_domain); + evals.push(evals_lde.evals); + } + + evals + } + + pub fn coeffs(&self, index: usize) -> &[F] { + debug_assert!(index < self.ldes.len()); + self.ldes[index].coeffs() + } +} + +#[cfg(test)] +mod tests { + use ark_poly::{ + domain::EvaluationDomain, univariate::DensePolynomial, DenseUVPolynomial, Polynomial, + Radix2EvaluationDomain, + }; + use channel::{fs_prover_channel::FSProverChannel, Channel}; + use felt::Felt252; + use randomness::{keccak256::PrngKeccak256, Prng}; + use sha3::Sha3_256; + + use super::MultiplicativeLDE; + + type TestProverChannel = FSProverChannel; + + fn generate_prover_channel() -> TestProverChannel { + let prng = PrngKeccak256::new_with_seed(&[0u8; 4]); + TestProverChannel::new(prng) + } + + fn gen_random_field_element(prover_channel: &mut TestProverChannel) -> Felt252 { + prover_channel.draw_felem() + } + + fn setup_test_environment() -> ( + Radix2EvaluationDomain, + DensePolynomial, + Vec, + Felt252, + ) { + let log_n = 4; + let n = 1 << log_n; + let mut test_prover_channel = generate_prover_channel(); + + let offset = gen_random_field_element(&mut test_prover_channel); + let domain = Radix2EvaluationDomain::::new(n) + .unwrap() + .get_coset(offset) + .unwrap(); + + let coeffs: Vec = (0..n) + .map(|_| gen_random_field_element(&mut test_prover_channel)) + .collect(); + + let poly = DensePolynomial::from_coefficients_vec(coeffs); + let src: Vec = domain.elements().map(|x| poly.evaluate(&x)).collect(); + + let eval_domain_offset = gen_random_field_element(&mut test_prover_channel); + + (domain, poly, src, eval_domain_offset) + } + + #[test] + fn lde_naked_test() { + let (domain, poly, src, eval_domain_offset) = setup_test_environment(); + + let dst_domain = domain.get_coset(eval_domain_offset).unwrap(); + + let lde_coeffs = domain.ifft(&src); + let lde_result = dst_domain.fft(&lde_coeffs); + + for (x, &result) in dst_domain.elements().zip(lde_result.iter()) { + let expected = poly.evaluate(&x); + assert_eq!(expected, result, "LDE result mismatch at x = {:?}", x); + } + } + + #[test] + fn multiplicative_lde_test() { + let (domain, poly, src, eval_domain_offset) = setup_test_environment(); + let mut lde = MultiplicativeLDE::new(domain, false); + + lde.add_eval(&src); + let evals = lde.eval(eval_domain_offset); + let eval_domain = domain.get_coset(eval_domain_offset).unwrap(); + + for (x, &result) in eval_domain.elements().zip(evals[0].iter()) { + let expected = poly.evaluate(&x); + assert_eq!(expected, result, "LDE result mismatch at x = {:?}", x); + } + } +} diff --git a/fri/src/lib.rs b/fri/src/lib.rs new file mode 100644 index 0000000..16f848f --- /dev/null +++ b/fri/src/lib.rs @@ -0,0 +1,2 @@ +mod lde; +mod stone_domain; diff --git a/fri/src/stone_domain.rs b/fri/src/stone_domain.rs new file mode 100644 index 0000000..b097dcd --- /dev/null +++ b/fri/src/stone_domain.rs @@ -0,0 +1,90 @@ +use ark_ff::FftField; +use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; + +#[allow(dead_code)] +pub fn get_field_element_at_index>( + domain: &E, + index: usize, +) -> F { + let log_len = domain.size().trailing_zeros() as usize; + domain + .elements() + .nth(translate_index(index, log_len)) + .unwrap() +} + +// change order of elements in domain +pub fn change_order_of_elements_in_domain(elements: &[F]) -> Vec { + // get smallest power of two that is greater than elements.len() + let size = elements.len().next_power_of_two(); + // 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)]) + } + + new_elements +} + +fn translate_index(index: usize, log_len: usize) -> usize { + let sft = std::mem::size_of::() * 8 - log_len; + index.reverse_bits() >> sft +} + +#[allow(dead_code)] +pub fn make_fft_domains( + domain_size_log: usize, + offset: F, +) -> Vec> { + let mut current_offset = offset; + let mut domains = Vec::with_capacity(domain_size_log + 1); + for i in (0..=domain_size_log).rev() { + let domain = Radix2EvaluationDomain::::new(1 << i) + .unwrap() + .get_coset(current_offset) + .unwrap(); + domains.push(domain); + current_offset = current_offset * current_offset; + } + domains +} + +#[cfg(test)] +mod tests { + use felt::Felt252; + + use super::*; + + #[test] + fn test_change_order_of_elements_in_domain() { + let elements = vec![ + Felt252::from(0u64), + Felt252::from(4u64), + Felt252::from(2u64), + Felt252::from(6u64), + Felt252::from(1u64), + Felt252::from(5u64), + Felt252::from(3u64), + Felt252::from(7u64), + ]; + let new_elements = change_order_of_elements_in_domain(&elements); + assert_eq!( + new_elements, + vec![ + Felt252::from(0u64), + Felt252::from(1u64), + Felt252::from(2u64), + Felt252::from(3u64), + Felt252::from(4u64), + Felt252::from(5u64), + Felt252::from(6u64), + Felt252::from(7u64), + ] + ); + } +}