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

SOT-127: FRI implementation #9

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).
VanhGer marked this conversation as resolved.
Show resolved Hide resolved

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;
zk-steve marked this conversation as resolved.
Show resolved Hide resolved
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;
zk-steve marked this conversation as resolved.
Show resolved Hide resolved
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
Loading