diff --git a/Cargo.toml b/Cargo.toml index 925f990..9ca5024 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["plonk", "kzg"] +members = ["plonk", "kzg", "fri"] resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html \ No newline at end of file diff --git a/fri/Cargo.toml b/fri/Cargo.toml new file mode 100644 index 0000000..71150c2 --- /dev/null +++ b/fri/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "fri" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[example]] +name = "fri-example" +path = "examples/example.rs" + +[dependencies] +ark-poly = "0.4.2" +ark-ff = "0.4.2" +sha2 = "0.11.0-pre.3" +rand = "0.8.5" diff --git a/fri/examples/example.rs b/fri/examples/example.rs new file mode 100644 index 0000000..9f4f8c6 --- /dev/null +++ b/fri/examples/example.rs @@ -0,0 +1,29 @@ +use ark_poly::univariate::DensePolynomial; +use ark_poly::DenseUVPolynomial; +use sha2::Sha256; + +use fri::fields::goldilocks::Fq; +use fri::prover::generate_proof; +use fri::verifier::verify; + +fn main() { + let coeff = vec![ + Fq::from(1), + Fq::from(2), + Fq::from(3), + Fq::from(4), + Fq::from(5), + Fq::from(6), + ]; + let poly = DensePolynomial::from_coefficients_vec(coeff); + + let blowup_factor: usize = 2; + let number_of_queries: usize = 2; + println!("Generate proof..."); + let proof = generate_proof::(poly, blowup_factor, number_of_queries); + println!("Verify...."); + let result = verify::(proof); + + assert!(result.is_ok()); + println!("Accepted!"); +} diff --git a/fri/fri.md b/fri/fri.md new file mode 100644 index 0000000..dc401e1 --- /dev/null +++ b/fri/fri.md @@ -0,0 +1,89 @@ +# FRI implementation + +## Overview +This is an implementation of FRI. You all can use this one as a library. + +## Details +This is an implementation of [FRI protocol](https://eccc.weizmann.ac.il/report/2017/134/). +We implement all the relevant steps for both proof generation and verification. +For detailed code, please refer to the `prover.rs`and `verifier.rs` files located in the `src` +directory. + +### What we use ? +We utilize the [Goldilocks field](https://xn--2-umb.com/22/goldilocks/) +and [SHA256](https://en.wikipedia.org/wiki/SHA-2) for the hash function. + +All the dependencies utilized in this project are sourced from the `ark` (or `arkworks`) +crates. For more information, please visit [arkworks.rs](https://arkworks.rs/). + + +### Set up + +Before proceeding, it's advisable to review the documentation on how to utilize this library: + +- Clone this repository: + ``` + git clone https://github.com/sota-zk-lab/zkp-implementation.git + ``` +- Enter to the `fri` folder and run: + ``` + cargo run --example fri-example + ``` +The above code will run the `main` function in `example.rs` files located in the `examples` +directory, which is the example usage of this library. + +### How it works + +We recommend reading our documentation of FRI +[here](https://github.com/sota-zk-labs/zkp-documents/blob/main/docs/fri.md) and slide +[here](https://github.com/sota-zk-labs/zkp-documents/blob/main/presentations/fri_implementation.pptx). + +To run our library, it's essential understand how to choose **blowup factor** and **number of queries**. +The blowup factor is the inverse of the +[Reed-Solomon code rate](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction#Constructions_(encoding)) +. The code rate $\rho$ is often chosen as $1/2$ (actually, $\rho \le 1/2$). Increasing the blowup factor +results in a slower prover performance but reduces verifier costs. + +To achieve $\lambda$ [bits of security](https://en.wikipedia.org/wiki/Security_level), +certain conditions must be met: +- The hash function used for building Merkle trees needs to have at least $2\lambda$ output bits. +- The field needs to have at least $2^{\lambda}$ elements +- The number of queries should be $\lceil \lambda / log_2\rho^{-1}$. + +In our implementation, we employ the SHA256 hash function, producing 256 bits of output. However, our +field is Goldilocks, which has a modulus of $p = 2^{64} - 2^{32} + 1$, leading to a security +parameter $\lambda$ of 64. + +### Run + +This library comes with some unit and integration tests. Run these tests with this command: +``` +cargo test +``` + +You can view each round in generating proof step and verifying step does by: +``` +cargo test -- --nocapture +``` + +## References +[Fast Reed-Solomon Interactive Oracle Proofs of Proximity](https://eccc.weizmann.ac.il/report/2017/134/)
+Eli Ben-Sasson, Iddo Bentov, Ynon Horesh, Michael Riabzev + +[Anatomy of a STARK, Part 3: FRI](https://aszepieniec.github.io/stark-anatomy/fri)
+Aszepieniec + +[How to code FRI from scratch](https://blog.lambdaclass.com/how-to-code-fri-from-scratch/)
+Lambda Class + +[STARKs, Part II: Thank Goodness It's FRI-day](https://vitalik.eth.limo/general/2017/11/22/starks_part_2.html)
+Vitalik Buterin + + + + + + + + + diff --git a/fri/src/fiat_shamir/mod.rs b/fri/src/fiat_shamir/mod.rs new file mode 100644 index 0000000..ece16b1 --- /dev/null +++ b/fri/src/fiat_shamir/mod.rs @@ -0,0 +1 @@ +pub mod transcript; diff --git a/fri/src/fiat_shamir/transcript.rs b/fri/src/fiat_shamir/transcript.rs new file mode 100644 index 0000000..caa00c6 --- /dev/null +++ b/fri/src/fiat_shamir/transcript.rs @@ -0,0 +1,247 @@ +use ark_ff::PrimeField; +use rand::prelude::StdRng; +use rand::SeedableRng; +use sha2::Digest; +use std::marker::PhantomData; + +/// A transcript for generating cryptographic challenges using the Fiat-Shamir transform with a cryptographic hash function and a prime field. +/// +/// The `Transcript` struct maintains an internal state and accumulates data to produce cryptographic challenges using the Fiat-Shamir transform. +/// It utilizes a cryptographic hash function `T` and a prime field `F` for generating challenges. +/// +/// # Type Parameters +/// +/// - `T`: A type implementing the `Digest` trait from the `sha2` crate, used as the cryptographic hash function. +/// - `F`: A prime field type implementing the `PrimeField` trait from the `ark_ff` crate, used for generating challenges. +/// +/// # Fiat-Shamir Transform +/// +/// The Fiat-Shamir transform is a method for transforming a protocol that involves interactive proofs into one that is non-interactive, +/// using the output of a random oracle (hash function) to simulate the interaction. +/// +/// # Examples +/// +/// ``` +/// use ark_ff::Field; +/// use rand::prelude::StdRng; +/// use rand::{Rng, SeedableRng}; +/// use sha2::Sha256; +/// use fri::fiat_shamir::transcript::Transcript; +/// use fri::fields::goldilocks::Fq; +/// +/// static SECRET_X: Fq = Fq::ZERO; +/// +/// let mut transcript = Transcript::::new(SECRET_X); +/// let query = Fq::from(928459); +/// transcript.digest(query); +/// let c1 = transcript.generate_a_challenge(); +/// ``` +#[derive(Default, Clone)] +pub struct Transcript { + data: Option>, + index: u64, + generated: bool, + + #[allow(dead_code)] + /// Phantom data for annotation purposes. + _phantom_data: PhantomData, + _phantom_data2: PhantomData, +} + +impl Transcript { + /// Constructs a new `Transcript` initialized with the given message value. + /// + /// # Parameters + /// + /// - `message`: A message value of type `F` used to initialize the transcript. + /// + /// # Returns + /// + /// A new `Transcript` initialized with the given message value. + pub fn new(message: F) -> Self { + let mut transcript = Self { + data: None, + index: 0, + generated: true, + _phantom_data: Default::default(), + _phantom_data2: Default::default(), + }; + transcript.digest(message); + transcript + } +} + +impl Transcript { + /// Updates the transcript by digesting the provided message. + /// + /// # Parameters + /// + /// - `message`: A message of type `F` to be digested into the transcript. + pub fn digest(&mut self, message: F) { + let mut hasher = T::default(); + hasher.update(self.data.take().unwrap_or_default()); + hasher.update(self.index.to_le_bytes()); + hasher.update(message.to_string()); + self.data = Some(hasher.finalize().to_vec()); + self.index += 1; + self.generated = false; + } + + fn generate_rng_with_seed(&mut self) -> StdRng { + if self.generated { + panic!("I'm hungry! Feed me something first"); + } + self.generated = true; + let mut seed: [u8; 8] = Default::default(); + seed.copy_from_slice(&self.data.clone().unwrap_or_default()[0..8]); + let seed = u64::from_le_bytes(seed); + StdRng::seed_from_u64(seed) + } + + /// Generates a cryptographic challenge using the internal state of the transcript. + /// + /// # Returns + /// + /// A cryptographic challenge of type `F`. + pub fn generate_a_challenge(&mut self) -> F { + let mut rng = self.generate_rng_with_seed(); + F::rand(&mut rng) + } + + /// Generates multiple cryptographic challenges using the internal state of the transcript. + /// + /// # Parameters + /// + /// - `number`: The number of challenges to generate. + /// + /// # Returns + /// + /// A vector containing the generated cryptographic challenges. + pub fn generate_challenges(&mut self, number: usize) -> Vec { + let mut rng = self.generate_rng_with_seed(); + (0..number).map(|_| F::rand(&mut rng)).collect() + } + + /// Generates multiple cryptographic challenges as `usize` values using the internal state of the transcript. + /// + /// # Parameters + /// + /// - `number`: The number of challenges to generate. + /// + /// # Returns + /// + /// A vector containing the generated cryptographic challenges as `usize` values. + pub fn generate_challenge_list_usize(&mut self, number: usize) -> Vec { + self.generate_challenges(number) + .into_iter() + .map(|field| field.into_bigint().as_ref()[0] as usize) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::Transcript; + use crate::fields::goldilocks::Fq; + use ark_ff::Field; + use rand::prelude::StdRng; + use rand::{Rng, SeedableRng}; + use sha2::Sha256; + + static SECRET_X: Fq = Fq::ZERO; + + #[test] + fn test_generate_a_challenge_should_return_different() { + // Ensure that each generated challenge is different when using different queries. + let mut transcript = Transcript::::new(SECRET_X); + let query = Fq::from(StdRng::from_entropy().gen::()); + transcript.digest(query); + let c1 = transcript.generate_a_challenge(); + transcript.digest(query); + let c2 = transcript.generate_a_challenge(); + assert_ne!(c1, c2); + } + + #[test] + fn test_generate_a_challenge_deterministic() { + // Ensure that the same query generates the same challenge. + let mut transcript = Transcript::::new(SECRET_X); + let query = Fq::from(928459); + + let mut transcript2 = Transcript::::new(SECRET_X); + transcript.digest(query); + transcript2.digest(query); + let c1 = transcript.generate_a_challenge(); + let c2 = transcript2.generate_a_challenge(); + assert_eq!(c1, c2); + } + + #[test] + fn test_generate_challenge_list_diff_elements() { + // Ensure that a list of generated challenges contains different elements. + let mut transcript = Transcript::::new(SECRET_X); + let size = 5; + transcript.digest(Fq::from(31313213)); + let g = transcript.generate_challenges(size); + assert_eq!(g.len(), size); + for i in 0..g.len() { + for j in 0..i { + assert_ne!(g[i].clone(), g[j].clone()); + } + } + } + + #[test] + fn test_generate_challenge_list_deterministic() { + // Ensure that generating challenges multiple times with the same inputs produces the same results. + let mut transcript = Transcript::::new(SECRET_X); + let mut transcript2 = Transcript::::new(SECRET_X); + let size = 5; + transcript.digest(Fq::from(31313213)); + transcript2.digest(Fq::from(31313213)); + let g = transcript.generate_challenges(size); + let g2 = transcript2.generate_challenges(size); + assert_eq!(g.len(), size); + assert_eq!(g, g2); + } + + #[test] + #[should_panic] + fn safe_guard() { + // Ensure that panic is triggered when generating challenges without digesting any more queries. + let mut transcript = Transcript::::new(SECRET_X); + let size = 5; + transcript.digest(Fq::from(StdRng::from_entropy().gen::())); + let _g = transcript.generate_challenges(size); + let _g2 = transcript.generate_challenges(size); + } + + #[test] + fn test_generate_index_list_diff_elements() { + // Ensure that a list of generated challenges as `usize` values contains different elements. + let mut transcript = Transcript::::new(SECRET_X); + let size = 5; + transcript.digest(Fq::from(31313213)); + let g = transcript.generate_challenge_list_usize(size); + assert_eq!(g.len(), size); + for i in 0..g.len() { + for j in 0..i { + assert_ne!(g[i].clone(), g[j].clone()); + } + } + } + + #[test] + fn test_generate_index_list_deterministic() { + // Ensure that generating challenges as `usize` values multiple times with the same inputs produces the same results. + let mut transcript = Transcript::::new(SECRET_X); + let mut transcript2 = Transcript::::new(SECRET_X); + let size = 5; + transcript.digest(Fq::from(31313213)); + transcript2.digest(Fq::from(31313213)); + let g = transcript.generate_challenge_list_usize(size); + let g2 = transcript2.generate_challenge_list_usize(size); + assert_eq!(g.len(), size); + assert_eq!(g, g2); + } +} diff --git a/fri/src/fields/goldilocks.rs b/fri/src/fields/goldilocks.rs new file mode 100644 index 0000000..35586dd --- /dev/null +++ b/fri/src/fields/goldilocks.rs @@ -0,0 +1,8 @@ +use ark_ff::fields::{Fp64, MontBackend, MontConfig}; + +// this Goldilocks implementation is inspired by Electron-Labs: https://github.com/Electron-Labs/fri-commitment +#[derive(MontConfig)] +#[modulus = "18446744069414584321"] +#[generator = "7"] +pub struct FqConfig; +pub type Fq = Fp64>; diff --git a/fri/src/fields/mod.rs b/fri/src/fields/mod.rs new file mode 100644 index 0000000..4c1e9c2 --- /dev/null +++ b/fri/src/fields/mod.rs @@ -0,0 +1 @@ +pub mod goldilocks; diff --git a/fri/src/fri_layer.rs b/fri/src/fri_layer.rs new file mode 100644 index 0000000..ba8441e --- /dev/null +++ b/fri/src/fri_layer.rs @@ -0,0 +1,57 @@ +use ark_ff::PrimeField; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{EvaluationDomain, GeneralEvaluationDomain, Polynomial}; + +use crate::merkle_tree::MerkleTree; + +/// Represents the state of FRI variables in each interaction. +#[derive(Clone)] +pub struct FriLayer { + /// Values of the committed polynomial evaluated at a subset Omega of the field F. + pub evaluations: Vec, + /// Merkle tree constructed from the evaluated values of the committed polynomial at a subset Omega of F. + pub merkle_tree: MerkleTree, + /// Coset value used for polynomial evaluation. + pub coset: F, + /// Size of the domain subset Omega. + pub domain_size: usize, +} + +impl FriLayer { + /// Constructs a new FRI layer from a given dense polynomial, coset value, and domain size. + /// + /// # Arguments + /// + /// * `poly` - A reference to a dense polynomial to be evaluated. + /// * `coset` - The coset value to be used in the evaluation of the polynomial. + /// * `domain_size` - The size of the domain subset Omega. + /// + /// # Returns + /// + /// * `FriLayer` - A new instance of `FriLayer` containing the evaluations and Merkle tree. + /// + /// # Panics + /// + /// This function will panic if the domain cannot be created with the given domain size. + pub fn from_poly(poly: &DensePolynomial, coset: F, domain_size: usize) -> Self { + // Create a domain for polynomial evaluation. + let domain = >::new(domain_size).unwrap(); + // Evaluate the polynomial at each point in the domain. + let evaluations = domain + .elements() + .map(|root| { + let cur1 = root * coset; // Multiply root by coset. + poly.evaluate(&cur1) // Evaluate polynomial at the modified root. + }) + .collect::>(); + // Create a Merkle tree from the evaluations. + let merkle_tree = MerkleTree::new(evaluations.clone()); + + Self { + evaluations, + merkle_tree, + coset, + domain_size, + } + } +} diff --git a/fri/src/hasher.rs b/fri/src/hasher.rs new file mode 100644 index 0000000..858c79e --- /dev/null +++ b/fri/src/hasher.rs @@ -0,0 +1,36 @@ +use ark_ff::PrimeField; +use sha2::{Digest, Sha256}; + +/// Computes a cryptographic hash of a single field element using SHA-256. +/// +/// # Arguments +/// +/// * `data` - A reference to a field element of type `F`. +/// +/// # Returns +/// +/// * `F` - A field element representing the hash value. +/// +pub fn hash(data: &F) -> F { + let mut hasher = Sha256::new(); + hasher.update(data.to_string()); + let h = hasher.finalize(); + F::from_le_bytes_mod_order(&h) +} + +/// Computes a cryptographic hash of a slice of field elements using SHA-256. +/// +/// # Arguments +/// +/// * `data` - A slice of field elements of type `F`. +/// +/// # Returns +/// +/// * `F` - A field element representing the hash value. +/// +pub fn hash_slice(data: &[F]) -> F { + let mut hasher = Sha256::new(); + data.iter().for_each(|d| hasher.update(d.to_string())); + let h = hasher.finalize(); + F::from_le_bytes_mod_order(&h) +} diff --git a/fri/src/lib.rs b/fri/src/lib.rs new file mode 100644 index 0000000..1b9684c --- /dev/null +++ b/fri/src/lib.rs @@ -0,0 +1,7 @@ +pub mod fiat_shamir; +pub mod fields; +mod fri_layer; +mod hasher; +mod merkle_tree; +pub mod prover; +pub mod verifier; diff --git a/fri/src/merkle_tree.rs b/fri/src/merkle_tree.rs new file mode 100644 index 0000000..6a46f66 --- /dev/null +++ b/fri/src/merkle_tree.rs @@ -0,0 +1,152 @@ +use ark_ff::PrimeField; + +use crate::hasher::{hash, hash_slice}; + +/// A proof for Merkle tree membership, which includes the leaf index, leaf value, hash proofs, and root. +#[derive(Debug, Clone)] +pub struct MerkleProof { + /// Index of the leaf the prover wants to reveal. + pub index: usize, + /// Value of the leaf the prover wants to reveal. + pub leaf_val: F, + /// Hash values of the neighboring nodes. + hash_proof: Vec, + /// Root value of the committed Merkle tree. + root: F, +} + +/// A Merkle tree structure that supports the creation of proofs and verification of membership. +#[derive(Debug, Clone)] +pub struct MerkleTree { + /// The internal nodes of the Merkle tree stored in levels. + internal_nodes: Vec>, + /// Values of the leaf nodes. + pub leaves: Vec, + /// Depth of the Merkle tree. + depth: usize, +} + +impl MerkleTree { + /// Constructs a new Merkle tree from the given evaluations (leaf values). + /// + /// # Arguments + /// + /// * `evaluations` - A vector of field elements representing the leaf values. + /// + /// # Returns + /// + /// * `MerkleTree` - A new Merkle tree instance. + /// + /// The method hashes the leaf values to create the first level of internal nodes, + /// and iteratively hashes pairs of nodes to construct the upper levels of the tree. + pub fn new(mut evaluations: Vec) -> Self { + let new_len = evaluations.len().next_power_of_two(); + let depth = new_len.ilog2() as usize; + + let first_level = evaluations.iter().map(hash).collect::>(); + + let mut internal_nodes = vec![first_level]; + + for i in 0..depth { + let next_level = internal_nodes[i].chunks(2).map(hash_slice).collect(); + internal_nodes.push(next_level); + } + + evaluations.resize(new_len, F::ZERO); // Fill the rest of the tree with 0 + + Self { + internal_nodes, + leaves: evaluations, + depth, + } + } + + /// Retrieves the root of the Merkle tree. + /// + /// # Returns + /// + /// * `F` - The root hash of the Merkle tree. + pub fn root(&self) -> F { + self.internal_nodes.last().unwrap()[0] + } + + /// Generates a Merkle proof for a leaf at the given index. + /// + /// # Arguments + /// + /// * `index` - The index of the leaf for which to generate the proof. + /// + /// # Returns + /// + /// * `MerkleProof` - A proof containing the leaf index, leaf value, hash proofs, and root. + pub fn generate_proof(&self, index: usize) -> MerkleProof { + let leaf_val = self.leaves[index]; + let mut hash_proof = Vec::with_capacity(self.depth); + let mut cur_index = index; + for i in 0..self.depth { + let neighbour = if cur_index % 2 == 0 { + // The current node is a left node, we need the right node. + self.internal_nodes[i][cur_index + 1] + } else { + self.internal_nodes[i][cur_index - 1] + }; + hash_proof.push(neighbour); + cur_index /= 2; + } + + MerkleProof { + index, + leaf_val, + hash_proof, + root: self.root(), + } + } +} + +/// Verifies a Merkle proof against the root of the Merkle tree. +/// +/// # Arguments +/// +/// * `proof` - A reference to a MerkleProof instance. +/// +/// # Returns +/// +/// * `bool` - `true` if the proof is valid, `false` otherwise. +/// +/// The function reconstructs the hash path from the leaf node to the root and checks if it matches the given root. +pub fn verify_merkle_proof(proof: &MerkleProof) -> bool { + let mut cur_index = proof.index; + let mut cur_hash = hash(&proof.leaf_val); + for i in 0..proof.hash_proof.len() { + if cur_index % 2 == 0 { + // The current node is a left node + let neighbour = proof.hash_proof[i]; + cur_hash = hash_slice(&[cur_hash, neighbour]); + } else { + let neighbour = proof.hash_proof[i]; + cur_hash = hash_slice(&[neighbour, cur_hash]); + } + cur_index /= 2; + } + cur_hash == proof.root +} + +#[cfg(test)] +mod tests { + use crate::fields::goldilocks::Fq; + + use super::*; + + #[test] + fn test_merkle() { + let leaves = vec![1, 2, 3, 4]; + let new_leaves: Vec = leaves.into_iter().map(Fq::from).collect(); + let tree = MerkleTree::new(new_leaves); + + let merkle_proof = tree.generate_proof(1); + // merkle_proof.index = 2; + let verify = verify_merkle_proof(&merkle_proof); + + assert_eq!(verify, true); + } +} diff --git a/fri/src/prover.rs b/fri/src/prover.rs new file mode 100644 index 0000000..29a601f --- /dev/null +++ b/fri/src/prover.rs @@ -0,0 +1,222 @@ +use crate::fiat_shamir::transcript::Transcript; +use ark_ff::PrimeField; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{DenseUVPolynomial, Polynomial}; +use sha2::Digest; +use std::ops::Mul; + +use crate::fri_layer::FriLayer; +use crate::merkle_tree::MerkleProof; + +#[derive(Clone, Debug)] +pub struct Decommitment { + pub evaluations: Vec, + // A list of evaluations for each query index in all layers, + pub auth_paths: Vec>, + // and their authentication paths in Merkle Tree + pub sym_evaluations: Vec, + // A list of evaluation for each symmetric query index in all layers, + pub sym_auth_paths: Vec>, // and their authentication paths in Merkle Tree +} + +#[derive(Clone, Debug)] +pub struct Proof { + pub domain_size: usize, + pub coset: F, + pub number_of_queries: usize, + pub layers_root: Vec, + pub const_val: F, + pub decommitment_list: Vec>, + // pub challenge_list: Vec +} + +/// Reduce the power in half with formula new_coeff = even_coeff + `random_r` * odd_coeff +fn fold_polynomial(poly: &DensePolynomial, random_r: F) -> DensePolynomial { + let coeff = poly.coeffs.clone(); + let even_coeff = coeff.iter().step_by(2).cloned().collect(); + let odd_coeff_mul_r: Vec = coeff.into_iter().skip(1).step_by(2).collect(); + + let even_poly = DensePolynomial::from_coefficients_vec(even_coeff); + let odd_poly = DensePolynomial::from_coefficients_vec(odd_coeff_mul_r); + even_poly + odd_poly.mul(random_r) +} + +/// Verify that `poly` have degree `k` or `number_layers` +/// +/// Also create prove for the evaluation of the polynomial +fn folding_phase( + mut poly: DensePolynomial, + mut coset: F, + mut domain_size: usize, + number_layers: usize, +) -> (F, Transcript, Vec>) { + let mut fri_layers: Vec> = Vec::with_capacity(number_layers.ilog2() as usize + 1); + let mut transcript = Transcript::new(F::ZERO); + + for _ in 0..number_layers { + let current_layer = FriLayer::from_poly(&poly, coset, domain_size); + transcript.digest(current_layer.merkle_tree.root()); + eprintln!( + "current_layer.merkle_tree.root() = {:#?}", + current_layer.merkle_tree.root() + ); + fri_layers.push(current_layer); + + poly = fold_polynomial(&poly, transcript.generate_a_challenge()); + coset = coset.square(); + domain_size /= 2; + } + + assert_eq!(poly.len(), 1); + let constant = poly.evaluate(&F::ZERO); + transcript.digest(constant); + + (constant, transcript, fri_layers) +} + +/// Create proof that prover did the folding phase correctly +/// +/// Evaluate current polynomial q(x) at two random symmetric point x1, x2 in subset Omega and +/// verify if the next step is the result of a 1st degree polynomial of random_r with q(x1) and q(x2) +fn query_phase( + number_of_queries: usize, + domain_size: usize, + transcript: &mut Transcript, + fri_layers: &Vec>, +) -> (Vec>, Vec) { + if fri_layers.is_empty() { + return (vec![], vec![]); + } + + let challenge_list = transcript + .generate_challenge_list_usize(number_of_queries) + .iter() + .map(|v| *v % domain_size) + .collect::>(); + + let mut decommitment_list = Vec::new(); + + for challenge in challenge_list.clone() { + // generate decommitment for each challenge. + let mut evaluations = vec![]; + let mut sym_evaluations = vec![]; + let mut auth_paths = vec![]; + let mut sym_auth_paths = vec![]; + + for layer in fri_layers { + // index and sym_index will be symmetric of each other in layer.domain_size finite field + let index = challenge % layer.domain_size; + let sym_index = (index + layer.domain_size / 2) % layer.domain_size; + + let evaluation = layer.evaluations[index]; + let sym_evaluation = layer.evaluations[sym_index]; + + let auth_path = layer.merkle_tree.generate_proof(index); + let sym_auth_path = layer.merkle_tree.generate_proof(sym_index); + + evaluations.push(evaluation); + sym_evaluations.push(sym_evaluation); + + auth_paths.push(auth_path); + sym_auth_paths.push(sym_auth_path); + } + + let cur_decommitment = Decommitment { + evaluations, + auth_paths, + sym_evaluations, + sym_auth_paths, + }; + decommitment_list.push(cur_decommitment); + } + + (decommitment_list, challenge_list) +} + +/// Generate a proof of FRI prover by going through 2 phase +/// +/// Folding phase and query phase +/// +/// This is the only method you should call for proving +pub fn generate_proof( + poly: DensePolynomial, + blowup_factor: usize, + number_of_queries: usize, +) -> Proof { + let domain_size = (poly.coeffs.len() * blowup_factor).next_power_of_two(); + let coset = F::GENERATOR; + let number_of_layers = domain_size.ilog2() as usize; + + let (const_val, mut transcript, fri_layers) = + folding_phase::(poly, coset, domain_size, number_of_layers); + let (decommitment_list, _) = + query_phase(number_of_queries, domain_size, &mut transcript, &fri_layers); + + let layers_root: Vec = fri_layers + .into_iter() + .map(|layer| layer.merkle_tree.root()) + .collect(); + + Proof { + domain_size, + coset, + number_of_queries, + layers_root, + const_val, + decommitment_list, + } +} + +#[cfg(test)] +mod tests { + use ark_ff::FftField; + use ark_poly::univariate::DensePolynomial; + use ark_poly::DenseUVPolynomial; + use sha2::Sha256; + + use crate::fields::goldilocks::Fq; + use crate::prover::{fold_polynomial, folding_phase, query_phase}; + + #[test] + fn test_fold_polynomial() { + let coeff = vec![Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)]; + let poly = DensePolynomial::from_coefficients_vec(coeff); + let random_r = Fq::from(1); + let folded_poly = fold_polynomial::(&poly, random_r); + + let res_coeff = vec![Fq::from(3), Fq::from(7)]; + assert_eq!( + folded_poly, + DensePolynomial::from_coefficients_vec(res_coeff) + ); + } + + #[test] + fn test_commit_phase() { + let coeff = vec![Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)]; + let poly = DensePolynomial::from_coefficients_vec(coeff); + let number_of_layers: usize = 2; + let coset = Fq::GENERATOR; + let (_const_val, _transcript, fri_layers) = + folding_phase::(poly, coset, 4, number_of_layers); + + assert_eq!(fri_layers[1].coset, Fq::from(49)); + assert_eq!(fri_layers[1].domain_size, 2); + } + + #[test] + fn test_query_phase() { + let domain_size = 4; + let coeff = vec![Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)]; + let poly = DensePolynomial::from_coefficients_vec(coeff); + let number_of_layers: usize = 2; + let coset = Fq::GENERATOR; + let (_const_val, mut transcript, fri_layers) = + folding_phase::(poly, coset, domain_size, number_of_layers); + let (decommitment_list, _) = query_phase(1, domain_size, &mut transcript, &fri_layers); + let decommitment = decommitment_list[0].clone(); + let auth_paths_layer2 = decommitment.auth_paths[0].index; + let sym_auth_paths_layer2 = decommitment.sym_auth_paths[0].index; + assert_eq!((auth_paths_layer2 + 2) % domain_size, sym_auth_paths_layer2); + } +} diff --git a/fri/src/verifier.rs b/fri/src/verifier.rs new file mode 100644 index 0000000..ce3524a --- /dev/null +++ b/fri/src/verifier.rs @@ -0,0 +1,171 @@ +use crate::fiat_shamir::transcript::Transcript; +use ark_ff::PrimeField; +use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; +use sha2::Digest; + +use crate::merkle_tree::verify_merkle_proof; +use crate::prover::{Decommitment, Proof}; + +/// Verify proof of FRI prover of 2 phase +/// +/// Folding phase and query phase +/// +/// This is the only method you should call for verifying +pub fn verify(proof: Proof) -> Result<(), String> { + // regenerate random_r list + let merkle_roots = proof.layers_root; + let mut transcript = Transcript::::new(F::ZERO); + let random_r_list = merkle_roots + .into_iter() + .map(|root| { + transcript.digest(root); + transcript.generate_a_challenge() + }) + .collect::>(); + transcript.digest(proof.const_val); + + // regenerate challenge list + let new_challenge_list = transcript + .generate_challenge_list_usize(proof.number_of_queries) + .into_iter() + .map(|v| v % proof.domain_size) + .collect::>(); + + // verify each query + for (challenge, decommitment) in new_challenge_list + .into_iter() + .zip(proof.decommitment_list.into_iter()) + { + verify_query( + &challenge, + &decommitment, + &random_r_list, + proof.domain_size, + proof.const_val, + proof.coset, + )? + } + Ok(()) +} + +fn verify_query( + challenge: &usize, + decommitment: &Decommitment, + random_r_list: &[F], + domain_size: usize, + const_val: F, + coset: F, +) -> Result<(), String> { + let mut cur_domain_size = domain_size; + let mut cur_coset = coset; + let two = F::from(2u128); + + for (((((i, eval), path), sym_eval), sym_path), random_r) in decommitment + .evaluations + .iter() + .enumerate() + .zip(decommitment.auth_paths.iter()) + .zip(decommitment.sym_evaluations.iter()) + .zip(decommitment.sym_auth_paths.iter()) + .zip(random_r_list.iter()) + { + let index = challenge % cur_domain_size; + let sym_index = (index + cur_domain_size / 2) % cur_domain_size; + let cur_domain = >::new(cur_domain_size).unwrap(); + + // verify path of merkle root + if index != path.index || sym_index != sym_path.index { + return Err(String::from("wrong index!")); + } + + if *eval != path.leaf_val || *sym_eval != sym_path.leaf_val { + return Err(String::from( + "the evaluation does not correspond to given path!", + )); + } + + if !verify_merkle_proof(path) || !verify_merkle_proof(sym_path) { + return Err(String::from("verify Merkle path failed!")); + } + + // verify folding + // Another way to compute q_fold let q_fold = (*eval + sym_eval) / two + (*random_r * (*eval - sym_eval)/(two * w_i)); + let w_i = cur_domain.element(index) * cur_coset; + + let q_fold = + (*random_r + w_i) * eval / (two * w_i) - (*random_r - w_i) * sym_eval / (two * w_i); + + if i != decommitment.evaluations.len() - 1 { + let next_layer_evaluation = decommitment.evaluations[i + 1]; + if q_fold != next_layer_evaluation { + return Err(String::from("folding wrong!")); + } + cur_domain_size /= 2; + cur_coset = cur_coset.square(); + continue; + } + + if q_fold != const_val { + // end of the folding process, the result must be equal to constant value + return Err(String::from("folding wrong!")); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use ark_poly::univariate::DensePolynomial; + use ark_poly::DenseUVPolynomial; + use sha2::Sha256; + + use crate::fields::goldilocks::Fq; + use crate::prover::generate_proof; + + use super::*; + + #[test] + fn test_verifier() { + let coeff = vec![Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)]; + let poly = DensePolynomial::from_coefficients_vec(coeff); + + let blowup_factor: usize = 2; + let number_of_queries: usize = 2; + let proof = generate_proof::(poly, blowup_factor, number_of_queries); + let result = verify::(proof); + assert!(result.is_ok()); + } + + #[test] + fn test_verifier2() { + let coeff = vec![ + Fq::from(1), + Fq::from(2), + Fq::from(3), + Fq::from(4), + Fq::from(5), + Fq::from(6), + ]; + let poly = DensePolynomial::from_coefficients_vec(coeff); + + let blowup_factor: usize = 2; + let number_of_queries: usize = 2; + let proof = generate_proof::(poly, blowup_factor, number_of_queries); + let result = verify::(proof); + assert!(result.is_ok()); + } + + #[test] + fn test_verifier3() { + let coeff = vec![Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)]; + let poly = DensePolynomial::from_coefficients_vec(coeff); + + let blowup_factor: usize = 2; + let number_of_queries: usize = 2; + let mut proof = generate_proof::(poly, blowup_factor, number_of_queries); + + proof.const_val -= Fq::from(1); + let result = verify::(proof); + assert!(result.is_err()); + } +} diff --git a/plonk/src/challenge.rs b/plonk/src/challenge.rs index cd698d0..bad89ae 100644 --- a/plonk/src/challenge.rs +++ b/plonk/src/challenge.rs @@ -53,7 +53,7 @@ impl ChallengeGenerator { kzg_commitment .inner() .serialize_uncompressed(HashMarshaller(&mut hasher)) - .expect("HashMarshaller::flush should be infallible!"); + .expect("HashMarshaller::serialize_uncompressed should be infallible!"); self.data = Some(hasher.finalize().to_vec()); self.generated = false; }