From 13519e66abc48dea9e96c1f919c2fcaaeae7ab52 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 22 Apr 2021 22:33:29 +0200 Subject: [PATCH 1/8] Merkle subtree proofs - WIP --- src/hash.rs | 1 + src/merkle_proofs.rs | 34 +++++++++++++++- src/merkle_tree.rs | 93 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/src/hash.rs b/src/hash.rs index d87d3e28..b16b50c0 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -273,6 +273,7 @@ pub(crate) fn merkle_root_inner(vecs: Vec>) -> Hash { .map(|leaf_set| hash_or_noop(leaf_set)) .collect::>(); while hashes.len() > 1 { + dbg!(&hashes); hashes = hashes .chunks(2) .map(|pair| compress(pair[0], pair[1])) diff --git a/src/merkle_proofs.rs b/src/merkle_proofs.rs index edcdee42..632931b6 100644 --- a/src/merkle_proofs.rs +++ b/src/merkle_proofs.rs @@ -1,10 +1,11 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; use crate::gates::gmimc::GMiMCGate; -use crate::hash::GMIMC_ROUNDS; use crate::hash::{compress, hash_or_noop}; +use crate::hash::{merkle_root_inner, GMIMC_ROUNDS}; use crate::proof::{Hash, HashTarget}; use crate::target::Target; +use crate::util::reverse_index_bits_in_place; use crate::wire::Wire; use anyhow::{ensure, Result}; @@ -47,6 +48,37 @@ pub(crate) fn verify_merkle_proof( Ok(()) } +/// Verifies that the given subtree is present at the given index in the Merkle tree with the +/// given root. +pub(crate) fn verify_merkle_proof_subtree( + mut subtree_leaves_data: Vec>, + subtree_index: usize, + merkle_root: Hash, + proof: &MerkleProof, + reverse_bits: bool, +) -> Result<()> { + let index = if reverse_bits { + // reverse_index_bits_in_place(&mut subtree_leaves_data); + crate::util::reverse_bits(subtree_index, proof.siblings.len()) + } else { + subtree_index + }; + dbg!(&subtree_leaves_data); + let mut current_digest = merkle_root_inner(subtree_leaves_data); + dbg!(current_digest); + for (i, &sibling_digest) in proof.siblings.iter().enumerate() { + let bit = (index >> i & 1) == 1; + current_digest = if bit { + compress(sibling_digest, current_digest) + } else { + compress(current_digest, sibling_digest) + } + } + ensure!(current_digest == merkle_root, "Invalid Merkle proof."); + + Ok(()) +} + impl CircuitBuilder { /// Verifies that the given leaf data is present at the given index in the Merkle tree with the /// given root. diff --git a/src/merkle_tree.rs b/src/merkle_tree.rs index f9a6d8f3..26fbc1a3 100644 --- a/src/merkle_tree.rs +++ b/src/merkle_tree.rs @@ -76,24 +76,65 @@ impl MerkleTree { .collect(), } } + + /// Create a Merkle proof for an entire subtree. + /// Example: + /// ``` + /// G + /// / \ + /// / \ + /// / \ + /// E F + /// / \ / \ + /// A B C D + /// ``` + /// `self.prove_subtree(0, 1)` gives a Merkle proof for the subtree E->(A,B), i.e., the + /// path (F,). + pub fn prove_subtree(&self, subtree_index: usize, subtree_height: usize) -> MerkleProof { + let index = if self.reverse_bits { + reverse_bits( + subtree_index, + log2_strict(self.leaves.len()) - subtree_height, + ) + } else { + subtree_index + }; + MerkleProof { + siblings: self + .layers + .iter() + .skip(subtree_height) + .scan(index, |acc, layer| { + let index = *acc ^ 1; + *acc >>= 1; + Some(layer[index]) + }) + .collect(), + } + } } #[cfg(test)] mod tests { use super::*; use crate::field::crandall_field::CrandallField; - use crate::merkle_proofs::verify_merkle_proof; + use crate::merkle_proofs::{verify_merkle_proof, verify_merkle_proof_subtree}; use crate::polynomial::division::divide_by_z_h; use anyhow::Result; + fn random_data(n: usize, k: usize) -> Vec> { + (0..n) + .map(|_| (0..k).map(|_| F::rand()).collect()) + .collect() + } + #[test] fn test_merkle_trees() -> Result<()> { type F = CrandallField; - let n = 1 << 10; - let leaves: Vec> = (0..n) - .map(|_| (0..10).map(|_| F::rand()).collect()) - .collect(); + let log_n = 3; + let n = 1 << log_n; + let leaves = random_data::(n, 7); let tree = MerkleTree::new(leaves.clone(), false); for i in 0..n { @@ -101,12 +142,54 @@ mod tests { verify_merkle_proof(tree.leaves[i].clone(), i, tree.root, &proof, false)?; } + for height in 0..=log_n { + for i in 0..(n >> height) { + let subtree_proof = tree.prove_subtree(i, height); + verify_merkle_proof_subtree( + tree.leaves[i << height..(i + 1) << height].to_vec(), + i, + tree.root, + &subtree_proof, + false, + )?; + } + } + let tree_reversed_bits = MerkleTree::new(leaves.clone(), true); for i in 0..n { let proof = tree_reversed_bits.prove(i); verify_merkle_proof(leaves[i].clone(), i, tree_reversed_bits.root, &proof, true)?; } + let (height, i) = (1, 0); + dbg!(height, i); + let subtree_proof = tree_reversed_bits.prove_subtree(i, height); + dbg!(&tree_reversed_bits, &subtree_proof); + verify_merkle_proof_subtree( + (i << height..(i + 1) << height) + .map(|j| tree_reversed_bits.leaves[j].to_vec()) + .collect(), + i, + tree_reversed_bits.root, + &subtree_proof, + true, + )?; + for height in 1..=log_n { + for i in 0..(n >> height) { + dbg!(height, i); + let subtree_proof = tree_reversed_bits.prove_subtree(i, height); + verify_merkle_proof_subtree( + (i << height..(i + 1) << height) + .map(|j| tree_reversed_bits.leaves[j].to_vec()) + .collect(), + i, + tree_reversed_bits.root, + &subtree_proof, + true, + )?; + } + } + Ok(()) } } From 3dcdc8835cd85cce08c22bd438675bc27fc0f13b Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 23 Apr 2021 11:05:31 +0200 Subject: [PATCH 2/8] Working Merkle subtree proofs --- src/hash.rs | 1 - src/merkle_proofs.rs | 6 +----- src/merkle_tree.rs | 20 ++++++++++---------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/hash.rs b/src/hash.rs index b16b50c0..d87d3e28 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -273,7 +273,6 @@ pub(crate) fn merkle_root_inner(vecs: Vec>) -> Hash { .map(|leaf_set| hash_or_noop(leaf_set)) .collect::>(); while hashes.len() > 1 { - dbg!(&hashes); hashes = hashes .chunks(2) .map(|pair| compress(pair[0], pair[1])) diff --git a/src/merkle_proofs.rs b/src/merkle_proofs.rs index 632931b6..c2ea7424 100644 --- a/src/merkle_proofs.rs +++ b/src/merkle_proofs.rs @@ -5,7 +5,6 @@ use crate::hash::{compress, hash_or_noop}; use crate::hash::{merkle_root_inner, GMIMC_ROUNDS}; use crate::proof::{Hash, HashTarget}; use crate::target::Target; -use crate::util::reverse_index_bits_in_place; use crate::wire::Wire; use anyhow::{ensure, Result}; @@ -51,21 +50,18 @@ pub(crate) fn verify_merkle_proof( /// Verifies that the given subtree is present at the given index in the Merkle tree with the /// given root. pub(crate) fn verify_merkle_proof_subtree( - mut subtree_leaves_data: Vec>, + subtree_leaves_data: Vec>, subtree_index: usize, merkle_root: Hash, proof: &MerkleProof, reverse_bits: bool, ) -> Result<()> { let index = if reverse_bits { - // reverse_index_bits_in_place(&mut subtree_leaves_data); crate::util::reverse_bits(subtree_index, proof.siblings.len()) } else { subtree_index }; - dbg!(&subtree_leaves_data); let mut current_digest = merkle_root_inner(subtree_leaves_data); - dbg!(current_digest); for (i, &sibling_digest) in proof.siblings.iter().enumerate() { let bit = (index >> i & 1) == 1; current_digest = if bit { diff --git a/src/merkle_tree.rs b/src/merkle_tree.rs index 26fbc1a3..d212e50e 100644 --- a/src/merkle_tree.rs +++ b/src/merkle_tree.rs @@ -79,7 +79,7 @@ impl MerkleTree { /// Create a Merkle proof for an entire subtree. /// Example: - /// ``` + /// ```tree /// G /// / \ /// / \ @@ -132,7 +132,7 @@ mod tests { fn test_merkle_trees() -> Result<()> { type F = CrandallField; - let log_n = 3; + let log_n = 8; let n = 1 << log_n; let leaves = random_data::(n, 7); @@ -162,25 +162,25 @@ mod tests { } let (height, i) = (1, 0); - dbg!(height, i); let subtree_proof = tree_reversed_bits.prove_subtree(i, height); - dbg!(&tree_reversed_bits, &subtree_proof); + let reversed_index = reverse_bits(i, log_n - height); verify_merkle_proof_subtree( - (i << height..(i + 1) << height) - .map(|j| tree_reversed_bits.leaves[j].to_vec()) + (reversed_index << height..(reversed_index + 1) << height) + .map(|j| tree_reversed_bits.leaves[j].clone()) .collect(), i, tree_reversed_bits.root, &subtree_proof, true, )?; - for height in 1..=log_n { + + for height in 0..=log_n { for i in 0..(n >> height) { - dbg!(height, i); + let reversed_index = reverse_bits(i, log_n - height); let subtree_proof = tree_reversed_bits.prove_subtree(i, height); verify_merkle_proof_subtree( - (i << height..(i + 1) << height) - .map(|j| tree_reversed_bits.leaves[j].to_vec()) + (reversed_index << height..(reversed_index + 1) << height) + .map(|j| tree_reversed_bits.leaves[j].clone()) .collect(), i, tree_reversed_bits.root, From 8b429ff0f169b481903735ef80c5dd50bdace743 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 23 Apr 2021 15:25:18 +0200 Subject: [PATCH 3/8] Clean tests --- src/merkle_tree.rs | 95 +++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 52 deletions(-) diff --git a/src/merkle_tree.rs b/src/merkle_tree.rs index d212e50e..5b695e79 100644 --- a/src/merkle_tree.rs +++ b/src/merkle_tree.rs @@ -128,6 +128,45 @@ mod tests { .collect() } + fn verify_all_leaves( + leaves: Vec>, + n: usize, + reverse_bits: bool, + ) -> Result<()> { + let tree = MerkleTree::new(leaves.clone(), reverse_bits); + for i in 0..n { + let proof = tree.prove(i); + verify_merkle_proof(leaves[i].clone(), i, tree.root, &proof, reverse_bits)?; + } + Ok(()) + } + fn verify_all_subtrees( + leaves: Vec>, + n: usize, + log_n: usize, + reverse_bits: bool, + ) -> Result<()> { + let tree = MerkleTree::new(leaves.clone(), reverse_bits); + for height in 0..=log_n { + for i in 0..(n >> height) { + let index = if reverse_bits { + crate::util::reverse_bits(i, log_n - height) + } else { + i + }; + let subtree_proof = tree.prove_subtree(i, height); + verify_merkle_proof_subtree( + tree.leaves[index << height..(index + 1) << height].to_vec(), + i, + tree.root, + &subtree_proof, + reverse_bits, + )?; + } + } + Ok(()) + } + #[test] fn test_merkle_trees() -> Result<()> { type F = CrandallField; @@ -136,59 +175,11 @@ mod tests { let n = 1 << log_n; let leaves = random_data::(n, 7); - let tree = MerkleTree::new(leaves.clone(), false); - for i in 0..n { - let proof = tree.prove(i); - verify_merkle_proof(tree.leaves[i].clone(), i, tree.root, &proof, false)?; - } + verify_all_leaves(leaves.clone(), n, false)?; + verify_all_subtrees(leaves.clone(), n, log_n, false)?; - for height in 0..=log_n { - for i in 0..(n >> height) { - let subtree_proof = tree.prove_subtree(i, height); - verify_merkle_proof_subtree( - tree.leaves[i << height..(i + 1) << height].to_vec(), - i, - tree.root, - &subtree_proof, - false, - )?; - } - } - - let tree_reversed_bits = MerkleTree::new(leaves.clone(), true); - for i in 0..n { - let proof = tree_reversed_bits.prove(i); - verify_merkle_proof(leaves[i].clone(), i, tree_reversed_bits.root, &proof, true)?; - } - - let (height, i) = (1, 0); - let subtree_proof = tree_reversed_bits.prove_subtree(i, height); - let reversed_index = reverse_bits(i, log_n - height); - verify_merkle_proof_subtree( - (reversed_index << height..(reversed_index + 1) << height) - .map(|j| tree_reversed_bits.leaves[j].clone()) - .collect(), - i, - tree_reversed_bits.root, - &subtree_proof, - true, - )?; - - for height in 0..=log_n { - for i in 0..(n >> height) { - let reversed_index = reverse_bits(i, log_n - height); - let subtree_proof = tree_reversed_bits.prove_subtree(i, height); - verify_merkle_proof_subtree( - (reversed_index << height..(reversed_index + 1) << height) - .map(|j| tree_reversed_bits.leaves[j].clone()) - .collect(), - i, - tree_reversed_bits.root, - &subtree_proof, - true, - )?; - } - } + verify_all_leaves(leaves.clone(), n, true)?; + verify_all_subtrees(leaves, n, log_n, true)?; Ok(()) } From 2dfdc39680d541684b3960e65f9a74c54f1eb452 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 26 Apr 2021 10:58:27 +0200 Subject: [PATCH 4/8] More progress on arity --- src/fri.rs | 124 +++++++++++++++++++++++++++++++++------------------ src/proof.rs | 10 +++-- 2 files changed, 88 insertions(+), 46 deletions(-) diff --git a/src/fri.rs b/src/fri.rs index 13885cc9..9813d032 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -8,6 +8,7 @@ use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::proof::{FriEvaluations, FriMerkleProofs, FriProof, FriQueryRound, Hash}; use crate::util::log2_strict; use anyhow::{ensure, Result}; +use std::intrinsics::rotate_left; use std::iter::FromIterator; /// Somewhat arbitrary. Smaller values will increase delta, but with diminishing returns, @@ -54,7 +55,6 @@ fn fri_l(codeword_len: usize, rate_log: usize, conjecture: bool) -> f64 { } } -// TODO: Different arity + PoW. /// Builds a FRI proof. fn fri_proof( // Coefficients of the polynomial on which the LDT is performed. @@ -103,14 +103,15 @@ fn fri_committed_trees( challenger.observe_hash(&trees[0].root); - for _ in 0..config.reduction_count { + for &arity_bits in &config.reduction_arity_bits { + let arity = 1 << arity_bits; let beta = challenger.get_challenge(); - // P(x) = P_0(x^2) + xP_1(x^2) becomes P_0(x) + beta*P_1(x) + // P(x) = sum_{i>(), ); values = fft(coeffs.clone()); @@ -176,48 +177,85 @@ fn fri_query_rounds( ) -> Vec> { let mut query_round_proofs = Vec::new(); for _ in 0..config.num_query_rounds { - let mut merkle_proofs = FriMerkleProofs { proofs: Vec::new() }; - let mut evals = FriEvaluations { - first_layer: (F::ZERO, F::ZERO), - rest: Vec::new(), - }; - // TODO: Challenger doesn't change between query rounds, so x is always the same. - // Once PoW is added, this should be fixed. - let x = challenger.get_challenge(); - let mut domain_size = n; - let mut x_index = x.to_canonical_u64() as usize; - for (i, tree) in trees.iter().enumerate() { - let next_domain_size = domain_size >> 1; - x_index %= domain_size; - let minus_x_index = (next_domain_size + x_index) % domain_size; - if i == 0 { - // For the first layer, we need to send the evaluation at `x` and `-x`. - evals.first_layer = (tree.get(x_index)[0], tree.get(minus_x_index)[0]); - } else { - // For the other layers, we only need to send the `-x`, the one at `x` can be inferred - // by the verifier. See the `compute_evaluation` function. - evals.rest.push(tree.get(minus_x_index)[0]); - } - merkle_proofs - .proofs - .push((tree.prove(x_index), tree.prove(minus_x_index))); - - domain_size = next_domain_size; - } - query_round_proofs.push(FriQueryRound { - evals, - merkle_proofs, - }); + fri_query_round(trees, challenger, n, &mut query_round_proofs, config); } query_round_proofs } -/// Computes P'(x^2) from P_even(x) and P_odd(x), where P' is the FRI reduced polynomial, -/// P_even is the even coefficients polynomial and P_odd is the odd coefficients polynomial. -fn compute_evaluation(x: F, last_e_x: F, last_e_x_minus: F, beta: F) -> F { - // P(x) = P_0(x^2) + xP_1(x^2) - // P'(x^2) = P_0(x^2) + beta*P_1(x^2) - // P'(x^2) = ((P(x)+P(-x))/2) + beta*((P(x)-P(-x))/(2x) +/// Returns the indices of all `y` in `F` with `y^arity=x^arity`, starting with `x` itself. +fn index_roots_coset( + x_index: usize, + next_domain_size: usize, + domain_size: usize, + arity: usize, +) -> Vec { + (0..arity) + .map(|i| (i * next_domain_size + x_index) % domain_size) + .collect() +} + +fn fri_query_round( + trees: &[MerkleTree], + challenger: &mut Challenger, + n: usize, + query_round_proofs: &mut Vec>, + config: &FriConfig, +) { + let mut merkle_proofs = FriMerkleProofs { proofs: Vec::new() }; + let mut evals = FriEvaluations { evals: Vec::new() }; + // TODO: Challenger doesn't change between query rounds, so x is always the same. + let x = challenger.get_challenge(); + let mut domain_size = n; + let mut x_index = x.to_canonical_u64() as usize; + for (i, tree) in trees.iter().enumerate() { + let arity_bits = config.reduction_arity_bits[i]; + let arity = 1 << arity_bits; + let next_domain_size = domain_size >> arity_bits; + x_index %= domain_size; + let roots_coset_indices = index_roots_coset(x_index, next_domain_size, domain_size, arity); + if i == 0 { + // For the first layer, we need to send the evaluation at `x` too. + evals.evals.push( + roots_coset_indices[1..] + .iter() + .map(|&index| tree.get(index)[0]) + .collect(), + ); + } else { + // For the other layers, we don't need to send the evaluation at `x`, since it can + // be inferred by the verifier. See the `compute_evaluation` function. + evals.evals.push( + roots_coset_indices + .iter() + .map(|&index| tree.get(index)[0]) + .collect(), + ); + } + dbg!(roots_coset_indices + .into_iter() + .map(|i| i & ((1 << log2_strict(next_domain_size)) - 1)) + .collect::>()); + merkle_proofs.proofs.push(tree.prove_subtree( + x_index & ((1 << log2_strict(next_domain_size)) - 1), + arity_bits, + )); + + domain_size = next_domain_size; + } + query_round_proofs.push(FriQueryRound { + evals, + merkle_proofs, + }); +} + +/// Computes P'(x^2) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity and P' is the FRI reduced polynomial. +fn compute_evaluation(x: F, arity_bits: usize, last_evals: Vec, beta: F) -> F { + let g = F::primitive_root_of_unity(arity_bits); + let points = g + .powers() + .take(1 << arity_bits) + .map(|y| x * y) + .collect::>(); (last_e_x + last_e_x_minus) / F::TWO + beta * (last_e_x - last_e_x_minus) / (F::TWO * x) } diff --git a/src/proof.rs b/src/proof.rs index 9e41bcd9..5ed27c13 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -84,15 +84,19 @@ pub struct ProofTarget { } // TODO: Implement FriEvaluationsTarget +/// Evaluations of the FRI committed polynomials. #[derive(Debug)] pub struct FriEvaluations { - pub first_layer: (F, F), - pub rest: Vec, + /// The first element of `evals` contains `arity` evaluations. + /// The other elements contain `arity-1` evaluations. + pub evals: Vec>, } // TODO: Implement FriEvaluationsTarget +/// Merkle proofs corresponding to the evaluations in a `FriEvaluation`. +/// These proofs are actually Merkle subtree proofs, for subtrees of height `arity_bits`. pub struct FriMerkleProofs { - pub proofs: Vec<(MerkleProof, MerkleProof)>, + pub proofs: Vec>, } // TODO: Implement FriQueryRoundTarget From 67aa704f6a7d3030d4720f68979cb86e1f10157e Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 26 Apr 2021 18:24:57 +0200 Subject: [PATCH 5/8] Working reduction arity --- src/field/lagrange.rs | 6 +- src/fri.rs | 167 ++++++++++++++++++++++++++---------------- src/merkle_proofs.rs | 5 +- 3 files changed, 110 insertions(+), 68 deletions(-) diff --git a/src/field/lagrange.rs b/src/field/lagrange.rs index 06204b60..228ac845 100644 --- a/src/field/lagrange.rs +++ b/src/field/lagrange.rs @@ -29,7 +29,7 @@ pub(crate) fn interpolant(points: &[(F, F)]) -> PolynomialCoeffs { /// Interpolate the polynomial defined by an arbitrary set of (point, value) pairs at the given /// point `x`. -fn interpolate(points: &[(F, F)], x: F, barycentric_weights: &[F]) -> F { +pub fn interpolate(points: &[(F, F)], x: F, barycentric_weights: &[F]) -> F { // If x is in the list of points, the Lagrange formula would divide by zero. for &(x_i, y_i) in points { if x_i == x { @@ -37,7 +37,7 @@ fn interpolate(points: &[(F, F)], x: F, barycentric_weights: &[F]) -> } } - let l_x: F = points.iter().map(|&(x_i, y_i)| x - x_i).product(); + let l_x: F = points.iter().map(|&(x_i, _y_i)| x - x_i).product(); let sum = (0..points.len()) .map(|i| { @@ -51,7 +51,7 @@ fn interpolate(points: &[(F, F)], x: F, barycentric_weights: &[F]) -> l_x * sum } -fn barycentric_weights(points: &[(F, F)]) -> Vec { +pub fn barycentric_weights(points: &[(F, F)]) -> Vec { let n = points.len(); (0..n) .map(|i| { diff --git a/src/fri.rs b/src/fri.rs index 7047fb06..10dd13c1 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -1,12 +1,13 @@ use crate::field::fft::fft; use crate::field::field::Field; +use crate::field::lagrange::{barycentric_weights, interpolate}; use crate::hash::hash_n_to_1; -use crate::merkle_proofs::verify_merkle_proof; +use crate::merkle_proofs::verify_merkle_proof_subtree; use crate::merkle_tree::MerkleTree; use crate::plonk_challenger::Challenger; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::proof::{FriEvaluations, FriMerkleProofs, FriProof, FriQueryRound, Hash}; -use crate::util::log2_strict; +use crate::util::{log2_strict, reverse_bits}; use anyhow::{ensure, Result}; /// Somewhat arbitrary. Smaller values will increase delta, but with diminishing returns, @@ -16,16 +17,14 @@ const EPSILON: f64 = 0.01; struct FriConfig { proof_of_work_bits: u32, + rate_bits: usize, + /// The arity of each FRI reduction step, expressed (i.e. the log2 of the actual arity). /// For example, `[3, 2, 1]` would describe a FRI reduction tree with 8-to-1 reduction, then /// 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, - /// Number of query rounds to perform. num_query_rounds: usize, } @@ -101,8 +100,9 @@ fn fri_committed_trees( challenger.observe_hash(&trees[0].root); - for &arity_bits in &config.reduction_arity_bits { - let arity = 1 << arity_bits; + let num_reductions = config.reduction_arity_bits.len(); + for i in 0..num_reductions { + let arity = 1 << config.reduction_arity_bits[i]; let beta = challenger.get_challenge(); // P(x) = sum_{i( .map(|chunk| chunk.iter().rev().fold(F::ZERO, |acc, &c| acc * beta + c)) .collect::>(), ); + if i == num_reductions - 1 { + break; + } values = fft(coeffs.clone()); let tree = MerkleTree::new(values.values.iter().map(|&v| vec![v]).collect(), true); challenger.observe_hash(&tree.root); trees.push(tree); } + challenger.observe_elements(&coeffs.coeffs); (trees, coeffs) } @@ -212,7 +216,7 @@ fn fri_query_round( if i == 0 { // For the first layer, we need to send the evaluation at `x` too. evals.evals.push( - roots_coset_indices[1..] + roots_coset_indices .iter() .map(|&index| tree.get(index)[0]) .collect(), @@ -221,16 +225,12 @@ fn fri_query_round( // For the other layers, we don't need to send the evaluation at `x`, since it can // be inferred by the verifier. See the `compute_evaluation` function. evals.evals.push( - roots_coset_indices + roots_coset_indices[1..] .iter() .map(|&index| tree.get(index)[0]) .collect(), ); } - dbg!(roots_coset_indices - .into_iter() - .map(|i| i & ((1 << log2_strict(next_domain_size)) - 1)) - .collect::>()); merkle_proofs.proofs.push(tree.prove_subtree( x_index & ((1 << log2_strict(next_domain_size)) - 1), arity_bits, @@ -244,34 +244,46 @@ fn fri_query_round( }); } -/// Computes P'(x^2) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity and P' is the FRI reduced polynomial. -fn compute_evaluation(x: F, arity_bits: usize, last_evals: Vec, beta: F) -> F { +/// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity and P' is the FRI reduced polynomial. +fn compute_evaluation(x: F, arity_bits: usize, last_evals: &[F], beta: F) -> F { let g = F::primitive_root_of_unity(arity_bits); let points = g .powers() + .zip(last_evals) .take(1 << arity_bits) - .map(|y| x * y) + .map(|(y, &e)| (x * y, e)) .collect::>(); - (last_e_x + last_e_x_minus) / F::TWO + beta * (last_e_x - last_e_x_minus) / (F::TWO * x) + let barycentric_weights = barycentric_weights(&points); + interpolate(&points, beta, &barycentric_weights) } fn verify_fri_proof( + purported_degree_log: usize, proof: &FriProof, challenger: &mut Challenger, config: &FriConfig, ) -> Result<()> { + let total_arities = config.reduction_arity_bits.iter().sum::(); + ensure!( + purported_degree_log + == log2_strict(proof.final_poly.len()) + total_arities - config.rate_bits, + "Final polynomial has wrong degree." + ); // Size of the LDE domain. - let n = proof.final_poly.len() << config.reduction_count; + let n = proof.final_poly.len() << total_arities; // Recover the random betas used in the FRI reductions. - let betas = proof.commit_phase_merkle_roots[..proof.commit_phase_merkle_roots.len() - 1] + // let betas = proof.commit_phase_merkle_roots[..proof.commit_phase_merkle_roots.len() - 1] + let betas = proof + .commit_phase_merkle_roots .iter() .map(|root| { challenger.observe_hash(root); challenger.get_challenge() }) .collect::>(); - challenger.observe_hash(proof.commit_phase_merkle_roots.last().unwrap()); + // challenger.observe_hash(proof.commit_phase_merkle_roots.last().unwrap()); + challenger.observe_elements(&proof.final_poly.coeffs); // Check PoW. fri_verify_proof_of_work(proof, challenger, config)?; @@ -281,7 +293,7 @@ fn verify_fri_proof( "Number of query rounds does not match config." ); ensure!( - config.reduction_count > 0, + !config.reduction_arity_bits.is_empty(), "Number of reductions should be non-zero." ); @@ -293,53 +305,66 @@ fn verify_fri_proof( let mut x_index = x.to_canonical_u64() as usize; // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. let mut subgroup_x = F::primitive_root_of_unity(log2_strict(n)).exp_usize(x_index % n); - for i in 0..config.reduction_count { + for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() { + let arity = 1 << arity_bits; x_index %= domain_size; - let next_domain_size = domain_size >> 1; - let minus_x_index = (next_domain_size + x_index) % domain_size; - let (e_x, e_x_minus, merkle_proof, merkle_proof_minus) = if i == 0 { - let (e_x, e_x_minus) = round_proof.evals.first_layer; - let (merkle_proof, merkle_proof_minus) = &round_proof.merkle_proofs.proofs[i]; - e_xs.push((e_x, e_x_minus)); - (e_x, e_x_minus, merkle_proof, merkle_proof_minus) + let next_domain_size = domain_size >> arity_bits; + let roots_coset_indices = + index_roots_coset(x_index, next_domain_size, domain_size, arity); + if i == 0 { + let evals = round_proof.evals.evals[0].clone(); + e_xs.push(evals); } else { - let (last_e_x, last_e_x_minus) = e_xs[i - 1]; - let e_x = compute_evaluation(subgroup_x, last_e_x, last_e_x_minus, betas[i - 1]); - let e_x_minus = round_proof.evals.rest[i - 1]; - let (merkle_proof, merkle_proof_minus) = &round_proof.merkle_proofs.proofs[i]; - e_xs.push((e_x, e_x_minus)); - (e_x, e_x_minus, merkle_proof, merkle_proof_minus) + let last_evals = &e_xs[i - 1]; + let e_x = compute_evaluation( + subgroup_x, + config.reduction_arity_bits[i - 1], + last_evals, + betas[i - 1], + ); + let mut evals = round_proof.evals.evals[i].clone(); + evals.insert(0, e_x); + e_xs.push(evals); }; - verify_merkle_proof( - vec![e_x], - x_index, + let sorted_evals = { + let mut sorted_evals_enumerate = e_xs[i].iter().enumerate().collect::>(); + sorted_evals_enumerate.sort_by_key(|&(j, _)| { + reverse_bits(roots_coset_indices[j], log2_strict(domain_size)) + }); + sorted_evals_enumerate + .into_iter() + .map(|(_, &e)| vec![e]) + .collect() + }; + verify_merkle_proof_subtree( + sorted_evals, + x_index & ((1 << log2_strict(next_domain_size)) - 1), proof.commit_phase_merkle_roots[i], - merkle_proof, - true, - )?; - verify_merkle_proof( - vec![e_x_minus], - minus_x_index, - proof.commit_phase_merkle_roots[i], - merkle_proof_minus, + &round_proof.merkle_proofs.proofs[i], true, )?; if i > 0 { - subgroup_x = subgroup_x.square(); + for _ in 0..config.reduction_arity_bits[i - 1] { + subgroup_x = subgroup_x.square(); + } } domain_size = next_domain_size; } - let (last_e_x, last_e_x_minus) = e_xs[config.reduction_count - 1]; + let last_evals = e_xs.last().unwrap(); + let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); let purported_eval = compute_evaluation( subgroup_x, - last_e_x, - last_e_x_minus, - betas[config.reduction_count - 1], + final_arity_bits, + last_evals, + *betas.last().unwrap(), ); + for _ in 0..final_arity_bits { + subgroup_x = subgroup_x.square(); + } // Final check of FRI. After all the reductions, we check that the final polynomial is equal // to the one sent by the prover. ensure!( - proof.final_poly.eval(subgroup_x.square()) == purported_eval, + proof.final_poly.eval(subgroup_x) == purported_eval, "Final polynomial evaluation is invalid." ); } @@ -353,41 +378,57 @@ mod tests { use crate::field::crandall_field::CrandallField; use crate::field::fft::ifft; use anyhow::Result; + use rand::Rng; fn test_fri( - degree: usize, + degree_log: usize, rate_bits: usize, - reduction_count: usize, + reduction_arity_bits: Vec, num_query_rounds: usize, ) -> Result<()> { type F = CrandallField; - let n = degree; + let n = 1 << degree_log; let evals = PolynomialValues::new((0..n).map(|_| F::rand()).collect()); let lde = evals.clone().lde(rate_bits); let config = FriConfig { - reduction_count, num_query_rounds, + rate_bits, proof_of_work_bits: 2, - reduction_arity_bits: Vec::new(), + reduction_arity_bits, }; let mut challenger = Challenger::new(); let proof = fri_proof(&ifft(lde.clone()), &lde, &mut challenger, &config); let mut challenger = Challenger::new(); - verify_fri_proof(&proof, &mut challenger, &config)?; + verify_fri_proof(degree_log, &proof, &mut challenger, &config)?; Ok(()) } + fn gen_arities(degree_log: usize) -> Vec { + let mut rng = rand::thread_rng(); + let mut arities = Vec::new(); + let mut remaining = degree_log; + while remaining > 0 { + let arity = rng.gen_range(0, remaining + 1); + arities.push(arity); + remaining -= arity; + } + arities + } + #[test] fn test_fri_multi_params() -> Result<()> { for degree_log in 1..6 { for rate_bits in 0..4 { - for reduction_count in 1..=(degree_log + rate_bits) { - for num_query_round in 0..4 { - test_fri(1 << degree_log, rate_bits, reduction_count, num_query_round)?; - } + for num_query_round in 0..4 { + test_fri( + degree_log, + rate_bits, + gen_arities(degree_log), + num_query_round, + )?; } } } diff --git a/src/merkle_proofs.rs b/src/merkle_proofs.rs index c2ea7424..6209d9ab 100644 --- a/src/merkle_proofs.rs +++ b/src/merkle_proofs.rs @@ -1,8 +1,9 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; use crate::gates::gmimc::GMiMCGate; +use crate::hash::GMIMC_ROUNDS; use crate::hash::{compress, hash_or_noop}; -use crate::hash::{merkle_root_inner, GMIMC_ROUNDS}; +use crate::merkle_tree::MerkleTree; use crate::proof::{Hash, HashTarget}; use crate::target::Target; use crate::wire::Wire; @@ -61,7 +62,7 @@ pub(crate) fn verify_merkle_proof_subtree( } else { subtree_index }; - let mut current_digest = merkle_root_inner(subtree_leaves_data); + let mut current_digest = MerkleTree::new(subtree_leaves_data, false).root; for (i, &sibling_digest) in proof.siblings.iter().enumerate() { let bit = (index >> i & 1) == 1; current_digest = if bit { From f40aba3205f38839b59714be27629813bfb91a95 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 26 Apr 2021 19:19:27 +0200 Subject: [PATCH 6/8] Cleaning and commens --- src/field/field.rs | 2 +- src/fri.rs | 59 ++++++++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/field/field.rs b/src/field/field.rs index cafc536a..b52045e4 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -4,8 +4,8 @@ use std::iter::{Product, Sum}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use num::Integer; -use rand::Rng; use rand::rngs::OsRng; +use rand::Rng; use crate::util::bits_u64; diff --git a/src/fri.rs b/src/fri.rs index 10dd13c1..052a7219 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -69,6 +69,7 @@ fn fri_proof( let (trees, final_coeffs) = fri_committed_trees(polynomial_coeffs, polynomial_values, challenger, config); + // PoW phase let current_hash = challenger.get_hash(); let pow_witness = fri_proof_of_work(current_hash, config); @@ -113,6 +114,8 @@ fn fri_committed_trees( .collect::>(), ); if i == num_reductions - 1 { + // We don't need a Merkle root for the final polynomial, since we send its + // coefficients directly to the verifier. break; } values = fft(coeffs.clone()); @@ -182,18 +185,6 @@ fn fri_query_rounds( query_round_proofs } -/// Returns the indices of all `y` in `F` with `y^arity=x^arity`, starting with `x` itself. -fn index_roots_coset( - x_index: usize, - next_domain_size: usize, - domain_size: usize, - arity: usize, -) -> Vec { - (0..arity) - .map(|i| (i * next_domain_size + x_index) % domain_size) - .collect() -} - fn fri_query_round( trees: &[MerkleTree], challenger: &mut Challenger, @@ -244,8 +235,22 @@ fn fri_query_round( }); } -/// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity and P' is the FRI reduced polynomial. +/// Returns the indices in the domain of all `y` in `F` with `y^arity=x^arity`, starting with `x` itself. +fn index_roots_coset( + x_index: usize, + next_domain_size: usize, + domain_size: usize, + arity: usize, +) -> Vec { + (0..arity) + .map(|i| (i * next_domain_size + x_index) % domain_size) + .collect() +} + +/// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity +/// and P' is the FRI reduced polynomial. fn compute_evaluation(x: F, arity_bits: usize, last_evals: &[F], beta: F) -> F { + // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. let g = F::primitive_root_of_unity(arity_bits); let points = g .powers() @@ -269,11 +274,11 @@ fn verify_fri_proof( == log2_strict(proof.final_poly.len()) + total_arities - config.rate_bits, "Final polynomial has wrong degree." ); + // Size of the LDE domain. let n = proof.final_poly.len() << total_arities; // Recover the random betas used in the FRI reductions. - // let betas = proof.commit_phase_merkle_roots[..proof.commit_phase_merkle_roots.len() - 1] let betas = proof .commit_phase_merkle_roots .iter() @@ -282,11 +287,11 @@ fn verify_fri_proof( challenger.get_challenge() }) .collect::>(); - // challenger.observe_hash(proof.commit_phase_merkle_roots.last().unwrap()); challenger.observe_elements(&proof.final_poly.coeffs); // Check PoW. fri_verify_proof_of_work(proof, challenger, config)?; + // Check that parameters are coherent. ensure!( config.num_query_rounds == proof.query_round_proofs.len(), @@ -299,7 +304,7 @@ fn verify_fri_proof( for round in 0..config.num_query_rounds { let round_proof = &proof.query_round_proofs[round]; - let mut e_xs = Vec::new(); + let mut evaluations = Vec::new(); let x = challenger.get_challenge(); let mut domain_size = n; let mut x_index = x.to_canonical_u64() as usize; @@ -309,13 +314,12 @@ fn verify_fri_proof( let arity = 1 << arity_bits; x_index %= domain_size; let next_domain_size = domain_size >> arity_bits; - let roots_coset_indices = - index_roots_coset(x_index, next_domain_size, domain_size, arity); if i == 0 { let evals = round_proof.evals.evals[0].clone(); - e_xs.push(evals); + evaluations.push(evals); } else { - let last_evals = &e_xs[i - 1]; + let last_evals = &evaluations[i - 1]; + // Infer P(y) from {P(x)}_{x^arity=y}. let e_x = compute_evaluation( subgroup_x, config.reduction_arity_bits[i - 1], @@ -323,11 +327,16 @@ fn verify_fri_proof( betas[i - 1], ); let mut evals = round_proof.evals.evals[i].clone(); + // Insert P(y) into the evaluation vector, since it wasn't included by the prover. evals.insert(0, e_x); - e_xs.push(evals); + evaluations.push(evals); }; let sorted_evals = { - let mut sorted_evals_enumerate = e_xs[i].iter().enumerate().collect::>(); + let roots_coset_indices = + index_roots_coset(x_index, next_domain_size, domain_size, arity); + let mut sorted_evals_enumerate = + evaluations[i].iter().enumerate().collect::>(); + // We need to sort the evaluations so that they match their order in the Merkle tree. sorted_evals_enumerate.sort_by_key(|&(j, _)| { reverse_bits(roots_coset_indices[j], log2_strict(domain_size)) }); @@ -343,14 +352,17 @@ fn verify_fri_proof( &round_proof.merkle_proofs.proofs[i], true, )?; + if i > 0 { + // Update the point x to x^arity. for _ in 0..config.reduction_arity_bits[i - 1] { subgroup_x = subgroup_x.square(); } } domain_size = next_domain_size; } - let last_evals = e_xs.last().unwrap(); + + let last_evals = evaluations.last().unwrap(); let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); let purported_eval = compute_evaluation( subgroup_x, @@ -361,6 +373,7 @@ fn verify_fri_proof( for _ in 0..final_arity_bits { subgroup_x = subgroup_x.square(); } + // Final check of FRI. After all the reductions, we check that the final polynomial is equal // to the one sent by the prover. ensure!( From 187b122c622327fd4827ccafdf5e3f660c6453cc Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 27 Apr 2021 08:44:34 +0200 Subject: [PATCH 7/8] Fixes based on Daniel's PR comments. --- src/fri.rs | 78 +++++++++++++++++++++------------------------- src/merkle_tree.rs | 1 - src/proof.rs | 23 +++++--------- 3 files changed, 42 insertions(+), 60 deletions(-) diff --git a/src/fri.rs b/src/fri.rs index 052a7219..85843c44 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -5,8 +5,9 @@ use crate::hash::hash_n_to_1; use crate::merkle_proofs::verify_merkle_proof_subtree; use crate::merkle_tree::MerkleTree; use crate::plonk_challenger::Challenger; +use crate::plonk_common::reduce_with_powers; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; -use crate::proof::{FriEvaluations, FriMerkleProofs, FriProof, FriQueryRound, Hash}; +use crate::proof::{FriProof, FriQueryRound, FriQueryStep, Hash}; use crate::util::{log2_strict, reverse_bits}; use anyhow::{ensure, Result}; @@ -74,7 +75,7 @@ fn fri_proof( let pow_witness = fri_proof_of_work(current_hash, config); // Query phase - let query_round_proofs = fri_query_rounds(&trees, challenger, n, config); + let query_round_proofs = fri_prover_query_rounds(&trees, challenger, n, config); FriProof { commit_phase_merkle_roots: trees.iter().map(|t| t.root).collect(), @@ -110,7 +111,7 @@ fn fri_committed_trees( coeffs .coeffs .chunks_exact(arity) - .map(|chunk| chunk.iter().rev().fold(F::ZERO, |acc, &c| acc * beta + c)) + .map(|chunk| reduce_with_powers(chunk, beta)) .collect::>(), ); if i == num_reductions - 1 { @@ -172,28 +173,24 @@ fn fri_verify_proof_of_work( Ok(()) } -fn fri_query_rounds( +fn fri_prover_query_rounds( trees: &[MerkleTree], challenger: &mut Challenger, n: usize, config: &FriConfig, ) -> Vec> { - let mut query_round_proofs = Vec::new(); - for _ in 0..config.num_query_rounds { - fri_query_round(trees, challenger, n, &mut query_round_proofs, config); - } - query_round_proofs + (0..config.num_query_rounds) + .map(|_| fri_query_round(trees, challenger, n, config)) + .collect() } fn fri_query_round( trees: &[MerkleTree], challenger: &mut Challenger, n: usize, - query_round_proofs: &mut Vec>, config: &FriConfig, -) { - let mut merkle_proofs = FriMerkleProofs { proofs: Vec::new() }; - let mut evals = FriEvaluations { evals: Vec::new() }; +) -> FriQueryRound { + let mut query_steps = Vec::new(); // TODO: Challenger doesn't change between query rounds, so x is always the same. let x = challenger.get_challenge(); let mut domain_size = n; @@ -203,40 +200,35 @@ fn fri_query_round( let arity = 1 << arity_bits; let next_domain_size = domain_size >> arity_bits; x_index %= domain_size; - let roots_coset_indices = index_roots_coset(x_index, next_domain_size, domain_size, arity); - if i == 0 { + let roots_coset_indices = coset_indices(x_index, next_domain_size, domain_size, arity); + let evals = if i == 0 { // For the first layer, we need to send the evaluation at `x` too. - evals.evals.push( - roots_coset_indices - .iter() - .map(|&index| tree.get(index)[0]) - .collect(), - ); + roots_coset_indices + .iter() + .map(|&index| tree.get(index)[0]) + .collect() } else { // For the other layers, we don't need to send the evaluation at `x`, since it can // be inferred by the verifier. See the `compute_evaluation` function. - evals.evals.push( - roots_coset_indices[1..] - .iter() - .map(|&index| tree.get(index)[0]) - .collect(), - ); - } - merkle_proofs.proofs.push(tree.prove_subtree( - x_index & ((1 << log2_strict(next_domain_size)) - 1), - arity_bits, - )); + roots_coset_indices[1..] + .iter() + .map(|&index| tree.get(index)[0]) + .collect() + }; + let merkle_proof = tree.prove_subtree(x_index & (next_domain_size - 1), arity_bits); + + query_steps.push(FriQueryStep { + merkle_proof, + evals, + }); domain_size = next_domain_size; } - query_round_proofs.push(FriQueryRound { - evals, - merkle_proofs, - }); + FriQueryRound { steps: query_steps } } /// Returns the indices in the domain of all `y` in `F` with `y^arity=x^arity`, starting with `x` itself. -fn index_roots_coset( +fn coset_indices( x_index: usize, next_domain_size: usize, domain_size: usize, @@ -250,12 +242,12 @@ fn index_roots_coset( /// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity /// and P' is the FRI reduced polynomial. fn compute_evaluation(x: F, arity_bits: usize, last_evals: &[F], beta: F) -> F { + debug_assert_eq!(last_evals.len(), 1 << arity_bits); // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. let g = F::primitive_root_of_unity(arity_bits); let points = g .powers() .zip(last_evals) - .take(1 << arity_bits) .map(|(y, &e)| (x * y, e)) .collect::>(); let barycentric_weights = barycentric_weights(&points); @@ -315,7 +307,7 @@ fn verify_fri_proof( x_index %= domain_size; let next_domain_size = domain_size >> arity_bits; if i == 0 { - let evals = round_proof.evals.evals[0].clone(); + let evals = round_proof.steps[0].evals.clone(); evaluations.push(evals); } else { let last_evals = &evaluations[i - 1]; @@ -326,14 +318,14 @@ fn verify_fri_proof( last_evals, betas[i - 1], ); - let mut evals = round_proof.evals.evals[i].clone(); + let mut evals = round_proof.steps[i].evals.clone(); // Insert P(y) into the evaluation vector, since it wasn't included by the prover. evals.insert(0, e_x); evaluations.push(evals); }; let sorted_evals = { let roots_coset_indices = - index_roots_coset(x_index, next_domain_size, domain_size, arity); + coset_indices(x_index, next_domain_size, domain_size, arity); let mut sorted_evals_enumerate = evaluations[i].iter().enumerate().collect::>(); // We need to sort the evaluations so that they match their order in the Merkle tree. @@ -347,9 +339,9 @@ fn verify_fri_proof( }; verify_merkle_proof_subtree( sorted_evals, - x_index & ((1 << log2_strict(next_domain_size)) - 1), + x_index & (next_domain_size - 1), proof.commit_phase_merkle_roots[i], - &round_proof.merkle_proofs.proofs[i], + &round_proof.steps[i].merkle_proof, true, )?; diff --git a/src/merkle_tree.rs b/src/merkle_tree.rs index d0e65058..a74415a7 100644 --- a/src/merkle_tree.rs +++ b/src/merkle_tree.rs @@ -122,7 +122,6 @@ mod tests { use crate::field::crandall_field::CrandallField; use crate::merkle_proofs::{verify_merkle_proof, verify_merkle_proof_subtree}; - use crate::polynomial::division::divide_by_z_h; use super::*; diff --git a/src/proof.rs b/src/proof.rs index 5ed27c13..f37081c0 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -83,26 +83,17 @@ pub struct ProofTarget { pub fri_proofs: Vec, } -// TODO: Implement FriEvaluationsTarget -/// Evaluations of the FRI committed polynomials. -#[derive(Debug)] -pub struct FriEvaluations { - /// The first element of `evals` contains `arity` evaluations. - /// The other elements contain `arity-1` evaluations. - pub evals: Vec>, -} - -// TODO: Implement FriEvaluationsTarget -/// Merkle proofs corresponding to the evaluations in a `FriEvaluation`. -/// These proofs are actually Merkle subtree proofs, for subtrees of height `arity_bits`. -pub struct FriMerkleProofs { - pub proofs: Vec>, +/// Evaluations and Merkle proof produced by the prover in a FRI query step. +// TODO: Implement FriQueryStepTarget +pub struct FriQueryStep { + pub evals: Vec, + pub merkle_proof: MerkleProof, } +/// Proof for a FRI query round. // TODO: Implement FriQueryRoundTarget pub struct FriQueryRound { - pub evals: FriEvaluations, - pub merkle_proofs: FriMerkleProofs, + pub steps: Vec>, } pub struct FriProof { From deb981e97b44a0538097aeff0a93acce35767d30 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 27 Apr 2021 09:21:04 +0200 Subject: [PATCH 8/8] More fixes --- src/fri.rs | 168 ++++++++++++++++++++++++++++------------------------- 1 file changed, 89 insertions(+), 79 deletions(-) diff --git a/src/fri.rs b/src/fri.rs index 85843c44..281fa850 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -218,8 +218,8 @@ fn fri_query_round( let merkle_proof = tree.prove_subtree(x_index & (next_domain_size - 1), arity_bits); query_steps.push(FriQueryStep { - merkle_proof, evals, + merkle_proof, }); domain_size = next_domain_size; @@ -294,89 +294,99 @@ fn verify_fri_proof( "Number of reductions should be non-zero." ); - for round in 0..config.num_query_rounds { - let round_proof = &proof.query_round_proofs[round]; - let mut evaluations = Vec::new(); - let x = challenger.get_challenge(); - let mut domain_size = n; - let mut x_index = x.to_canonical_u64() as usize; - // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. - let mut subgroup_x = F::primitive_root_of_unity(log2_strict(n)).exp_usize(x_index % n); - for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() { - let arity = 1 << arity_bits; - x_index %= domain_size; - let next_domain_size = domain_size >> arity_bits; - if i == 0 { - let evals = round_proof.steps[0].evals.clone(); - evaluations.push(evals); - } else { - let last_evals = &evaluations[i - 1]; - // Infer P(y) from {P(x)}_{x^arity=y}. - let e_x = compute_evaluation( - subgroup_x, - config.reduction_arity_bits[i - 1], - last_evals, - betas[i - 1], - ); - let mut evals = round_proof.steps[i].evals.clone(); - // Insert P(y) into the evaluation vector, since it wasn't included by the prover. - evals.insert(0, e_x); - evaluations.push(evals); - }; - let sorted_evals = { - let roots_coset_indices = - coset_indices(x_index, next_domain_size, domain_size, arity); - let mut sorted_evals_enumerate = - evaluations[i].iter().enumerate().collect::>(); - // We need to sort the evaluations so that they match their order in the Merkle tree. - sorted_evals_enumerate.sort_by_key(|&(j, _)| { - reverse_bits(roots_coset_indices[j], log2_strict(domain_size)) - }); - sorted_evals_enumerate - .into_iter() - .map(|(_, &e)| vec![e]) - .collect() - }; - verify_merkle_proof_subtree( - sorted_evals, - x_index & (next_domain_size - 1), - proof.commit_phase_merkle_roots[i], - &round_proof.steps[i].merkle_proof, - true, - )?; - - if i > 0 { - // Update the point x to x^arity. - for _ in 0..config.reduction_arity_bits[i - 1] { - subgroup_x = subgroup_x.square(); - } - } - domain_size = next_domain_size; - } - - let last_evals = evaluations.last().unwrap(); - let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); - let purported_eval = compute_evaluation( - subgroup_x, - final_arity_bits, - last_evals, - *betas.last().unwrap(), - ); - for _ in 0..final_arity_bits { - subgroup_x = subgroup_x.square(); - } - - // Final check of FRI. After all the reductions, we check that the final polynomial is equal - // to the one sent by the prover. - ensure!( - proof.final_poly.eval(subgroup_x) == purported_eval, - "Final polynomial evaluation is invalid." - ); + for round_proof in &proof.query_round_proofs { + fri_verifier_query_round(&proof, challenger, n, &betas, round_proof, config)?; } Ok(()) } +fn fri_verifier_query_round( + proof: &FriProof, + challenger: &mut Challenger, + n: usize, + betas: &[F], + round_proof: &FriQueryRound, + config: &FriConfig, +) -> Result<()> { + let mut evaluations = Vec::new(); + let x = challenger.get_challenge(); + let mut domain_size = n; + let mut x_index = x.to_canonical_u64() as usize; + // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. + let mut subgroup_x = F::primitive_root_of_unity(log2_strict(n)).exp_usize(x_index % n); + for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() { + let arity = 1 << arity_bits; + x_index %= domain_size; + let next_domain_size = domain_size >> arity_bits; + if i == 0 { + let evals = round_proof.steps[0].evals.clone(); + evaluations.push(evals); + } else { + let last_evals = &evaluations[i - 1]; + // Infer P(y) from {P(x)}_{x^arity=y}. + let e_x = compute_evaluation( + subgroup_x, + config.reduction_arity_bits[i - 1], + last_evals, + betas[i - 1], + ); + let mut evals = round_proof.steps[i].evals.clone(); + // Insert P(y) into the evaluation vector, since it wasn't included by the prover. + evals.insert(0, e_x); + evaluations.push(evals); + }; + let sorted_evals = { + let roots_coset_indices = coset_indices(x_index, next_domain_size, domain_size, arity); + let mut sorted_evals_enumerate = evaluations[i].iter().enumerate().collect::>(); + // We need to sort the evaluations so that they match their order in the Merkle tree. + sorted_evals_enumerate.sort_by_key(|&(j, _)| { + reverse_bits(roots_coset_indices[j], log2_strict(domain_size)) + }); + sorted_evals_enumerate + .into_iter() + .map(|(_, &e)| vec![e]) + .collect() + }; + verify_merkle_proof_subtree( + sorted_evals, + x_index & (next_domain_size - 1), + proof.commit_phase_merkle_roots[i], + &round_proof.steps[i].merkle_proof, + true, + )?; + + if i > 0 { + // Update the point x to x^arity. + for _ in 0..config.reduction_arity_bits[i - 1] { + subgroup_x = subgroup_x.square(); + } + } + domain_size = next_domain_size; + } + + let last_evals = evaluations.last().unwrap(); + let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); + let purported_eval = compute_evaluation( + subgroup_x, + final_arity_bits, + last_evals, + *betas.last().unwrap(), + ); + for _ in 0..final_arity_bits { + subgroup_x = subgroup_x.square(); + } + + // Final check of FRI. After all the reductions, we check that the final polynomial is equal + // to the one sent by the prover. + ensure!( + proof.final_poly.eval(subgroup_x) == purported_eval, + "Final polynomial evaluation is invalid." + ); + + Ok(()) +} + #[cfg(test)] mod tests { use super::*;