mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-10 17:53:06 +00:00
Merge pull request #24 from mir-protocol/fri-reduction-arity
Fri reduction arity
This commit is contained in:
commit
d5da6308b5
@ -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;
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ pub(crate) fn interpolant<F: Field>(points: &[(F, F)]) -> PolynomialCoeffs<F> {
|
||||
|
||||
/// Interpolate the polynomial defined by an arbitrary set of (point, value) pairs at the given
|
||||
/// point `x`.
|
||||
fn interpolate<F: Field>(points: &[(F, F)], x: F, barycentric_weights: &[F]) -> F {
|
||||
pub fn interpolate<F: Field>(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<F: Field>(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<F: Field>(points: &[(F, F)], x: F, barycentric_weights: &[F]) ->
|
||||
l_x * sum
|
||||
}
|
||||
|
||||
fn barycentric_weights<F: Field>(points: &[(F, F)]) -> Vec<F> {
|
||||
pub fn barycentric_weights<F: Field>(points: &[(F, F)]) -> Vec<F> {
|
||||
let n = points.len();
|
||||
F::batch_multiplicative_inverse(
|
||||
&(0..n)
|
||||
|
||||
343
src/fri.rs
343
src/fri.rs
@ -1,12 +1,14 @@
|
||||
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::plonk_common::reduce_with_powers;
|
||||
use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues};
|
||||
use crate::proof::{FriEvaluations, FriMerkleProofs, FriProof, FriQueryRound, Hash};
|
||||
use crate::util::log2_strict;
|
||||
use crate::proof::{FriProof, FriQueryRound, FriQueryStep, Hash};
|
||||
use crate::util::{log2_strict, reverse_bits};
|
||||
use anyhow::{ensure, Result};
|
||||
|
||||
/// Somewhat arbitrary. Smaller values will increase delta, but with diminishing returns,
|
||||
@ -16,16 +18,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<usize>,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
@ -53,7 +53,6 @@ fn fri_l(codeword_len: usize, rate_log: usize, conjecture: bool) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Different arity + PoW.
|
||||
/// Builds a FRI proof.
|
||||
fn fri_proof<F: Field>(
|
||||
// Coefficients of the polynomial on which the LDT is performed.
|
||||
@ -71,11 +70,12 @@ fn fri_proof<F: Field>(
|
||||
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);
|
||||
|
||||
// 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(),
|
||||
@ -102,22 +102,30 @@ fn fri_committed_trees<F: Field>(
|
||||
|
||||
challenger.observe_hash(&trees[0].root);
|
||||
|
||||
for _ in 0..config.reduction_count {
|
||||
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) = P_0(x^2) + xP_1(x^2) becomes P_0(x) + beta*P_1(x)
|
||||
// P(x) = sum_{i<r} x^i * P_i(x^r) becomes sum_{i<r} beta^i * P_i(x).
|
||||
coeffs = PolynomialCoeffs::new(
|
||||
coeffs
|
||||
.coeffs
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| chunk[0] + beta * chunk[1])
|
||||
.chunks_exact(arity)
|
||||
.map(|chunk| reduce_with_powers(chunk, beta))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
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());
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -165,148 +173,217 @@ fn fri_verify_proof_of_work<F: Field>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fri_query_rounds<F: Field>(
|
||||
fn fri_prover_query_rounds<F: Field>(
|
||||
trees: &[MerkleTree<F>],
|
||||
challenger: &mut Challenger<F>,
|
||||
n: usize,
|
||||
config: &FriConfig,
|
||||
) -> Vec<FriQueryRound<F>> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
query_round_proofs
|
||||
(0..config.num_query_rounds)
|
||||
.map(|_| fri_query_round(trees, challenger, n, config))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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<F: Field>(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)
|
||||
(last_e_x + last_e_x_minus) / F::TWO + beta * (last_e_x - last_e_x_minus) / (F::TWO * x)
|
||||
fn fri_query_round<F: Field>(
|
||||
trees: &[MerkleTree<F>],
|
||||
challenger: &mut Challenger<F>,
|
||||
n: usize,
|
||||
config: &FriConfig,
|
||||
) -> FriQueryRound<F> {
|
||||
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;
|
||||
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 = 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.
|
||||
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.
|
||||
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 {
|
||||
evals,
|
||||
merkle_proof,
|
||||
});
|
||||
|
||||
domain_size = next_domain_size;
|
||||
}
|
||||
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 coset_indices(
|
||||
x_index: usize,
|
||||
next_domain_size: usize,
|
||||
domain_size: usize,
|
||||
arity: usize,
|
||||
) -> Vec<usize> {
|
||||
(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<F: Field>(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)
|
||||
.map(|(y, &e)| (x * y, e))
|
||||
.collect::<Vec<_>>();
|
||||
let barycentric_weights = barycentric_weights(&points);
|
||||
interpolate(&points, beta, &barycentric_weights)
|
||||
}
|
||||
|
||||
fn verify_fri_proof<F: Field>(
|
||||
purported_degree_log: usize,
|
||||
proof: &FriProof<F>,
|
||||
challenger: &mut Challenger<F>,
|
||||
config: &FriConfig,
|
||||
) -> Result<()> {
|
||||
let total_arities = config.reduction_arity_bits.iter().sum::<usize>();
|
||||
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
|
||||
.iter()
|
||||
.map(|root| {
|
||||
challenger.observe_hash(root);
|
||||
challenger.get_challenge()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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(),
|
||||
"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."
|
||||
);
|
||||
|
||||
for round in 0..config.num_query_rounds {
|
||||
let round_proof = &proof.query_round_proofs[round];
|
||||
let mut e_xs = 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 in 0..config.reduction_count {
|
||||
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)
|
||||
} 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)
|
||||
};
|
||||
verify_merkle_proof(
|
||||
vec![e_x],
|
||||
x_index,
|
||||
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,
|
||||
true,
|
||||
)?;
|
||||
if i > 0 {
|
||||
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<F: Field>(
|
||||
proof: &FriProof<F>,
|
||||
challenger: &mut Challenger<F>,
|
||||
n: usize,
|
||||
betas: &[F],
|
||||
round_proof: &FriQueryRound<F>,
|
||||
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::<Vec<_>>();
|
||||
// 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_e_x, last_e_x_minus) = e_xs[config.reduction_count - 1];
|
||||
let purported_eval = compute_evaluation(
|
||||
subgroup_x,
|
||||
last_e_x,
|
||||
last_e_x_minus,
|
||||
betas[config.reduction_count - 1],
|
||||
);
|
||||
// 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,
|
||||
"Final polynomial evaluation is invalid."
|
||||
);
|
||||
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(())
|
||||
}
|
||||
|
||||
@ -316,41 +393,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<usize>,
|
||||
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<usize> {
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ use crate::field::field::Field;
|
||||
use crate::gates::gmimc::GMiMCGate;
|
||||
use crate::hash::GMIMC_ROUNDS;
|
||||
use crate::hash::{compress, hash_or_noop};
|
||||
use crate::merkle_tree::MerkleTree;
|
||||
use crate::proof::{Hash, HashTarget};
|
||||
use crate::target::Target;
|
||||
use crate::wire::Wire;
|
||||
@ -47,6 +48,34 @@ pub(crate) fn verify_merkle_proof<F: Field>(
|
||||
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<F: Field>(
|
||||
subtree_leaves_data: Vec<Vec<F>>,
|
||||
subtree_index: usize,
|
||||
merkle_root: Hash<F>,
|
||||
proof: &MerkleProof<F>,
|
||||
reverse_bits: bool,
|
||||
) -> Result<()> {
|
||||
let index = if reverse_bits {
|
||||
crate::util::reverse_bits(subtree_index, proof.siblings.len())
|
||||
} else {
|
||||
subtree_index
|
||||
};
|
||||
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 {
|
||||
compress(sibling_digest, current_digest)
|
||||
} else {
|
||||
compress(current_digest, sibling_digest)
|
||||
}
|
||||
}
|
||||
ensure!(current_digest == merkle_root, "Invalid Merkle proof.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<F: Field> CircuitBuilder<F> {
|
||||
/// Verifies that the given leaf data is present at the given index in the Merkle tree with the
|
||||
/// given root.
|
||||
|
||||
@ -78,6 +78,42 @@ impl<F: Field> MerkleTree<F> {
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a Merkle proof for an entire subtree.
|
||||
/// Example:
|
||||
/// ```tree
|
||||
/// 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<F> {
|
||||
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)]
|
||||
@ -85,31 +121,68 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::field::crandall_field::CrandallField;
|
||||
use crate::merkle_proofs::verify_merkle_proof;
|
||||
use crate::polynomial::division::divide_by_z_h;
|
||||
use crate::merkle_proofs::{verify_merkle_proof, verify_merkle_proof_subtree};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn random_data<F: Field>(n: usize, k: usize) -> Vec<Vec<F>> {
|
||||
(0..n)
|
||||
.map(|_| (0..k).map(|_| F::rand()).collect())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn verify_all_leaves<F: Field>(
|
||||
leaves: Vec<Vec<F>>,
|
||||
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<F: Field>(
|
||||
leaves: Vec<Vec<F>>,
|
||||
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;
|
||||
|
||||
let n = 1 << 10;
|
||||
let leaves: Vec<Vec<F>> = (0..n)
|
||||
.map(|_| (0..10).map(|_| F::rand()).collect())
|
||||
.collect();
|
||||
let log_n = 8;
|
||||
let n = 1 << log_n;
|
||||
let leaves = random_data::<F>(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)?;
|
||||
|
||||
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)?;
|
||||
}
|
||||
verify_all_leaves(leaves.clone(), n, true)?;
|
||||
verify_all_subtrees(leaves, n, log_n, true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
19
src/proof.rs
19
src/proof.rs
@ -83,22 +83,17 @@ pub struct ProofTarget {
|
||||
pub fri_proofs: Vec<FriProofTarget>,
|
||||
}
|
||||
|
||||
// TODO: Implement FriEvaluationsTarget
|
||||
#[derive(Debug)]
|
||||
pub struct FriEvaluations<F: Field> {
|
||||
pub first_layer: (F, F),
|
||||
pub rest: Vec<F>,
|
||||
}
|
||||
|
||||
// TODO: Implement FriEvaluationsTarget
|
||||
pub struct FriMerkleProofs<F: Field> {
|
||||
pub proofs: Vec<(MerkleProof<F>, MerkleProof<F>)>,
|
||||
/// Evaluations and Merkle proof produced by the prover in a FRI query step.
|
||||
// TODO: Implement FriQueryStepTarget
|
||||
pub struct FriQueryStep<F: Field> {
|
||||
pub evals: Vec<F>,
|
||||
pub merkle_proof: MerkleProof<F>,
|
||||
}
|
||||
|
||||
/// Proof for a FRI query round.
|
||||
// TODO: Implement FriQueryRoundTarget
|
||||
pub struct FriQueryRound<F: Field> {
|
||||
pub evals: FriEvaluations<F>,
|
||||
pub merkle_proofs: FriMerkleProofs<F>,
|
||||
pub steps: Vec<FriQueryStep<F>>,
|
||||
}
|
||||
|
||||
pub struct FriProof<F: Field> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user