Skip to content

Commit

Permalink
feat: implement commitment phase of fri verifier (#13)
Browse files Browse the repository at this point in the history
* bugfix: fix bug caused by not handling mont form of stone
* feat: Add helper function for LDE calculation
* feat: implement fri parameters and foler function for verifying fri
* feat: implement commitment phase of fri verifier
  • Loading branch information
jaehunkim authored Sep 23, 2024
1 parent b500ae4 commit 3df25b4
Show file tree
Hide file tree
Showing 8 changed files with 536 additions and 4 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ sha3 = "0.10.8"
blake2 = "0.10.6"
hex-literal = "0.4.1"
num-bigint = "0.4.3"
serde_json = "1.0.122"
serde = { version = "1.0.205", features = ["derive"] }
paste = "1.0"
hex = "0.4"
rand = "0.8"
rand = "0.8"
21 changes: 20 additions & 1 deletion felt/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use ark_ff::{
fields::{MontBackend, MontConfig},
Fp256, Zero,
BigInteger, Fp256, PrimeField, Zero,
};
use std::fmt::Write;

#[derive(MontConfig)]
#[modulus = "3618502788666131213697322783095070105623107215331596699973092056135872020481"]
Expand All @@ -27,6 +28,24 @@ pub fn hex(hex: &str) -> Felt252 {
res
}

/// This method is used for testing / debugging purposes.
pub fn felt_252_to_hex<F: PrimeField>(felt: &F) -> String {
let bigint = felt.into_bigint().to_bytes_be();
let hex_string = bigint.iter().fold(String::new(), |mut output, b| {
let _ = write!(output, "{:02x}", b);
output
});

// remove leading 0
let hex_string = hex_string.trim_start_matches('0').to_string();
// add leading 0x
format!("0x{}", hex_string)
}

pub fn byte_size<F: PrimeField>() -> usize {
(F::MODULUS_BIT_SIZE.div_ceil(8) * 8) as usize
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 4 additions & 0 deletions fri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ felt = { path = "../felt" }
randomness = { path = "../randomness" }
channel = { path = "../channel" }
sha3.workspace = true
serde_json.workspace = true
serde.workspace = true
commitment_scheme = { path = "../commitment_scheme" }
anyhow.workspace = true
29 changes: 29 additions & 0 deletions fri/src/folder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use ark_ff::FftField;

pub struct MultiplicativeFriFolder;

#[allow(dead_code)]
impl MultiplicativeFriFolder {
pub fn next_layer_element_from_two_previous_layer_elements<F: FftField>(
f_x: F,
f_minus_x: F,
eval_point: F,
x_inv: F,
) -> F {
Self::fold(f_x, f_minus_x, eval_point, x_inv)
}

/// Interpolating a line through (x, f(x)) and (-x, f(-x))
/// then evaluating it at "eval_point"
/// Multiplicative case folding formula:
/// f(x) = g(x^2) + xh(x^2)
/// f(-x) = g((-x)^2) - xh((-x)^2) = g(x^2) - xh(x^2)
/// =>
/// 2g(x^2) = f(x) + f(-x)
/// 2h(x^2) = (f(x) - f(-x))/x
/// =>
/// 2g(x^2) + 2ah(x^2) = f(x) + f(-x) + a(f(x) - f(-x))/x.
fn fold<F: FftField>(f_x: F, f_minus_x: F, eval_point: F, x_inv: F) -> F {
f_x + f_minus_x + eval_point * (f_x - f_minus_x) * x_inv
}
}
4 changes: 2 additions & 2 deletions fri/src/lde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl<F: PrimeField> MultiplicativeLDE<F> {

// 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>> {
pub fn batch_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() {
Expand Down Expand Up @@ -143,7 +143,7 @@ mod tests {
let mut lde = MultiplicativeLDE::new(domain, false);

lde.add_eval(&src);
let evals = lde.eval(eval_domain_offset);
let evals = lde.batch_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()) {
Expand Down
3 changes: 3 additions & 0 deletions fri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
mod folder;
mod lde;
mod parameters;
mod stone_domain;
mod verifier;
87 changes: 87 additions & 0 deletions fri/src/parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::marker::PhantomData;

use ark_ff::FftField;
use ark_poly::EvaluationDomain;
use serde::Deserialize;

#[derive(Deserialize)]
#[allow(dead_code)]
pub struct FriParameters<F: FftField, E: EvaluationDomain<F>> {
#[serde(skip)]
pub ph: PhantomData<F>,

/// A list of fri_step_i (one per FRI layer). FRI reduction in the i-th layer will be 2^fri_step_i
/// and the total reduction factor will be $2^{\sum_i \fri_step_i}$. The size of fri_step_list is
/// the number of FRI layers.
///
/// For example, if fri_step_0 = 3, the second layer will be of size N/8 (where N is the size of the
/// first layer). It means that the two merkle trees for layers of sizes N/2 and N/4 will be
/// skipped. On the other hand, it means that each coset in the first layer is of size 8 instead
/// of 2. Also note that in the fri_step_0=1 case we send 2 additional field elements per query (one
/// for each of the two layers that we skipped). So, while we send more field elements in the
/// fri_step_0=3 case (8 rather than 4), we refrain from sending the authentication paths for the
/// two skipped layers.
///
/// For a simple FRI usage, take fri_step_list = {1, 1, ..., 1}.
pub fri_step_list: Vec<usize>,

/// In the original FRI protocol, one has to reduce the degree from N to 1 by using a total of
/// log2(N) fri steps (sum of fri_step_list = log2(N)). This has two disadvantages:
/// 1. The last layers are small but still require Merkle authentication paths which are
/// non-negligible.
/// 2. It requires N to be of the form 2^n.
///
/// In our implementation, we reduce the degree from N to R (last_layer_degree_bound) for a
/// relatively small R using log2(N/R) fri steps. To do it we send the R coefficients of the
/// last FRI layer instead of continuing with additional FRI layers.
///
/// To reduce proof-length, it is always better to pick last_layer_degree_bound > 1.
pub last_layer_degree_bound: usize,
pub n_queries: usize,
#[serde(skip)]
pub fft_domains: Vec<E>,

/// If greater than 0, used to apply proof of work right before randomizing the FRI queries. Since
/// the probability to draw bad queries is relatively high (~rho for each query), while the
/// probability to draw bad x^(0) values is ~1/|F|, the queries are more vulnerable to enumeration.
pub proof_of_work_bits: usize,
}

#[allow(dead_code)]
impl<F: FftField, E: EvaluationDomain<F>> FriParameters<F, E> {
pub fn new(
fri_step_list: Vec<usize>,
last_layer_degree_bound: usize,
n_queries: usize,
fft_domains: Vec<E>,
proof_of_work_bits: usize,
) -> Self {
FriParameters {
ph: PhantomData,
fri_step_list,
last_layer_degree_bound,
n_queries,
fft_domains,
proof_of_work_bits,
}
}

pub fn with_fft_domains(mut self, fft_domains: Vec<E>) -> Self {
self.fft_domains = fft_domains;
self
}
}

#[allow(dead_code)]
pub struct FriProverConfig {
pub max_non_chunked_layer_size: u64,
pub n_chunks_between_layers: usize,
pub log_n_max_in_memory_fri_layer_elements: usize,
}

#[allow(dead_code)]
impl FriProverConfig {
pub const DEFAULT_MAX_NON_CHUNKED_LAYER_SIZE: u64 = 32768;
pub const DEFAULT_NUMBER_OF_CHUNKS_BETWEEN_LAYERS: usize = 32;
pub const ALL_IN_MEMORY_LAYERS: usize = 63;
}
Loading

0 comments on commit 3df25b4

Please sign in to comment.