diff --git a/plonky2/src/batch_fri/oracle.rs b/plonky2/src/batch_fri/oracle.rs index 192e374451..93474ee009 100644 --- a/plonky2/src/batch_fri/oracle.rs +++ b/plonky2/src/batch_fri/oracle.rs @@ -1,5 +1,5 @@ #[cfg(not(feature = "std"))] -use alloc::{format, vec::Vec}; +use alloc::{format, vec, vec::Vec}; use itertools::Itertools; use plonky2_field::extension::Extendable; @@ -19,6 +19,7 @@ use crate::hash::batch_merkle_tree::BatchMerkleTree; use crate::hash::hash_types::RichField; use crate::iop::challenger::Challenger; use crate::plonk::config::GenericConfig; +use crate::plonk::plonk_common::PlonkOracle; use crate::timed; use crate::util::reducing::ReducingFactor; use crate::util::timing::TimingTree; @@ -151,9 +152,15 @@ impl, C: GenericConfig, const D: usize> // where the `k_i`s are chosen such that each power of `alpha` appears only once in the final sum. // There are usually two batches for the openings at `zeta` and `g * zeta`. // The oracles used in Plonky2 are given in `FRI_ORACLES` in `plonky2/src/plonk/plonk_common.rs`. - for FriBatchInfo { point, polynomials } in &instance.batches { + for (idx, FriBatchInfo { point, polynomials }) in instance.batches.iter().enumerate() { + let is_zk = fri_params.hiding; + let nb_r_polys: usize = polynomials + .iter() + .map(|p| (p.oracle_index == PlonkOracle::R.index) as usize) + .sum(); + let last_poly = polynomials.len() - nb_r_polys * (idx == 0) as usize; // Collect the coefficients of all the polynomials in `polynomials`. - let polys_coeff = polynomials.iter().map(|fri_poly| { + let polys_coeff = polynomials[..last_poly].iter().map(|fri_poly| { &oracles[fri_poly.oracle_index].polynomials[fri_poly.polynomial_index] }); let composition_poly = timed!( @@ -165,6 +172,28 @@ impl, C: GenericConfig, const D: usize> quotient.coeffs.push(F::Extension::ZERO); // pad back to power of two alpha.shift_poly(&mut final_poly); final_poly += quotient; + + if is_zk && idx == 0 { + let degree = 1 << degree_bits[i]; + let mut composition_poly = PolynomialCoeffs::empty(); + polynomials[last_poly..] + .iter() + .enumerate() + .for_each(|(i, fri_poly)| { + let mut cur_coeffs = oracles[fri_poly.oracle_index].polynomials + [fri_poly.polynomial_index] + .coeffs + .clone(); + cur_coeffs.reverse(); + cur_coeffs.extend(vec![F::ZERO; degree * i]); + cur_coeffs.reverse(); + cur_coeffs.extend(vec![F::ZERO; 2 * degree - cur_coeffs.len()]); + composition_poly += PolynomialCoeffs { coeffs: cur_coeffs }; + }); + + alpha.shift_poly(&mut final_poly); + final_poly += composition_poly.to_extension(); + } } assert_eq!(final_poly.len(), 1 << degree_bits[i]); diff --git a/plonky2/src/batch_fri/prover.rs b/plonky2/src/batch_fri/prover.rs index 770c2c2285..56c908e963 100644 --- a/plonky2/src/batch_fri/prover.rs +++ b/plonky2/src/batch_fri/prover.rs @@ -30,8 +30,8 @@ pub fn batch_fri_proof, C: GenericConfig, fri_params: &FriParams, timing: &mut TimingTree, ) -> FriProof { - let n = lde_polynomial_coeffs.len(); - assert_eq!(lde_polynomial_values[0].len(), n); + let mut n = lde_polynomial_coeffs.len(); + assert_eq!(lde_polynomial_values[0].len(), lde_polynomial_coeffs.len()); // The polynomial vectors should be sorted by degree, from largest to smallest, with no duplicate degrees. assert!(lde_polynomial_values .windows(2) @@ -49,6 +49,12 @@ pub fn batch_fri_proof, C: GenericConfig, } assert_eq!(cur_poly_index, lde_polynomial_values.len()); + // In the zk case, the final polynomial polynomial to be reduced has degree double that + // of the original batch FRI polynomial. + if fri_params.hiding { + n /= 2; + } + // Commit phase let (trees, final_coeffs) = timed!( timing, diff --git a/plonky2/src/batch_fri/recursive_verifier.rs b/plonky2/src/batch_fri/recursive_verifier.rs index 95731ba403..531da17198 100644 --- a/plonky2/src/batch_fri/recursive_verifier.rs +++ b/plonky2/src/batch_fri/recursive_verifier.rs @@ -2,6 +2,7 @@ use alloc::{format, vec::Vec}; use itertools::Itertools; +use plonky2_field::types::Field; use crate::field::extension::Extendable; use crate::fri::proof::{ @@ -15,6 +16,7 @@ use crate::iop::ext_target::{flatten_target, ExtensionTarget}; use crate::iop::target::{BoolTarget, Target}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::config::{AlgebraicHasher, GenericConfig}; +use crate::plonk::plonk_common::PlonkOracle; use crate::util::reducing::ReducingFactorTarget; use crate::with_context; @@ -62,7 +64,8 @@ impl, const D: usize> CircuitBuilder { PrecomputedReducedOpeningsTarget::from_os_and_alpha( opn, challenges.fri_alpha, - self + self, + params.hiding, ) ); precomputed_reduced_evals.push(pre); @@ -165,13 +168,24 @@ impl, const D: usize> CircuitBuilder { let mut alpha = ReducingFactorTarget::new(alpha); let mut sum = self.zero_extension(); - for (batch, reduced_openings) in instance[index] + for (idx, (batch, reduced_openings)) in instance[index] .batches .iter() .zip(&precomputed_reduced_evals.reduced_openings_at_point) + .enumerate() { + // If we are in the zk case, the `R` polynomial (the last polynomials in the first batch) is added to + // the batch polynomial independently, without being quotiented. So the final polynomial becomes: + // `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i) + alpha^n R(X)`, where `n` is the degree + // of the batch polynomial. let FriBatchInfoTarget { point, polynomials } = batch; - let evals = polynomials + let is_zk = params.hiding; + let nb_r_polys: usize = polynomials + .iter() + .map(|p| (p.oracle_index == PlonkOracle::R.index) as usize) + .sum(); + let last_poly = polynomials.len() - nb_r_polys * (idx == 0) as usize; + let evals = polynomials[..last_poly] .iter() .map(|p| { let poly_blinding = instance[index].oracles[p.oracle_index].blinding; @@ -184,6 +198,31 @@ impl, const D: usize> CircuitBuilder { let denominator = self.sub_extension(subgroup_x, *point); sum = alpha.shift(sum, self); sum = self.div_add_extension(numerator, denominator, sum); + + // If we are in the zk case, we still have to add `R(X)` to the batch. + if is_zk && idx == 0 { + polynomials[last_poly..] + .iter() + .enumerate() + .for_each(|(i, p)| { + let poly_blinding = instance[index].oracles[p.oracle_index].blinding; + let salted = params.hiding && poly_blinding; + let eval = proof.unsalted_eval(p.oracle_index, p.polynomial_index, salted); + sum = alpha.shift(sum, self); + let val = self + .constant_extension(F::Extension::from_canonical_u32((i == 0) as u32)); + let power = + self.exp_power_of_2_extension(subgroup_x, i * params.degree_bits); + let pi = + self.constant_extension(F::Extension::from_canonical_u32(i as u32)); + let power = self.mul_extension(power, pi); + let shift_val = self.add_extension(val, power); + + let eval_extension = eval.to_ext_target(self.zero()); + let tmp = self.mul_extension(eval_extension, shift_val); + sum = self.add_extension(sum, tmp); + }); + } } sum @@ -210,7 +249,7 @@ impl, const D: usize> CircuitBuilder { Self::assert_noncanonical_indices_ok(¶ms.config); let mut x_index_bits = self.low_bits(x_index, n, F::BITS); - let cap_index = + let initial_cap_index = self.le_sum(x_index_bits[x_index_bits.len() - params.config.cap_height..].iter()); with_context!( self, @@ -221,7 +260,7 @@ impl, const D: usize> CircuitBuilder { &x_index_bits, &round_proof.initial_trees_proof, initial_merkle_caps, - cap_index + initial_cap_index ) ); @@ -252,6 +291,11 @@ impl, const D: usize> CircuitBuilder { ); batch_index += 1; + // In case of zk, the finaly polynomial's degree bits is increased by 1. + let cap_index = self.le_sum( + x_index_bits[x_index_bits.len() + params.hiding as usize - params.config.cap_height..] + .iter(), + ); for (i, &arity_bits) in params.reduction_arity_bits.iter().enumerate() { let evals = &round_proof.steps[i].evals; diff --git a/plonky2/src/batch_fri/verifier.rs b/plonky2/src/batch_fri/verifier.rs index b7dd552b70..9402aecd7c 100644 --- a/plonky2/src/batch_fri/verifier.rs +++ b/plonky2/src/batch_fri/verifier.rs @@ -17,6 +17,7 @@ use crate::hash::hash_types::RichField; use crate::hash::merkle_proofs::{verify_batch_merkle_proof_to_cap, verify_merkle_proof_to_cap}; use crate::hash::merkle_tree::MerkleCap; use crate::plonk::config::{GenericConfig, Hasher}; +use crate::plonk::plonk_common::PlonkOracle; use crate::util::reducing::ReducingFactor; use crate::util::reverse_bits; @@ -46,7 +47,8 @@ pub fn verify_batch_fri_proof< let mut precomputed_reduced_evals = Vec::with_capacity(openings.len()); for opn in openings { - let pre = PrecomputedReducedOpenings::from_os_and_alpha(opn, challenges.fri_alpha); + let pre = + PrecomputedReducedOpenings::from_os_and_alpha(opn, challenges.fri_alpha, params.hiding); precomputed_reduced_evals.push(pre); } let degree_bits = degree_bits @@ -123,13 +125,24 @@ fn batch_fri_combine_initial< let mut alpha = ReducingFactor::new(alpha); let mut sum = F::Extension::ZERO; - for (batch, reduced_openings) in instances[index] + // If we are in the zk case, the `R` polynomial (the last polynomials in the first batch) is added to + // the batch polynomial independently, without being quotiented. So the final polynomial becomes: + // `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i) + alpha^n R(X)`, where `n` is the degree + // of the batch polynomial. + for (idx, (batch, reduced_openings)) in instances[index] .batches .iter() .zip(&precomputed_reduced_evals.reduced_openings_at_point) + .enumerate() { let FriBatchInfo { point, polynomials } = batch; - let evals = polynomials + let is_zk = params.hiding; + let nb_r_polys: usize = polynomials + .iter() + .map(|p| (p.oracle_index == PlonkOracle::R.index) as usize) + .sum(); + let last_poly = polynomials.len() - nb_r_polys * (idx == 0) as usize; + let evals = polynomials[..last_poly] .iter() .map(|p| { let poly_blinding = instances[index].oracles[p.oracle_index].blinding; @@ -142,6 +155,23 @@ fn batch_fri_combine_initial< let denominator = subgroup_x - *point; sum = alpha.shift(sum); sum += numerator / denominator; + + // If we are in the zk case, we still have to add `R(X)` to the batch. + if is_zk && idx == 0 { + polynomials[last_poly..] + .iter() + .enumerate() + .for_each(|(i, p)| { + let poly_blinding = instances[index].oracles[p.oracle_index].blinding; + let salted = params.hiding && poly_blinding; + let eval = proof.unsalted_eval(p.oracle_index, p.polynomial_index, salted); + sum = alpha.shift(sum); + let shift_val = F::Extension::from_canonical_usize((i == 0) as usize) + + subgroup_x.exp_power_of_2(i * params.degree_bits) + * F::Extension::from_canonical_usize(i); + sum += F::Extension::from_basefield(eval) * shift_val; + }); + } } sum diff --git a/plonky2/src/fri/mod.rs b/plonky2/src/fri/mod.rs index 5f18600c3c..25b02e2c57 100644 --- a/plonky2/src/fri/mod.rs +++ b/plonky2/src/fri/mod.rs @@ -51,6 +51,7 @@ impl FriConfig { self.rate_bits, self.cap_height, self.num_query_rounds, + hiding, ); FriParams { config: self.clone(), @@ -87,7 +88,7 @@ pub struct FriParams { impl FriParams { pub fn total_arities(&self) -> usize { - self.reduction_arity_bits.iter().sum() + self.reduction_arity_bits.iter().sum::() } pub(crate) fn max_arity_bits(&self) -> Option { @@ -103,7 +104,7 @@ impl FriParams { } pub fn final_poly_bits(&self) -> usize { - self.degree_bits - self.total_arities() + self.degree_bits + self.hiding as usize - self.total_arities() } pub fn final_poly_len(&self) -> usize { diff --git a/plonky2/src/fri/oracle.rs b/plonky2/src/fri/oracle.rs index 3e1ac781b1..dea74dcf23 100644 --- a/plonky2/src/fri/oracle.rs +++ b/plonky2/src/fri/oracle.rs @@ -1,5 +1,5 @@ #[cfg(not(feature = "std"))] -use alloc::{format, vec::Vec}; +use alloc::{format, vec, vec::Vec}; use itertools::Itertools; use plonky2_field::types::Field; @@ -17,6 +17,7 @@ use crate::hash::hash_types::RichField; use crate::hash::merkle_tree::MerkleTree; use crate::iop::challenger::Challenger; use crate::plonk::config::GenericConfig; +use crate::plonk::plonk_common::PlonkOracle; use crate::timed; use crate::util::reducing::ReducingFactor; use crate::util::timing::TimingTree; @@ -194,9 +195,23 @@ impl, C: GenericConfig, const D: usize> // where the `k_i`s are chosen such that each power of `alpha` appears only once in the final sum. // There are usually two batches for the openings at `zeta` and `g * zeta`. // The oracles used in Plonky2 are given in `FRI_ORACLES` in `plonky2/src/plonk/plonk_common.rs`. - for FriBatchInfo { point, polynomials } in &instance.batches { - // Collect the coefficients of all the polynomials in `polynomials`. - let polys_coeff = polynomials.iter().map(|fri_poly| { + // + // If we are in the zk case, the `R` polynomial (the last polynomials in the first batch) is added to + // the batch polynomial independently, without being quotiented. So the final polynomial becomes: + // `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i) + alpha^n R(X)`, where `n` is the degree + // of the batch polynomial. + // Then, since the degree of `R` is double that of the batch polynomial in our cimplementation, we need to + // compute one extra step in FRI to reach the correct degree. + let is_zk = fri_params.hiding; + + for (idx, FriBatchInfo { point, polynomials }) in instance.batches.iter().enumerate() { + let nb_r_polys: usize = polynomials + .iter() + .map(|p| (p.oracle_index == PlonkOracle::R.index) as usize) + .sum(); + let last_poly = polynomials.len() - nb_r_polys * (idx == 0) as usize; + // Collect the coefficients of all the polynomials in `polynomials` until `last_poly`. + let polys_coeff = polynomials[..last_poly].iter().map(|fri_poly| { &oracles[fri_poly.oracle_index].polynomials[fri_poly.polynomial_index] }); let composition_poly = timed!( @@ -208,6 +223,29 @@ impl, C: GenericConfig, const D: usize> quotient.coeffs.push(F::Extension::ZERO); // pad back to power of two alpha.shift_poly(&mut final_poly); final_poly += quotient; + + // If we are in the zk case, we still have to add `R(X)` to the batch. + if is_zk && idx == 0 { + let degree = 1 << oracles[0].degree_log; + let mut composition_poly = PolynomialCoeffs::empty(); + polynomials[last_poly..] + .iter() + .enumerate() + .for_each(|(i, fri_poly)| { + let mut cur_coeffs = oracles[fri_poly.oracle_index].polynomials + [fri_poly.polynomial_index] + .coeffs + .clone(); + cur_coeffs.reverse(); + cur_coeffs.extend(vec![F::ZERO; degree * i]); + cur_coeffs.reverse(); + cur_coeffs.extend(vec![F::ZERO; 2 * degree - cur_coeffs.len()]); + composition_poly += PolynomialCoeffs { coeffs: cur_coeffs }; + }); + + alpha.shift_poly(&mut final_poly); + final_poly += composition_poly.to_extension(); + } } let lde_final_poly = final_poly.lde(fri_params.config.rate_bits); diff --git a/plonky2/src/fri/proof.rs b/plonky2/src/fri/proof.rs index 6c8145eca0..f2b57d45ae 100644 --- a/plonky2/src/fri/proof.rs +++ b/plonky2/src/fri/proof.rs @@ -144,8 +144,8 @@ impl, H: Hasher, const D: usize> FriProof, H: Hasher, const D: usize> FriProof>= reduction_arity_bits[i]; + let index_within_coset = index & ((1 << params.reduction_arity_bits[i]) - 1); + index >>= params.reduction_arity_bits[i]; steps_indices[i].push(index); let mut evals = query_step.evals; // Remove the element that can be inferred. @@ -215,7 +215,7 @@ impl, H: Hasher, const D: usize> FriProof>= reduction_arity_bits[j]; + index >>= params.reduction_arity_bits[j]; let query_step = FriQueryStep { evals: steps_evals[j][i].clone(), merkle_proof: steps_proofs[j][i].clone(), @@ -256,8 +256,8 @@ impl, H: Hasher, const D: usize> CompressedFriPr } = &challenges.fri_challenges; let mut fri_inferred_elements = fri_inferred_elements.0.into_iter(); let cap_height = params.config.cap_height; - let reduction_arity_bits = ¶ms.reduction_arity_bits; - let num_reductions = reduction_arity_bits.len(); + + let num_reductions = params.reduction_arity_bits.len(); let num_initial_trees = query_round_proofs .initial_trees_proofs .values() @@ -274,7 +274,8 @@ impl, H: Hasher, const D: usize> CompressedFriPr let mut steps_evals = vec![vec![]; num_reductions]; let mut steps_proofs = vec![vec![]; num_reductions]; let height = params.degree_bits + params.config.rate_bits; - let heights = reduction_arity_bits + let heights = params + .reduction_arity_bits .iter() .scan(height, |acc, &bits| { *acc -= bits; @@ -295,8 +296,8 @@ impl, H: Hasher, const D: usize> CompressedFriPr initial_trees_proofs[i].push(proof); } for i in 0..num_reductions { - let index_within_coset = index & ((1 << reduction_arity_bits[i]) - 1); - index >>= reduction_arity_bits[i]; + let index_within_coset = index & ((1 << params.reduction_arity_bits[i]) - 1); + index >>= params.reduction_arity_bits[i]; let FriQueryStep { mut evals, merkle_proof, @@ -324,7 +325,10 @@ impl, H: Hasher, const D: usize> CompressedFriPr .map(|(ls, is, ps)| decompress_merkle_proofs(ls, is, &ps, height, cap_height)) .collect::>(); let steps_proofs = izip!(&steps_evals, &steps_indices, steps_proofs, heights) - .map(|(ls, is, ps, h)| decompress_merkle_proofs(ls, is, &ps, h, cap_height)) + .map(|(ls, is, ps, h)| { + let cur_h = if params.hiding { h + 1 } else { h }; + decompress_merkle_proofs(ls, is, &ps, cur_h, cap_height) + }) .collect::>(); let mut decompressed_query_proofs = Vec::with_capacity(num_reductions); diff --git a/plonky2/src/fri/prover.rs b/plonky2/src/fri/prover.rs index b385fb5369..aed566909f 100644 --- a/plonky2/src/fri/prover.rs +++ b/plonky2/src/fri/prover.rs @@ -28,8 +28,13 @@ pub fn fri_proof, C: GenericConfig, const fri_params: &FriParams, timing: &mut TimingTree, ) -> FriProof { - let n = lde_polynomial_values.len(); - assert_eq!(lde_polynomial_coeffs.len(), n); + // In the zk case, the polynomial to be reduced has degree double that of the original batch FRI polynomial. + let n = if fri_params.hiding { + lde_polynomial_values.len() / 2 + } else { + lde_polynomial_values.len() + }; + assert_eq!(lde_polynomial_coeffs.len(), lde_polynomial_values.len()); // Commit phase let (trees, final_coeffs) = timed!( @@ -76,6 +81,7 @@ fn fri_committed_trees, C: GenericConfig, let mut trees = Vec::with_capacity(fri_params.reduction_arity_bits.len()); let mut shift = F::MULTIPLICATIVE_GROUP_GENERATOR; + for arity_bits in &fri_params.reduction_arity_bits { let arity = 1 << arity_bits; @@ -196,22 +202,33 @@ fn fri_prover_query_round< fri_params: &FriParams, ) -> FriQueryRound { let mut query_steps = Vec::new(); + let initial_proof = initial_merkle_trees .iter() - .map(|t| (t.get(x_index).to_vec(), t.prove(x_index))) + .filter_map(|t| { + if !t.leaves.is_empty() { + Some((t.get(x_index).to_vec(), t.prove(x_index))) + } else { + None + } + }) .collect::>(); - for (i, tree) in trees.iter().enumerate() { - let arity_bits = fri_params.reduction_arity_bits[i]; - let evals = unflatten(tree.get(x_index >> arity_bits)); - let merkle_proof = tree.prove(x_index >> arity_bits); - query_steps.push(FriQueryStep { - evals, - merkle_proof, - }); - - x_index >>= arity_bits; + for (i, tree) in trees.iter().enumerate() { + if !tree.leaves.is_empty() { + let arity_bits = fri_params.reduction_arity_bits[i]; + let evals = unflatten(tree.get(x_index >> arity_bits)); + let merkle_proof = tree.prove(x_index >> arity_bits); + + query_steps.push(FriQueryStep { + evals, + merkle_proof, + }); + + x_index >>= arity_bits; + } } + FriQueryRound { initial_trees_proof: FriInitialTreeProof { evals_proofs: initial_proof, diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 16e02f6a81..41214477fc 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -2,6 +2,7 @@ use alloc::{format, vec::Vec}; use itertools::Itertools; +use plonky2_field::types::Field; use crate::field::extension::Extendable; use crate::fri::proof::{ @@ -18,10 +19,15 @@ use crate::iop::ext_target::{flatten_target, ExtensionTarget}; use crate::iop::target::{BoolTarget, Target}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::config::{AlgebraicHasher, GenericConfig}; +use crate::plonk::plonk_common::PlonkOracle; use crate::util::reducing::ReducingFactorTarget; use crate::util::{log2_strict, reverse_index_bits_in_place}; use crate::with_context; +// Length by which the Merkle proof is increased in the case of zk. +// This corresponds to the extra degree bit of the final computed polynomial. +const ZK_EXTRA_MERKLE_LENGTH: usize = 1; + impl, const D: usize> CircuitBuilder { /// 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. @@ -145,7 +151,8 @@ impl, const D: usize> CircuitBuilder { PrecomputedReducedOpeningsTarget::from_os_and_alpha( openings, challenges.fri_alpha, - self + self, + params.hiding, ) ); @@ -226,13 +233,24 @@ impl, const D: usize> CircuitBuilder { let mut alpha = ReducingFactorTarget::new(alpha); let mut sum = self.zero_extension(); - for (batch, reduced_openings) in instance + // If we are in the zk case, the `R` polynomial (the last polynomials in the first batch) is added to + // the batch polynomial independently, without being quotiented. So the final polynomial becomes: + // `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i) + alpha^n R(X)`, where `n` is the degree + // of the batch polynomial. + for (idx, (batch, reduced_openings)) in instance .batches .iter() .zip(&precomputed_reduced_evals.reduced_openings_at_point) + .enumerate() { let FriBatchInfoTarget { point, polynomials } = batch; - let evals = polynomials + let is_zk = params.hiding; + let nb_r_polys: usize = polynomials + .iter() + .map(|p| (p.oracle_index == PlonkOracle::R.index) as usize) + .sum(); + let last_poly = polynomials.len() - nb_r_polys * (idx == 0) as usize; + let evals = polynomials[..last_poly] .iter() .map(|p| { let poly_blinding = instance.oracles[p.oracle_index].blinding; @@ -245,6 +263,31 @@ impl, const D: usize> CircuitBuilder { let denominator = self.sub_extension(subgroup_x, *point); sum = alpha.shift(sum, self); sum = self.div_add_extension(numerator, denominator, sum); + + // If we are in the zk case, we still have to add `R(X)` to the batch. + if is_zk && idx == 0 { + polynomials[last_poly..] + .iter() + .enumerate() + .for_each(|(i, p)| { + let poly_blinding = instance.oracles[p.oracle_index].blinding; + let salted = params.hiding && poly_blinding; + let eval = proof.unsalted_eval(p.oracle_index, p.polynomial_index, salted); + sum = alpha.shift(sum, self); + let val = self + .constant_extension(F::Extension::from_canonical_u32((i == 0) as u32)); + let power = + self.exp_power_of_2_extension(subgroup_x, i * params.degree_bits); + let pi = + self.constant_extension(F::Extension::from_canonical_u32(i as u32)); + let power = self.mul_extension(power, pi); + let shift_val = self.add_extension(val, power); + + let eval_extension = eval.to_ext_target(self.zero()); + let tmp = self.mul_extension(eval_extension, shift_val); + sum = self.add_extension(sum, tmp); + }); + } } sum @@ -271,7 +314,7 @@ impl, const D: usize> CircuitBuilder { Self::assert_noncanonical_indices_ok(¶ms.config); let mut x_index_bits = self.low_bits(x_index, n_log, F::BITS); - let cap_index = + let initial_cap_index = self.le_sum(x_index_bits[x_index_bits.len() - params.config.cap_height..].iter()); with_context!( self, @@ -280,7 +323,7 @@ impl, const D: usize> CircuitBuilder { &x_index_bits, &round_proof.initial_trees_proof, initial_merkle_caps, - cap_index + initial_cap_index ) ); @@ -308,6 +351,11 @@ impl, const D: usize> CircuitBuilder { ) ); + // In case of zk, the finaly polynomial's degree bits is increased by 1. + let cap_index = self.le_sum( + x_index_bits[x_index_bits.len() + params.hiding as usize - params.config.cap_height..] + .iter(), + ); for (i, &arity_bits) in params.reduction_arity_bits.iter().enumerate() { let evals = &round_proof.steps[i].evals; @@ -397,6 +445,7 @@ impl, const D: usize> CircuitBuilder { let query_round_proofs = (0..num_queries) .map(|_| self.add_virtual_fri_query(num_leaves_per_oracle, params)) .collect(); + let final_poly = self.add_virtual_poly_coeff_ext(params.final_poly_len()); let pow_witness = self.add_virtual_target(); FriProofTarget { @@ -420,6 +469,9 @@ impl, const D: usize> CircuitBuilder { self.add_virtual_fri_initial_trees_proof(num_leaves_per_oracle, merkle_proof_len); let mut steps = Vec::with_capacity(params.reduction_arity_bits.len()); + + // In case of zk, the finaly polynomial's degree bits is increased by `ZK_EXTRA_MERKLE_LENGTH`. + merkle_proof_len += ZK_EXTRA_MERKLE_LENGTH * params.hiding as usize; for &arity_bits in ¶ms.reduction_arity_bits { assert!(merkle_proof_len >= arity_bits); merkle_proof_len -= arity_bits; @@ -472,11 +524,21 @@ impl PrecomputedReducedOpeningsTarget { openings: &FriOpeningsTarget, alpha: ExtensionTarget, builder: &mut CircuitBuilder, + is_zk: bool, ) -> Self { + // We commit to two extra polynomials in the case of zk: + // the lower and higher coefficients of the random `R` polynomial. + // Those `R` polynomials should not be taken into account when + // computing the reduced openings. + let nb_r_polys = is_zk as usize * 2; let reduced_openings_at_point = openings .batches .iter() - .map(|batch| ReducingFactorTarget::new(alpha).reduce(&batch.values, builder)) + .enumerate() + .map(|(i, batch)| { + let last_values = batch.values.len() - nb_r_polys * (i == 0) as usize; + ReducingFactorTarget::new(alpha).reduce(&batch.values[..last_values], builder) + }) .collect(); Self { reduced_openings_at_point, diff --git a/plonky2/src/fri/reduction_strategies.rs b/plonky2/src/fri/reduction_strategies.rs index e7f5d799ff..b0d0097ab1 100644 --- a/plonky2/src/fri/reduction_strategies.rs +++ b/plonky2/src/fri/reduction_strategies.rs @@ -33,11 +33,22 @@ impl FriReductionStrategy { rate_bits: usize, cap_height: usize, num_queries: usize, + hiding: bool, ) -> Vec { match self { - FriReductionStrategy::Fixed(reduction_arity_bits) => reduction_arity_bits.to_vec(), + FriReductionStrategy::Fixed(reduction_arity_bits) => { + // In the case of zk, we need an extra FRI step to divide the polynomial's degree by two. + if hiding { + let mut tmp = vec![1]; + tmp.extend(reduction_arity_bits); + tmp + } else { + reduction_arity_bits.to_vec() + } + } &FriReductionStrategy::ConstantArityBits(arity_bits, final_poly_bits) => { - let mut result = Vec::new(); + // In the case of zk, we need an extra FRI step to divide the polynomial's degree by two. + let mut result = if hiding { vec![1] } else { Vec::new() }; while degree_bits > final_poly_bits && degree_bits + rate_bits - arity_bits >= cap_height { diff --git a/plonky2/src/fri/validate_shape.rs b/plonky2/src/fri/validate_shape.rs index be675ed61b..be974cb92d 100644 --- a/plonky2/src/fri/validate_shape.rs +++ b/plonky2/src/fri/validate_shape.rs @@ -64,7 +64,13 @@ where } ensure!(steps.len() == params.reduction_arity_bits.len()); - let mut codeword_len_bits = params.lde_bits(); + + // In the case of zk, the final polynomial has degree double that of the original batch polynomial. + let mut codeword_len_bits = if params.hiding { + params.lde_bits() + 1 + } else { + params.lde_bits() + }; for (step, arity_bits) in steps.iter().zip(¶ms.reduction_arity_bits) { let FriQueryStep { evals, diff --git a/plonky2/src/fri/verifier.rs b/plonky2/src/fri/verifier.rs index 89faa0f6e7..76fba140ab 100644 --- a/plonky2/src/fri/verifier.rs +++ b/plonky2/src/fri/verifier.rs @@ -14,6 +14,7 @@ use crate::hash::hash_types::RichField; use crate::hash::merkle_proofs::verify_merkle_proof_to_cap; use crate::hash::merkle_tree::MerkleCap; use crate::plonk::config::{GenericConfig, Hasher}; +use crate::plonk::plonk_common::PlonkOracle; use crate::util::reducing::ReducingFactor; use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place}; @@ -85,8 +86,11 @@ pub fn verify_fri_proof< "Number of query rounds does not match config." ); - let precomputed_reduced_evals = - PrecomputedReducedOpenings::from_os_and_alpha(openings, challenges.fri_alpha); + let precomputed_reduced_evals = PrecomputedReducedOpenings::from_os_and_alpha( + openings, + challenges.fri_alpha, + params.hiding, + ); for (&x_index, round_proof) in challenges .fri_query_indices .iter() @@ -137,13 +141,24 @@ pub(crate) fn fri_combine_initial< let mut alpha = ReducingFactor::new(alpha); let mut sum = F::Extension::ZERO; - for (batch, reduced_openings) in instance + // If we are in the zk case, the `R` polynomial (the last polynomials in the first batch) is added to + // the batch polynomial independently, without being quotiented. So the final polynomial becomes: + // `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i) + alpha^n R(X)`, where `n` is the degree + // of the batch polynomial. + for (idx, (batch, reduced_openings)) in instance .batches .iter() .zip(&precomputed_reduced_evals.reduced_openings_at_point) + .enumerate() { let FriBatchInfo { point, polynomials } = batch; - let evals = polynomials + let is_zk = params.hiding; + let nb_r_polys: usize = polynomials + .iter() + .map(|p| (p.oracle_index == PlonkOracle::R.index) as usize) + .sum(); + let last_poly = polynomials.len() - nb_r_polys * (idx == 0) as usize; + let evals = polynomials[..last_poly] .iter() .map(|p| { let poly_blinding = instance.oracles[p.oracle_index].blinding; @@ -156,6 +171,23 @@ pub(crate) fn fri_combine_initial< let denominator = subgroup_x - *point; sum = alpha.shift(sum); sum += numerator / denominator; + + // If we are in the zk case, we still have to add `R(X)` to the batch. + if is_zk && idx == 0 { + polynomials[last_poly..] + .iter() + .enumerate() + .for_each(|(i, p)| { + let poly_blinding = instance.oracles[p.oracle_index].blinding; + let salted = params.hiding && poly_blinding; + let eval = proof.unsalted_eval(p.oracle_index, p.polynomial_index, salted); + sum = alpha.shift(sum); + let shift_val = F::Extension::from_canonical_usize((i == 0) as usize) + + subgroup_x.exp_power_of_2(i * params.degree_bits) + * F::Extension::from_canonical_usize(i); + sum += F::Extension::from_basefield(eval) * shift_val; + }); + } } sum @@ -248,11 +280,24 @@ pub(crate) struct PrecomputedReducedOpenings, const } impl, const D: usize> PrecomputedReducedOpenings { - pub(crate) fn from_os_and_alpha(openings: &FriOpenings, alpha: F::Extension) -> Self { + pub(crate) fn from_os_and_alpha( + openings: &FriOpenings, + alpha: F::Extension, + is_zk: bool, + ) -> Self { + // We commit to two extra polynomials in the case of zk: + // the lower and higher coefficients of the random `R` polynomial. + // Those `R` polynomials should not be taken into account when + // computing the reduced openings. + let nb_r_polys = is_zk as usize * 2; let reduced_openings_at_point = openings .batches .iter() - .map(|batch| ReducingFactor::new(alpha).reduce(batch.values.iter())) + .enumerate() + .map(|(i, batch)| { + let last_values = batch.values.len() - nb_r_polys * (i == 0) as usize; + ReducingFactor::new(alpha).reduce(batch.values[..last_values].iter()) + }) .collect(); Self { reduced_openings_at_point, diff --git a/plonky2/src/fri/witness_util.rs b/plonky2/src/fri/witness_util.rs index 041d43c134..12d742ba4b 100644 --- a/plonky2/src/fri/witness_util.rs +++ b/plonky2/src/fri/witness_util.rs @@ -1,3 +1,6 @@ +#[cfg(not(feature = "std"))] +use alloc::vec; + use anyhow::Result; use itertools::Itertools; @@ -60,6 +63,7 @@ where for (&t, &x) in st.evals.iter().zip_eq(&s.evals) { witness.set_extension_target(t, x)?; } + for (&t, &x) in st .merkle_proof .siblings diff --git a/plonky2/src/iop/witness.rs b/plonky2/src/iop/witness.rs index abf1a779da..b5a4e5cca9 100644 --- a/plonky2/src/iop/witness.rs +++ b/plonky2/src/iop/witness.rs @@ -2,7 +2,7 @@ use alloc::{vec, vec::Vec}; use core::iter::zip; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, ensure, Result}; use hashbrown::HashMap; use itertools::{zip_eq, Itertools}; @@ -45,6 +45,24 @@ pub trait WitnessWrite { Ok(()) } + fn set_opt_cap_target>( + &mut self, + opt_ct: &Option, + opt_value: &Option>, + ) -> Result<()> + where + F: RichField, + { + ensure!(opt_ct.is_some() == opt_value.is_some()); + if let (Some(ct), Some(value)) = (opt_ct, opt_value) { + for (ht, h) in ct.0.iter().zip(&value.0) { + self.set_hash_target(*ht, *h)?; + } + } + + Ok(()) + } + fn set_extension_target( &mut self, et: ExtensionTarget, @@ -128,6 +146,7 @@ pub trait WitnessWrite { &proof.plonk_zs_partial_products_cap, )?; self.set_cap_target(&proof_target.quotient_polys_cap, &proof.quotient_polys_cap)?; + self.set_opt_cap_target(&proof_target.opt_random_r, &proof.opt_random_r)?; self.set_fri_openings( &proof_target.openings.to_fri_openings(), diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index 441e810374..5b0a86a8bf 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -576,7 +576,7 @@ impl, const D: usize> CommonCircuitData { } fn fri_oracles(&self) -> Vec { - vec![ + let mut oracles = vec![ FriOracleInfo { num_polys: self.num_preprocessed_polys(), blinding: PlonkOracle::CONSTANTS_SIGMAS.blinding, @@ -593,7 +593,15 @@ impl, const D: usize> CommonCircuitData { num_polys: self.num_quotient_polys(), blinding: PlonkOracle::QUOTIENT.blinding, }, - ] + ]; + if self.num_r_polys() > 0 { + oracles.push(FriOracleInfo { + num_polys: self.num_r_polys(), + blinding: PlonkOracle::R.blinding, + }); + } + + oracles } fn fri_preprocessed_polys(&self) -> Vec { @@ -648,19 +656,69 @@ impl, const D: usize> CommonCircuitData { ..self.num_zs_partial_products_polys() + self.num_all_lookup_polys(), ) } - pub(crate) const fn num_quotient_polys(&self) -> usize { - self.config.num_challenges * self.quotient_degree_factor + + /// Returns the value of `h`, corresponding to the degree of random polynomials added to the quotient polynomial chunks. + pub(crate) fn computed_h(&self) -> usize { + assert!(self.config.zero_knowledge); + let arities: Vec = self + .fri_params + .reduction_arity_bits + .iter() + .map(|x| 1 << x) + .collect(); + let total_fri_folding_points: usize = arities.iter().map(|x| x - 1).sum::(); + let final_poly_coeffs: usize = + (1 << (self.fri_params.degree_bits + 1)) / arities.iter().product::(); + let fri_openings = self.config.fri_config.num_query_rounds + * (1 + D * total_fri_folding_points + D * final_poly_coeffs); + // Number of FRI openings + n_deep + fri_openings + D + } + + pub(crate) fn num_quotient_polys(&self) -> usize { + // In the case of zk, the quotient polynomials are split into smaller chunks. + if self.config.zero_knowledge { + let h = self.computed_h(); + let d = self.degree() - h; + assert!(self.degree() > h); + + let total_nb_chunks = self.quotient_degree().div_ceil(d); + self.config.num_challenges * total_nb_chunks + } else { + self.config.num_challenges * self.quotient_degree_factor + } + } + + /// Returns the information for the random `R` polynomials. + fn fri_r_polys(&self) -> Vec { + FriPolynomialInfo::from_range(PlonkOracle::R.index, 0..self.num_r_polys()) + } + + pub(crate) const fn num_r_polys(&self) -> usize { + 2 * (self.config.zero_knowledge as usize) } fn fri_all_polys(&self) -> Vec { - [ - self.fri_preprocessed_polys(), - self.fri_wire_polys(), - self.fri_zs_partial_products_polys(), - self.fri_quotient_polys(), - self.fri_lookup_polys(), - ] - .concat() + if self.num_r_polys() > 0 { + [ + self.fri_preprocessed_polys(), + self.fri_wire_polys(), + self.fri_zs_partial_products_polys(), + self.fri_quotient_polys(), + self.fri_lookup_polys(), + self.fri_r_polys(), + ] + .concat() + } else { + [ + self.fri_preprocessed_polys(), + self.fri_wire_polys(), + self.fri_zs_partial_products_polys(), + self.fri_quotient_polys(), + self.fri_lookup_polys(), + ] + .concat() + } } } diff --git a/plonky2/src/plonk/get_challenges.rs b/plonky2/src/plonk/get_challenges.rs index 45d79f99aa..d0d4a05c5f 100644 --- a/plonky2/src/plonk/get_challenges.rs +++ b/plonky2/src/plonk/get_challenges.rs @@ -28,6 +28,7 @@ fn get_challenges, C: GenericConfig, cons wires_cap: &MerkleCap, plonk_zs_partial_products_cap: &MerkleCap, quotient_polys_cap: &MerkleCap, + opt_random_r: &Option>, openings: &OpeningSet, commit_phase_merkle_caps: &[MerkleCap], final_poly: &PolynomialCoeffs, @@ -69,6 +70,11 @@ fn get_challenges, C: GenericConfig, cons let plonk_alphas = challenger.get_n_challenges(num_challenges); challenger.observe_cap::(quotient_polys_cap); + + if let Some(random_r) = opt_random_r { + challenger.observe_cap::(random_r); + } + let plonk_zeta = challenger.get_extension_challenge::(); challenger.observe_openings(&openings.to_fri_openings()); @@ -114,6 +120,7 @@ impl, C: GenericConfig, const D: usize> wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof: FriProof { @@ -129,6 +136,7 @@ impl, C: GenericConfig, const D: usize> wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, commit_phase_merkle_caps, final_poly, @@ -153,6 +161,7 @@ impl, C: GenericConfig, const D: usize> wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof: CompressedFriProof { @@ -168,6 +177,7 @@ impl, C: GenericConfig, const D: usize> wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, commit_phase_merkle_caps, final_poly, @@ -201,6 +211,7 @@ impl, C: GenericConfig, const D: usize> let precomputed_reduced_evals = PrecomputedReducedOpenings::from_os_and_alpha( &self.proof.openings.to_fri_openings(), *fri_alpha, + common_data.config.zero_knowledge, ); let log_n = common_data.degree_bits() + common_data.config.fri_config.rate_bits; // Simulate the proof verification and collect the inferred elements. @@ -260,6 +271,7 @@ impl, const D: usize> CircuitBuilder { wires_cap: &MerkleCapTarget, plonk_zs_partial_products_cap: &MerkleCapTarget, quotient_polys_cap: &MerkleCapTarget, + opt_random_r: &Option, openings: &OpeningSetTarget, commit_phase_merkle_caps: &[MerkleCapTarget], final_poly: &PolynomialCoeffsExtTarget, @@ -304,6 +316,11 @@ impl, const D: usize> CircuitBuilder { let plonk_alphas = challenger.get_n_challenges(self, num_challenges); challenger.observe_cap(quotient_polys_cap); + + if let Some(random_r_cap) = opt_random_r { + challenger.observe_cap(random_r_cap); + } + let plonk_zeta = challenger.get_extension_challenge(self); challenger.observe_openings(&openings.to_fri_openings()); @@ -340,6 +357,7 @@ impl ProofWithPublicInputsTarget { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof: FriProofTarget { @@ -355,6 +373,7 @@ impl ProofWithPublicInputsTarget { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, commit_phase_merkle_caps, final_poly, diff --git a/plonky2/src/plonk/plonk_common.rs b/plonky2/src/plonk/plonk_common.rs index 170bfa170a..b337718521 100644 --- a/plonky2/src/plonk/plonk_common.rs +++ b/plonky2/src/plonk/plonk_common.rs @@ -38,6 +38,10 @@ impl PlonkOracle { index: 3, blinding: true, }; + pub const R: PlonkOracle = PlonkOracle { + index: 4, + blinding: true, + }; } pub const fn salt_size(salted: bool) -> usize { diff --git a/plonky2/src/plonk/proof.rs b/plonky2/src/plonk/proof.rs index 8cb8911a52..bb32989c1b 100644 --- a/plonky2/src/plonk/proof.rs +++ b/plonky2/src/plonk/proof.rs @@ -38,6 +38,9 @@ pub struct Proof, C: GenericConfig, const pub plonk_zs_partial_products_cap: MerkleCap, /// Merkle cap of LDEs of the quotient polynomial components. pub quotient_polys_cap: MerkleCap, + /// Optional Merkle cap for the random polynomial used, in the zk case, + /// to hide FRI's batch polynomial. + pub opt_random_r: Option>, /// Purported values of each polynomial at the challenge point. pub openings: OpeningSet, /// A batch FRI argument for all openings. @@ -49,6 +52,7 @@ pub struct ProofTarget { pub wires_cap: MerkleCapTarget, pub plonk_zs_partial_products_cap: MerkleCapTarget, pub quotient_polys_cap: MerkleCapTarget, + pub opt_random_r: Option, pub openings: OpeningSetTarget, pub opening_proof: FriProofTarget, } @@ -60,6 +64,7 @@ impl, C: GenericConfig, const D: usize> P wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof, } = self; @@ -68,6 +73,7 @@ impl, C: GenericConfig, const D: usize> P wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof: opening_proof.compress(indices, params), } @@ -137,6 +143,9 @@ pub struct CompressedProof, C: GenericConfig, /// Merkle cap of LDEs of the quotient polynomial components. pub quotient_polys_cap: MerkleCap, + /// Optional Merkle cap for the random polynomial used, in the zk case, + /// to hide FRI's batch polynomial. + pub opt_random_r: Option>, /// Purported values of each polynomial at the challenge point. pub openings: OpeningSet, /// A compressed batch FRI argument for all openings. @@ -157,6 +166,7 @@ impl, C: GenericConfig, const D: usize> wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof, } = self; @@ -165,6 +175,7 @@ impl, C: GenericConfig, const D: usize> wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof: opening_proof.decompress(challenges, fri_inferred_elements, params), } @@ -308,6 +319,7 @@ pub struct OpeningSet, const D: usize> { pub quotient_polys: Vec, pub lookup_zs: Vec, pub lookup_zs_next: Vec, + pub opt_random_r: Option>, } impl, const D: usize> OpeningSet { @@ -318,6 +330,7 @@ impl, const D: usize> OpeningSet { wires_commitment: &PolynomialBatch, zs_partial_products_lookup_commitment: &PolynomialBatch, quotient_polys_commitment: &PolynomialBatch, + opt_random_r: &Option>, common_data: &CommonCircuitData, ) -> Self { let eval_commitment = |z: F::Extension, c: &PolynomialBatch| { @@ -334,6 +347,10 @@ impl, const D: usize> OpeningSet { let zs_partial_products_lookup_next_eval = eval_commitment(g * zeta, zs_partial_products_lookup_commitment); let quotient_polys = eval_commitment(zeta, quotient_polys_commitment); + // `R` is a random polynomial used in the zk case to hide the batch FRI polynomial. + let random_r_polys = opt_random_r + .as_ref() + .map(|random_r| eval_commitment(zeta, random_r)); Self { constants: constants_sigmas_eval[common_data.constants_range()].to_vec(), @@ -347,35 +364,27 @@ impl, const D: usize> OpeningSet { lookup_zs: zs_partial_products_lookup_eval[common_data.lookup_range()].to_vec(), lookup_zs_next: zs_partial_products_lookup_next_eval[common_data.lookup_range()] .to_vec(), + opt_random_r: random_r_polys, } } pub(crate) fn to_fri_openings(&self) -> FriOpenings { let has_lookup = !self.lookup_zs.is_empty(); - let zeta_batch = if has_lookup { - FriOpeningBatch { - values: [ - self.constants.as_slice(), - self.plonk_sigmas.as_slice(), - self.wires.as_slice(), - self.plonk_zs.as_slice(), - self.partial_products.as_slice(), - self.quotient_polys.as_slice(), - self.lookup_zs.as_slice(), - ] - .concat(), - } - } else { - FriOpeningBatch { - values: [ - self.constants.as_slice(), - self.plonk_sigmas.as_slice(), - self.wires.as_slice(), - self.plonk_zs.as_slice(), - self.partial_products.as_slice(), - self.quotient_polys.as_slice(), - ] - .concat(), - } + let mut zeta_batch = FriOpeningBatch { + values: [ + self.constants.as_slice(), + self.plonk_sigmas.as_slice(), + self.wires.as_slice(), + self.plonk_zs.as_slice(), + self.partial_products.as_slice(), + self.quotient_polys.as_slice(), + ] + .concat(), + }; + if has_lookup { + zeta_batch.values.extend(self.lookup_zs.as_slice()); + } + if let Some(random_r) = &self.opt_random_r { + zeta_batch.values.extend(random_r.as_slice()); }; let zeta_next_batch = if has_lookup { FriOpeningBatch { @@ -404,36 +413,28 @@ pub struct OpeningSetTarget { pub next_lookup_zs: Vec>, pub partial_products: Vec>, pub quotient_polys: Vec>, + pub opt_random_r: Option>>, } impl OpeningSetTarget { pub(crate) fn to_fri_openings(&self) -> FriOpeningsTarget { let has_lookup = !self.lookup_zs.is_empty(); - let zeta_batch = if has_lookup { - FriOpeningBatchTarget { - values: [ - self.constants.as_slice(), - self.plonk_sigmas.as_slice(), - self.wires.as_slice(), - self.plonk_zs.as_slice(), - self.partial_products.as_slice(), - self.quotient_polys.as_slice(), - self.lookup_zs.as_slice(), - ] - .concat(), - } - } else { - FriOpeningBatchTarget { - values: [ - self.constants.as_slice(), - self.plonk_sigmas.as_slice(), - self.wires.as_slice(), - self.plonk_zs.as_slice(), - self.partial_products.as_slice(), - self.quotient_polys.as_slice(), - ] - .concat(), - } + let mut zeta_batch = FriOpeningBatchTarget { + values: [ + self.constants.as_slice(), + self.plonk_sigmas.as_slice(), + self.wires.as_slice(), + self.plonk_zs.as_slice(), + self.partial_products.as_slice(), + self.quotient_polys.as_slice(), + ] + .concat(), + }; + if has_lookup { + zeta_batch.values.extend(self.lookup_zs.as_slice()); + } + if let Some(random_r) = &self.opt_random_r { + zeta_batch.values.extend(random_r.as_slice()); }; let zeta_next_batch = if has_lookup { FriOpeningBatchTarget { diff --git a/plonky2/src/plonk/prover.rs b/plonky2/src/plonk/prover.rs index fcd784f326..451248de35 100644 --- a/plonky2/src/plonk/prover.rs +++ b/plonky2/src/plonk/prover.rs @@ -280,12 +280,46 @@ where quotient_poly.trim_to_len(quotient_degree).expect( "Quotient has failed, the vanishing polynomial is not divisible by Z_H", ); + // Split quotient into degree-n chunks. - quotient_poly.chunks(degree) + // In the zk case, we split the quotient into degree-(n-h) chunks, where `h` is computed by `computed_h`. + // This is so that we can add random polynomials of degree n > n-h and still keep chhunks of degree a power of 2. + // See "A note on adding zero-knowledge to STARKs" (https://eprint.iacr.org/2024/1037.pdf) for details. + if common_data.config.zero_knowledge { + let h = common_data.computed_h(); + let d = degree - h; + assert!(degree > h); + + let total_nb_chunks = quotient_poly.len().div_ceil(d); + let random_ts = vec![ + PolynomialCoeffs { + coeffs: F::rand_vec(h) // coeffs: vec![F::ZERO; h] + }; + total_nb_chunks - 1 + ]; + // Let (t_i)i be the random polynomials, and (q_i)i be the k quotient chunks of degree n. + // We compute: + // - q'_0(X) = q_0(X) + Xˆn * t_0(X) + // - q'_i(X) = q_i(X) + Xˆn * t_i(X) - t_(i-1)(X) + // - q'_k(X) = q_k(X) - t_(k-1)(X) + // Then, the sum of q' over i is equal to the sum of q over i. + let mut quotients = quotient_poly.chunks(d); + quotients[0].coeffs.extend(&random_ts[0].coeffs); + for i in 1..total_nb_chunks - 1 { + quotients[i] -= random_ts[i - 1].clone(); + quotients[i].coeffs.extend(&random_ts[i].coeffs); + } + quotients[total_nb_chunks - 1] -= random_ts[total_nb_chunks - 2].clone(); + quotients[total_nb_chunks - 1] + .pad(degree) + .expect("Degree is greater than the current length."); + quotients + } else { + quotient_poly.chunks(degree) + } }) .collect() ); - let quotient_polys_commitment = timed!( timing, "commit to quotient polys", @@ -301,6 +335,38 @@ where challenger.observe_cap::("ient_polys_commitment.merkle_tree.cap); + // If we are in the zk case, we need to commit to an extra random polynomial, that will be used to hide the batch FRI polynomial. + let random_r_commitment = if config.zero_knowledge { + // The random polynomial is of degree 2 * |H| with H the subgroup. + let d = 1 << common_data.fri_params.degree_bits; + let n = 2 * d; + + // We commit to the lower and higher coefficients of `R` separately, so that the required size of the + // `fft_root_table` remains unchanged. + let random_r = F::rand_vec(n); + let random_low = PolynomialCoeffs::new(random_r[..d].to_vec()); + let high_coeffs = random_r[d..].to_vec(); + let random_high = PolynomialCoeffs::new(high_coeffs); + let random_r_commitment = timed!( + timing, + "commit to random batch polynomial", + PolynomialBatch::::from_coeffs( + vec![random_low, random_high], + config.fri_config.rate_bits, + config.zero_knowledge && PlonkOracle::ZS_PARTIAL_PRODUCTS.blinding, + config.fri_config.cap_height, + timing, + prover_data.fft_root_table.as_ref(), + ) + ); + + challenger.observe_cap::(&random_r_commitment.merkle_tree.cap); + + Some(random_r_commitment) + } else { + None + }; + let zeta = challenger.get_extension_challenge::(); // To avoid leaking witness data, we want to ensure that our opening locations, `zeta` and // `g * zeta`, are not in our subgroup `H`. It suffices to check `zeta` only, since @@ -321,36 +387,73 @@ where &wires_commitment, &partial_products_zs_and_lookup_commitment, "ient_polys_commitment, + &random_r_commitment, common_data ) ); challenger.observe_openings(&openings.to_fri_openings()); let instance = common_data.get_fri_instance(zeta); - let opening_proof = timed!( - timing, - "compute opening proofs", - PolynomialBatch::::prove_openings( - &instance, - &[ - &prover_data.constants_sigmas_commitment, - &wires_commitment, - &partial_products_zs_and_lookup_commitment, - "ient_polys_commitment, - ], - &mut challenger, - &common_data.fri_params, + // If we are in the zk case, the proof needs to take into account one extra random polynomial commitment. + let proof = if let Some(random_r_com) = random_r_commitment { + let opening_proof = timed!( timing, - ) - ); - - let proof = Proof:: { - wires_cap: wires_commitment.merkle_tree.cap, - plonk_zs_partial_products_cap: partial_products_zs_and_lookup_commitment.merkle_tree.cap, - quotient_polys_cap: quotient_polys_commitment.merkle_tree.cap, - openings, - opening_proof, + "compute opening proofs", + PolynomialBatch::::prove_openings( + &instance, + &[ + &prover_data.constants_sigmas_commitment, + &wires_commitment, + &partial_products_zs_and_lookup_commitment, + "ient_polys_commitment, + &random_r_com + ], + &mut challenger, + &common_data.fri_params, + timing, + ) + ); + + Proof:: { + wires_cap: wires_commitment.merkle_tree.cap, + plonk_zs_partial_products_cap: partial_products_zs_and_lookup_commitment + .merkle_tree + .cap, + quotient_polys_cap: quotient_polys_commitment.merkle_tree.cap, + opt_random_r: Some(random_r_com.merkle_tree.cap), + openings, + opening_proof, + } + } else { + let opening_proof = timed!( + timing, + "compute opening proofs", + PolynomialBatch::::prove_openings( + &instance, + &[ + &prover_data.constants_sigmas_commitment, + &wires_commitment, + &partial_products_zs_and_lookup_commitment, + "ient_polys_commitment, + ], + &mut challenger, + &common_data.fri_params, + timing, + ) + ); + + Proof:: { + wires_cap: wires_commitment.merkle_tree.cap, + plonk_zs_partial_products_cap: partial_products_zs_and_lookup_commitment + .merkle_tree + .cap, + quotient_polys_cap: quotient_polys_commitment.merkle_tree.cap, + opt_random_r: None, + openings, + opening_proof, + } }; + Ok(ProofWithPublicInputs:: { proof, public_inputs, diff --git a/plonky2/src/plonk/validate_shape.rs b/plonky2/src/plonk/validate_shape.rs index 304aa04a23..5fd1faff1b 100644 --- a/plonky2/src/plonk/validate_shape.rs +++ b/plonky2/src/plonk/validate_shape.rs @@ -39,6 +39,7 @@ where wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r: opt_random_r_cap, openings, // The shape of the opening proof will be checked in the FRI verifier (see // validate_fri_proof_shape), so we ignore it here. @@ -54,11 +55,20 @@ where quotient_polys, lookup_zs, lookup_zs_next, + opt_random_r, } = openings; let cap_height = common_data.fri_params.config.cap_height; ensure!(wires_cap.height() == cap_height); ensure!(plonk_zs_partial_products_cap.height() == cap_height); ensure!(quotient_polys_cap.height() == cap_height); + if common_data.config.zero_knowledge { + let random_r_cap = opt_random_r_cap + .clone() + .expect("There is a random polynomial in zk."); + ensure!(random_r_cap.height() == cap_height); + } else { + ensure!(opt_random_r_cap.is_none()); + } ensure!(constants.len() == common_data.num_constants); ensure!(plonk_sigmas.len() == config.num_routed_wires); ensure!(wires.len() == config.num_wires); @@ -68,5 +78,14 @@ where ensure!(quotient_polys.len() == common_data.num_quotient_polys()); ensure!(lookup_zs.len() == common_data.num_all_lookup_polys()); ensure!(lookup_zs_next.len() == common_data.num_all_lookup_polys()); + if common_data.config.zero_knowledge { + let random_r = opt_random_r + .clone() + .expect("There is a random polynomial in zk."); + ensure!(random_r.len() == common_data.num_r_polys()); + } else { + ensure!(opt_random_r.is_none()); + } + Ok(()) } diff --git a/plonky2/src/plonk/verifier.rs b/plonky2/src/plonk/verifier.rs index fa1bc14b84..190ce6d12f 100644 --- a/plonky2/src/plonk/verifier.rs +++ b/plonky2/src/plonk/verifier.rs @@ -81,6 +81,21 @@ pub(crate) fn verify_with_challenges< // Check each polynomial identity, of the form `vanishing(x) = Z_H(x) quotient(x)`, at zeta. let quotient_polys_zeta = &proof.openings.quotient_polys; + let (zeta_pow_deg_for_reducing, chunk_size) = if common_data.config.zero_knowledge { + // In the zk case, the quotient chunk size is smaller. This means the power of zeta for reducing the quotient chunks + // is also smaller. + let h = common_data.computed_h(); + let d = common_data.degree() - h; + let chunk_size = (common_data.quotient_degree_factor * common_data.degree()).div_ceil(d); + (challenges.plonk_zeta.exp_u64(d as u64), chunk_size) + } else { + ( + challenges + .plonk_zeta + .exp_power_of_2(common_data.degree_bits()), + common_data.quotient_degree_factor, + ) + }; let zeta_pow_deg = challenges .plonk_zeta .exp_power_of_2(common_data.degree_bits()); @@ -90,26 +105,30 @@ pub(crate) fn verify_with_challenges< // where the "real" quotient polynomial is `t(X) = t_0(X) + t_1(X)*X^n + t_2(X)*X^{2n} + ...`. // So to reconstruct `t(zeta)` we can compute `reduce_with_powers(chunk, zeta^n)` for each // `quotient_degree_factor`-sized chunk of the original evaluations. - for (i, chunk) in quotient_polys_zeta - .chunks(common_data.quotient_degree_factor) - .enumerate() - { - ensure!(vanishing_polys_zeta[i] == z_h_zeta * reduce_with_powers(chunk, zeta_pow_deg)); + for (i, chunk) in quotient_polys_zeta.chunks(chunk_size).enumerate() { + ensure!( + vanishing_polys_zeta[i] + == z_h_zeta * reduce_with_powers(chunk, zeta_pow_deg_for_reducing) + ); } - let merkle_caps = &[ + let mut merkle_caps = [ verifier_data.constants_sigmas_cap.clone(), proof.wires_cap, // In the lookup case, `plonk_zs_partial_products_cap` should also include the lookup commitment. proof.plonk_zs_partial_products_cap, proof.quotient_polys_cap, - ]; + ] + .to_vec(); + if let Some(random_r) = proof.opt_random_r { + merkle_caps.push(random_r); + } verify_fri_proof::( &common_data.get_fri_instance(challenges.plonk_zeta), &proof.openings.to_fri_openings(), &challenges.fri_challenges, - merkle_caps, + &merkle_caps, &proof.opening_proof, &common_data.fri_params, )?; diff --git a/plonky2/src/recursion/conditional_recursive_verifier.rs b/plonky2/src/recursion/conditional_recursive_verifier.rs index 0bca23f7ff..d7e08d168b 100644 --- a/plonky2/src/recursion/conditional_recursive_verifier.rs +++ b/plonky2/src/recursion/conditional_recursive_verifier.rs @@ -77,6 +77,7 @@ impl, const D: usize> CircuitBuilder { wires_cap: wires_cap0, plonk_zs_partial_products_cap: plonk_zs_partial_products_cap0, quotient_polys_cap: quotient_polys_cap0, + opt_random_r: opt_random_r_cap0, openings: openings0, opening_proof: opening_proof0, }, @@ -88,6 +89,7 @@ impl, const D: usize> CircuitBuilder { wires_cap: wires_cap1, plonk_zs_partial_products_cap: plonk_zs_partial_products_cap1, quotient_polys_cap: quotient_polys_cap1, + opt_random_r: opt_random_r_cap1, openings: openings1, opening_proof: opening_proof1, }, @@ -102,6 +104,7 @@ impl, const D: usize> CircuitBuilder { ); let selected_quotient_polys_cap = self.select_cap(b, quotient_polys_cap0, quotient_polys_cap1); + let selected_random_r = self.select_opt_cap(b, opt_random_r_cap0, opt_random_r_cap1); let selected_openings = self.select_opening_set(b, openings0, openings1); let selected_opening_proof = self.select_opening_proof(b, opening_proof0, opening_proof1); @@ -111,6 +114,7 @@ impl, const D: usize> CircuitBuilder { wires_cap: selected_wires_cap, plonk_zs_partial_products_cap: selected_plonk_zs_partial_products_cap, quotient_polys_cap: selected_quotient_polys_cap, + opt_random_r: selected_random_r, openings: selected_openings, opening_proof: selected_opening_proof, }, @@ -156,6 +160,29 @@ impl, const D: usize> CircuitBuilder { ) } + /// Computes `if b { opt_cap0 } else { opt_cap1 }`. + fn select_opt_cap( + &mut self, + b: BoolTarget, + opt_cap0: &Option, + opt_cap1: &Option, + ) -> Option { + assert_eq!(opt_cap0.is_some(), opt_cap1.is_some()); + + if let (Some(cap0), Some(cap1)) = (opt_cap0, opt_cap1) { + assert_eq!(cap0.0.len(), cap1.0.len()); + Some(MerkleCapTarget( + cap0.0 + .iter() + .zip_eq(&cap1.0) + .map(|(h0, h1)| self.select_hash(b, *h0, *h1)) + .collect(), + )) + } else { + None + } + } + /// Computes `if b { v0 } else { v1 }`. fn select_vec_cap( &mut self, @@ -203,6 +230,7 @@ impl, const D: usize> CircuitBuilder { next_lookup_zs: self.select_vec_ext(b, &os0.next_lookup_zs, &os1.next_lookup_zs), partial_products: self.select_vec_ext(b, &os0.partial_products, &os1.partial_products), quotient_polys: self.select_vec_ext(b, &os0.quotient_polys, &os1.quotient_polys), + opt_random_r: self.select_opt_vec_ext(b, &os0.opt_random_r, &os1.opt_random_r), } } @@ -219,6 +247,26 @@ impl, const D: usize> CircuitBuilder { .collect() } + /// Computes `if b { opt_v0 } else { opt_v1 }`. + fn select_opt_vec_ext( + &mut self, + b: BoolTarget, + opt_v0: &Option>>, + opt_v1: &Option>>, + ) -> Option>> { + assert_eq!(opt_v0.is_some(), opt_v1.is_some()); + if let (Some(v0), Some(v1)) = (opt_v0, opt_v1) { + Some( + v0.iter() + .zip_eq(v1) + .map(|(e0, e1)| self.select_ext(b, *e0, *e1)) + .collect(), + ) + } else { + None + } + } + /// Computes `if b { proof0 } else { proof1 }`. fn select_opening_proof( &mut self, diff --git a/plonky2/src/recursion/dummy_circuit.rs b/plonky2/src/recursion/dummy_circuit.rs index 115a4c32fd..8a7d62ff61 100644 --- a/plonky2/src/recursion/dummy_circuit.rs +++ b/plonky2/src/recursion/dummy_circuit.rs @@ -87,14 +87,17 @@ pub fn dummy_circuit, C: GenericConfig, c common_data: &CommonCircuitData, ) -> CircuitData { let config = common_data.config.clone(); - assert!( - !common_data.config.zero_knowledge, - "Degree calculation can be off if zero-knowledge is on." - ); + if common_data.config.zero_knowledge { + log::warn!("Degree calculation can be off if zero-knowledge is on."); + } // Number of `NoopGate`s to add to get a circuit of size `degree` in the end. // Need to account for public input hashing, a `PublicInputGate` and a `ConstantGate`. - let degree = common_data.degree(); + let mut degree = common_data.degree(); + // In the zk case, the degree is double what the original batch FRI polynomial is. + if common_data.config.zero_knowledge { + degree /= 2; + } let num_noop_gate = degree - common_data.num_public_inputs.div_ceil(8) - 2; let mut builder = CircuitBuilder::::new(config); @@ -175,6 +178,7 @@ where wires_cap: MerkleCapTarget(vec![]), plonk_zs_partial_products_cap: MerkleCapTarget(vec![]), quotient_polys_cap: MerkleCapTarget(vec![]), + opt_random_r: None, openings: OpeningSetTarget::default(), opening_proof: FriProofTarget { commit_phase_merkle_caps: vec![], @@ -191,6 +195,7 @@ where wires_cap: MerkleCap(vec![]), plonk_zs_partial_products_cap: MerkleCap(vec![]), quotient_polys_cap: MerkleCap(vec![]), + opt_random_r: None, openings: OpeningSet::default(), opening_proof: FriProof { commit_phase_merkle_caps: vec![], diff --git a/plonky2/src/recursion/recursive_verifier.rs b/plonky2/src/recursion/recursive_verifier.rs index 16a8ba85b2..8b6bbafe7b 100644 --- a/plonky2/src/recursion/recursive_verifier.rs +++ b/plonky2/src/recursion/recursive_verifier.rs @@ -74,6 +74,27 @@ impl, const D: usize> CircuitBuilder { let s_sigmas = &proof.openings.plonk_sigmas; let partial_products = &proof.openings.partial_products; + let (zeta_pow_deg_for_reducing, chunk_size) = if inner_common_data.config.zero_knowledge { + // In the zk case, the quotient chunk size is smaller. This means the power of zeta for reducing the quotient chunks + // is also smaller. + let h = inner_common_data.computed_h(); + let d = inner_common_data.degree() - h; + let chunk_size = + (inner_common_data.quotient_degree_factor * inner_common_data.degree()).div_ceil(d); + ( + self.exp_u64_extension(challenges.plonk_zeta, d as u64), + chunk_size, + ) + } else { + ( + self.exp_power_of_2_extension( + challenges.plonk_zeta, + inner_common_data.degree_bits(), + ), + inner_common_data.quotient_degree_factor, + ) + }; + let zeta_pow_deg = self.exp_power_of_2_extension(challenges.plonk_zeta, inner_common_data.degree_bits()); let vanishing_polys_zeta = with_context!( @@ -100,26 +121,29 @@ impl, const D: usize> CircuitBuilder { with_context!(self, "check vanishing and quotient polynomials.", { let quotient_polys_zeta = &proof.openings.quotient_polys; - let mut scale = ReducingFactorTarget::new(zeta_pow_deg); + let mut scale = ReducingFactorTarget::new(zeta_pow_deg_for_reducing); let z_h_zeta = self.sub_extension(zeta_pow_deg, one); - for (i, chunk) in quotient_polys_zeta - .chunks(inner_common_data.quotient_degree_factor) - .enumerate() - { + for (i, chunk) in quotient_polys_zeta.chunks(chunk_size).enumerate() { let recombined_quotient = scale.reduce(chunk, self); let computed_vanishing_poly = self.mul_extension(z_h_zeta, recombined_quotient); self.connect_extension(vanishing_polys_zeta[i], computed_vanishing_poly); } }); - let merkle_caps = &[ + let mut merkle_caps = [ inner_verifier_data.constants_sigmas_cap.clone(), proof.wires_cap.clone(), proof.plonk_zs_partial_products_cap.clone(), proof.quotient_polys_cap.clone(), - ]; + ] + .to_vec(); + + if let Some(random_r) = proof.opt_random_r.clone() { + merkle_caps.push(random_r); + } let fri_instance = inner_common_data.get_fri_instance_target(self, challenges.plonk_zeta); + with_context!( self, "verify FRI proof", @@ -127,7 +151,7 @@ impl, const D: usize> CircuitBuilder { &fri_instance, &proof.openings.to_fri_openings(), &challenges.fri_challenges, - merkle_caps, + &merkle_caps, &proof.opening_proof, &inner_common_data.fri_params, ) @@ -162,10 +186,19 @@ impl, const D: usize> CircuitBuilder { num_leaves_per_oracle.push(common_data.num_quotient_polys() + salt); } + if common_data.num_r_polys() > 0 { + num_leaves_per_oracle.push(common_data.num_r_polys() + salt); + } + ProofTarget { wires_cap: self.add_virtual_cap(cap_height), plonk_zs_partial_products_cap: self.add_virtual_cap(cap_height), quotient_polys_cap: self.add_virtual_cap(cap_height), + opt_random_r: if common_data.config.zero_knowledge { + Some(self.add_virtual_cap(cap_height)) + } else { + None + }, openings: self.add_opening_set(common_data), opening_proof: self.add_virtual_fri_proof(num_leaves_per_oracle, fri_params), } @@ -191,6 +224,11 @@ impl, const D: usize> CircuitBuilder { next_lookup_zs: self.add_virtual_extension_targets(num_lookups), partial_products: self.add_virtual_extension_targets(total_partial_products), quotient_polys: self.add_virtual_extension_targets(common_data.num_quotient_polys()), + opt_random_r: if common_data.config.zero_knowledge { + Some(self.add_virtual_extension_targets(common_data.num_r_polys())) + } else { + None + }, } } } diff --git a/plonky2/src/util/serialization/mod.rs b/plonky2/src/util/serialization/mod.rs index 393db6c699..ee31e3c853 100644 --- a/plonky2/src/util/serialization/mod.rs +++ b/plonky2/src/util/serialization/mod.rs @@ -200,6 +200,26 @@ pub trait Read { (0..length).map(|_| self.read_field_ext::()).collect() } + /// Optionally reads a vector of elements from the field extension of `F` from `self`. + #[inline] + fn read_opt_field_ext_vec( + &mut self, + length: usize, + ) -> IoResult>> + where + F: RichField + Extendable, + { + let is_zk = self.read_bool()?; + if is_zk { + let res = (0..length) + .map(|_| self.read_field_ext::()) + .collect::, _>>()?; + Ok(Some(res)) + } else { + Ok(None) + } + } + /// Reads a Target from `self.` #[inline] fn read_target(&mut self) -> IoResult { @@ -252,6 +272,24 @@ pub trait Read { .collect::, _>>() } + /// Optionally reads a vector of ExtensionTarget from `self`. + #[inline] + fn read_opt_target_ext_vec( + &mut self, + ) -> IoResult>>> { + let is_zk = self.read_bool()?; + if is_zk { + let length = self.read_usize()?; + + let res = (0..length) + .map(|_| self.read_target_ext::()) + .collect::, _>>()?; + Ok(Some(res)) + } else { + Ok(None) + } + } + /// Reads a hash value from `self`. #[inline] fn read_hash(&mut self) -> IoResult @@ -313,6 +351,22 @@ pub trait Read { )) } + /// Optionally reads a value of type [`MerkleCapTarget`] from `self`. + #[inline] + fn read_opt_target_merkle_cap(&mut self) -> IoResult> { + let is_zk = self.read_bool()?; + if is_zk { + let length = self.read_usize()?; + Ok(Some(MerkleCapTarget( + (0..length) + .map(|_| self.read_target_hash()) + .collect::, _>>()?, + ))) + } else { + Ok(None) + } + } + /// Reads a value of type [`MerkleTree`] from `self`. #[inline] fn read_merkle_tree(&mut self) -> IoResult> @@ -358,9 +412,8 @@ pub trait Read { let lookup_zs_next = self.read_field_ext_vec::(common_data.num_all_lookup_polys())?; let partial_products = self .read_field_ext_vec::(common_data.num_partial_products * config.num_challenges)?; - let quotient_polys = self.read_field_ext_vec::( - common_data.quotient_degree_factor * config.num_challenges, - )?; + let quotient_polys = self.read_field_ext_vec::(common_data.num_quotient_polys())?; + let opt_random_r = self.read_opt_field_ext_vec::(common_data.num_r_polys())?; Ok(OpeningSet { constants, plonk_sigmas, @@ -371,6 +424,7 @@ pub trait Read { quotient_polys, lookup_zs, lookup_zs_next, + opt_random_r, }) } @@ -386,6 +440,7 @@ pub trait Read { let next_lookup_zs = self.read_target_ext_vec::()?; let partial_products = self.read_target_ext_vec::()?; let quotient_polys = self.read_target_ext_vec::()?; + let opt_random_r = self.read_opt_target_ext_vec::()?; Ok(OpeningSetTarget { constants, @@ -397,6 +452,7 @@ pub trait Read { next_lookup_zs, partial_products, quotient_polys, + opt_random_r, }) } @@ -457,11 +513,16 @@ pub trait Read { let zs_partial_p = self.read_merkle_proof()?; evals_proofs.push((zs_partial_v, zs_partial_p)); - let quotient_v = - self.read_field_vec(config.num_challenges * common_data.quotient_degree_factor + salt)?; + let quotient_v = self.read_field_vec(common_data.num_quotient_polys() + salt)?; let quotient_p = self.read_merkle_proof()?; evals_proofs.push((quotient_v, quotient_p)); + if common_data.config.zero_knowledge { + let random_r_v = self.read_field_vec(common_data.num_r_polys() + salt)?; + let random_v_p = self.read_merkle_proof()?; + evals_proofs.push((random_r_v, random_v_p)); + } + Ok(FriInitialTreeProof { evals_proofs }) } @@ -570,6 +631,7 @@ pub trait Read { C: GenericConfig, { let config = &common_data.config; + let commit_phase_merkle_caps = (0..common_data.fri_params.reduction_arity_bits.len()) .map(|_| self.read_merkle_cap(config.fri_config.cap_height)) .collect::, _>>()?; @@ -978,12 +1040,18 @@ pub trait Read { let wires_cap = self.read_merkle_cap(config.fri_config.cap_height)?; let plonk_zs_partial_products_cap = self.read_merkle_cap(config.fri_config.cap_height)?; let quotient_polys_cap = self.read_merkle_cap(config.fri_config.cap_height)?; + let opt_random_r = if common_data.config.zero_knowledge { + Some(self.read_merkle_cap(config.fri_config.cap_height)?) + } else { + None + }; let openings = self.read_opening_set::(common_data)?; let opening_proof = self.read_fri_proof::(common_data)?; Ok(Proof { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof, }) @@ -995,12 +1063,14 @@ pub trait Read { let wires_cap = self.read_target_merkle_cap()?; let plonk_zs_partial_products_cap = self.read_target_merkle_cap()?; let quotient_polys_cap = self.read_target_merkle_cap()?; + let opt_random_r = self.read_opt_target_merkle_cap()?; let openings = self.read_target_opening_set::()?; let opening_proof = self.read_target_fri_proof::()?; Ok(ProofTarget { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof, }) @@ -1098,6 +1168,7 @@ pub trait Read { C: GenericConfig, { let config = &common_data.config; + let commit_phase_merkle_caps = (0..common_data.fri_params.reduction_arity_bits.len()) .map(|_| self.read_merkle_cap(config.fri_config.cap_height)) .collect::, _>>()?; @@ -1128,12 +1199,18 @@ pub trait Read { let wires_cap = self.read_merkle_cap(config.fri_config.cap_height)?; let plonk_zs_partial_products_cap = self.read_merkle_cap(config.fri_config.cap_height)?; let quotient_polys_cap = self.read_merkle_cap(config.fri_config.cap_height)?; + let opt_random_r = if common_data.config.zero_knowledge { + Some(self.read_merkle_cap(config.fri_config.cap_height)?) + } else { + None + }; let openings = self.read_opening_set::(common_data)?; let opening_proof = self.read_compressed_fri_proof::(common_data)?; Ok(CompressedProof { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, + opt_random_r, openings, opening_proof, }) @@ -1294,6 +1371,25 @@ pub trait Write { Ok(()) } + /// Optionally writes a vector `v` of elements from the field extension of `F` to `self`. + #[inline] + fn write_opt_field_ext_vec( + &mut self, + opt_v: &Option>, + ) -> IoResult<()> + where + F: RichField + Extendable, + { + self.write_bool(opt_v.is_some())?; + if let Some(v) = opt_v { + for &a in v { + self.write_field_ext::(a)?; + } + } + + Ok(()) + } + /// Writes a Target `x` to `self.` #[inline] fn write_target(&mut self, x: Target) -> IoResult<()> { @@ -1354,6 +1450,23 @@ pub trait Write { Ok(()) } + /// Optionally writes a vector of ExtensionTarget `v` to `self.` + #[inline] + fn write_opt_target_ext_vec( + &mut self, + opt_v: &Option>>, + ) -> IoResult<()> { + self.write_bool(opt_v.is_some())?; + if let Some(v) = opt_v { + self.write_usize(v.len())?; + for &elem in v.iter() { + self.write_target_ext(elem)?; + } + } + + Ok(()) + } + /// Writes a hash `h` to `self`. #[inline] fn write_hash(&mut self, h: H::Hash) -> IoResult<()> @@ -1402,6 +1515,22 @@ pub trait Write { Ok(()) } + /// Writes `opt_cap`, an optional value of type [`MerkleCap`], to `self`. + #[inline] + fn write_opt_merkle_cap(&mut self, opt_cap: &Option>) -> IoResult<()> + where + F: RichField, + H: Hasher, + { + self.write_bool(opt_cap.is_some())?; + if let Some(cap) = opt_cap { + for &a in &cap.0 { + self.write_hash::(a)?; + } + } + Ok(()) + } + /// Writes `cap`, a value of type [`MerkleCapTarget`], to `self`. #[inline] fn write_target_merkle_cap(&mut self, cap: &MerkleCapTarget) -> IoResult<()> { @@ -1412,6 +1541,20 @@ pub trait Write { Ok(()) } + /// Writes `opt_cap`, an optional value of type [`MerkleCapTarget`], to `self`. + #[inline] + fn write_opt_target_merkle_cap(&mut self, opt_cap: &Option) -> IoResult<()> { + self.write_bool(opt_cap.is_some())?; + if let Some(cap) = opt_cap { + self.write_usize(cap.0.len())?; + for a in &cap.0 { + self.write_target_hash(a)?; + } + } + + Ok(()) + } + /// Writes `tree`, a value of type [`MerkleTree`], to `self`. #[inline] fn write_merkle_tree(&mut self, tree: &MerkleTree) -> IoResult<()> @@ -1445,7 +1588,8 @@ pub trait Write { self.write_field_ext_vec::(&os.lookup_zs)?; self.write_field_ext_vec::(&os.lookup_zs_next)?; self.write_field_ext_vec::(&os.partial_products)?; - self.write_field_ext_vec::(&os.quotient_polys) + self.write_field_ext_vec::(&os.quotient_polys)?; + self.write_opt_field_ext_vec::(&os.opt_random_r) } /// Writes a value `os` of type [`OpeningSet`] to `self.` @@ -1462,7 +1606,8 @@ pub trait Write { self.write_target_ext_vec::(&os.lookup_zs)?; self.write_target_ext_vec::(&os.next_lookup_zs)?; self.write_target_ext_vec::(&os.partial_products)?; - self.write_target_ext_vec::(&os.quotient_polys) + self.write_target_ext_vec::(&os.quotient_polys)?; + self.write_opt_target_ext_vec::(&os.opt_random_r) } /// Writes a value `p` of type [`MerkleProof`] to `self.` @@ -1982,6 +2127,9 @@ pub trait Write { self.write_merkle_cap(&proof.wires_cap)?; self.write_merkle_cap(&proof.plonk_zs_partial_products_cap)?; self.write_merkle_cap(&proof.quotient_polys_cap)?; + if let Some(random_r) = proof.opt_random_r.clone() { + self.write_merkle_cap(&random_r)?; + } self.write_opening_set(&proof.openings)?; self.write_fri_proof::(&proof.opening_proof) } @@ -1992,6 +2140,7 @@ pub trait Write { self.write_target_merkle_cap(&proof.wires_cap)?; self.write_target_merkle_cap(&proof.plonk_zs_partial_products_cap)?; self.write_target_merkle_cap(&proof.quotient_polys_cap)?; + self.write_opt_target_merkle_cap(&proof.opt_random_r)?; self.write_target_opening_set(&proof.openings)?; self.write_target_fri_proof::(&proof.opening_proof) } @@ -2088,6 +2237,9 @@ pub trait Write { self.write_merkle_cap(&proof.wires_cap)?; self.write_merkle_cap(&proof.plonk_zs_partial_products_cap)?; self.write_merkle_cap(&proof.quotient_polys_cap)?; + if let Some(random_r) = proof.opt_random_r.clone() { + self.write_merkle_cap(&random_r)?; + } self.write_opening_set(&proof.openings)?; self.write_compressed_fri_proof::(&proof.opening_proof) }