Merge pull request #86 from mir-protocol/fix_z_check

Fix high degree `z` check by using partial products
This commit is contained in:
wborgeaud 2021-07-14 08:40:01 +02:00 committed by GitHub
commit c24fe65f44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 437 additions and 116 deletions

View File

@ -21,6 +21,7 @@ use crate::plonk_common::PlonkPolynomials;
use crate::polynomial::commitment::ListPolynomialCommitment;
use crate::polynomial::polynomial::PolynomialValues;
use crate::target::Target;
use crate::util::partial_products::num_partial_products;
use crate::util::{log2_ceil, log2_strict, transpose, transpose_poly_values};
use crate::wire::Wire;
@ -391,6 +392,7 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Builds a "full circuit", with both prover and verifier data.
pub fn build(mut self) -> CircuitData<F, D> {
let quotient_degree_factor = 7; // TODO: add this as a parameter.
let start = Instant::now();
info!(
"Degree before blinding & padding: {}",
@ -402,6 +404,10 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
let gates = self.gates.iter().cloned().collect();
let (gate_tree, max_filtered_constraint_degree, num_constants) = Tree::from_gates(gates);
assert!(
max_filtered_constraint_degree <= quotient_degree_factor + 1,
"Constraints are too high degree."
);
let prefixed_gates = PrefixedGate::from_tree(gate_tree);
let degree_bits = log2_strict(degree);
@ -444,6 +450,9 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
.max()
.expect("No gates?");
let num_partial_products =
num_partial_products(self.config.num_routed_wires, quotient_degree_factor);
// TODO: This should also include an encoding of gate constraints.
let circuit_digest_parts = [
constants_sigmas_root.elements.to_vec(),
@ -455,10 +464,11 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
config: self.config,
degree_bits,
gates: prefixed_gates,
max_filtered_constraint_degree,
quotient_degree_factor,
num_gate_constraints,
num_constants,
k_is,
num_partial_products,
circuit_digest,
};

View File

@ -1,4 +1,4 @@
use std::ops::Range;
use std::ops::{Range, RangeFrom};
use anyhow::Result;
@ -145,8 +145,8 @@ pub struct CommonCircuitData<F: Extendable<D>, const D: usize> {
/// The types of gates used in this circuit, along with their prefixes.
pub(crate) gates: Vec<PrefixedGate<F, D>>,
/// The maximum degree of a filter times a constraint by any gate.
pub(crate) max_filtered_constraint_degree: usize,
/// The degree of the PLONK quotient polynomial.
pub(crate) quotient_degree_factor: usize,
/// The largest number of constraints imposed by any gate.
pub(crate) num_gate_constraints: usize,
@ -157,6 +157,10 @@ pub struct CommonCircuitData<F: Extendable<D>, const D: usize> {
/// The `{k_i}` valued used in `S_ID_i` in Plonk's permutation argument.
pub(crate) k_is: Vec<F>,
/// The number of partial products needed to compute the `Z` polynomials and the number
/// of partial products needed to compute the final product.
pub(crate) num_partial_products: (usize, usize),
/// A digest of the "circuit" (i.e. the instance, minus public inputs), which can be used to
/// seed Fiat-Shamir.
pub(crate) circuit_digest: Hash<F>,
@ -184,7 +188,7 @@ impl<F: Extendable<D>, const D: usize> CommonCircuitData<F, D> {
}
pub fn quotient_degree(&self) -> usize {
(self.max_filtered_constraint_degree - 1) * self.degree()
self.quotient_degree_factor * self.degree()
}
pub fn total_constraints(&self) -> usize {
@ -201,6 +205,16 @@ impl<F: Extendable<D>, const D: usize> CommonCircuitData<F, D> {
pub fn sigmas_range(&self) -> Range<usize> {
self.num_constants..self.num_constants + self.config.num_routed_wires
}
/// Range of the `z`s polynomials in the `zs_partial_products_commitment`.
pub fn zs_range(&self) -> Range<usize> {
0..self.config.num_challenges
}
/// Range of the partial products polynomials in the `zs_partial_products_commitment`.
pub fn partial_products_range(&self) -> RangeFrom<usize> {
self.config.num_challenges..
}
}
/// The `Target` version of `VerifierCircuitData`, for use inside recursive circuits. Note that this

View File

@ -183,7 +183,7 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// Polynomials opened at `x` and `g x`, i.e., the Zs polynomials.
let zs_evals = proof
.unsalted_evals(PlonkPolynomials::ZS)
.unsalted_evals(PlonkPolynomials::ZS_PARTIAL_PRODUCTS)
.iter()
.map(|&e| self.convert_to_ext(e))
.collect::<Vec<_>>();

View File

@ -1,5 +1,6 @@
use anyhow::{ensure, Result};
use crate::circuit_data::CommonCircuitData;
use crate::field::extension_field::{flatten, Extendable, FieldExtension, Frobenius};
use crate::field::field::Field;
use crate::field::interpolation::{barycentric_weights, interpolate, interpolate2};
@ -75,8 +76,9 @@ pub fn verify_fri_proof<F: Field + Extendable<D>, const D: usize>(
initial_merkle_roots: &[Hash<F>],
proof: &FriProof<F, D>,
challenger: &mut Challenger<F>,
config: &FriConfig,
common_data: &CommonCircuitData<F, D>,
) -> Result<()> {
let config = &common_data.config.fri_config;
let total_arities = config.reduction_arity_bits.iter().sum::<usize>();
ensure!(
purported_degree_log
@ -122,7 +124,7 @@ pub fn verify_fri_proof<F: Field + Extendable<D>, const D: usize>(
n,
&betas,
round_proof,
config,
common_data,
)?;
}
@ -147,8 +149,9 @@ fn fri_combine_initial<F: Field + Extendable<D>, const D: usize>(
os: &OpeningSet<F, D>,
zeta: F::Extension,
subgroup_x: F,
config: &FriConfig,
common_data: &CommonCircuitData<F, D>,
) -> F::Extension {
let config = &common_data.config.fri_config;
assert!(D > 1, "Not implemented for D=1.");
let degree_log = proof.evals_proofs[0].1.siblings.len() - config.rate_bits;
let subgroup_x = F::Extension::from_basefield(subgroup_x);
@ -167,12 +170,17 @@ fn fri_combine_initial<F: Field + Extendable<D>, const D: usize>(
]
.iter()
.flat_map(|&p| proof.unsalted_evals(p))
.chain(
&proof.unsalted_evals(PlonkPolynomials::ZS_PARTIAL_PRODUCTS)
[common_data.partial_products_range()],
)
.map(|&e| F::Extension::from_basefield(e));
let single_openings = os
.constants
.iter()
.chain(&os.plonk_s_sigmas)
.chain(&os.quotient_polys);
.chain(&os.quotient_polys)
.chain(&os.partial_products);
let single_diffs = single_evals
.into_iter()
.zip(single_openings)
@ -185,9 +193,10 @@ fn fri_combine_initial<F: Field + Extendable<D>, const D: usize>(
// Polynomials opened at `x` and `g x`, i.e., the Zs polynomials.
let zs_evals = proof
.unsalted_evals(PlonkPolynomials::ZS)
.unsalted_evals(PlonkPolynomials::ZS_PARTIAL_PRODUCTS)
.iter()
.map(|&e| F::Extension::from_basefield(e));
.map(|&e| F::Extension::from_basefield(e))
.take(common_data.zs_range().end);
let zs_composition_eval = alpha.clone().reduce(zs_evals);
let zeta_right = F::Extension::primitive_root_of_unity(degree_log) * zeta;
let zs_interpol = interpolate2(
@ -236,8 +245,9 @@ fn fri_verifier_query_round<F: Field + Extendable<D>, const D: usize>(
n: usize,
betas: &[F::Extension],
round_proof: &FriQueryRound<F, D>,
config: &FriConfig,
common_data: &CommonCircuitData<F, D>,
) -> Result<()> {
let config = &common_data.config.fri_config;
let mut evaluations: Vec<Vec<F::Extension>> = Vec::new();
let x = challenger.get_challenge();
let mut domain_size = n;
@ -262,7 +272,7 @@ fn fri_verifier_query_round<F: Field + Extendable<D>, const D: usize>(
os,
zeta,
subgroup_x,
config,
common_data,
)
} else {
let last_evals = &evaluations[i - 1];

View File

@ -438,6 +438,7 @@ mod tests {
use crate::field::extension_field::quartic::QuarticCrandallField;
use crate::field::field::Field;
use crate::fri::FriConfig;
use crate::verifier::verify;
use crate::witness::PartialWitness;
#[test]
@ -461,5 +462,7 @@ mod tests {
let data = builder.build();
let proof = data.prove(PartialWitness::new());
verify(proof, &data.verifier_only, &data.common).unwrap();
}
}

View File

@ -79,6 +79,7 @@ mod tests {
use crate::field::crandall_field::CrandallField;
use crate::field::extension_field::quartic::QuarticCrandallField;
use crate::field::field::Field;
use crate::verifier::verify;
use crate::witness::PartialWitness;
fn real_insert<const D: usize>(
@ -116,6 +117,8 @@ mod tests {
let data = builder.build();
let proof = data.prove(PartialWitness::new());
verify(proof, &data.verifier_only, &data.common).unwrap();
}
#[test]

View File

@ -66,6 +66,7 @@ mod tests {
use crate::field::extension_field::FieldExtension;
use crate::field::field::Field;
use crate::field::interpolation::{interpolant, interpolate};
use crate::verifier::verify;
use crate::witness::PartialWitness;
#[test]
@ -103,6 +104,8 @@ mod tests {
let data = builder.build();
let proof = data.prove(PartialWitness::new());
verify(proof, &data.verifier_only, &data.common).unwrap();
}
#[test]
@ -135,5 +138,7 @@ mod tests {
let data = builder.build();
let proof = data.prove(PartialWitness::new());
verify(proof, &data.verifier_only, &data.common).unwrap();
}
}

View File

@ -118,6 +118,7 @@ mod tests {
use crate::field::crandall_field::CrandallField;
use crate::field::extension_field::quartic::QuarticCrandallField;
use crate::field::field::Field;
use crate::verifier::verify;
use crate::witness::PartialWitness;
fn real_rotate<const D: usize>(
@ -150,6 +151,8 @@ mod tests {
let data = builder.build();
let proof = data.prove(PartialWitness::new());
verify(proof, &data.verifier_only, &data.common).unwrap();
}
#[test]

View File

@ -41,6 +41,7 @@ mod tests {
use crate::circuit_data::CircuitConfig;
use crate::field::crandall_field::CrandallField;
use crate::field::field::Field;
use crate::verifier::verify;
use crate::witness::PartialWitness;
#[test]
@ -67,5 +68,7 @@ mod tests {
let data = builder.build();
let proof = data.prove(PartialWitness::new());
verify(proof, &data.verifier_only, &data.common).unwrap();
}
}

View File

@ -74,7 +74,7 @@ impl<F: Extendable<D>, const D: usize> Tree<GateRef<F, D>> {
// Iterate backwards from `max_degree` to try to find a tree with a lower degree
// but the same number of constants.
'optdegree: for degree in (0..max_degree).rev() {
if let Some(mut tree) = Self::find_tree(&gates, degree, max_constants) {
if let Some(tree) = Self::find_tree(&gates, degree, max_constants) {
let num_constants = tree.num_constants();
if num_constants > best_num_constants {
break 'optdegree;

View File

@ -72,6 +72,7 @@ impl<F: Field> Challenger<F> {
wires,
plonk_zs,
plonk_zs_right,
partial_products,
quotient_polys,
} = os;
for v in &[
@ -80,6 +81,7 @@ impl<F: Field> Challenger<F> {
wires,
plonk_zs,
plonk_zs_right,
partial_products,
quotient_polys,
] {
self.observe_extension_elements(v);

View File

@ -9,6 +9,7 @@ use crate::gates::gate::{GateRef, PrefixedGate};
use crate::polynomial::commitment::SALT_SIZE;
use crate::polynomial::polynomial::PolynomialCoeffs;
use crate::target::Target;
use crate::util::partial_products::check_partial_products;
use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
/// Holds the Merkle tree index and blinding flag of a set of polynomials used in FRI.
@ -37,7 +38,7 @@ impl PlonkPolynomials {
index: 1,
blinding: true,
};
pub const ZS: PolynomialsIndexBlinding = PolynomialsIndexBlinding {
pub const ZS_PARTIAL_PRODUCTS: PolynomialsIndexBlinding = PolynomialsIndexBlinding {
index: 2,
blinding: true,
};
@ -50,7 +51,7 @@ impl PlonkPolynomials {
match i {
0 => Self::CONSTANTS_SIGMAS,
1 => Self::WIRES,
2 => Self::ZS,
2 => Self::ZS_PARTIAL_PRODUCTS,
3 => Self::QUOTIENT,
_ => panic!("There are only 4 sets of polynomials in Plonk."),
}
@ -64,41 +65,80 @@ pub(crate) fn eval_vanishing_poly<F: Extendable<D>, const D: usize>(
common_data: &CommonCircuitData<F, D>,
x: F::Extension,
vars: EvaluationVars<F, D>,
local_plonk_zs: &[F::Extension],
next_plonk_zs: &[F::Extension],
local_zs: &[F::Extension],
next_zs: &[F::Extension],
partial_products: &[F::Extension],
s_sigmas: &[F::Extension],
betas: &[F],
gammas: &[F],
alphas: &[F],
) -> Vec<F::Extension> {
let partial_products_degree = common_data.quotient_degree_factor;
let (num_prods, final_num_prod) = common_data.num_partial_products;
let constraint_terms =
evaluate_gate_constraints(&common_data.gates, common_data.num_gate_constraints, vars);
// The L_1(x) (Z(x) - 1) vanishing terms.
let mut vanishing_z_1_terms = Vec::new();
// The terms checking the partial products.
let mut vanishing_partial_products_terms = Vec::new();
// The Z(x) f'(x) - g'(x) Z(g x) terms.
let mut vanishing_v_shift_terms = Vec::new();
for i in 0..common_data.config.num_challenges {
let z_x = local_plonk_zs[i];
let z_gz = next_plonk_zs[i];
let z_x = local_zs[i];
let z_gz = next_zs[i];
vanishing_z_1_terms.push(eval_l_1(common_data.degree(), x) * (z_x - F::Extension::ONE));
let mut f_prime = F::Extension::ONE;
let mut g_prime = F::Extension::ONE;
for j in 0..common_data.config.num_routed_wires {
let wire_value = vars.local_wires[j];
let k_i = common_data.k_is[j];
let s_id = x * k_i.into();
let s_sigma = s_sigmas[j];
f_prime *= wire_value + s_id * betas[i].into() + gammas[i].into();
g_prime *= wire_value + s_sigma * betas[i].into() + gammas[i].into();
}
vanishing_v_shift_terms.push(f_prime * z_x - g_prime * z_gz);
let numerator_values = (0..common_data.config.num_routed_wires)
.map(|j| {
let wire_value = vars.local_wires[j];
let k_i = common_data.k_is[j];
let s_id = x * k_i.into();
wire_value + s_id * betas[i].into() + gammas[i].into()
})
.collect::<Vec<_>>();
let denominator_values = (0..common_data.config.num_routed_wires)
.map(|j| {
let wire_value = vars.local_wires[j];
let s_sigma = s_sigmas[j];
wire_value + s_sigma * betas[i].into() + gammas[i].into()
})
.collect::<Vec<_>>();
let quotient_values = (0..common_data.config.num_routed_wires)
.map(|j| numerator_values[j] / denominator_values[j])
.collect::<Vec<_>>();
// The partial products considered for this iteration of `i`.
let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods];
// Check the quotient partial products.
let mut partial_product_check = check_partial_products(
&quotient_values,
current_partial_products,
partial_products_degree,
);
// The first checks are of the form `q - n/d` which is a rational function not a polynomial.
// We multiply them by `d` to get checks of the form `q*d - n` which low-degree polynomials.
denominator_values
.chunks(partial_products_degree)
.zip(partial_product_check.iter_mut())
.for_each(|(d, q)| {
*q *= d.iter().copied().product();
});
vanishing_partial_products_terms.extend(partial_product_check);
// The quotient final product is the product of the last `final_num_prod` elements.
let quotient: F::Extension = current_partial_products[num_prods - final_num_prod..]
.iter()
.copied()
.product();
vanishing_v_shift_terms.push(quotient * z_x - z_gz);
}
let vanishing_terms = [
vanishing_z_1_terms,
vanishing_partial_products_terms,
vanishing_v_shift_terms,
constraint_terms,
]
@ -114,42 +154,80 @@ pub(crate) fn eval_vanishing_poly_base<F: Extendable<D>, const D: usize>(
index: usize,
x: F,
vars: EvaluationVarsBase<F>,
local_plonk_zs: &[F],
next_plonk_zs: &[F],
local_zs: &[F],
next_zs: &[F],
partial_products: &[F],
s_sigmas: &[F],
betas: &[F],
gammas: &[F],
alphas: &[F],
z_h_on_coset: &ZeroPolyOnCoset<F>,
) -> Vec<F> {
let partial_products_degree = common_data.quotient_degree_factor;
let (num_prods, final_num_prod) = common_data.num_partial_products;
let constraint_terms =
evaluate_gate_constraints_base(&common_data.gates, common_data.num_gate_constraints, vars);
// The L_1(x) (Z(x) - 1) vanishing terms.
let mut vanishing_z_1_terms = Vec::new();
// The terms checking the partial products.
let mut vanishing_partial_products_terms = Vec::new();
// The Z(x) f'(x) - g'(x) Z(g x) terms.
let mut vanishing_v_shift_terms = Vec::new();
for i in 0..common_data.config.num_challenges {
let z_x = local_plonk_zs[i];
let z_gz = next_plonk_zs[i];
let z_x = local_zs[i];
let z_gz = next_zs[i];
vanishing_z_1_terms.push(z_h_on_coset.eval_l1(index, x) * (z_x - F::ONE));
let mut f_prime = F::ONE;
let mut g_prime = F::ONE;
for j in 0..common_data.config.num_routed_wires {
let wire_value = vars.local_wires[j];
let k_i = common_data.k_is[j];
let s_id = k_i * x;
let s_sigma = s_sigmas[j];
f_prime *= wire_value + betas[i] * s_id + gammas[i];
g_prime *= wire_value + betas[i] * s_sigma + gammas[i];
}
vanishing_v_shift_terms.push(f_prime * z_x - g_prime * z_gz);
}
let numerator_values = (0..common_data.config.num_routed_wires)
.map(|j| {
let wire_value = vars.local_wires[j];
let k_i = common_data.k_is[j];
let s_id = k_i * x;
wire_value + betas[i] * s_id + gammas[i]
})
.collect::<Vec<_>>();
let denominator_values = (0..common_data.config.num_routed_wires)
.map(|j| {
let wire_value = vars.local_wires[j];
let s_sigma = s_sigmas[j];
wire_value + betas[i] * s_sigma + gammas[i]
})
.collect::<Vec<_>>();
let quotient_values = (0..common_data.config.num_routed_wires)
.map(|j| numerator_values[j] / denominator_values[j])
.collect::<Vec<_>>();
// The partial products considered for this iteration of `i`.
let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods];
// Check the quotient partial products.
let mut partial_product_check = check_partial_products(
&quotient_values,
current_partial_products,
partial_products_degree,
);
// The first checks are of the form `q - n/d` which is a rational function not a polynomial.
// We multiply them by `d` to get checks of the form `q*d - n` which low-degree polynomials.
denominator_values
.chunks(partial_products_degree)
.zip(partial_product_check.iter_mut())
.for_each(|(d, q)| {
*q *= d.iter().copied().product();
});
vanishing_partial_products_terms.extend(partial_product_check);
// The quotient final product is the product of the last `final_num_prod` elements.
let quotient: F = current_partial_products[num_prods - final_num_prod..]
.iter()
.copied()
.product();
vanishing_v_shift_terms.push(quotient * z_x - z_gz);
}
let vanishing_terms = [
vanishing_z_1_terms,
vanishing_partial_products_terms,
vanishing_v_shift_terms,
constraint_terms,
]

View File

@ -147,6 +147,13 @@ impl<F: Field> ListPolynomialCommitment<F> {
// Final low-degree polynomial that goes into FRI.
let mut final_poly = PolynomialCoeffs::empty();
let mut zs_polys = commitments[PlonkPolynomials::ZS_PARTIAL_PRODUCTS.index]
.polynomials
.iter()
.map(|p| p.to_extension())
.collect::<Vec<_>>();
let partial_products_polys = zs_polys.split_off(common_data.zs_range().end);
// Polynomials opened at a single point.
let single_polys = [
PlonkPolynomials::CONSTANTS_SIGMAS,
@ -154,7 +161,8 @@ impl<F: Field> ListPolynomialCommitment<F> {
]
.iter()
.flat_map(|&p| &commitments[p.index].polynomials)
.map(|p| p.to_extension());
.map(|p| p.to_extension())
.chain(partial_products_polys);
let single_composition_poly = alpha.reduce_polys(single_polys);
let single_quotient = Self::compute_quotient([zeta], single_composition_poly);
@ -162,11 +170,7 @@ impl<F: Field> ListPolynomialCommitment<F> {
alpha.reset();
// Zs polynomials are opened at `zeta` and `g*zeta`.
let zs_polys = commitments[PlonkPolynomials::ZS.index]
.polynomials
.iter()
.map(|p| p.to_extension());
let zs_composition_poly = alpha.reduce_polys(zs_polys);
let zs_composition_poly = alpha.reduce_polys(zs_polys.into_iter());
let zs_quotient = Self::compute_quotient([zeta, g * zeta], zs_composition_poly);
alpha.shift_poly(&mut final_poly);
@ -254,7 +258,7 @@ impl<F: Field + Extendable<D>, const D: usize> OpeningProof<F, D> {
os: &OpeningSet<F, D>,
merkle_roots: &[Hash<F>],
challenger: &mut Challenger<F>,
fri_config: &FriConfig,
common_data: &CommonCircuitData<F, D>,
) -> Result<()> {
challenger.observe_opening_set(os);
@ -268,7 +272,7 @@ impl<F: Field + Extendable<D>, const D: usize> OpeningProof<F, D> {
merkle_roots,
&self.fri_proof,
challenger,
fri_config,
common_data,
)
}
}
@ -310,7 +314,7 @@ mod tests {
}
fn check_batch_polynomial_commitment<F: Field + Extendable<D>, const D: usize>() -> Result<()> {
let ks = [10, 2, 3, 8];
let ks = [10, 2, 10, 8];
let degree_log = 11;
let fri_config = FriConfig {
proof_of_work_bits: 2,
@ -327,10 +331,11 @@ mod tests {
},
degree_bits: 0,
gates: vec![],
max_filtered_constraint_degree: 0,
quotient_degree_factor: 0,
num_gate_constraints: 0,
num_constants: 4,
k_is: vec![F::ONE; 6],
num_partial_products: (0, 0),
circuit_digest: Hash::from_partial(vec![]),
};
@ -362,7 +367,7 @@ mod tests {
lpcs[3].merkle_tree.root,
],
&mut Challenger::new(),
&common_data.config.fri_config,
&common_data,
)
}

View File

@ -154,6 +154,7 @@ pub struct OpeningSet<F: Field + Extendable<D>, const D: usize> {
pub wires: Vec<F::Extension>,
pub plonk_zs: Vec<F::Extension>,
pub plonk_zs_right: Vec<F::Extension>,
pub partial_products: Vec<F::Extension>,
pub quotient_polys: Vec<F::Extension>,
}
@ -163,7 +164,7 @@ impl<F: Field + Extendable<D>, const D: usize> OpeningSet<F, D> {
g: F::Extension,
constants_sigmas_commitment: &ListPolynomialCommitment<F>,
wires_commitment: &ListPolynomialCommitment<F>,
plonk_zs_commitment: &ListPolynomialCommitment<F>,
zs_partial_products_commitment: &ListPolynomialCommitment<F>,
quotient_polys_commitment: &ListPolynomialCommitment<F>,
common_data: &CommonCircuitData<F, D>,
) -> Self {
@ -174,12 +175,17 @@ impl<F: Field + Extendable<D>, const D: usize> OpeningSet<F, D> {
.collect::<Vec<_>>()
};
let constants_sigmas_eval = eval_commitment(z, constants_sigmas_commitment);
let zs_partial_products_eval = eval_commitment(z, zs_partial_products_commitment);
Self {
constants: constants_sigmas_eval[common_data.constants_range()].to_vec(),
plonk_s_sigmas: constants_sigmas_eval[common_data.sigmas_range()].to_vec(),
wires: eval_commitment(z, wires_commitment),
plonk_zs: eval_commitment(z, plonk_zs_commitment),
plonk_zs_right: eval_commitment(g * z, plonk_zs_commitment),
plonk_zs: zs_partial_products_eval[common_data.zs_range()].to_vec(),
plonk_zs_right: eval_commitment(g * z, zs_partial_products_commitment)
[common_data.zs_range()]
.to_vec(),
partial_products: zs_partial_products_eval[common_data.partial_products_range()]
.to_vec(),
quotient_polys: eval_commitment(z, quotient_polys_commitment),
}
}

View File

@ -12,6 +12,7 @@ use crate::polynomial::commitment::ListPolynomialCommitment;
use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues};
use crate::proof::Proof;
use crate::timed;
use crate::util::partial_products::partial_products;
use crate::util::{log2_ceil, transpose};
use crate::vars::EvaluationVarsBase;
use crate::witness::{PartialWitness, Witness};
@ -78,21 +79,34 @@ pub(crate) fn prove<F: Extendable<D>, const D: usize>(
let betas = challenger.get_n_challenges(num_challenges);
let gammas = challenger.get_n_challenges(num_challenges);
let plonk_z_vecs = timed!(
compute_zs(&witness, &betas, &gammas, prover_data, common_data),
"to compute Z's"
assert!(
common_data.quotient_degree_factor + 1 <=common_data.config.num_routed_wires,
"When the number of routed wires is smaller that the degree, we should change the logic to avoid computing partial products."
);
let mut partial_products = timed!(
all_wires_permutation_partial_products(&witness, &betas, &gammas, prover_data, common_data),
"to compute partial products"
);
let plonk_zs_commitment = timed!(
let plonk_z_vecs = timed!(compute_zs(&partial_products, common_data), "to compute Z's");
// The first polynomial in `partial_products` represent the final product used in the
// computation of `Z`. It isn't needed anymore so we discard it.
partial_products.iter_mut().for_each(|part| {
part.remove(0);
});
let zs_partial_products = [plonk_z_vecs, partial_products.concat()].concat();
let zs_partial_products_commitment = timed!(
ListPolynomialCommitment::new(
plonk_z_vecs,
zs_partial_products,
fri_config.rate_bits,
PlonkPolynomials::ZS.blinding
PlonkPolynomials::ZS_PARTIAL_PRODUCTS.blinding
),
"to commit to Z's"
);
challenger.observe_hash(&plonk_zs_commitment.merkle_tree.root);
challenger.observe_hash(&zs_partial_products_commitment.merkle_tree.root);
let alphas = challenger.get_n_challenges(num_challenges);
@ -101,7 +115,7 @@ pub(crate) fn prove<F: Extendable<D>, const D: usize>(
common_data,
prover_data,
&wires_commitment,
&plonk_zs_commitment,
&zs_partial_products_commitment,
&betas,
&gammas,
&alphas,
@ -116,7 +130,7 @@ pub(crate) fn prove<F: Extendable<D>, const D: usize>(
.flat_map(|mut quotient_poly| {
quotient_poly.trim();
quotient_poly.pad(quotient_degree).expect(
"The quotient polynomial doesn't have the right degree.\
"The quotient polynomial doesn't have the right degree. \
This may be because the `Z`s polynomials are still too high degree.",
);
// Split t into degree-n chunks.
@ -144,7 +158,7 @@ pub(crate) fn prove<F: Extendable<D>, const D: usize>(
&[
&prover_data.constants_sigmas_commitment,
&wires_commitment,
&plonk_zs_commitment,
&zs_partial_products_commitment,
&quotient_polys_commitment,
],
zeta,
@ -161,50 +175,103 @@ pub(crate) fn prove<F: Extendable<D>, const D: usize>(
Proof {
wires_root: wires_commitment.merkle_tree.root,
plonk_zs_root: plonk_zs_commitment.merkle_tree.root,
plonk_zs_root: zs_partial_products_commitment.merkle_tree.root,
quotient_polys_root: quotient_polys_commitment.merkle_tree.root,
openings,
opening_proof,
}
}
fn compute_zs<F: Extendable<D>, const D: usize>(
/// Compute the partial products used in the `Z` polynomials.
fn all_wires_permutation_partial_products<F: Extendable<D>, const D: usize>(
witness: &Witness<F>,
betas: &[F],
gammas: &[F],
prover_data: &ProverOnlyCircuitData<F, D>,
common_data: &CommonCircuitData<F, D>,
) -> Vec<PolynomialValues<F>> {
) -> Vec<Vec<PolynomialValues<F>>> {
(0..common_data.config.num_challenges)
.map(|i| compute_z(witness, betas[i], gammas[i], prover_data, common_data))
.map(|i| {
wires_permutation_partial_products(
witness,
betas[i],
gammas[i],
prover_data,
common_data,
)
})
.collect()
}
fn compute_z<F: Extendable<D>, const D: usize>(
/// Compute the partial products used in the `Z` polynomial.
/// Returns the polynomials interpolating `partial_products(f / g)`
/// where `f, g` are the products in the definition of `Z`: `Z(g^i) = f / g`.
fn wires_permutation_partial_products<F: Extendable<D>, const D: usize>(
witness: &Witness<F>,
beta: F,
gamma: F,
prover_data: &ProverOnlyCircuitData<F, D>,
common_data: &CommonCircuitData<F, D>,
) -> PolynomialValues<F> {
) -> Vec<PolynomialValues<F>> {
let degree = common_data.quotient_degree_factor;
let subgroup = &prover_data.subgroup;
let mut plonk_z_points = vec![F::ONE];
let k_is = &common_data.k_is;
let values = subgroup
.par_iter()
.enumerate()
.map(|(i, &x)| {
let s_sigmas = &prover_data.sigmas[i];
let quotient_values = (0..common_data.config.num_routed_wires)
.map(|j| {
let wire_value = witness.get_wire(i, j);
let k_i = k_is[j];
let s_id = k_i * x;
let s_sigma = s_sigmas[j];
let numerator = wire_value + beta * s_id + gamma;
let denominator = wire_value + beta * s_sigma + gamma;
numerator / denominator
})
.collect::<Vec<_>>();
let quotient_partials = partial_products(&quotient_values, degree);
// This is the final product for the quotient.
let quotient = quotient_partials
[common_data.num_partial_products.0 - common_data.num_partial_products.1..]
.iter()
.copied()
.product();
// We add the quotient at the beginning of the vector to reuse them later in the computation of `Z`.
[vec![quotient], quotient_partials].concat()
})
.collect::<Vec<_>>();
transpose(&values)
.into_par_iter()
.map(PolynomialValues::new)
.collect()
}
fn compute_zs<F: Extendable<D>, const D: usize>(
partial_products: &[Vec<PolynomialValues<F>>],
common_data: &CommonCircuitData<F, D>,
) -> Vec<PolynomialValues<F>> {
(0..common_data.config.num_challenges)
.map(|i| compute_z(&partial_products[i], common_data))
.collect()
}
/// Compute the `Z` polynomial by reusing the computations done in `wires_permutation_partial_products`.
fn compute_z<F: Extendable<D>, const D: usize>(
partial_products: &[PolynomialValues<F>],
common_data: &CommonCircuitData<F, D>,
) -> PolynomialValues<F> {
let mut plonk_z_points = vec![F::ONE];
for i in 1..common_data.degree() {
let x = subgroup[i - 1];
let mut numerator = F::ONE;
let mut denominator = F::ONE;
let s_sigmas = &prover_data.sigmas[i - 1];
for j in 0..common_data.config.num_routed_wires {
let wire_value = witness.get_wire(i - 1, j);
let k_i = k_is[j];
let s_id = k_i * x;
let s_sigma = s_sigmas[j];
numerator *= wire_value + beta * s_id + gamma;
denominator *= wire_value + beta * s_sigma + gamma;
}
let quotient = partial_products[0].values[i - 1];
let last = *plonk_z_points.last().unwrap();
plonk_z_points.push(last * numerator / denominator);
plonk_z_points.push(last * quotient);
}
plonk_z_points.into()
}
@ -213,28 +280,27 @@ fn compute_quotient_polys<'a, F: Extendable<D>, const D: usize>(
common_data: &CommonCircuitData<F, D>,
prover_data: &'a ProverOnlyCircuitData<F, D>,
wires_commitment: &'a ListPolynomialCommitment<F>,
plonk_zs_commitment: &'a ListPolynomialCommitment<F>,
zs_partial_products_commitment: &'a ListPolynomialCommitment<F>,
betas: &[F],
gammas: &[F],
alphas: &[F],
) -> Vec<PolynomialCoeffs<F>> {
let num_challenges = common_data.config.num_challenges;
let max_filtered_constraint_degree_bits = log2_ceil(common_data.max_filtered_constraint_degree);
let max_degree_bits = log2_ceil(common_data.quotient_degree_factor + 1);
assert!(
max_filtered_constraint_degree_bits <= common_data.config.rate_bits,
max_degree_bits <= common_data.config.rate_bits,
"Having constraints of degree higher than the rate is not supported yet. \
If we need this in the future, we can precompute the larger LDE before computing the `ListPolynomialCommitment`s."
);
// We reuse the LDE computed in `ListPolynomialCommitment` and extract every `step` points to get
// an LDE matching `max_filtered_constraint_degree`.
let step = 1 << (common_data.config.rate_bits - max_filtered_constraint_degree_bits);
let step = 1 << (common_data.config.rate_bits - max_degree_bits);
// When opening the `Z`s polys at the "next" point in Plonk, need to look at the point `next_step`
// steps away since we work on an LDE of degree `max_filtered_constraint_degree`.
let next_step = 1 << max_filtered_constraint_degree_bits;
let next_step = 1 << max_degree_bits;
let points =
F::two_adic_subgroup(common_data.degree_bits + max_filtered_constraint_degree_bits);
let points = F::two_adic_subgroup(common_data.degree_bits + max_degree_bits);
let lde_size = points.len();
// Retrieve the LDE values at index `i`.
@ -242,8 +308,7 @@ fn compute_quotient_polys<'a, F: Extendable<D>, const D: usize>(
comm.get_lde_values(i * step)
};
let z_h_on_coset =
ZeroPolyOnCoset::new(common_data.degree_bits, max_filtered_constraint_degree_bits);
let z_h_on_coset = ZeroPolyOnCoset::new(common_data.degree_bits, max_degree_bits);
let quotient_values: Vec<Vec<F>> = points
.into_par_iter()
@ -255,11 +320,14 @@ fn compute_quotient_polys<'a, F: Extendable<D>, const D: usize>(
let local_constants = &local_constants_sigmas[common_data.constants_range()];
let s_sigmas = &local_constants_sigmas[common_data.sigmas_range()];
let local_wires = get_at_index(wires_commitment, i);
let local_plonk_zs = get_at_index(plonk_zs_commitment, i);
let next_plonk_zs = get_at_index(plonk_zs_commitment, i_next);
let local_zs_partial_products = get_at_index(zs_partial_products_commitment, i);
let local_zs = &local_zs_partial_products[common_data.zs_range()];
let next_zs =
&get_at_index(zs_partial_products_commitment, i_next)[common_data.zs_range()];
let partial_products = &local_zs_partial_products[common_data.partial_products_range()];
debug_assert_eq!(local_wires.len(), common_data.config.num_wires);
debug_assert_eq!(local_plonk_zs.len(), num_challenges);
debug_assert_eq!(local_zs.len(), num_challenges);
let vars = EvaluationVarsBase {
local_constants,
@ -270,8 +338,9 @@ fn compute_quotient_polys<'a, F: Extendable<D>, const D: usize>(
i,
shifted_x,
vars,
local_plonk_zs,
next_plonk_zs,
local_zs,
next_zs,
partial_products,
s_sigmas,
betas,
gammas,

View File

@ -1,3 +1,4 @@
pub mod partial_products;
pub mod scaling;
pub(crate) mod timing;

View File

@ -0,0 +1,95 @@
use std::iter::Product;
use std::ops::Sub;
use crate::util::ceil_div_usize;
/// Compute partial products of the original vector `v` such that all products consist of `max_degree`
/// or less elements. This is done until we've computed the product `P` of all elements in the vector.
pub fn partial_products<T: Product + Copy>(v: &[T], max_degree: usize) -> Vec<T> {
let mut res = Vec::new();
let mut remainder = v.to_vec();
while remainder.len() > max_degree {
let new_partials = remainder
.chunks(max_degree)
// TODO: can filter out chunks of length 1.
.map(|chunk| chunk.iter().copied().product())
.collect::<Vec<_>>();
res.extend_from_slice(&new_partials);
remainder = new_partials;
}
res
}
/// Returns a tuple `(a,b)`, where `a` is the length of the output of `partial_products()` on a
/// vector of length `n`, and `b` is the number of elements needed to compute the final product.
pub fn num_partial_products(n: usize, max_degree: usize) -> (usize, usize) {
debug_assert!(max_degree > 1);
let mut res = 0;
let mut remainder = n;
while remainder > max_degree {
let new_partials_len = ceil_div_usize(remainder, max_degree);
res += new_partials_len;
remainder = new_partials_len;
}
(res, remainder)
}
/// Checks that the partial products of `v` are coherent with those in `partials` by only computing
/// products of size `max_degree` or less.
pub fn check_partial_products<T: Product + Copy + Sub<Output = T>>(
v: &[T],
partials: &[T],
max_degree: usize,
) -> Vec<T> {
let mut res = Vec::new();
let mut remainder = v.to_vec();
let mut partials = partials.to_vec();
while remainder.len() > max_degree {
let products = remainder
.chunks(max_degree)
.map(|chunk| chunk.iter().copied().product())
.collect::<Vec<T>>();
res.extend(products.iter().zip(&partials).map(|(&a, &b)| a - b));
remainder = partials.drain(..products.len()).collect();
}
res
}
#[cfg(test)]
mod tests {
use num::Zero;
use super::*;
#[test]
fn test_partial_products() {
let v = vec![1, 2, 3, 4, 5, 6];
let p = partial_products(&v, 2);
assert_eq!(p, vec![2, 12, 30, 24, 30]);
let nums = num_partial_products(v.len(), 2);
assert_eq!(p.len(), nums.0);
assert!(check_partial_products(&v, &p, 2)
.iter()
.all(|x| x.is_zero()));
assert_eq!(
v.into_iter().product::<i32>(),
p[p.len() - nums.1..].iter().copied().product(),
);
let v = vec![1, 2, 3, 4, 5, 6];
let p = partial_products(&v, 3);
assert_eq!(p, vec![6, 120]);
let nums = num_partial_products(v.len(), 3);
assert_eq!(p.len(), nums.0);
assert!(check_partial_products(&v, &p, 3)
.iter()
.all(|x| x.is_zero()));
assert_eq!(
v.into_iter().product::<i32>(),
p[p.len() - nums.1..].iter().copied().product(),
);
}
}

View File

@ -174,6 +174,7 @@ mod tests {
use crate::circuit_data::CircuitConfig;
use crate::field::crandall_field::CrandallField;
use crate::field::extension_field::quartic::QuarticCrandallField;
use crate::verifier::verify;
use crate::witness::PartialWitness;
fn test_reduce_gadget(n: usize) {
@ -205,6 +206,8 @@ mod tests {
let data = builder.build();
let proof = data.prove(PartialWitness::new());
verify(proof, &data.verifier_only, &data.common).unwrap();
}
#[test]

View File

@ -2,8 +2,9 @@ use anyhow::{ensure, Result};
use crate::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData};
use crate::field::extension_field::Extendable;
use crate::field::field::Field;
use crate::plonk_challenger::Challenger;
use crate::plonk_common::{eval_vanishing_poly, eval_zero_poly};
use crate::plonk_common::{eval_vanishing_poly, eval_zero_poly, reduce_with_powers};
use crate::proof::Proof;
use crate::vars::EvaluationVars;
@ -13,7 +14,6 @@ pub(crate) fn verify<F: Extendable<D>, const D: usize>(
common_data: &CommonCircuitData<F, D>,
) -> Result<()> {
let config = &common_data.config;
let fri_config = &config.fri_config;
let num_challenges = config.num_challenges;
let mut challenger = Challenger::new();
@ -37,17 +37,19 @@ pub(crate) fn verify<F: Extendable<D>, const D: usize>(
local_constants,
local_wires,
};
let local_plonk_zs = &proof.openings.plonk_zs;
let next_plonk_zs = &proof.openings.plonk_zs_right;
let local_zs = &proof.openings.plonk_zs;
let next_zs = &proof.openings.plonk_zs_right;
let s_sigmas = &proof.openings.plonk_s_sigmas;
let partial_products = &proof.openings.partial_products;
// Evaluate the vanishing polynomial at our challenge point, zeta.
let vanishing_polys_zeta = eval_vanishing_poly(
common_data,
zeta,
vars,
local_plonk_zs,
next_plonk_zs,
local_zs,
next_zs,
partial_products,
s_sigmas,
&betas,
&gammas,
@ -56,9 +58,18 @@ pub(crate) fn verify<F: Extendable<D>, const D: usize>(
// 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 z_h_zeta = eval_zero_poly(common_data.degree(), zeta);
for i in 0..num_challenges {
ensure!(vanishing_polys_zeta[i] == z_h_zeta * quotient_polys_zeta[i]);
let zeta_pow_deg = zeta.exp_power_of_2(common_data.degree_bits);
let z_h_zeta = zeta_pow_deg - F::Extension::ONE;
// `quotient_polys_zeta` holds `num_challenges * quotient_degree_factor` evaluations.
// Each chunk of `quotient_degree_factor` holds the evaluations of `t_0(zeta),...,t_{quotient_degree_factor-1}(zeta)`
// 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));
}
let evaluations = proof.openings.clone();
@ -75,7 +86,7 @@ pub(crate) fn verify<F: Extendable<D>, const D: usize>(
&evaluations,
merkle_roots,
&mut challenger,
fri_config,
common_data,
)?;
Ok(())