diff --git a/src/fri/prover.rs b/src/fri/prover.rs index 431fbe70..52db4579 100644 --- a/src/fri/prover.rs +++ b/src/fri/prover.rs @@ -74,15 +74,12 @@ fn fri_committed_trees, const D: usize>( let arity = 1 << config.reduction_arity_bits[i]; reverse_index_bits_in_place(&mut values.values); - let tree = MerkleTree::new( - values - .values - .par_chunks(arity) - .map(|chunk: &[F::Extension]| flatten(chunk)) - .collect(), - config.cap_height, - false, - ); + let chunked_values = values + .values + .par_chunks(arity) + .map(|chunk: &[F::Extension]| flatten(chunk)) + .collect(); + let tree = MerkleTree::new(chunked_values, config.cap_height, false); challenger.observe_cap(&tree.cap); trees.push(tree); diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs index 5871a0c7..29a6ff4f 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -20,7 +20,7 @@ impl, const D: usize> CircuitBuilder { fn compute_evaluation( &mut self, x: Target, - old_x_index_bits: &[Target], + x_index_within_coset_bits: &[Target], arity_bits: usize, last_evals: &[ExtensionTarget], beta: ExtensionTarget, @@ -35,8 +35,9 @@ impl, const D: usize> CircuitBuilder { // The evaluation vector needs to be reordered first. let mut evals = last_evals.to_vec(); reverse_index_bits_in_place(&mut evals); - // Want `g^(arity - rev_old_x_index)` as in the out-of-circuit version. Compute it as `(g^-1)^rev_old_x_index`. - let start = self.exp_from_bits(g_inv_t, old_x_index_bits.iter().rev()); + // Want `g^(arity - rev_x_index_within_coset)` as in the out-of-circuit version. Compute it + // as `(g^-1)^rev_x_index_within_coset`. + let start = self.exp_from_bits(g_inv_t, x_index_within_coset_bits.iter().rev()); let coset_start = self.mul(start, x); // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. @@ -294,7 +295,6 @@ impl, const D: usize> CircuitBuilder { x_index_bits[x_index_bits.len() - common_data.config.fri_config.cap_height..] .into_iter(), ); - let mut domain_size = n; with_context!( self, "check FRI initial proof", @@ -305,7 +305,6 @@ impl, const D: usize> CircuitBuilder { cap_index ) ); - let mut old_x_index_bits = Vec::new(); // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. let mut subgroup_x = with_context!(self, "compute x from its index", { @@ -316,79 +315,63 @@ impl, const D: usize> CircuitBuilder { self.mul(g, phi) }); - let mut evaluations: Vec>> = Vec::new(); + // old_eval is the last derived evaluation; it will be checked for consistency with its + // committed "parent" value in the next iteration. + let mut old_eval = with_context!( + self, + "combine initial oracles", + self.fri_combine_initial( + &round_proof.initial_trees_proof, + alpha, + zeta, + subgroup_x, + precomputed_reduced_evals, + common_data, + ) + ); + for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() { - let next_domain_size = domain_size >> arity_bits; - let e_x = if i == 0 { - with_context!( - self, - "combine initial oracles", - self.fri_combine_initial( - &round_proof.initial_trees_proof, - alpha, - zeta, - subgroup_x, - precomputed_reduced_evals, - common_data, - ) + let evals = &round_proof.steps[i].evals; + + // Split x_index into the index of the coset x is in, and the index of x within that coset. + let coset_index_bits = x_index_bits[arity_bits..].to_vec(); + let x_index_within_coset_bits = &x_index_bits[..arity_bits]; + let x_index_within_coset = self.le_sum(x_index_within_coset_bits.iter()); + + // Check consistency with our old evaluation from the previous round. + self.random_access(x_index_within_coset, old_eval, evals.clone()); + + // Infer P(y) from {P(x)}_{x^arity=y}. + old_eval = with_context!( + self, + "infer evaluation using interpolation", + self.compute_evaluation( + subgroup_x, + &x_index_within_coset_bits, + arity_bits, + evals, + betas[i], ) - } else { - let last_evals = &evaluations[i - 1]; - // Infer P(y) from {P(x)}_{x^arity=y}. - with_context!( - self, - "infer evaluation using interpolation", - self.compute_evaluation( - subgroup_x, - &old_x_index_bits, - config.reduction_arity_bits[i - 1], - last_evals, - betas[i - 1], - ) - ) - }; - let evals = round_proof.steps[i].evals.clone(); - // Insert P(y) into the evaluation vector, since it wasn't included by the prover. - let high_x_index_bits = x_index_bits.split_off(arity_bits); - old_x_index_bits = x_index_bits; - let low_x_index = self.le_sum(old_x_index_bits.iter()); - self.random_access(low_x_index, e_x, evals.clone()); + ); + with_context!( self, "verify FRI round Merkle proof.", self.verify_merkle_proof_with_cap_index( - flatten_target(&evals), - &high_x_index_bits, + flatten_target(evals), + &coset_index_bits, cap_index, &proof.commit_phase_merkle_caps[i], &round_proof.steps[i].merkle_proof, ) ); - evaluations.push(evals); - if i > 0 { - // Update the point x to x^arity. - subgroup_x = self.exp_power_of_2(subgroup_x, config.reduction_arity_bits[i - 1]); - } - domain_size = next_domain_size; - x_index_bits = high_x_index_bits; + // Update the point x to x^arity. + subgroup_x = self.exp_power_of_2(subgroup_x, arity_bits); + + x_index_bits = coset_index_bits; } - let last_evals = evaluations.last().unwrap(); - let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); - let purported_eval = with_context!( - self, - "infer final evaluation using interpolation", - self.compute_evaluation( - subgroup_x, - &old_x_index_bits, - final_arity_bits, - last_evals, - *betas.last().unwrap(), - ) - ); - subgroup_x = self.exp_power_of_2(subgroup_x, final_arity_bits); - // Final check of FRI. After all the reductions, we check that the final polynomial is equal // to the one sent by the prover. let eval = with_context!( @@ -396,7 +379,7 @@ impl, const D: usize> CircuitBuilder { "evaluate final polynomial", proof.final_poly.eval_scalar(self, subgroup_x) ); - self.assert_equal_extension(eval, purported_eval); + self.assert_equal_extension(eval, old_eval); } } diff --git a/src/fri/verifier.rs b/src/fri/verifier.rs index 021ab122..63359cd4 100644 --- a/src/fri/verifier.rs +++ b/src/fri/verifier.rs @@ -19,7 +19,7 @@ use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place}; /// and P' is the FRI reduced polynomial. fn compute_evaluation, const D: usize>( x: F, - old_x_index: usize, + x_index_within_coset: usize, arity_bits: usize, last_evals: &[F::Extension], beta: F::Extension, @@ -32,8 +32,8 @@ fn compute_evaluation, const D: usize>( // The evaluation vector needs to be reordered first. let mut evals = last_evals.to_vec(); reverse_index_bits_in_place(&mut evals); - let rev_old_x_index = reverse_bits(old_x_index, arity_bits); - let coset_start = x * g.exp((arity - rev_old_x_index) as u64); + let rev_x_index_within_coset = reverse_bits(x_index_within_coset, arity_bits); + let coset_start = x * g.exp((arity - rev_x_index_within_coset) as u64); // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. let points = g .powers() @@ -258,79 +258,66 @@ fn fri_verifier_query_round, const D: usize>( ) -> Result<()> { let config = &common_data.config.fri_config; let x = challenger.get_challenge(); - let mut domain_size = n; let mut x_index = x.to_canonical_u64() as usize % n; fri_verify_initial_proof( x_index, &round_proof.initial_trees_proof, initial_merkle_caps, )?; - let mut old_x_index = 0; // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. let log_n = log2_strict(n); let mut subgroup_x = F::MULTIPLICATIVE_GROUP_GENERATOR * F::primitive_root_of_unity(log_n).exp(reverse_bits(x_index, log_n) as u64); - let mut evaluations: Vec> = Vec::new(); + // old_eval is the last derived evaluation; it will be checked for consistency with its + // committed "parent" value in the next iteration. + let mut old_eval = fri_combine_initial( + &round_proof.initial_trees_proof, + alpha, + zeta, + subgroup_x, + precomputed_reduced_evals, + common_data, + ); + for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() { let arity = 1 << arity_bits; - let next_domain_size = domain_size >> arity_bits; - let e_x = if i == 0 { - fri_combine_initial( - &round_proof.initial_trees_proof, - alpha, - zeta, - subgroup_x, - precomputed_reduced_evals, - common_data, - ) - } else { - let last_evals = &evaluations[i - 1]; - // Infer P(y) from {P(x)}_{x^arity=y}. - compute_evaluation( - subgroup_x, - old_x_index, - config.reduction_arity_bits[i - 1], - last_evals, - betas[i - 1], - ) - }; let evals = &round_proof.steps[i].evals; - // Insert P(y) into the evaluation vector, since it wasn't included by the prover. - ensure!(evals[x_index & (arity - 1)] == e_x); + + // Split x_index into the index of the coset x is in, and the index of x within that coset. + let coset_index = x_index >> arity_bits; + let x_index_within_coset = x_index & (arity - 1); + + // Check consistency with our old evaluation from the previous round. + ensure!(evals[x_index_within_coset] == old_eval); + + // Infer P(y) from {P(x)}_{x^arity=y}. + old_eval = compute_evaluation( + subgroup_x, + x_index_within_coset, + arity_bits, + evals, + betas[i], + ); + verify_merkle_proof( flatten(evals), - x_index >> arity_bits, + coset_index, &proof.commit_phase_merkle_caps[i], &round_proof.steps[i].merkle_proof, false, )?; - evaluations.push(evals.to_vec()); - if i > 0 { - // Update the point x to x^arity. - subgroup_x = subgroup_x.exp_power_of_2(config.reduction_arity_bits[i - 1]); - } - domain_size = next_domain_size; - old_x_index = x_index & (arity - 1); - x_index >>= arity_bits; + // Update the point x to x^arity. + subgroup_x = subgroup_x.exp_power_of_2(arity_bits); + + x_index = coset_index; } - let last_evals = evaluations.last().unwrap(); - let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); - let purported_eval = compute_evaluation( - subgroup_x, - old_x_index, - final_arity_bits, - last_evals, - *betas.last().unwrap(), - ); - subgroup_x = subgroup_x.exp_power_of_2(final_arity_bits); - // 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.into()) == purported_eval, + proof.final_poly.eval(subgroup_x.into()) == old_eval, "Final polynomial evaluation is invalid." );