Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add helper function for LDE calculation #12

Merged
merged 9 commits into from
Sep 13, 2024
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["felt", "commitment_scheme", "poseidon", "randomness", "channel"]
members = ["felt", "commitment_scheme", "poseidon", "randomness", "channel", "fri"]
resolver = "2"

[workspace.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion channel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down
12 changes: 12 additions & 0 deletions fri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
154 changes: 154 additions & 0 deletions fri/src/lde.rs
Original file line number Diff line number Diff line change
@@ -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<F: PrimeField> {
pub ldes: Vec<DensePolynomial<F>>,
pub base: Radix2EvaluationDomain<F>,
pub reversed_order: bool,
}

#[allow(dead_code)]
impl<F: PrimeField> MultiplicativeLDE<F> {
pub fn new(base: Radix2EvaluationDomain<F>, 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<Vec<F>> {
let eval_domain = self.base.get_coset(offset).unwrap();
let mut evals: Vec<Vec<F>> = 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<Felt252, PrngKeccak256, Sha3_256>;

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<Felt252>,
DensePolynomial<Felt252>,
Vec<Felt252>,
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::<Felt252>::new(n)
.unwrap()
.get_coset(offset)
.unwrap();

let coeffs: Vec<Felt252> = (0..n)
.map(|_| gen_random_field_element(&mut test_prover_channel))
.collect();

let poly = DensePolynomial::from_coefficients_vec(coeffs);
let src: Vec<Felt252> = 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);
}
}
}
2 changes: 2 additions & 0 deletions fri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod lde;
mod stone_domain;
90 changes: 90 additions & 0 deletions fri/src/stone_domain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use ark_ff::FftField;
use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};

#[allow(dead_code)]
pub fn get_field_element_at_index<F: FftField, E: EvaluationDomain<F>>(
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<F: FftField>(elements: &[F]) -> Vec<F> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a multiplicative group of order 8:
Typically, the elements are ordered as g^0, g^1, g^2, g^3, ...
However, in Stone's method, they use two types of element ordering:
One is the normal ordering, and the other is where
elements are ordered as g^0, g^4, g^2, g^6, ...

// 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::<usize>() * 8 - log_len;
index.reverse_bits() >> sft
}

#[allow(dead_code)]
pub fn make_fft_domains<F: FftField>(
domain_size_log: usize,
offset: F,
) -> Vec<Radix2EvaluationDomain<F>> {
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::<F>::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),
]
);
}
}