From ad24f5d4d1206fc13563b4f52074a6216c6797c0 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 12 Jul 2021 14:25:28 +0200 Subject: [PATCH] Almost working recursive verifier --- src/fri/recursive_verifier.rs | 4 +- src/fri/verifier.rs | 2 +- src/gadgets/arithmetic_extension.rs | 10 + src/gadgets/insert.rs | 7 +- src/gates/gate.rs | 28 ++- src/generator.rs | 6 + src/lib.rs | 1 + src/plonk_challenger.rs | 29 ++- src/plonk_common.rs | 253 ++------------------ src/polynomial/commitment.rs | 40 +++- src/proof.rs | 10 +- src/prover.rs | 9 +- src/recursive_verifier.rs | 111 ++++++++- src/util/partial_products.rs | 29 +++ src/vanishing_poly.rs | 356 ++++++++++++++++++++++++++++ src/vars.rs | 6 + src/verifier.rs | 5 +- 17 files changed, 643 insertions(+), 263 deletions(-) create mode 100644 src/vanishing_poly.rs diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs index 31ab0a25..fad2317c 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -1,6 +1,7 @@ use itertools::izip; use crate::circuit_builder::CircuitBuilder; +use crate::circuit_data::CommonCircuitData; use crate::field::extension_field::target::{flatten_target, ExtensionTarget}; use crate::field::extension_field::Extendable; use crate::field::field::Field; @@ -73,8 +74,9 @@ impl, const D: usize> CircuitBuilder { initial_merkle_roots: &[HashTarget], proof: &FriProofTarget, challenger: &mut RecursiveChallenger, - config: &FriConfig, + common_data: &CommonCircuitData, ) { + let config = &common_data.config.fri_config; let total_arities = config.reduction_arity_bits.iter().sum::(); debug_assert_eq!( purported_degree_log, diff --git a/src/fri/verifier.rs b/src/fri/verifier.rs index e0716cde..5ff068b6 100644 --- a/src/fri/verifier.rs +++ b/src/fri/verifier.rs @@ -178,7 +178,7 @@ fn fri_combine_initial, const D: usize>( let single_openings = os .constants .iter() - .chain(&os.plonk_s_sigmas) + .chain(&os.plonk_sigmas) .chain(&os.quotient_polys) .chain(&os.partial_products); let single_diffs = single_evals diff --git a/src/gadgets/arithmetic_extension.rs b/src/gadgets/arithmetic_extension.rs index 3bf9c8a9..57968a23 100644 --- a/src/gadgets/arithmetic_extension.rs +++ b/src/gadgets/arithmetic_extension.rs @@ -252,6 +252,16 @@ impl, const D: usize> CircuitBuilder { self.arithmetic_extension(F::ONE, F::ONE, a_ext, b, c) } + /// Like `mul_sub`, but for `ExtensionTarget`s. + pub fn mul_sub_extension( + &mut self, + a: ExtensionTarget, + b: ExtensionTarget, + c: ExtensionTarget, + ) -> ExtensionTarget { + self.arithmetic_extension(F::ONE, F::NEG_ONE, a, b, c) + } + /// Like `mul_sub`, but for `ExtensionTarget`s. pub fn scalar_mul_sub_extension( &mut self, diff --git a/src/gadgets/insert.rs b/src/gadgets/insert.rs index 29e360fc..aee4dcd3 100644 --- a/src/gadgets/insert.rs +++ b/src/gadgets/insert.rs @@ -49,7 +49,7 @@ impl, const D: usize> CircuitBuilder { let mut already_inserted = self.zero(); let mut new_list = Vec::new(); - for i in 0..v.len() { + for i in 0..=v.len() { let one = self.one(); let cur_index = self.constant(F::from_canonical_usize(i)); @@ -63,7 +63,9 @@ impl, const D: usize> CircuitBuilder { already_inserted = self.add(already_inserted, insert_here); let not_already_inserted = self.sub(one, already_inserted); - new_item = self.scalar_mul_add_extension(not_already_inserted, v[i], new_item); + if i < v.len() { + new_item = self.scalar_mul_add_extension(not_already_inserted, v[i], new_item); + } new_list.push(new_item); } @@ -106,6 +108,7 @@ mod tests { let elem = builder.constant_extension(FF::rand()); let inserted = real_insert(i, elem, &v); let purported_inserted = builder.insert(it, elem, v.clone()); + assert_eq!(inserted.len(), purported_inserted.len()); for (x, y) in inserted.into_iter().zip(purported_inserted) { builder.route_extension(x, y); diff --git a/src/gates/gate.rs b/src/gates/gate.rs index 4b37892c..cce665f9 100644 --- a/src/gates/gate.rs +++ b/src/gates/gate.rs @@ -8,6 +8,7 @@ use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field::Field; use crate::gates::gate_tree::Tree; use crate::generator::WitnessGenerator; +use crate::target::Target; use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; /// A custom gate. @@ -76,9 +77,11 @@ pub trait Gate, const D: usize>: 'static + Send + Sync { fn eval_filtered_recursively( &self, builder: &mut CircuitBuilder, - vars: EvaluationTargets, + mut vars: EvaluationTargets, + prefix: &[bool], ) -> Vec> { - // TODO: Filter + let filter = compute_filter_recursively(builder, prefix, vars.local_constants); + vars.remove_prefix(prefix); self.eval_unfiltered_recursively(builder, vars) } @@ -167,3 +170,24 @@ fn compute_filter(prefix: &[bool], constants: &[K]) -> K { }) .product() } + +fn compute_filter_recursively, const D: usize>( + builder: &mut CircuitBuilder, + prefix: &[bool], + constants: &[ExtensionTarget], +) -> ExtensionTarget { + let one = builder.one_extension(); + let v = prefix + .iter() + .enumerate() + .map(|(i, &b)| { + if b { + constants[i] + } else { + builder.sub_extension(one, constants[i]) + } + }) + .collect::>(); + + builder.mul_many_extension(&v) +} diff --git a/src/generator.rs b/src/generator.rs index a47c5267..ee2001ec 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -55,6 +55,12 @@ pub(crate) fn generate_partial_witness( pending_generator_indices = next_pending_generator_indices; } + for i in 0..generators.len() { + if !expired_generator_indices.contains(&i) { + dbg!(i); + break; + } + } assert_eq!( expired_generator_indices.len(), generators.len(), diff --git a/src/lib.rs b/src/lib.rs index adfdf2cf..50c0bb4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod recursive_verifier; pub mod rescue; pub mod target; pub mod util; +pub mod vanishing_poly; pub mod vars; pub mod verifier; pub mod wire; diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index 149dceb3..da7253f0 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -5,7 +5,7 @@ use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field::Field; use crate::hash::{permute, SPONGE_RATE, SPONGE_WIDTH}; -use crate::proof::{Hash, HashTarget, OpeningSet}; +use crate::proof::{Hash, HashTarget, OpeningSet, OpeningSetTarget}; use crate::target::Target; /// Observes prover messages, and generates challenges by hashing the transcript. @@ -68,7 +68,7 @@ impl Challenger { { let OpeningSet { constants, - plonk_s_sigmas, + plonk_sigmas, wires, plonk_zs, plonk_zs_right, @@ -77,7 +77,7 @@ impl Challenger { } = os; for v in &[ constants, - plonk_s_sigmas, + plonk_sigmas, wires, plonk_zs, plonk_zs_right, @@ -211,6 +211,29 @@ impl RecursiveChallenger { } } + pub fn observe_opening_set(&mut self, os: &OpeningSetTarget) { + let OpeningSetTarget { + constants, + plonk_sigmas, + wires, + plonk_zs, + plonk_zs_right, + partial_products, + quotient_polys, + } = os; + for v in &[ + constants, + plonk_sigmas, + wires, + plonk_zs, + plonk_zs_right, + partial_products, + quotient_polys, + ] { + self.observe_extension_elements(v); + } + } + pub fn observe_hash(&mut self, hash: &HashTarget) { self.observe_elements(&hash.elements) } diff --git a/src/plonk_common.rs b/src/plonk_common.rs index 5179b474..2c3a0817 100644 --- a/src/plonk_common.rs +++ b/src/plonk_common.rs @@ -58,237 +58,6 @@ impl PlonkPolynomials { } } -/// Evaluate the vanishing polynomial at `x`. In this context, the vanishing polynomial is a random -/// linear combination of gate constraints, plus some other terms relating to the permutation -/// argument. All such terms should vanish on `H`. -pub(crate) fn eval_vanishing_poly, const D: usize>( - common_data: &CommonCircuitData, - x: F::Extension, - vars: EvaluationVars, - local_zs: &[F::Extension], - next_zs: &[F::Extension], - partial_products: &[F::Extension], - s_sigmas: &[F::Extension], - betas: &[F], - gammas: &[F], - alphas: &[F], -) -> Vec { - let max_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_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 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::>(); - 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::>(); - let quotient_values = (0..common_data.config.num_routed_wires) - .map(|j| numerator_values[j] / denominator_values[j]) - .collect::>(); - - // 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("ient_values, current_partial_products, max_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(max_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, - ] - .concat(); - - let alphas = &alphas.iter().map(|&a| a.into()).collect::>(); - reduce_with_powers_multi(&vanishing_terms, alphas) -} - -/// Like `eval_vanishing_poly`, but specialized for base field points. -pub(crate) fn eval_vanishing_poly_base, const D: usize>( - common_data: &CommonCircuitData, - index: usize, - x: F, - vars: EvaluationVarsBase, - local_zs: &[F], - next_zs: &[F], - partial_products: &[F], - s_sigmas: &[F], - betas: &[F], - gammas: &[F], - alphas: &[F], - z_h_on_coset: &ZeroPolyOnCoset, -) -> Vec { - let max_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_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 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::>(); - 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::>(); - let quotient_values = (0..common_data.config.num_routed_wires) - .map(|j| numerator_values[j] / denominator_values[j]) - .collect::>(); - - // The partial products considered for this iteration of `i`. - let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods]; - // Check the numerator partial products. - let mut partial_product_check = - check_partial_products("ient_values, current_partial_products, max_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(max_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, - ] - .concat(); - - reduce_with_powers_multi(&vanishing_terms, alphas) -} - -/// Evaluates all gate constraints. -/// -/// `num_gate_constraints` is the largest number of constraints imposed by any gate. It is not -/// strictly necessary, but it helps performance by ensuring that we allocate a vector with exactly -/// the capacity that we need. -pub fn evaluate_gate_constraints, const D: usize>( - gates: &[PrefixedGate], - num_gate_constraints: usize, - vars: EvaluationVars, -) -> Vec { - let mut constraints = vec![F::Extension::ZERO; num_gate_constraints]; - for gate in gates { - let gate_constraints = gate.gate.0.eval_filtered(vars, &gate.prefix); - for (i, c) in gate_constraints.into_iter().enumerate() { - debug_assert!( - i < num_gate_constraints, - "num_constraints() gave too low of a number" - ); - constraints[i] += c; - } - } - constraints -} - -pub fn evaluate_gate_constraints_base, const D: usize>( - gates: &[PrefixedGate], - num_gate_constraints: usize, - vars: EvaluationVarsBase, -) -> Vec { - let mut constraints = vec![F::ZERO; num_gate_constraints]; - for gate in gates { - let gate_constraints = gate.gate.0.eval_filtered_base(vars, &gate.prefix); - for (i, c) in gate_constraints.into_iter().enumerate() { - debug_assert!( - i < num_gate_constraints, - "num_constraints() gave too low of a number" - ); - constraints[i] += c; - } - } - constraints -} - -pub fn evaluate_gate_constraints_recursively, const D: usize>( - builder: &mut CircuitBuilder, - gates: &[GateRef], - num_gate_constraints: usize, - vars: EvaluationTargets, -) -> Vec> { - let mut constraints = vec![builder.zero_extension(); num_gate_constraints]; - for gate in gates { - let gate_constraints = gate.0.eval_filtered_recursively(builder, vars); - for (i, c) in gate_constraints.into_iter().enumerate() { - constraints[i] = builder.add_extension(constraints[i], c); - } - } - constraints -} - /// Evaluate the polynomial which vanishes on any multiplicative subgroup of a given order `n`. pub(crate) fn eval_zero_poly(n: usize, x: F) -> F { // Z(x) = x^n - 1 @@ -354,6 +123,28 @@ pub(crate) fn eval_l_1(n: usize, x: F) -> F { eval_zero_poly(n, x) / (F::from_canonical_usize(n) * (x - F::ONE)) } +pub(crate) fn eval_l_1_recursively, const D: usize>( + builder: &mut CircuitBuilder, + n: usize, + x: ExtensionTarget, + x_pow_n: ExtensionTarget, +) -> ExtensionTarget { + // L_1(x) = (x^n - 1) / (n * (x - 1)) + // = Z(x) / (n * (x - 1)) + let one = builder.one_extension(); + let neg_one = builder.neg_one(); + let neg_one = builder.convert_to_ext(neg_one); + let eval_zero_poly = builder.sub_extension(x_pow_n, one); + let denominator = builder.arithmetic_extension( + F::from_canonical_usize(n), + F::from_canonical_usize(n), + x, + one, + neg_one, + ); + builder.div_unsafe_extension(eval_zero_poly, denominator) +} + /// For each alpha in alphas, compute a reduction of the given terms using powers of alpha. pub(crate) fn reduce_with_powers_multi(terms: &[F], alphas: &[F]) -> Vec { alphas diff --git a/src/polynomial/commitment.rs b/src/polynomial/commitment.rs index 286f03c9..bb6f0e4e 100644 --- a/src/polynomial/commitment.rs +++ b/src/polynomial/commitment.rs @@ -1,19 +1,21 @@ use anyhow::Result; use rayon::prelude::*; +use crate::circuit_builder::CircuitBuilder; use crate::circuit_data::CommonCircuitData; +use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::extension_field::{FieldExtension, Frobenius}; use crate::field::field::Field; use crate::fri::{prover::fri_proof, verifier::verify_fri_proof, FriConfig}; use crate::merkle_tree::MerkleTree; -use crate::plonk_challenger::Challenger; +use crate::plonk_challenger::{Challenger, RecursiveChallenger}; use crate::plonk_common::PlonkPolynomials; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; -use crate::proof::{FriProof, FriProofTarget, Hash, OpeningSet}; +use crate::proof::{FriProof, FriProofTarget, Hash, HashTarget, OpeningSet, OpeningSetTarget}; use crate::timed; use crate::util::scaling::ReducingFactor; -use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place, transpose}; +use crate::util::{log2_ceil, log2_strict, reverse_bits, reverse_index_bits_in_place, transpose}; pub const SALT_SIZE: usize = 2; @@ -245,13 +247,14 @@ impl ListPolynomialCommitment { } } -pub struct OpeningProof, const D: usize> { +#[derive(Clone)] +pub struct OpeningProof, const D: usize> { pub fri_proof: FriProof, // TODO: Get the degree from `CommonCircuitData` instead. quotient_degree: usize, } -impl, const D: usize> OpeningProof { +impl, const D: usize> OpeningProof { pub fn verify( &self, zeta: F::Extension, @@ -281,6 +284,33 @@ pub struct OpeningProofTarget { pub fri_proof: FriProofTarget, } +impl OpeningProofTarget { + pub fn verify>( + &self, + zeta: ExtensionTarget, + os: &OpeningSetTarget, + merkle_roots: &[HashTarget], + challenger: &mut RecursiveChallenger, + common_data: &CommonCircuitData, + builder: &mut CircuitBuilder, + ) { + challenger.observe_opening_set(os); + + let alpha = challenger.get_extension_challenge(builder); + + builder.verify_fri_proof( + log2_ceil(common_data.degree()), + &os, + zeta, + alpha, + merkle_roots, + &self.fri_proof, + challenger, + common_data, + ); + } +} + #[cfg(test)] mod tests { use anyhow::Result; diff --git a/src/proof.rs b/src/proof.rs index 2c83fb33..353a0080 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -61,6 +61,7 @@ impl HashTarget { } } +#[derive(Clone)] pub struct Proof, const D: usize> { /// Merkle root of LDEs of wire values. pub wires_root: Hash, @@ -83,6 +84,7 @@ pub struct ProofTarget { } /// Evaluations and Merkle proof produced by the prover in a FRI query step. +#[derive(Clone)] pub struct FriQueryStep, const D: usize> { pub evals: Vec, pub merkle_proof: MerkleProof, @@ -96,6 +98,7 @@ pub struct FriQueryStepTarget { /// Evaluations and Merkle proofs of the original set of polynomials, /// before they are combined into a composition polynomial. +#[derive(Clone)] pub struct FriInitialTreeProof { pub evals_proofs: Vec<(Vec, MerkleProof)>, } @@ -120,6 +123,7 @@ impl FriInitialTreeProofTarget { } /// Proof for a FRI query round. +#[derive(Clone)] pub struct FriQueryRound, const D: usize> { pub initial_trees_proof: FriInitialTreeProof, pub steps: Vec>, @@ -131,6 +135,7 @@ pub struct FriQueryRoundTarget { pub steps: Vec>, } +#[derive(Clone)] pub struct FriProof, const D: usize> { /// A Merkle root for each reduced polynomial in the commit phase. pub commit_phase_merkle_roots: Vec>, @@ -153,7 +158,7 @@ pub struct FriProofTarget { /// The purported values of each polynomial at a single point. pub struct OpeningSet, const D: usize> { pub constants: Vec, - pub plonk_s_sigmas: Vec, + pub plonk_sigmas: Vec, pub wires: Vec, pub plonk_zs: Vec, pub plonk_zs_right: Vec, @@ -181,7 +186,7 @@ impl, const D: usize> OpeningSet { 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(), + plonk_sigmas: constants_sigmas_eval[common_data.sigmas_range()].to_vec(), wires: eval_commitment(z, wires_commitment), plonk_zs: zs_partial_products_eval[common_data.zs_range()].to_vec(), plonk_zs_right: eval_commitment(g * z, zs_partial_products_commitment) @@ -195,6 +200,7 @@ impl, const D: usize> OpeningSet { } /// The purported values of each polynomial at a single point. +#[derive(Clone, Debug)] pub struct OpeningSetTarget { pub constants: Vec>, pub plonk_sigmas: Vec>, diff --git a/src/prover.rs b/src/prover.rs index 63c32995..8194551f 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -7,13 +7,14 @@ use crate::circuit_data::{CommonCircuitData, ProverOnlyCircuitData}; use crate::field::extension_field::Extendable; use crate::generator::generate_partial_witness; use crate::plonk_challenger::Challenger; -use crate::plonk_common::{eval_vanishing_poly_base, PlonkPolynomials, ZeroPolyOnCoset}; +use crate::plonk_common::{PlonkPolynomials, ZeroPolyOnCoset}; 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::vanishing_poly::eval_vanishing_poly_base; use crate::vars::EvaluationVarsBase; use crate::witness::{PartialWitness, Witness}; @@ -50,6 +51,12 @@ pub(crate) fn prove, const D: usize>( "to check copy constraints" ); + if degree > 7 { + dbg!(witness.get_wire(8, 16)); + dbg!(witness.get_wire(8, 17)); + dbg!(witness.get_wire(8, 18)); + dbg!(witness.get_wire(8, 19)); + } let wires_values: Vec> = timed!( witness .wire_values diff --git a/src/recursive_verifier.rs b/src/recursive_verifier.rs index 228ad38a..33912663 100644 --- a/src/recursive_verifier.rs +++ b/src/recursive_verifier.rs @@ -1,8 +1,15 @@ +use env_logger::builder; + use crate::circuit_builder::CircuitBuilder; -use crate::circuit_data::{CircuitConfig, VerifierCircuitTarget}; +use crate::circuit_data::{CircuitConfig, CommonCircuitData, VerifierCircuitTarget}; use crate::field::extension_field::Extendable; +use crate::field::field::Field; use crate::gates::gate::{GateRef, PrefixedGate}; -use crate::proof::ProofTarget; +use crate::plonk_challenger::RecursiveChallenger; +use crate::proof::{HashTarget, ProofTarget}; +use crate::util::scaling::ReducingFactorTarget; +use crate::vanishing_poly::eval_vanishing_poly_recursively; +use crate::vars::EvaluationTargets; const MIN_WIRES: usize = 120; // TODO: Double check. const MIN_ROUTED_WIRES: usize = 28; // TODO: Double check. @@ -11,15 +18,94 @@ impl, const D: usize> CircuitBuilder { /// Recursively verifies an inner proof. pub fn add_recursive_verifier( &mut self, - inner_proof: ProofTarget, + proof: ProofTarget, inner_config: &CircuitConfig, - inner_circuit_data: &VerifierCircuitTarget, - inner_gates: &[PrefixedGate], + inner_verifier_data: &VerifierCircuitTarget, + inner_common_data: &CommonCircuitData, ) { assert!(self.config.num_wires >= MIN_WIRES); assert!(self.config.num_wires >= MIN_ROUTED_WIRES); + let one = self.one_extension(); - todo!() + let num_challenges = inner_config.num_challenges; + + let mut challenger = RecursiveChallenger::new(self); + + let digest = + HashTarget::from_vec(self.constants(&inner_common_data.circuit_digest.elements)); + challenger.observe_hash(&digest); + + challenger.observe_hash(&proof.wires_root); + let betas = challenger.get_n_challenges(self, num_challenges); + let gammas = challenger.get_n_challenges(self, num_challenges); + + challenger.observe_hash(&proof.plonk_zs_root); + let alphas = challenger.get_n_challenges(self, num_challenges); + + challenger.observe_hash(&proof.quotient_polys_root); + let zeta = challenger.get_extension_challenge(self); + + let local_constants = &proof.openings.constants; + let local_wires = &proof.openings.wires; + let vars = EvaluationTargets { + local_constants, + local_wires, + }; + let local_zs = &proof.openings.plonk_zs; + let next_zs = &proof.openings.plonk_zs_right; + let s_sigmas = &proof.openings.plonk_sigmas; + let partial_products = &proof.openings.partial_products; + + let zeta_pow_deg = self.exp_u64_extension(zeta, inner_common_data.degree() as u64); + // Evaluate the vanishing polynomial at our challenge point, zeta. + let vanishing_polys_zeta = eval_vanishing_poly_recursively( + self, + inner_common_data, + zeta, + zeta_pow_deg, + vars, + local_zs, + next_zs, + partial_products, + s_sigmas, + &betas, + &gammas, + &alphas, + ); + + // let quotient_polys_zeta = &proof.openings.quotient_polys; + // let zeta_pow_deg = self.exp_u64_extension(zeta, 1 << inner_common_data.degree_bits as u64); + // 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() + // { + // let mut scale = ReducingFactorTarget::new(zeta_pow_deg); + // let mut rhs = scale.reduce(chunk, self); + // rhs = self.mul_extension(z_h_zeta, rhs); + // dbg!(self.num_gates()); + // self.route_extension(vanishing_polys_zeta[i], rhs); + // } + // + // let evaluations = proof.openings.clone(); + // + // let merkle_roots = &[ + // inner_verifier_data.constants_sigmas_root, + // proof.wires_root, + // proof.plonk_zs_root, + // proof.quotient_polys_root, + // ]; + // + // proof.opening_proof.verify( + // zeta, + // &evaluations, + // merkle_roots, + // &mut challenger, + // inner_common_data, + // self, + // ); + // dbg!(self.num_gates()); + // dbg!(self.generators.len()); } } @@ -80,8 +166,7 @@ mod tests { let openings = OpeningSetTarget { constants: builder.add_virtual_extension_targets(proof.openings.constants.len()), - plonk_sigmas: builder - .add_virtual_extension_targets(proof.openings.plonk_s_sigmas.len()), + plonk_sigmas: builder.add_virtual_extension_targets(proof.openings.plonk_sigmas.len()), wires: builder.add_virtual_extension_targets(proof.openings.wires.len()), plonk_zs: builder.add_virtual_extension_targets(proof.openings.plonk_zs.len()), plonk_zs_right: builder @@ -141,7 +226,7 @@ mod tests { .openings .plonk_sigmas .iter() - .zip(&proof.openings.plonk_s_sigmas) + .zip(&proof.openings.plonk_sigmas) { pw.set_extension_target(t, x); } @@ -240,6 +325,7 @@ mod tests { data.common, ) }; + verify(proof.clone(), &vd, &cd).unwrap(); let config = CircuitConfig::large_config(); let mut builder = CircuitBuilder::::new(config.clone()); @@ -252,12 +338,11 @@ mod tests { }; pw.set_hash_target(inner_data.constants_sigmas_root, vd.constants_sigmas_root); - let gates = cd.gates; - - builder.add_recursive_verifier(pt, &config, &inner_data, &gates); + builder.add_recursive_verifier(pt, &config, &inner_data, &cd); + dbg!(builder.num_gates()); let data = builder.build(); - let recursive_proof = data.prove(PartialWitness::new()); + let recursive_proof = data.prove(pw); verify(recursive_proof, &data.verifier_only, &data.common).unwrap(); } diff --git a/src/util/partial_products.rs b/src/util/partial_products.rs index 1d169b8d..ed87002e 100644 --- a/src/util/partial_products.rs +++ b/src/util/partial_products.rs @@ -1,6 +1,9 @@ use std::iter::Product; use std::ops::Sub; +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::Extendable; use crate::util::ceil_div_usize; /// Compute partial products of the original vector `v` such that all products consist of `max_degree` @@ -58,6 +61,32 @@ pub fn check_partial_products>( res } +pub fn check_partial_products_recursively, const D: usize>( + builder: &mut CircuitBuilder, + v: &[ExtensionTarget], + partials: &[ExtensionTarget], + max_degree: usize, +) -> Vec> { + 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| builder.mul_many_extension(chunk)) + .collect::>(); + res.extend( + products + .iter() + .zip(&partials) + .map(|(&a, &b)| builder.sub_extension(a, b)), + ); + remainder = partials.drain(..products.len()).collect(); + } + + res +} + #[cfg(test)] mod tests { use num::Zero; diff --git a/src/vanishing_poly.rs b/src/vanishing_poly.rs new file mode 100644 index 00000000..a5f63ac2 --- /dev/null +++ b/src/vanishing_poly.rs @@ -0,0 +1,356 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::circuit_data::CommonCircuitData; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::Extendable; +use crate::field::field::Field; +use crate::gates::gate::{Gate, GateRef, PrefixedGate}; +use crate::plonk_common; +use crate::plonk_common::{eval_l_1_recursively, ZeroPolyOnCoset}; +use crate::target::Target; +use crate::util::partial_products::{check_partial_products, check_partial_products_recursively}; +use crate::util::scaling::ReducingFactorTarget; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; + +/// Evaluate the vanishing polynomial at `x`. In this context, the vanishing polynomial is a random +/// linear combination of gate constraints, plus some other terms relating to the permutation +/// argument. All such terms should vanish on `H`. +pub(crate) fn eval_vanishing_poly, const D: usize>( + common_data: &CommonCircuitData, + x: F::Extension, + vars: EvaluationVars, + local_zs: &[F::Extension], + next_zs: &[F::Extension], + partial_products: &[F::Extension], + s_sigmas: &[F::Extension], + betas: &[F], + gammas: &[F], + alphas: &[F], +) -> Vec { + let max_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_zs[i]; + let z_gz = next_zs[i]; + vanishing_z_1_terms + .push(plonk_common::eval_l_1(common_data.degree(), x) * (z_x - F::Extension::ONE)); + + 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::>(); + 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::>(); + let quotient_values = (0..common_data.config.num_routed_wires) + .map(|j| numerator_values[j] / denominator_values[j]) + .collect::>(); + + // 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("ient_values, current_partial_products, max_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(max_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, + ] + .concat(); + dbg!(&vanishing_terms); + + let alphas = &alphas.iter().map(|&a| a.into()).collect::>(); + plonk_common::reduce_with_powers_multi(&vanishing_terms, alphas) +} + +/// Like `eval_vanishing_poly`, but specialized for base field points. +pub(crate) fn eval_vanishing_poly_base, const D: usize>( + common_data: &CommonCircuitData, + index: usize, + x: F, + vars: EvaluationVarsBase, + local_zs: &[F], + next_zs: &[F], + partial_products: &[F], + s_sigmas: &[F], + betas: &[F], + gammas: &[F], + alphas: &[F], + z_h_on_coset: &ZeroPolyOnCoset, +) -> Vec { + let max_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_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 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::>(); + 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::>(); + let quotient_values = (0..common_data.config.num_routed_wires) + .map(|j| numerator_values[j] / denominator_values[j]) + .collect::>(); + + // The partial products considered for this iteration of `i`. + let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods]; + // Check the numerator partial products. + let mut partial_product_check = + check_partial_products("ient_values, current_partial_products, max_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(max_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, + ] + .concat(); + + plonk_common::reduce_with_powers_multi(&vanishing_terms, alphas) +} + +/// Evaluates all gate constraints. +/// +/// `num_gate_constraints` is the largest number of constraints imposed by any gate. It is not +/// strictly necessary, but it helps performance by ensuring that we allocate a vector with exactly +/// the capacity that we need. +pub fn evaluate_gate_constraints, const D: usize>( + gates: &[PrefixedGate], + num_gate_constraints: usize, + vars: EvaluationVars, +) -> Vec { + let mut constraints = vec![F::Extension::ZERO; num_gate_constraints]; + for gate in gates { + let gate_constraints = gate.gate.0.eval_filtered(vars, &gate.prefix); + for (i, c) in gate_constraints.into_iter().enumerate() { + debug_assert!( + i < num_gate_constraints, + "num_constraints() gave too low of a number" + ); + constraints[i] += c; + } + } + constraints +} + +pub fn evaluate_gate_constraints_base, const D: usize>( + gates: &[PrefixedGate], + num_gate_constraints: usize, + vars: EvaluationVarsBase, +) -> Vec { + let mut constraints = vec![F::ZERO; num_gate_constraints]; + for gate in gates { + let gate_constraints = gate.gate.0.eval_filtered_base(vars, &gate.prefix); + for (i, c) in gate_constraints.into_iter().enumerate() { + debug_assert!( + i < num_gate_constraints, + "num_constraints() gave too low of a number" + ); + constraints[i] += c; + } + } + constraints +} + +pub fn evaluate_gate_constraints_recursively, const D: usize>( + builder: &mut CircuitBuilder, + gates: &[PrefixedGate], + num_gate_constraints: usize, + vars: EvaluationTargets, +) -> Vec> { + let mut constraints = vec![builder.zero_extension(); num_gate_constraints]; + for gate in gates { + let gate_constraints = gate + .gate + .0 + .eval_filtered_recursively(builder, vars, &gate.prefix); + for (i, c) in gate_constraints.into_iter().enumerate() { + constraints[i] = builder.add_extension(constraints[i], c); + } + } + constraints +} + +/// Evaluate the vanishing polynomial at `x`. In this context, the vanishing polynomial is a random +/// linear combination of gate constraints, plus some other terms relating to the permutation +/// argument. All such terms should vanish on `H`. +pub(crate) fn eval_vanishing_poly_recursively, const D: usize>( + builder: &mut CircuitBuilder, + common_data: &CommonCircuitData, + x: ExtensionTarget, + x_pow_deg: ExtensionTarget, + vars: EvaluationTargets, + local_zs: &[ExtensionTarget], + next_zs: &[ExtensionTarget], + partial_products: &[ExtensionTarget], + s_sigmas: &[ExtensionTarget], + betas: &[Target], + gammas: &[Target], + alphas: &[Target], +) -> Vec> { + let max_degree = common_data.quotient_degree_factor; + let (num_prods, final_num_prod) = common_data.num_partial_products; + + let constraint_terms = evaluate_gate_constraints_recursively( + builder, + &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_zs[i]; + let z_gz = next_zs[i]; + let l1 = eval_l_1_recursively(builder, common_data.degree(), x, x_pow_deg); + vanishing_z_1_terms.push(builder.arithmetic_extension(F::ONE, F::NEG_ONE, l1, z_x, l1)); + + let numerator_values = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = vars.local_wires[j]; + let k_i = builder.constant(common_data.k_is[j]); + let s_id = builder.scalar_mul_ext(k_i, x); + let gamma_ext = builder.convert_to_ext(gammas[i]); + let tmp = builder.scalar_mul_add_extension(betas[i], s_id, wire_value); + builder.add_extension(tmp, gamma_ext) + }) + .collect::>(); + 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]; + let gamma_ext = builder.convert_to_ext(gammas[i]); + let tmp = builder.scalar_mul_add_extension(betas[i], s_sigma, wire_value); + builder.add_extension(tmp, gamma_ext) + }) + .collect::>(); + let quotient_values = (0..common_data.config.num_routed_wires) + .map(|j| builder.div_unsafe_extension(numerator_values[j], denominator_values[j])) + .collect::>(); + + // 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_recursively( + builder, + "ient_values, + current_partial_products, + max_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(max_degree) + .zip(partial_product_check.iter_mut()) + .for_each(|(d, q)| { + let tmp = builder.mul_many_extension(d); + *q = builder.mul_extension(*q, tmp); + }); + vanishing_partial_products_terms.extend(partial_product_check); + + // The quotient final product is the product of the last `final_num_prod` elements. + let quotient = + builder.mul_many_extension(¤t_partial_products[num_prods - final_num_prod..]); + vanishing_v_shift_terms.push(builder.mul_sub_extension(quotient, z_x, z_gz)); + } + + let vanishing_terms = [ + vanishing_z_1_terms, + vanishing_partial_products_terms, + vanishing_v_shift_terms, + constraint_terms, + ] + .concat(); + dbg!(&vanishing_terms); + + alphas + .iter() + .map(|&alpha| { + let alpha = builder.convert_to_ext(alpha); + let mut alpha = ReducingFactorTarget::new(alpha); + alpha.reduce(&vanishing_terms, builder) + }) + .collect() +} diff --git a/src/vars.rs b/src/vars.rs index 7e9b372f..17d51051 100644 --- a/src/vars.rs +++ b/src/vars.rs @@ -39,6 +39,12 @@ impl<'a, F: Field> EvaluationVarsBase<'a, F> { } } +impl<'a, const D: usize> EvaluationTargets<'a, D> { + pub fn remove_prefix(&mut self, prefix: &[bool]) { + self.local_constants = &self.local_constants[prefix.len()..]; + } +} + #[derive(Copy, Clone)] pub struct EvaluationTargets<'a, const D: usize> { pub(crate) local_constants: &'a [ExtensionTarget], diff --git a/src/verifier.rs b/src/verifier.rs index 64d4b371..e1d6d967 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -4,8 +4,9 @@ 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, reduce_with_powers}; +use crate::plonk_common::{eval_zero_poly, reduce_with_powers}; use crate::proof::Proof; +use crate::vanishing_poly::eval_vanishing_poly; use crate::vars::EvaluationVars; pub(crate) fn verify, const D: usize>( @@ -39,7 +40,7 @@ pub(crate) fn verify, const D: usize>( }; let local_zs = &proof.openings.plonk_zs; let next_zs = &proof.openings.plonk_zs_right; - let s_sigmas = &proof.openings.plonk_s_sigmas; + let s_sigmas = &proof.openings.plonk_sigmas; let partial_products = &proof.openings.partial_products; // Evaluate the vanishing polynomial at our challenge point, zeta.