From aa50387d36c5a2795debe578c6ac1ee43f705b4e Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 9 Apr 2021 18:24:19 +0200 Subject: [PATCH] Started implementing FRI --- src/fri.rs | 72 +++++++++++++++++++++++++++++++ src/gadgets/merkle_proofs.rs | 83 +++++++++++++++++++++++++++++++++++- src/proof.rs | 5 ++- 3 files changed, 156 insertions(+), 4 deletions(-) diff --git a/src/fri.rs b/src/fri.rs index 3c166779..2275a362 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -1,3 +1,11 @@ +use crate::field::field::Field; +use crate::hash::{compress, hash_n_to_hash}; +use crate::plonk_challenger::Challenger; +use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; +use crate::proof::{Hash, FriProof}; +use crate::field::fft::fft; +use crate::gadgets::merkle_proofs::MerkleTree; + /// Somewhat arbitrary. Smaller values will increase delta, but with diminishing returns, /// while increasing L, potentially requiring more challenge points. const EPSILON: f64 = 0.01; @@ -10,6 +18,12 @@ struct FriConfig { /// a 4-to-1 reduction, then a 2-to-1 reduction. After these reductions, the reduced polynomial /// is sent directly. reduction_arity_bits: Vec, + + /// Number of reductions in the FRI protocol. So if the original domain has size `2^n`, + /// then the final domain will have size `2^(n-reduction_count)`. + reduction_count: usize, + + rate_bits: usize, } fn fri_delta(rate_log: usize, conjecture: bool) -> f64 { @@ -34,3 +48,61 @@ fn fri_l(codeword_len: usize, rate_log: usize, conjecture: bool) -> f64 { 1.0 / (2.0 * EPSILON * rate.sqrt()) } } + +// TODO: Different arity + PoW. +/// Performs a FRI round. +fn fri_round( + polynomial_coeffs: &PolynomialCoeffs, + polynomial_values: &PolynomialValues, + challenger: &mut Challenger, + config: &FriConfig, +) -> FriProof { + let n = polynomial_values.values.len(); + assert_eq!( + polynomial_coeffs.coeffs.len(), + n + ); + let mut trees = vec![MerkleTree::new(polynomial_values.values.iter().map(|&v| vec![v]).collect())]; + let mut root = trees.last().unwrap().root; + let mut coeffs = polynomial_coeffs.clone(); + let mut values; + + challenger.observe_hash(&root); + + // Commit phase + for _ in 0..config.reduction_count { + let beta = challenger.get_challenge(); + coeffs = PolynomialCoeffs::new( + coeffs + .coeffs + .chunks_exact(2) + .map(|chunk| chunk[0] + beta * chunk[1]) + .collect::>(), + ); + values = fft(coeffs.clone().lde(config.rate_bits)); + + let tree = MerkleTree::new(values.values.iter().map(|&v| vec![v]).collect()); + challenger.observe_hash(&tree.root); + trees.push(tree); + } + + // Query phase + let mut merkle_proofs = Vec::new(); + let mut evals = Vec::new(); + for i in 0..config.reduction_count { + let x = challenger.get_challenge(); + let x_index = (x.to_canonical_u64() as usize) % n; + let n2 = n>>1; + evals.extend(std::array::IntoIter::new([polynomial_values.values[x_index], polynomial_values.values[n2 + x_index]])); + merkle_proofs.extend(std::array::IntoIter::new([trees[i].prove(x_index), trees[i].prove(n2 + x_index)])); + } + + FriProof { + commit_phase_merkle_roots: trees.iter().map(|t| t.root).collect(), + initial_merkle_proofs: vec![], + intermediate_merkle_proofs: merkle_proofs, + final_poly: coeffs + } + +} + diff --git a/src/gadgets/merkle_proofs.rs b/src/gadgets/merkle_proofs.rs index 246406b6..595081ef 100644 --- a/src/gadgets/merkle_proofs.rs +++ b/src/gadgets/merkle_proofs.rs @@ -1,8 +1,10 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; +use crate::hash::{compress, hash_n_to_hash}; use crate::proof::{Hash, HashTarget}; use crate::target::Target; +#[derive(Clone, Debug)] pub struct MerkleProof { /// The Merkle digest of each sibling subtree, staying from the bottommost layer. pub siblings: Vec>, @@ -13,6 +15,55 @@ pub struct MerkleProofTarget { pub siblings: Vec, } +#[derive(Clone, Debug)] +pub struct MerkleTree { + /// The data in the leaves of the Merkle tree. + pub leaves: Vec>, + + /// The layers of hashes in the tree. The first layer is the one at the bottom. + pub layers: Vec>>, + + /// The Merkle root. + pub root: Hash, +} + +impl MerkleTree { + pub fn new(leaves: Vec>) -> Self { + let mut layers = vec![leaves.iter().map(|l| hash_n_to_hash(l.clone(), false)).collect::>()]; + loop { + match layers.last() { + Some(l) if l.len() > 1 => { + layers.push(l.chunks(2).map(|chunk| compress(chunk[0], chunk[1])).collect::>()); + }, + _ => break + } + } + let root = layers.pop().unwrap()[0]; + Self { + leaves, + layers, + root + } + + } + + /// Create a Merkle proof from a leaf index. + pub fn prove(&self, leaf_index: usize) -> MerkleProof { + MerkleProof { + siblings: self.layers + .iter() + .scan(leaf_index, |acc, layer| { + let index = *acc ^ 1; + *acc >>= 1; + Some(layer[index]) + }) + .collect(), + } + } +} + + + /// Verifies that the given leaf data is present at the given index in the Merkle tree with the /// given root. pub(crate) fn verify_merkle_proof( @@ -20,8 +71,18 @@ pub(crate) fn verify_merkle_proof( leaf_index: usize, merkle_root: Hash, proof: MerkleProof, -) { - todo!() +) -> bool { + let mut index = leaf_index; + let mut h = hash_n_to_hash(leaf_data, false); + for s in &proof.siblings { + h = if index & 1 == 0 { + compress(h, *s) + } else { + compress(*s, h) + }; + index >>= 1; + } + h == merkle_root } impl CircuitBuilder { @@ -37,3 +98,21 @@ impl CircuitBuilder { todo!() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::field::crandall_field::CrandallField; + + #[test] + fn test_merkle_proofs() { + type F = CrandallField; + let num_leaves = 128; + let leaves = (0..num_leaves).map(|_| vec![F::rand()]).collect::>(); + let tree = MerkleTree::new(leaves); + for i in 0..num_leaves { + let proof = tree.prove(i); + assert!(verify_merkle_proof(tree.leaves[i].clone(),i, tree.root, proof)); + } + } +} diff --git a/src/proof.rs b/src/proof.rs index d25196cb..f30ef028 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,9 +1,10 @@ use crate::field::field::Field; use crate::target::Target; use crate::gadgets::merkle_proofs::{MerkleProofTarget, MerkleProof}; +use crate::polynomial::polynomial::PolynomialCoeffs; /// Represents a ~256 bit hash output. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Hash { pub(crate) elements: [F; 4], } @@ -60,7 +61,7 @@ pub struct FriProof { /// Merkle proofs for the reduced polynomials that were sent in the commit phase. pub intermediate_merkle_proofs: Vec>, /// The final polynomial in coefficient form. - pub final_poly: Vec, + pub final_poly: PolynomialCoeffs, } /// Represents a single FRI query, i.e. a path through the reduction tree.