-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f2435a2
commit 1a26594
Showing
15 changed files
with
1,037 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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::<Sha256, Fq>(poly, blowup_factor, number_of_queries); | ||
println!("Verify...."); | ||
let result = verify::<Sha256, Fq>(proof); | ||
|
||
assert!(result.is_ok()); | ||
println!("Accepted!"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/)<br/> | ||
Eli Ben-Sasson, Iddo Bentov, Ynon Horesh, Michael Riabzev | ||
[Anatomy of a STARK, Part 3: FRI](https://aszepieniec.github.io/stark-anatomy/fri)<br/> | ||
Aszepieniec | ||
[How to code FRI from scratch](https://blog.lambdaclass.com/how-to-code-fri-from-scratch/) <br/> | ||
Lambda Class | ||
[STARKs, Part II: Thank Goodness It's FRI-day](https://vitalik.eth.limo/general/2017/11/22/starks_part_2.html)<br/> | ||
Vitalik Buterin | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod transcript; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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::<Sha256, Fq>::new(SECRET_X); | ||
/// let query = Fq::from(928459); | ||
/// transcript.digest(query); | ||
/// let c1 = transcript.generate_a_challenge(); | ||
/// ``` | ||
#[derive(Default, Clone)] | ||
pub struct Transcript<T: Digest + Default, F: PrimeField> { | ||
data: Option<Vec<u8>>, | ||
index: u64, | ||
generated: bool, | ||
|
||
#[allow(dead_code)] | ||
/// Phantom data for annotation purposes. | ||
_phantom_data: PhantomData<T>, | ||
_phantom_data2: PhantomData<F>, | ||
} | ||
|
||
impl<T: Digest + Default, F: PrimeField> Transcript<T, F> { | ||
/// 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<T: Digest + Default, F: PrimeField> Transcript<T, F> { | ||
/// 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<F> { | ||
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<usize> { | ||
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::<Sha256, Fq>::new(SECRET_X); | ||
let query = Fq::from(StdRng::from_entropy().gen::<u128>()); | ||
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::<Sha256, Fq>::new(SECRET_X); | ||
let query = Fq::from(928459); | ||
|
||
let mut transcript2 = Transcript::<Sha256, Fq>::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::<Sha256, Fq>::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::<Sha256, Fq>::new(SECRET_X); | ||
let mut transcript2 = Transcript::<Sha256, Fq>::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::<Sha256, Fq>::new(SECRET_X); | ||
let size = 5; | ||
transcript.digest(Fq::from(StdRng::from_entropy().gen::<u128>())); | ||
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::<Sha256, Fq>::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::<Sha256, Fq>::new(SECRET_X); | ||
let mut transcript2 = Transcript::<Sha256, Fq>::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); | ||
} | ||
} |
Oops, something went wrong.