From 42d5b80a7a5f1ec96e08f3082fd733b23e1108ac Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 4 Jun 2021 15:40:54 +0200 Subject: [PATCH] `BaseSum` gate --- src/fri/mod.rs | 1 + src/fri/prover.rs | 2 +- src/fri/recursive_verifier.rs | 306 ++++++++++++++++++++++++++++++++++ src/fri/verifier.rs | 3 +- src/gates/base_sum.rs | 169 +++++++++++++++++++ src/gates/mod.rs | 1 + src/plonk_challenger.rs | 14 ++ src/plonk_common.rs | 11 +- 8 files changed, 501 insertions(+), 6 deletions(-) create mode 100644 src/fri/recursive_verifier.rs create mode 100644 src/gates/base_sum.rs diff --git a/src/fri/mod.rs b/src/fri/mod.rs index c147e8c6..6351a2af 100644 --- a/src/fri/mod.rs +++ b/src/fri/mod.rs @@ -1,6 +1,7 @@ use crate::polynomial::commitment::SALT_SIZE; pub mod prover; +mod recursive_verifier; pub mod verifier; /// Somewhat arbitrary. Smaller values will increase delta, but with diminishing returns, diff --git a/src/fri/prover.rs b/src/fri/prover.rs index d1eeadcf..a919b559 100644 --- a/src/fri/prover.rs +++ b/src/fri/prover.rs @@ -106,7 +106,7 @@ fn fri_proof_of_work(current_hash: Hash, config: &FriConfig) -> F { false, ) .to_canonical_u64() - .leading_zeros() + .trailing_zeros() >= config.proof_of_work_bits }) .map(F::from_canonical_u64) diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs new file mode 100644 index 00000000..a64b02fe --- /dev/null +++ b/src/fri/recursive_verifier.rs @@ -0,0 +1,306 @@ +use anyhow::{ensure, Result}; +use itertools::izip; + +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::{flatten, Extendable, FieldExtension, OEF}; +use crate::field::field::Field; +use crate::field::lagrange::{barycentric_weights, interpolant, interpolate}; +use crate::fri::FriConfig; +use crate::hash::hash_n_to_1; +use crate::merkle_proofs::verify_merkle_proof; +use crate::plonk_challenger::{Challenger, RecursiveChallenger}; +use crate::plonk_common::reduce_with_iter; +use crate::proof::{ + FriInitialTreeProof, FriProof, FriProofTarget, FriQueryRound, Hash, OpeningSet, +}; +use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place}; + +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. + fn compute_evaluation() { + todo!(); + // debug_assert_eq!(last_evals.len(), 1 << arity_bits); + // + // let g = F::primitive_root_of_unity(arity_bits); + // + // // The evaluation vector needs to be reordered first. + // let mut evals = last_evals.to_vec(); + // reverse_index_bits_in_place(&mut evals); + // evals.rotate_left(reverse_bits(old_x_index, arity_bits)); + // + // // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. + // let points = g + // .powers() + // .zip(evals) + // .map(|(y, e)| ((x * y).into(), e)) + // .collect::>(); + // let barycentric_weights = barycentric_weights(&points); + // interpolate(&points, beta, &barycentric_weights) + } + + fn fri_verify_proof_of_work( + &mut self, + proof: &FriProofTarget, + challenger: &mut RecursiveChallenger, + config: &FriConfig, + ) -> Result<()> { + let mut inputs = challenger.get_hash(self).elements.to_vec(); + inputs.push(proof.pow_witness); + + let hash = self.hash_n_to_m(inputs, 1, false)[0]; + let purported_hash_bits = self.split_le_virtual(hash, 64); + // for &b in &purported_hash_bits { + // self.generate_copy(b, self.zero()); + // } + // ensure!( + // hash.to_canonical_u64().trailing_zeros() >= config.proof_of_work_bits, + // "Invalid proof of work witness." + // ); + + Ok(()) + } + + // pub fn verify_fri_proof( + // purported_degree_log: usize, + // // Openings of the PLONK polynomials. + // os: &OpeningSet, + // // Point at which the PLONK polynomials are opened. + // zeta: F::Extension, + // // Scaling factor to combine polynomials. + // alpha: F::Extension, + // initial_merkle_roots: &[Hash], + // proof: &FriProof, + // challenger: &mut Challenger, + // config: &FriConfig, + // ) -> Result<()> { + // let total_arities = config.reduction_arity_bits.iter().sum::(); + // ensure!( + // purported_degree_log + // == log2_strict(proof.final_poly.len()) + total_arities - config.rate_bits, + // "Final polynomial has wrong degree." + // ); + // + // // Size of the LDE domain. + // let n = proof.final_poly.len() << total_arities; + // + // // Recover the random betas used in the FRI reductions. + // let betas = proof + // .commit_phase_merkle_roots + // .iter() + // .map(|root| { + // challenger.observe_hash(root); + // challenger.get_extension_challenge() + // }) + // .collect::>(); + // challenger.observe_extension_elements(&proof.final_poly.coeffs); + // + // // Check PoW. + // fri_verify_proof_of_work(proof, challenger, config)?; + // + // // Check that parameters are coherent. + // ensure!( + // config.num_query_rounds == proof.query_round_proofs.len(), + // "Number of query rounds does not match config." + // ); + // ensure!( + // !config.reduction_arity_bits.is_empty(), + // "Number of reductions should be non-zero." + // ); + // + // for round_proof in &proof.query_round_proofs { + // fri_verifier_query_round( + // os, + // zeta, + // alpha, + // initial_merkle_roots, + // &proof, + // challenger, + // n, + // &betas, + // round_proof, + // config, + // )?; + // } + // + // Ok(()) + // } + // + // fn fri_verify_initial_proof( + // x_index: usize, + // proof: &FriInitialTreeProof, + // initial_merkle_roots: &[Hash], + // ) -> Result<()> { + // for ((evals, merkle_proof), &root) in proof.evals_proofs.iter().zip(initial_merkle_roots) { + // verify_merkle_proof(evals.clone(), x_index, root, merkle_proof, false)?; + // } + // + // Ok(()) + // } + // + // fn fri_combine_initial, const D: usize>( + // proof: &FriInitialTreeProof, + // alpha: F::Extension, + // os: &OpeningSet, + // zeta: F::Extension, + // subgroup_x: F, + // config: &FriConfig, + // ) -> F::Extension { + // 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); + // let mut alpha_powers = alpha.powers(); + // let mut sum = F::Extension::ZERO; + // + // // We will add three terms to `sum`: + // // - one for polynomials opened at `x` only + // // - one for polynomials opened at `x` and `g x` + // // - one for polynomials opened at `x` and its conjugate + // + // let evals = [0, 1, 4] + // .iter() + // .flat_map(|&i| proof.unsalted_evals(i, config)) + // .map(|&e| F::Extension::from_basefield(e)); + // let openings = os + // .constants + // .iter() + // .chain(&os.plonk_sigmas) + // .chain(&os.quotient_polys); + // let numerator = izip!(evals, openings, &mut alpha_powers) + // .map(|(e, &o, a)| a * (e - o)) + // .sum::(); + // let denominator = subgroup_x - zeta; + // sum += numerator / denominator; + // + // let ev: F::Extension = proof + // .unsalted_evals(3, config) + // .iter() + // .zip(alpha_powers.clone()) + // .map(|(&e, a)| a * e.into()) + // .sum(); + // let zeta_right = F::Extension::primitive_root_of_unity(degree_log) * zeta; + // let zs_interpol = interpolant(&[ + // (zeta, reduce_with_iter(&os.plonk_zs, alpha_powers.clone())), + // ( + // zeta_right, + // reduce_with_iter(&os.plonk_zs_right, &mut alpha_powers), + // ), + // ]); + // let numerator = ev - zs_interpol.eval(subgroup_x); + // let denominator = (subgroup_x - zeta) * (subgroup_x - zeta_right); + // sum += numerator / denominator; + // + // let ev: F::Extension = proof + // .unsalted_evals(2, config) + // .iter() + // .zip(alpha_powers.clone()) + // .map(|(&e, a)| a * e.into()) + // .sum(); + // let zeta_frob = zeta.frobenius(); + // let wire_evals_frob = os.wires.iter().map(|e| e.frobenius()).collect::>(); + // let wires_interpol = interpolant(&[ + // (zeta, reduce_with_iter(&os.wires, alpha_powers.clone())), + // (zeta_frob, reduce_with_iter(&wire_evals_frob, alpha_powers)), + // ]); + // let numerator = ev - wires_interpol.eval(subgroup_x); + // let denominator = (subgroup_x - zeta) * (subgroup_x - zeta_frob); + // sum += numerator / denominator; + // + // sum + // } + // + // fn fri_verifier_query_round, const D: usize>( + // os: &OpeningSet, + // zeta: F::Extension, + // alpha: F::Extension, + // initial_merkle_roots: &[Hash], + // proof: &FriProof, + // challenger: &mut Challenger, + // n: usize, + // betas: &[F::Extension], + // round_proof: &FriQueryRound, + // config: &FriConfig, + // ) -> Result<()> { + // let mut evaluations: Vec> = Vec::new(); + // let x = challenger.get_challenge(); + // let mut domain_size = n; + // let mut x_index = x.to_canonical_u64() as usize % n; + // fri_verify_initial_proof( + // x_index, + // &round_proof.initial_trees_proof, + // initial_merkle_roots, + // )?; + // let mut old_x_index = 0; + // // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. + // let log_n = log2_strict(n); + // let mut subgroup_x = F::MULTIPLICATIVE_GROUP_GENERATOR + // * F::primitive_root_of_unity(log_n).exp(reverse_bits(x_index, log_n) as u64); + // for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() { + // let arity = 1 << arity_bits; + // let next_domain_size = domain_size >> arity_bits; + // let e_x = if i == 0 { + // fri_combine_initial( + // &round_proof.initial_trees_proof, + // alpha, + // os, + // zeta, + // subgroup_x, + // config, + // ) + // } else { + // let last_evals = &evaluations[i - 1]; + // // Infer P(y) from {P(x)}_{x^arity=y}. + // compute_evaluation( + // subgroup_x, + // old_x_index, + // config.reduction_arity_bits[i - 1], + // last_evals, + // betas[i - 1], + // ) + // }; + // let mut evals = round_proof.steps[i].evals.clone(); + // // Insert P(y) into the evaluation vector, since it wasn't included by the prover. + // evals.insert(x_index & (arity - 1), e_x); + // evaluations.push(evals); + // verify_merkle_proof( + // flatten(&evaluations[i]), + // x_index >> arity_bits, + // proof.commit_phase_merkle_roots[i], + // &round_proof.steps[i].merkle_proof, + // false, + // )?; + // + // if i > 0 { + // // Update the point x to x^arity. + // for _ in 0..config.reduction_arity_bits[i - 1] { + // subgroup_x = subgroup_x.square(); + // } + // } + // domain_size = next_domain_size; + // old_x_index = x_index; + // x_index >>= arity_bits; + // } + // + // let last_evals = evaluations.last().unwrap(); + // let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); + // let purported_eval = compute_evaluation( + // subgroup_x, + // old_x_index, + // final_arity_bits, + // last_evals, + // *betas.last().unwrap(), + // ); + // for _ in 0..final_arity_bits { + // subgroup_x = subgroup_x.square(); + // } + // + // // Final check of FRI. After all the reductions, we check that the final polynomial is equal + // // to the one sent by the prover. + // ensure!( + // proof.final_poly.eval(subgroup_x.into()) == purported_eval, + // "Final polynomial evaluation is invalid." + // ); + // + // Ok(()) + // } +} diff --git a/src/fri/verifier.rs b/src/fri/verifier.rs index e6e8cae7..156d170f 100644 --- a/src/fri/verifier.rs +++ b/src/fri/verifier.rs @@ -56,8 +56,7 @@ fn fri_verify_proof_of_work, const D: usize>( false, ); ensure!( - hash.to_canonical_u64().leading_zeros() - >= config.proof_of_work_bits + F::ORDER.leading_zeros(), + hash.to_canonical_u64().trailing_zeros() >= config.proof_of_work_bits, "Invalid proof of work witness." ); diff --git a/src/gates/base_sum.rs b/src/gates/base_sum.rs new file mode 100644 index 00000000..f7ebd3a0 --- /dev/null +++ b/src/gates/base_sum.rs @@ -0,0 +1,169 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::circuit_data::CircuitConfig; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::Extendable; +use crate::field::field::Field; +use crate::gates::gate::{Gate, GateRef}; +use crate::generator::{SimpleGenerator, WitnessGenerator}; +use crate::plonk_common::{reduce_with_powers, reduce_with_powers_recursive}; +use crate::target::Target; +use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::wire::Wire; +use crate::witness::PartialWitness; +use std::ops::Range; + +/// A gate which can sum base W limbs. +#[derive(Debug)] +pub struct BaseSumGate { + num_limbs: usize, +} + +impl BaseSumGate { + pub fn new, const D: usize>(config: &CircuitConfig) -> GateRef { + GateRef::new(BaseSumGate:: { + num_limbs: config.num_routed_wires - 1, + }) + } + + pub const WIRE_SUM: usize = 0; + pub const WIRE_LIMBS_START: usize = 1; + + /// Returns the index of the `i`th limb wire. + pub fn limbs(&self) -> Range { + Self::WIRE_LIMBS_START..Self::WIRE_LIMBS_START + self.num_limbs + } +} + +impl, const D: usize, const B: usize> Gate for BaseSumGate { + fn id(&self) -> String { + format!("{:?}", self) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let sum = vars.local_wires[Self::WIRE_SUM]; + let limbs = vars.local_wires[self.limbs()].to_vec(); + let computed_sum = reduce_with_powers(&limbs, F::Extension::from_canonical_usize(B)); + let mut constraints = vec![computed_sum - sum]; + for limb in limbs { + constraints.push( + (0..B) + .map(|i| limb - F::Extension::from_canonical_usize(i)) + .product(), + ); + } + constraints + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let base = builder.constant(F::from_canonical_usize(B)); + let sum = vars.local_wires[Self::WIRE_SUM]; + let limbs = vars.local_wires[self.limbs()].to_vec(); + let computed_sum = + reduce_with_powers_recursive(builder, &vars.local_wires[self.limbs()], base); + let mut constraints = vec![builder.sub_extension(computed_sum, sum)]; + for limb in limbs { + constraints.push({ + let mut acc = builder.one_extension(); + (0..B).for_each(|i| { + let it = builder.constant_extension(F::from_canonical_usize(i).into()); + let diff = builder.sub_extension(limb, it); + acc = builder.mul_extension(acc, diff); + }); + acc + }); + } + constraints + } + + fn generators( + &self, + gate_index: usize, + _local_constants: &[F], + ) -> Vec>> { + let gen = BaseSplitGenerator:: { + gate_index, + num_limbs: self.num_limbs, + }; + vec![Box::new(gen)] + } + + fn num_wires(&self) -> usize { + self.num_limbs + 1 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + B + } + + fn num_constraints(&self) -> usize { + 1 + B + } +} + +#[derive(Debug)] +pub struct BaseSplitGenerator { + gate_index: usize, + num_limbs: usize, +} + +impl SimpleGenerator for BaseSplitGenerator { + fn dependencies(&self) -> Vec { + vec![Target::Wire(Wire { + gate: self.gate_index, + input: BaseSumGate::::WIRE_SUM, + })] + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + let mut sum_value = witness + .get_target(Target::Wire(Wire { + gate: self.gate_index, + input: BaseSumGate::::WIRE_SUM, + })) + .to_canonical_u64() as usize; + let limbs = (BaseSumGate::::WIRE_LIMBS_START + ..BaseSumGate::::WIRE_LIMBS_START + self.num_limbs) + .map(|i| { + Target::Wire(Wire { + gate: self.gate_index, + input: i, + }) + }); + + let mut result = PartialWitness::new(); + for b in limbs { + let b_value = sum_value % B; + result.set_target(b, F::from_canonical_usize(b_value)); + sum_value /= B; + } + + debug_assert_eq!( + sum_value, 0, + "Integer too large to fit in given number of bits" + ); + + result + } +} + +#[cfg(test)] +mod tests { + use crate::circuit_data::CircuitConfig; + use crate::field::crandall_field::CrandallField; + use crate::gates::base_sum::BaseSumGate; + use crate::gates::gate_testing::test_low_degree; + + #[test] + fn low_degree() { + let config = CircuitConfig::default(); + test_low_degree(BaseSumGate::<6>::new::(&config)) + } +} diff --git a/src/gates/mod.rs b/src/gates/mod.rs index ebcf6e3f..b25337ff 100644 --- a/src/gates/mod.rs +++ b/src/gates/mod.rs @@ -5,5 +5,6 @@ pub mod gmimc; mod interpolation; pub(crate) mod noop; +mod base_sum; #[cfg(test)] mod gate_testing; diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index 69891761..2b1739d5 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -255,6 +255,20 @@ impl RecursiveChallenger { (0..n).map(|_| self.get_challenge(builder)).collect() } + pub fn get_hash, const D: usize>( + &mut self, + builder: &mut CircuitBuilder, + ) -> HashTarget { + HashTarget { + elements: [ + self.get_challenge(builder), + self.get_challenge(builder), + self.get_challenge(builder), + self.get_challenge(builder), + ], + } + } + /// Absorb any buffered inputs. After calling this, the input buffer will be empty. fn absorb_buffered_inputs, const D: usize>( &mut self, diff --git a/src/plonk_common.rs b/src/plonk_common.rs index 990e6ea7..daa131ef 100644 --- a/src/plonk_common.rs +++ b/src/plonk_common.rs @@ -103,10 +103,15 @@ pub(crate) fn reduce_with_powers(terms: &[F], alpha: F) -> F { pub(crate) fn reduce_with_powers_recursive, const D: usize>( builder: &mut CircuitBuilder, - terms: Vec, + terms: &[ExtensionTarget], alpha: Target, -) -> Target { - todo!() +) -> ExtensionTarget { + let mut sum = builder.zero_extension(); + for &term in terms.iter().rev() { + sum = builder.scalar_mul_ext(alpha, sum); + sum = builder.add_extension(sum, term); + } + sum } pub(crate) fn reduce_with_iter(terms: &[F], coeffs: I) -> F