Skip to content

Commit

Permalink
SOT-127: Add fri
Browse files Browse the repository at this point in the history
  • Loading branch information
bjergsen243 authored and zk-steve committed Jun 26, 2024
1 parent f2435a2 commit 1a26594
Show file tree
Hide file tree
Showing 15 changed files with 1,037 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
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
15 changes: 15 additions & 0 deletions fri/Cargo.toml
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"
29 changes: 29 additions & 0 deletions fri/examples/example.rs
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!");
}
89 changes: 89 additions & 0 deletions fri/fri.md
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
1 change: 1 addition & 0 deletions fri/src/fiat_shamir/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod transcript;
247 changes: 247 additions & 0 deletions fri/src/fiat_shamir/transcript.rs
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);
}
}
Loading

0 comments on commit 1a26594

Please sign in to comment.