diff --git a/arithmetic/src/lib.rs b/arithmetic/src/lib.rs index 82bc0477..6f2f010b 100644 --- a/arithmetic/src/lib.rs +++ b/arithmetic/src/lib.rs @@ -1,7 +1,12 @@ mod errors; mod multilinear_polynomial; +mod util; mod virtual_polynomial; pub use errors::ArithErrors; -pub use multilinear_polynomial::{random_zero_mle_list, DenseMultilinearExtension}; +pub use multilinear_polynomial::{ + batch_evaluate, merge_polynomials, random_mle_list, random_zero_mle_list, + DenseMultilinearExtension, +}; +pub use util::{build_l, get_batched_nv}; pub use virtual_polynomial::{build_eq_x_r, VPAuxInfo, VirtualPolynomial}; diff --git a/arithmetic/src/multilinear_polynomial.rs b/arithmetic/src/multilinear_polynomial.rs index 077edecb..26efe042 100644 --- a/arithmetic/src/multilinear_polynomial.rs +++ b/arithmetic/src/multilinear_polynomial.rs @@ -1,9 +1,50 @@ +use crate::{build_l, get_batched_nv, util::get_uni_domain, ArithErrors}; use ark_ff::PrimeField; -use ark_std::{end_timer, rand::RngCore, start_timer}; +use ark_poly::{ + univariate::DensePolynomial, EvaluationDomain, Evaluations, MultilinearExtension, Polynomial, + Radix2EvaluationDomain, +}; +use ark_std::{end_timer, log2, rand::RngCore, start_timer}; use std::rc::Rc; pub use ark_poly::DenseMultilinearExtension; +/// Sample a random list of multilinear polynomials. +/// Returns +/// - the list of polynomials, +/// - its sum of polynomial evaluations over the boolean hypercube. +pub fn random_mle_list( + nv: usize, + degree: usize, + rng: &mut R, +) -> (Vec>>, F) { + let start = start_timer!(|| "sample random mle list"); + let mut multiplicands = Vec::with_capacity(degree); + for _ in 0..degree { + multiplicands.push(Vec::with_capacity(1 << nv)) + } + let mut sum = F::zero(); + + for _ in 0..(1 << nv) { + let mut product = F::one(); + + for e in multiplicands.iter_mut() { + let val = F::rand(rng); + e.push(val); + product *= val; + } + sum += product; + } + + let list = multiplicands + .into_iter() + .map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x))) + .collect(); + + end_timer!(start); + (list, sum) +} + // Build a randomize list of mle-s whose sum is zero. pub fn random_zero_mle_list( nv: usize, @@ -31,3 +72,143 @@ pub fn random_zero_mle_list( end_timer!(start); list } + +/// Input a list of polynomials and a same list of points, built a merged +/// polynomial, and compute the evaluation of the points at each polynomial +pub fn batch_evaluate( + polynomials: &[Rc>], + points: &[Vec], +) -> Result<(Vec, Rc>), ArithErrors> { + if polynomials.len() != points.len() { + return Err(ArithErrors::InvalidParameters( + "polynomials and points have different sizes".to_string(), + )); + } + let num_var = polynomials[0].num_vars; + let domain = get_uni_domain::(points.len())?; + + let poly_merged = merge_polynomials(polynomials)?; + let l = build_l(num_var, points, &domain)?; + let wl = compute_w_circ_l(&poly_merged, &l)?; + let evals: Vec = (0..points.len()) + .map(|i| wl.evaluate(&domain.element(i))) + .collect(); + + Ok((evals, poly_merged)) +} + +/// merge a set of polynomials. Returns an error if the +/// polynomials do not share a same number of nvs. +pub fn merge_polynomials( + polynomials: &[Rc>], +) -> Result>, ArithErrors> { + let nv = polynomials[0].num_vars(); + for poly in polynomials.iter() { + if nv != poly.num_vars() { + return Err(ArithErrors::InvalidParameters( + "num_vars do not match for polynomials".to_string(), + )); + } + } + + let merged_nv = get_batched_nv(nv, polynomials.len()); + let mut scalars = vec![]; + for poly in polynomials.iter() { + scalars.extend_from_slice(poly.to_evaluations().as_slice()); + } + scalars.extend_from_slice(vec![F::zero(); (1 << merged_nv) - scalars.len()].as_ref()); + Ok(Rc::new(DenseMultilinearExtension::from_evaluations_vec( + merged_nv, scalars, + ))) +} + +/// Compute W \circ l. +/// +/// Given an MLE W, and a list of univariate polynomials l, generate the +/// univariate polynomial that composes W with l. +/// +/// Returns an error if l's length does not matches number of variables in W. +pub(crate) fn compute_w_circ_l( + w: &DenseMultilinearExtension, + l: &[DensePolynomial], +) -> Result, ArithErrors> { + let timer = start_timer!(|| "compute W \\circ l"); + + if w.num_vars != l.len() { + return Err(ArithErrors::InvalidParameters(format!( + "l's length ({}) does not match num_variables ({})", + l.len(), + w.num_vars(), + ))); + } + + let mut res_eval: Vec = vec![]; + + // TODO: consider to pass this in from caller + // uni_degree is (product of each prefix's) + (2 * MLEs) + // = (l.len() - (num_vars - log(l.len())) + 2) * l[0].degree + let uni_degree = (l.len() - w.num_vars + log2(l.len()) as usize + 2) * l[0].degree(); + + let domain = match Radix2EvaluationDomain::::new(uni_degree) { + Some(p) => p, + None => { + return Err(ArithErrors::InvalidParameters( + "failed to build radix 2 domain".to_string(), + )) + }, + }; + for point in domain.elements() { + // we reverse the order here because the coefficient vec are stored in + // bit-reversed order + let l_eval: Vec = l.iter().rev().map(|x| x.evaluate(&point)).collect(); + res_eval.push(w.evaluate(l_eval.as_ref()).unwrap()) + } + let evaluation = Evaluations::from_vec_and_domain(res_eval, domain); + let res = evaluation.interpolate(); + + end_timer!(timer); + Ok(res) +} + +#[cfg(test)] +mod tests { + + use super::*; + use ark_bls12_381::Fr; + use ark_std::test_rng; + + #[test] + fn test_merge_poly() { + test_merge_poly_helper::() + } + + fn test_merge_poly_helper() { + let mut rng = test_rng(); + let num_mle = 3; + let nv = 5; + + let polynomials: Vec>> = (0..num_mle) + .map(|_| Rc::new(DenseMultilinearExtension::rand(nv, &mut rng))) + .collect(); + let points: Vec> = (0..num_mle) + .map(|_| (0..nv).map(|_| F::rand(&mut rng)).collect()) + .collect(); + let eval = polynomials + .iter() + .zip(points.iter()) + .map(|(poly, point)| poly.evaluate(point).unwrap()); + + let (eval_rec, _merged) = batch_evaluate(&polynomials, &points).unwrap(); + + for (i, e) in eval.enumerate() { + println!("{} {}", i, e) + } + for (i, e) in eval_rec.iter().enumerate() { + println!("{} {}", i, e) + } + + // for (a, &b) in eval.zip(eval_rec.iter()) { + // assert_eq!(a, b) + // } + } +} diff --git a/arithmetic/src/util.rs b/arithmetic/src/util.rs new file mode 100644 index 00000000..d39f229a --- /dev/null +++ b/arithmetic/src/util.rs @@ -0,0 +1,73 @@ +use crate::ArithErrors; +use ark_ff::PrimeField; +use ark_poly::{ + univariate::DensePolynomial, EvaluationDomain, Evaluations, Radix2EvaluationDomain, +}; +use ark_std::log2; + +/// Decompose an integer into a binary vector in little endian. +pub(crate) fn bit_decompose(input: u64, num_var: usize) -> Vec { + let mut res = Vec::with_capacity(num_var); + let mut i = input; + for _ in 0..num_var { + res.push(i & 1 == 1); + i >>= 1; + } + res +} + +/// Given a list of points, build `l(points)` which is a list of univariate +/// polynomials that goes through the points +pub fn build_l( + num_var: usize, + points: &[Vec], + domain: &Radix2EvaluationDomain, +) -> Result>, ArithErrors> { + let prefix_len = log2(points.len()) as usize; + let mut uni_polys = Vec::new(); + + // 1.1 build the indexes and the univariate polys that go through the indexes + let indexes: Vec> = (0..points.len()) + .map(|x| bit_decompose(x as u64, prefix_len)) + .collect(); + for i in 0..prefix_len { + let eval: Vec = indexes + .iter() + .map(|x| F::from(x[prefix_len - i - 1])) + .collect(); + + uni_polys.push(Evaluations::from_vec_and_domain(eval, *domain).interpolate()); + } + + // 1.2 build the actual univariate polys that go through the points + for i in 0..num_var { + let mut eval: Vec = points.iter().map(|x| x[i]).collect(); + eval.extend_from_slice(vec![F::zero(); domain.size as usize - eval.len()].as_slice()); + uni_polys.push(Evaluations::from_vec_and_domain(eval, *domain).interpolate()) + } + + Ok(uni_polys) +} + +/// Return the number of variables that one need for an MLE to +/// batch the list of MLEs +#[inline] +pub fn get_batched_nv(num_var: usize, polynomials_len: usize) -> usize { + num_var + log2(polynomials_len) as usize +} + +/// get the domain for the univariate polynomial +#[inline] +pub(crate) fn get_uni_domain( + uni_poly_degree: usize, +) -> Result, ArithErrors> { + let domain = match Radix2EvaluationDomain::::new(uni_poly_degree) { + Some(p) => p, + None => { + return Err(ArithErrors::InvalidParameters( + "failed to build radix 2 domain".to_string(), + )) + }, + }; + Ok(domain) +} diff --git a/arithmetic/src/virtual_polynomial.rs b/arithmetic/src/virtual_polynomial.rs index df7ef13a..fe00da47 100644 --- a/arithmetic/src/virtual_polynomial.rs +++ b/arithmetic/src/virtual_polynomial.rs @@ -1,7 +1,7 @@ //! This module defines our main mathematical object `VirtualPolynomial`; and //! various functions associated with it. -use crate::{errors::ArithErrors, multilinear_polynomial::random_zero_mle_list}; +use crate::{random_mle_list, random_zero_mle_list, ArithErrors}; use ark_ff::PrimeField; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_serialize::{CanonicalSerialize, SerializationError, Write}; @@ -324,42 +324,6 @@ impl VirtualPolynomial { } } -/// Sample a random list of multilinear polynomials. -/// Returns -/// - the list of polynomials, -/// - its sum of polynomial evaluations over the boolean hypercube. -fn random_mle_list( - nv: usize, - degree: usize, - rng: &mut R, -) -> (Vec>>, F) { - let start = start_timer!(|| "sample random mle list"); - let mut multiplicands = Vec::with_capacity(degree); - for _ in 0..degree { - multiplicands.push(Vec::with_capacity(1 << nv)) - } - let mut sum = F::zero(); - - for _ in 0..(1 << nv) { - let mut product = F::one(); - - for e in multiplicands.iter_mut() { - let val = F::rand(rng); - e.push(val); - product *= val; - } - sum += product; - } - - let list = multiplicands - .into_iter() - .map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x))) - .collect(); - - end_timer!(start); - (list, sum) -} - // This function build the eq(x, r) polynomial for any given r. // // Evaluate diff --git a/hyperplonk/src/lib.rs b/hyperplonk/src/lib.rs index a0ec86a7..30c6ec98 100644 --- a/hyperplonk/src/lib.rs +++ b/hyperplonk/src/lib.rs @@ -1,27 +1,34 @@ //! Main module for the HyperPlonk PolyIOP. -use crate::utils::eval_f; -use arithmetic::VPAuxInfo; +use crate::subroutine::{ + perm_check::{ + estimate_perm_check_param_size, perm_check_prover_subroutine, + perm_check_verifier_subroutine, + }, + zero_check::{ + estimate_zero_check_param_size, zero_check_prover_subroutine, + zero_check_verifier_subroutine, + }, +}; use ark_ec::PairingEngine; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; -use ark_std::{end_timer, log2, start_timer, One, Zero}; +use ark_std::{end_timer, log2, start_timer, Zero}; use errors::HyperPlonkErrors; use pcs::prelude::{compute_qx_degree, merge_polynomials, PCSErrors, PolynomialCommitmentScheme}; use poly_iop::{ - identity_permutation_mle, prelude::{PermutationCheck, SumCheck, ZeroCheck}, PolyIOP, }; use selectors::SelectorColumn; -use std::{marker::PhantomData, rc::Rc}; +use std::{cmp::max, rc::Rc}; use structs::{HyperPlonkParams, HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkVerifyingKey}; use transcript::IOPTranscript; -use utils::build_f; use witness::WitnessColumn; mod errors; mod selectors; mod structs; +mod subroutine; mod utils; mod witness; @@ -118,37 +125,52 @@ where ) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors> { let num_vars = params.nv; let log_num_witness_polys = params.log_n_wires; - - // number of variables in merged polynomial for Multilinear-KZG - let merged_nv = num_vars + log_num_witness_polys; - // degree of q(x) for Univariate-KZG - let supported_uni_degree = compute_qx_degree(num_vars, 1 << log_num_witness_polys); - - // extract PCS prover and verifier keys from SRS - let (pcs_prover_param, pcs_verifier_param) = PCS::trim( - pcs_srs, - log2(supported_uni_degree) as usize, - Some(merged_nv + 1), - )?; - + let (zero_check_nv, zero_check_uni_degree) = + estimate_zero_check_param_size(num_vars, params.log_n_wires); + println!( + "zero check nv: {}, uni {}", + zero_check_nv, zero_check_uni_degree + ); + let (perm_check_nv, perm_check_uni_degree) = + estimate_perm_check_param_size(num_vars, params.log_n_wires); + let nv = max(zero_check_nv, perm_check_nv); + let uni_degree = max(zero_check_uni_degree, perm_check_uni_degree); + + // // number of variables in merged polynomial for Multilinear-KZG + // let merged_nv = num_vars + log_num_witness_polys; + // // degree of q(x) for Univariate-KZG + // let q_x_degree = compute_qx_degree(num_vars, 1 << log_num_witness_polys); + // let prod_x_degree = merged_nv * 4; + // let supported_uni_degree = max(prod_x_degree, q_x_degree); + // println!("q(x) degree: {}\nnum_vars: {}\nmerged_vars {}\nprod x {}", + // q_x_degree, num_vars, merged_nv, prod_x_degree); extract PCS prover + // and verifier keys from SRS + let (pcs_prover_param, pcs_verifier_param) = + PCS::trim(pcs_srs, log2(uni_degree) as usize, Some(nv + 1))?; + + println!("here"); // build permutation oracles let permutation_oracles = Rc::new(DenseMultilinearExtension::from_evaluations_slice( - merged_nv, + num_vars + log_num_witness_polys, permutation, )); + println!("here"); let perm_com = PCS::commit(&pcs_prover_param, &permutation_oracles)?; + println!("here"); // build selector oracles and commit to it let selector_oracles: Vec>> = selectors .iter() .map(|s| Rc::new(DenseMultilinearExtension::from(s))) .collect(); + println!("here"); let selector_com = selector_oracles .iter() .map(|poly| PCS::commit(&pcs_prover_param, poly)) .collect::, PCSErrors>>()?; + println!("here"); Ok(( Self::ProvingKey { params: params.clone(), @@ -217,6 +239,7 @@ where // online public input of length 2^\ell let ell = pk.params.log_pub_input_len; + println!("here"); let witness_polys: Vec>> = witnesses .iter() .map(|w| Rc::new(DenseMultilinearExtension::from(w))) @@ -258,6 +281,7 @@ where pi_poly, pi_in_w0, ))); } + println!("here"); // ======================================================================= // 1. Commit Witness polynomials `w_i(x)` and append commitment to // transcript @@ -277,7 +301,7 @@ where w_merged.num_vars, merged_nv ))); } - let w_merged_com = PCS::commit(&pk.pcs_param, &Rc::new(w_merged.clone()))?; + let w_merged_com = PCS::commit(&pk.pcs_param, &w_merged)?; transcript.append_serializable_element(b"w", &w_merged_com)?; end_timer!(step); @@ -294,255 +318,37 @@ where // // in vanilla plonk, and obtain a ZeroCheckSubClaim // ======================================================================= - let step = start_timer!(|| "ZeroCheck on f"); - - let fx = build_f( - &pk.params.gate_func, - pk.params.nv, - &pk.selector_oracles, - &witness_polys, - )?; - - let zero_check_proof = >::prove(&fx, &mut transcript)?; - end_timer!(step); + let ( + zero_check_proof, + witness_zero_check_openings, + witness_zero_check_evals, + selector_oracle_openings, + selector_oracle_evals, + ) = zero_check_prover_subroutine(pk, &witness_polys, &mut transcript)?; // ======================================================================= // 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracles`, and // obtain a PermCheckSubClaim. - // - // 3.1. `generate_challenge` from current transcript (generate beta, gamma) - // 3.2. `compute_product` to build `prod(x)` etc. from f, g and s_perm - // 3.3. push a commitment of `prod(x)` to the transcript - // 3.4. `update_challenge` with the updated transcript - // 3.5. `prove` to generate the proof - // 3.6. open `prod(0,x)`, `prod(1, x)`, `prod(x, 0)`, `prod(x, 1)` at - // zero_check.point // ======================================================================= - let step = start_timer!(|| "Permutation check on w_i(x)"); - - // 3.1 `generate_challenge` from current transcript (generate beta, gamma) - let mut permutation_challenge = Self::generate_challenge(&mut transcript)?; - - // 3.2. `compute_product` to build `prod(x)` etc. from f, g and s_perm - - // This function returns 3 MLEs: - // - prod(x) - // - numerator - // - denominator - // See function signature for details. - let prod_x_and_aux_info = Self::compute_prod_evals( - &permutation_challenge, - &w_merged, - &w_merged, - &pk.permutation_oracles, - )?; - let prod_x = Rc::new(prod_x_and_aux_info[0].clone()); - - // 3.3 push a commitment of `prod(x)` to the transcript - let prod_com = PCS::commit(&pk.pcs_param, &prod_x)?; - - // 3.4. `update_challenge` with the updated transcript - Self::update_challenge(&mut permutation_challenge, &mut transcript, &prod_com)?; - - // 3.5. `prove` to generate the proof - let perm_check_proof = >::prove( - &prod_x_and_aux_info, - &permutation_challenge, - &mut transcript, - )?; - - // 3.6 open prod(0,x), prod(1, x), prod(x, 0), prod(x, 1) at zero_check.point - // prod(0, x) - let tmp_point = [perm_check_proof.point.as_slice(), &[E::Fr::zero()]].concat(); - let (prod_0_x_opening, prod_0_x_eval) = PCS::open(&pk.pcs_param, &prod_x, &tmp_point)?; - #[cfg(feature = "extensive_sanity_checks")] - { - // sanity check - let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { - HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), - ) - })?; - if eval != prod_0_x_eval { - return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), - )); - } - } - // prod(1, x) - let tmp_point = [perm_check_proof.point.as_slice(), &[E::Fr::one()]].concat(); - let (prod_1_x_opening, prod_1_x_eval) = PCS::open(&pk.pcs_param, &prod_x, &tmp_point)?; - #[cfg(feature = "extensive_sanity_checks")] - { - // sanity check - let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { - HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), - ) - })?; - if eval != prod_1_x_eval { - return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), - )); - } - } - // prod(x, 0) - let tmp_point = [&[E::Fr::zero()], perm_check_proof.point.as_slice()].concat(); - let (prod_x_0_opening, prod_x_0_eval) = PCS::open(&pk.pcs_param, &prod_x, &tmp_point)?; - #[cfg(feature = "extensive_sanity_checks")] - { - // sanity check - let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { - HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), - ) - })?; - - if eval != prod_x_0_eval { - return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), - )); - } - } - // prod(x, 1) - let tmp_point = [&[E::Fr::one()], perm_check_proof.point.as_slice()].concat(); - let (prod_x_1_opening, prod_x_1_eval) = PCS::open(&pk.pcs_param, &prod_x, &tmp_point)?; - #[cfg(feature = "extensive_sanity_checks")] - { - // sanity check - let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { - HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), - ) - })?; - if eval != prod_x_1_eval { - return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), - )); - } - } - end_timer!(step); + let ( + perm_check_proof, + witness_perm_check_opening, + witness_perm_check_eval, + perm_oracle_opening, + perm_oracle_eval, + prod_com, + prod_opening, + prod_evals, + ) = perm_check_prover_subroutine(pk, &witness_polys, &mut transcript)?; // ======================================================================= // 4. Generate evaluations and corresponding proofs - // - permutation check evaluations and proofs - // - wi_poly(r_perm_check) where r_perm_check is from perm_check_proof - // - selector_poly(r_perm_check) - // - // - zero check evaluations and proofs - // - wi_poly(r_zero_check) where r_zero_check is from zero_check_proof - // - selector_poly(r_zero_check) // // - public input consistency checks // - pi_poly(r_pi) where r_pi is sampled from transcript // ======================================================================= let step = start_timer!(|| "opening and evaluations"); - // 4.1 permutation check - let mut witness_zero_check_evals = vec![]; - let mut witness_zero_check_openings = vec![]; - // TODO: parallelization - // TODO: Batch opening - - // open permutation check proof - let (witness_perm_check_opening, witness_perm_check_eval) = PCS::open( - &pk.pcs_param, - &Rc::new(w_merged.clone()), - &perm_check_proof.point, - )?; - - #[cfg(feature = "extensive_sanity_checks")] - { - // sanity checks - let eval = w_merged.evaluate(&perm_check_proof.point).ok_or_else(|| { - HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), - ) - })?; - if eval != witness_perm_check_eval { - return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), - )); - } - } - - // 4.2 open zero check proof - // TODO: batch opening - for wire_poly in witness_polys { - // Open zero check proof - let (zero_proof, zero_eval) = - PCS::open(&pk.pcs_param, &wire_poly, &zero_check_proof.point)?; - { - let eval = wire_poly.evaluate(&zero_check_proof.point).ok_or_else(|| { - HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), - ) - })?; - if eval != zero_eval { - return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), - )); - } - } - witness_zero_check_evals.push(zero_eval); - witness_zero_check_openings.push(zero_proof); - } - - // Open permutation polynomial at perm_check_point - let (s_perm_opening, s_perm_eval) = PCS::open( - &pk.pcs_param, - &pk.permutation_oracles, - &perm_check_proof.point, - )?; - - #[cfg(feature = "extensive_sanity_checks")] - { - // sanity check - let eval = pk - .permutation_oracles - .evaluate(&perm_check_proof.point) - .ok_or_else(|| { - HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), - ) - })?; - if eval != s_perm_eval { - return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), - )); - } - } - - // Open selector polynomial at zero_check_point - let mut selector_oracle_openings = vec![]; - let mut selector_oracle_evals = vec![]; - - // TODO: parallelization - for selector_poly in pk.selector_oracles.iter() { - // Open zero check proof - // during verification, use this eval against subclaim - let (zero_proof, zero_eval) = - PCS::open(&pk.pcs_param, selector_poly, &zero_check_proof.point)?; - - #[cfg(feature = "extensive_sanity_checks")] - { - let eval = selector_poly - .evaluate(&zero_check_proof.point) - .ok_or_else(|| { - HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), - ) - })?; - if eval != zero_eval { - return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), - )); - } - } - selector_oracle_openings.push(zero_proof); - selector_oracle_evals.push(zero_eval); - } - // 4.3 public input consistency checks let r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?; @@ -577,17 +383,12 @@ where // ======================================================================= // We do not validate prod(x), this is checked by subclaim prod_commit: prod_com, - prod_evals: vec![prod_0_x_eval, prod_1_x_eval, prod_x_0_eval, prod_x_1_eval], - prod_openings: vec![ - prod_0_x_opening, - prod_1_x_opening, - prod_x_0_opening, - prod_x_1_opening, - ], + prod_evals, + prod_opening, witness_perm_check_opening, witness_perm_check_eval, - perm_oracle_opening: s_perm_opening, - perm_oracle_eval: s_perm_eval, + perm_oracle_opening, + perm_oracle_eval, // ======================================================================= // PCS components: zero check // ======================================================================= @@ -634,7 +435,6 @@ where /// - check permutation check evaluations /// - check zero check evaluations /// - public input consistency checks - /// 4. check subclaim validity // todo fn verify( vk: &Self::VerifyingKey, pub_input: &[E::Fr], @@ -643,11 +443,7 @@ where let start = start_timer!(|| "hyperplonk verification"); let mut transcript = IOPTranscript::::new(b"hyperplonk"); - // witness assignment of length 2^n let num_var = vk.params.nv; - let log_num_witness_polys = vk.params.log_n_wires; - // number of variables in merged polynomial for Multilinear-KZG - let merged_nv = num_var + log_num_witness_polys; // online public input of length 2^\ell let ell = vk.params.log_pub_input_len; @@ -676,237 +472,22 @@ where // = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x) // // ======================================================================= - let step = start_timer!(|| "verify zero check"); - // Zero check and sum check have different AuxInfo because `w_merged` and - // `Prod(x)` have degree and num_vars - let zero_check_aux_info = VPAuxInfo:: { - // TODO: get the real max degree from gate_func - // Here we use 6 is because the test has q[0] * w[0]^5 which is degree 6 - max_degree: 6, - num_variables: num_var, - phantom: PhantomData::default(), - }; - - // push witness to transcript - transcript.append_serializable_element(b"w", &proof.w_merged_com)?; - - let zero_check_sub_claim = >::verify( - &proof.zero_check_proof, - &zero_check_aux_info, - &mut transcript, - )?; - - let zero_check_point = &zero_check_sub_claim.sum_check_sub_claim.point; - - // check zero check subclaim - let f_eval = eval_f( - &vk.params.gate_func, - &proof.selector_oracle_evals, - &proof.witness_zero_check_evals, - )?; - if f_eval != zero_check_sub_claim.expected_evaluation { - return Err(HyperPlonkErrors::InvalidProof( - "zero check evaluation failed".to_string(), - )); + if !zero_check_verifier_subroutine(vk, proof, &mut transcript)? { + return Ok(false); } - end_timer!(step); // ======================================================================= // 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracles` // ======================================================================= - let step = start_timer!(|| "verify permutation check"); - // Zero check and sum check have different AuxInfo because `w_merged` and - // `Prod(x)` have degree and num_vars - let perm_check_aux_info = VPAuxInfo:: { - // Prod(x) has a max degree of 2 - max_degree: 2, - // degree of merged poly - num_variables: merged_nv, - phantom: PhantomData::default(), - }; - let mut challenge = >::generate_challenge(&mut transcript)?; - >::update_challenge( - &mut challenge, - &mut transcript, - &proof.prod_commit, - )?; - - let perm_check_sub_claim = >::verify( - &proof.perm_check_proof, - &perm_check_aux_info, - &mut transcript, - )?; - let perm_check_point = &perm_check_sub_claim - .zero_check_sub_claim - .sum_check_sub_claim - .point; - - // check perm check subclaim: - // proof.witness_perm_check_eval ?= perm_check_sub_claim.expected_eval - // - // Q(x) := prod(1,x) - prod(x, 0) * prod(x, 1) - // + alpha * ( - // (g(x) + beta * s_perm(x) + gamma) * prod(0, x) - // - (f(x) + beta * s_id(x) + gamma)) - // where - // - Q(x) is perm_check_sub_claim.zero_check.exp_eval - // - prod(1, x) ... from prod(x) evaluated over (1, zero_point) - // - g(x), f(x) are both w_merged over (zero_point) - // - s_perm(x) and s_id(x) from vk_param.perm_oracle - // - alpha, beta, gamma from challenge - let alpha = challenge - .alpha - .ok_or_else(|| HyperPlonkErrors::InvalidVerifier("alpha is not set".to_string()))?; - - let s_id = identity_permutation_mle::(perm_check_point.len()); - let s_id_eval = s_id.evaluate(perm_check_point).ok_or_else(|| { - HyperPlonkErrors::InvalidVerifier("unable to evaluate s_id(x)".to_string()) - })?; - - let q_x_rec = proof.prod_evals[1] - proof.prod_evals[2] * proof.prod_evals[3] - + alpha - * ((proof.witness_perm_check_eval - + challenge.beta * proof.perm_oracle_eval - + challenge.gamma) - * proof.prod_evals[0] - - (proof.witness_perm_check_eval - + challenge.beta * s_id_eval - + challenge.gamma)); - - if q_x_rec - != perm_check_sub_claim - .zero_check_sub_claim - .expected_evaluation - { - return Err(HyperPlonkErrors::InvalidVerifier( - "evaluation failed".to_string(), - )); + if !perm_check_verifier_subroutine(vk, proof, &mut transcript)? { + return Ok(false); } - end_timer!(step); // ======================================================================= // 3. Verify the opening against the commitment // ======================================================================= let step = start_timer!(|| "verify commitments"); - // ======================================================================= - // 3.1 check permutation check evaluations - // ======================================================================= - // witness for permutation check - if !PCS::verify( - &vk.pcs_param, - &proof.w_merged_com, - perm_check_point, - &proof.witness_perm_check_eval, - &proof.witness_perm_check_opening, - )? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - - if !PCS::verify( - &vk.pcs_param, - &vk.perm_com, - perm_check_point, - &proof.perm_oracle_eval, - &proof.perm_oracle_opening, - )? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - - // prod(x) for permutation check - // TODO: batch verification - - // prod(0, x) - if !PCS::verify( - &vk.pcs_param, - &proof.prod_commit, - &[perm_check_point.as_slice(), &[E::Fr::zero()]].concat(), - &proof.prod_evals[0], - &proof.prod_openings[0], - )? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - // prod(1, x) - if !PCS::verify( - &vk.pcs_param, - &proof.prod_commit, - &[perm_check_point.as_slice(), &[E::Fr::one()]].concat(), - &proof.prod_evals[1], - &proof.prod_openings[1], - )? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - // prod(x, 0) - if !PCS::verify( - &vk.pcs_param, - &proof.prod_commit, - &[&[E::Fr::zero()], perm_check_point.as_slice()].concat(), - &proof.prod_evals[2], - &proof.prod_openings[2], - )? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - // prod(x, 1) - if !PCS::verify( - &vk.pcs_param, - &proof.prod_commit, - &[&[E::Fr::one()], perm_check_point.as_slice()].concat(), - &proof.prod_evals[3], - &proof.prod_openings[3], - )? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - - // ======================================================================= - // 3.2 check zero check evaluations - // ======================================================================= - // witness for zero check - // TODO: batch verification - for (commitment, (opening, eval)) in proof.witness_commits.iter().zip( - proof - .witness_zero_check_openings - .iter() - .zip(proof.witness_zero_check_evals.iter()), - ) { - if !PCS::verify(&vk.pcs_param, commitment, zero_check_point, eval, opening)? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - } - - // selector for zero check - // TODO: for now we only support a single selector polynomial - for (opening, eval) in proof - .selector_oracle_openings - .iter() - .zip(proof.selector_oracle_evals.iter()) - { - if !PCS::verify( - &vk.pcs_param, - &vk.selector_com[0], - perm_check_point, - eval, - opening, - )? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - } - // ======================================================================= // 3.3 public input consistency checks // ======================================================================= @@ -938,7 +519,7 @@ mod tests { use super::*; use crate::{selectors::SelectorColumn, structs::CustomizedGates, witness::WitnessColumn}; use ark_bls12_381::Bls12_381; - use ark_std::test_rng; + use ark_std::{test_rng, One}; use pcs::prelude::KZGMultilinearPCS; use poly_iop::random_permutation_mle; diff --git a/hyperplonk/src/structs.rs b/hyperplonk/src/structs.rs index e5e45097..0d1a2a01 100644 --- a/hyperplonk/src/structs.rs +++ b/hyperplonk/src/structs.rs @@ -51,7 +51,7 @@ pub struct HyperPlonkProof< pub prod_evals: Vec, /// prod(x)'s openings /// sequence: prod(0,x), prod(1, x), prod(x, 0), prod(x, 1) - pub prod_openings: Vec, + pub prod_opening: PCS::BatchProof, /// PCS openings for witness on permutation check point // TODO: replace me with a batch opening pub witness_perm_check_opening: PCS::Proof, diff --git a/hyperplonk/src/subroutine/mod.rs b/hyperplonk/src/subroutine/mod.rs new file mode 100644 index 00000000..cce369a7 --- /dev/null +++ b/hyperplonk/src/subroutine/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod perm_check; +pub(crate) mod zero_check; diff --git a/hyperplonk/src/subroutine/perm_check.rs b/hyperplonk/src/subroutine/perm_check.rs new file mode 100644 index 00000000..b2ac5cdb --- /dev/null +++ b/hyperplonk/src/subroutine/perm_check.rs @@ -0,0 +1,472 @@ +use crate::{ + errors::HyperPlonkErrors, + structs::{HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkVerifyingKey}, +}; +use arithmetic::VPAuxInfo; +use ark_ec::PairingEngine; +use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; +use ark_std::{end_timer, start_timer, One, Zero}; +use pcs::prelude::{merge_polynomials, PolynomialCommitmentScheme}; +use poly_iop::{ + identity_permutation_mle, + prelude::{build_prod_partial_eval, IOPProof, PermutationCheck, ZeroCheck}, + PolyIOP, +}; +use std::{marker::PhantomData, rc::Rc}; +use transcript::IOPTranscript; + +/// Estimate the PCS parameter sizes for permutation check +/// Returns +/// - degree of univariate polynomial +/// - number over vars in multilinear polynomial +pub(crate) fn estimate_perm_check_param_size( + num_vars: usize, + log_n_wires: usize, +) -> (usize, usize) { + // we first get the num_var for m_merged that is to be used for perm check + let merged_nv = num_vars + log_n_wires; + // we merge all the product into a single MLE + // whose number variable = merged_nv + 2 + let merged_prod_nv = merged_nv + 2; + // to batch open its commitment we will need a univariate q(x) + // whose degree is merged_nv * 4 + let uni_degree = merged_nv * 4; + (merged_prod_nv, uni_degree) +} + +/// Internal function to generate +/// - permutation check proof +/// - PCS openings and evaluations for witness and permutation polynomials at +/// zero check point +pub(crate) fn perm_check_prover_subroutine( + pk: &HyperPlonkProvingKey, + witness_polys: &[PCS::Polynomial], + transcript: &mut IOPTranscript, +) -> Result< + ( + IOPProof, + PCS::Proof, + PCS::Evaluation, + PCS::Proof, + PCS::Evaluation, + PCS::Commitment, + PCS::BatchProof, + Vec, + ), + HyperPlonkErrors, +> +where + E: PairingEngine, + PCS: PolynomialCommitmentScheme< + E, + Polynomial = Rc>, + Point = Vec, + Evaluation = E::Fr, + >, +{ + // ======================================================================= + // 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracles`, and + // obtain a PermCheckSubClaim. + // + // 3.1. `generate_challenge` from current transcript (generate beta, gamma) + // 3.2. `compute_product` to build `prod(x)` etc. from f, g and s_perm + // 3.3. push a commitment of `prod(x)` to the transcript + // 3.4. `update_challenge` with the updated transcript + // 3.5. `prove` to generate the proof + // 3.6. open `prod(0,x)`, `prod(1, x)`, `prod(x, 0)`, `prod(x, 1)` at + // zero_check.point + // ======================================================================= + let step = start_timer!(|| "Permutation check on w_i(x)"); + let w_merged = merge_polynomials(&witness_polys)?; + + // 3.1 `generate_challenge` from current transcript (generate beta, gamma) + let mut permutation_challenge = + as PermutationCheck>::generate_challenge(transcript)?; + + // 3.2. `compute_product` to build `prod(x)` etc. from f, g and s_perm + + // This function returns 3 MLEs: + // - prod(x) + // - numerator + // - denominator + // See function signature for details. + let prod_x_and_aux_info = as PermutationCheck>::compute_prod_evals( + &permutation_challenge, + &w_merged, + &w_merged, + &pk.permutation_oracles, + )?; + let prod_x = prod_x_and_aux_info[0].clone(); + + // 3.3 push a commitment of `prod(x)` to the transcript + let prod_com = PCS::commit(&pk.pcs_param, &prod_x)?; + + // 3.4. `update_challenge` with the updated transcript + as PermutationCheck>::update_challenge( + &mut permutation_challenge, + transcript, + &prod_com, + )?; + + // 3.5. `prove` to generate the proof + let perm_check_proof = as PermutationCheck>::prove( + &prod_x_and_aux_info, + &permutation_challenge, + transcript, + )?; + + // 3.6 open prod(0,x), prod(1, x), prod(x, 0), prod(x, 1) at zero_check.point + + let point_0_x = [perm_check_proof.point.as_slice(), &[E::Fr::zero()]].concat(); + let point_1_x = [perm_check_proof.point.as_slice(), &[E::Fr::one()]].concat(); + let point_x_0 = [&[E::Fr::zero()], perm_check_proof.point.as_slice()].concat(); + let point_x_1 = [&[E::Fr::one()], perm_check_proof.point.as_slice()].concat(); + + // prod(0, x) + let (prod_0_x_opening, prod_0_x_eval) = PCS::open(&pk.pcs_param, &prod_x, &point_0_x)?; + #[cfg(feature = "extensive_sanity_checks")] + { + // sanity check + let eval = prod_x.evaluate(&point_0_x).ok_or_else(|| { + HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string()) + })?; + if eval != prod_0_x_eval { + return Err(HyperPlonkErrors::InvalidProver( + "Evaluation is different from PCS opening".to_string(), + )); + } + } + // prod(1, x) + let (prod_1_x_opening, prod_1_x_eval) = PCS::open(&pk.pcs_param, &prod_x, &point_1_x)?; + #[cfg(feature = "extensive_sanity_checks")] + { + // sanity check + let eval = prod_x.evaluate(&point_1_x).ok_or_else(|| { + HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string()) + })?; + if eval != prod_1_x_eval { + return Err(HyperPlonkErrors::InvalidProver( + "Evaluation is different from PCS opening".to_string(), + )); + } + } + // prod(x, 0) + let tmp_point = [&[E::Fr::zero()], perm_check_proof.point.as_slice()].concat(); + let (prod_x_0_opening, prod_x_0_eval) = PCS::open(&pk.pcs_param, &prod_x, &tmp_point)?; + #[cfg(feature = "extensive_sanity_checks")] + { + // sanity check + let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { + HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string()) + })?; + + if eval != prod_x_0_eval { + return Err(HyperPlonkErrors::InvalidProver( + "Evaluation is different from PCS opening".to_string(), + )); + } + } + // prod(x, 1) + let tmp_point = [&[E::Fr::one()], perm_check_proof.point.as_slice()].concat(); + let (prod_x_1_opening, prod_x_1_eval) = PCS::open(&pk.pcs_param, &prod_x, &tmp_point)?; + #[cfg(feature = "extensive_sanity_checks")] + { + // sanity check + let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { + HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string()) + })?; + if eval != prod_x_1_eval { + return Err(HyperPlonkErrors::InvalidProver( + "Evaluation is different from PCS opening".to_string(), + )); + } + } + let prod_openings = vec![ + prod_0_x_opening, + prod_1_x_opening, + prod_x_0_opening, + prod_x_1_opening, + ]; + let prod_evals = vec![prod_0_x_eval, prod_1_x_eval, prod_x_0_eval, prod_x_1_eval]; + println!("prod evals: {:?}\n", prod_evals); + + let (prod_opening, _prod_evals) = PCS::multi_open( + &pk.pcs_param, + &prod_com, + &[ + prod_x.clone(), + prod_x.clone(), + prod_x.clone(), + prod_x.clone(), + ], + &[point_0_x, point_1_x, point_x_0, point_x_1], + )?; + + println!("here"); + println!("prod evals: {:?}\n", prod_evals); + + println!("here"); + + let prod_partial_mles = build_prod_partial_eval(&prod_x)?; + let (prod_opening, _prod_evals) = PCS::multi_open( + &pk.pcs_param, + &prod_com, + &prod_partial_mles, + // &[ + // prod_x.clone(), + // prod_x.clone(), + // prod_x.clone(), + // prod_x.clone(), + // ], + &[ + perm_check_proof.point.clone(), + perm_check_proof.point.clone(), + perm_check_proof.point.clone(), + perm_check_proof.point.clone(), + ], + )?; + + println!("here"); + println!("prod evals: {:?}\n", prod_evals); + + // ======================================================================= + // 4. Generate evaluations and corresponding proofs + // - permutation check evaluations and proofs + // - wi_poly(r_perm_check) where r_perm_check is from perm_check_proof + // - selector_poly(r_perm_check) + // + // ======================================================================= + // 4.1 permutation check + + // open permutation check proof + let (witness_perm_check_opening, witness_perm_check_eval) = + PCS::open(&pk.pcs_param, &w_merged, &perm_check_proof.point)?; + + #[cfg(feature = "extensive_sanity_checks")] + { + // sanity checks + let eval = w_merged.evaluate(&perm_check_proof.point).ok_or_else(|| { + HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string()) + })?; + if eval != witness_perm_check_eval { + return Err(HyperPlonkErrors::InvalidProver( + "Evaluation is different from PCS opening".to_string(), + )); + } + } + + // Open permutation polynomial at perm_check_point + let (s_perm_opening, s_perm_eval) = PCS::open( + &pk.pcs_param, + &pk.permutation_oracles, + &perm_check_proof.point, + )?; + + #[cfg(feature = "extensive_sanity_checks")] + { + // sanity check + let eval = pk + .permutation_oracles + .evaluate(&perm_check_proof.point) + .ok_or_else(|| { + HyperPlonkErrors::InvalidParameters( + "evaluation dimension does not match".to_string(), + ) + })?; + if eval != s_perm_eval { + return Err(HyperPlonkErrors::InvalidProver( + "Evaluation is different from PCS opening".to_string(), + )); + } + } + + end_timer!(step); + Ok(( + perm_check_proof, + witness_perm_check_opening, + witness_perm_check_eval, + s_perm_opening, + s_perm_eval, + prod_com, + prod_opening, + prod_evals, + )) +} + +/// Internal function to verify the zero check component +/// is correct in the proof. +pub(crate) fn perm_check_verifier_subroutine( + vk: &HyperPlonkVerifyingKey, + proof: &HyperPlonkProof, + transcript: &mut IOPTranscript, +) -> Result +where + E: PairingEngine, + PCS: PolynomialCommitmentScheme< + E, + Polynomial = Rc>, + Point = Vec, + Evaluation = E::Fr, + >, + ZC: ZeroCheck, + PC: PermutationCheck>, +{ + let num_var = vk.params.nv; + + let log_num_witness_polys = vk.params.log_n_wires; + // number of variables in merged polynomial for Multilinear-KZG + let merged_nv = num_var + log_num_witness_polys; + + // ======================================================================= + // 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracles` + // ======================================================================= + let step = start_timer!(|| "verify permutation check"); + // Zero check and sum check have different AuxInfo because `w_merged` and + // `Prod(x)` have degree and num_vars + let perm_check_aux_info = VPAuxInfo:: { + // Prod(x) has a max degree of 2 + max_degree: 2, + // degree of merged poly + num_variables: merged_nv, + phantom: PhantomData::default(), + }; + let mut challenge = + as PermutationCheck>::generate_challenge(transcript)?; + as PermutationCheck>::update_challenge( + &mut challenge, + transcript, + &proof.prod_commit, + )?; + + let perm_check_sub_claim = as PermutationCheck>::verify( + &proof.perm_check_proof, + &perm_check_aux_info, + transcript, + )?; + let perm_check_point = &perm_check_sub_claim + .zero_check_sub_claim + .sum_check_sub_claim + .point; + + // check perm check subclaim: + // proof.witness_perm_check_eval ?= perm_check_sub_claim.expected_eval + // + // Q(x) := prod(1,x) - prod(x, 0) * prod(x, 1) + // + alpha * ( + // (g(x) + beta * s_perm(x) + gamma) * prod(0, x) + // - (f(x) + beta * s_id(x) + gamma)) + // where + // - Q(x) is perm_check_sub_claim.zero_check.exp_eval + // - prod(1, x) ... from prod(x) evaluated over (1, zero_point) + // - g(x), f(x) are both w_merged over (zero_point) + // - s_perm(x) and s_id(x) from vk_param.perm_oracle + // - alpha, beta, gamma from challenge + let alpha = challenge + .alpha + .ok_or_else(|| HyperPlonkErrors::InvalidVerifier("alpha is not set".to_string()))?; + + let s_id = identity_permutation_mle::(perm_check_point.len()); + let s_id_eval = s_id.evaluate(perm_check_point).ok_or_else(|| { + HyperPlonkErrors::InvalidVerifier("unable to evaluate s_id(x)".to_string()) + })?; + + let q_x_rec = proof.prod_evals[1] - proof.prod_evals[2] * proof.prod_evals[3] + + alpha + * ((proof.witness_perm_check_eval + + challenge.beta * proof.perm_oracle_eval + + challenge.gamma) + * proof.prod_evals[0] + - (proof.witness_perm_check_eval + challenge.beta * s_id_eval + challenge.gamma)); + + if q_x_rec + != perm_check_sub_claim + .zero_check_sub_claim + .expected_evaluation + { + return Err(HyperPlonkErrors::InvalidVerifier( + "evaluation failed".to_string(), + )); + } + + end_timer!(step); + // ======================================================================= + // 3.1 check permutation check evaluations + // ======================================================================= + // witness for permutation check + if !PCS::verify( + &vk.pcs_param, + &proof.w_merged_com, + perm_check_point, + &proof.witness_perm_check_eval, + &proof.witness_perm_check_opening, + )? { + return Err(HyperPlonkErrors::InvalidProof( + "pcs verification failed".to_string(), + )); + } + + if !PCS::verify( + &vk.pcs_param, + &vk.perm_com, + perm_check_point, + &proof.perm_oracle_eval, + &proof.perm_oracle_opening, + )? { + return Err(HyperPlonkErrors::InvalidProof( + "pcs verification failed".to_string(), + )); + } + + // prod(x) for permutation check + // TODO: batch verification + + // // prod(0, x) + // if !PCS::verify( + // &vk.pcs_param, + // &proof.prod_commit, + // &[perm_check_point.as_slice(), &[E::Fr::zero()]].concat(), + // &proof.prod_evals[0], + // &proof.prod_openings[0], + // )? { + // return Err(HyperPlonkErrors::InvalidProof( + // "pcs verification failed".to_string(), + // )); + // } + // // prod(1, x) + // if !PCS::verify( + // &vk.pcs_param, + // &proof.prod_commit, + // &[perm_check_point.as_slice(), &[E::Fr::one()]].concat(), + // &proof.prod_evals[1], + // &proof.prod_openings[1], + // )? { + // return Err(HyperPlonkErrors::InvalidProof( + // "pcs verification failed".to_string(), + // )); + // } + // // prod(x, 0) + // if !PCS::verify( + // &vk.pcs_param, + // &proof.prod_commit, + // &[&[E::Fr::zero()], perm_check_point.as_slice()].concat(), + // &proof.prod_evals[2], + // &proof.prod_openings[2], + // )? { + // return Err(HyperPlonkErrors::InvalidProof( + // "pcs verification failed".to_string(), + // )); + // } + // // prod(x, 1) + // if !PCS::verify( + // &vk.pcs_param, + // &proof.prod_commit, + // &[&[E::Fr::one()], perm_check_point.as_slice()].concat(), + // &proof.prod_evals[3], + // &proof.prod_openings[3], + // )? { + // return Err(HyperPlonkErrors::InvalidProof( + // "pcs verification failed".to_string(), + // )); + // } + Ok(true) +} diff --git a/hyperplonk/src/subroutine/zero_check.rs b/hyperplonk/src/subroutine/zero_check.rs new file mode 100644 index 00000000..ae64b82e --- /dev/null +++ b/hyperplonk/src/subroutine/zero_check.rs @@ -0,0 +1,251 @@ +use crate::{ + errors::HyperPlonkErrors, + structs::{HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkVerifyingKey}, + utils::{build_f, eval_f}, +}; +use arithmetic::{DenseMultilinearExtension, VPAuxInfo}; +use ark_ec::PairingEngine; +use ark_poly::MultilinearExtension; +use ark_std::{end_timer, start_timer}; +use pcs::prelude::PolynomialCommitmentScheme; +use poly_iop::prelude::{IOPProof, PermutationCheck, PolyIOP, ZeroCheck}; +use std::{marker::PhantomData, rc::Rc}; +use transcript::IOPTranscript; + +/// Estimate the PCS parameter sizes for zero check +/// Returns +/// - degree of univariate polynomial +/// - number over vars in multilinear polynomial +pub(crate) fn estimate_zero_check_param_size( + num_vars: usize, + log_n_wires: usize, +) -> (usize, usize) { + // we merge all the witness into a single MLE w_merged + // whose number variable = num_var + log(num_wires) + let merged_nv = num_vars + log_n_wires; + // to batch open its commitment we will need a univariate q(x) + // whose degree is num_vars * n_wires + let uni_degree = num_vars * (1 << log_n_wires); + (merged_nv, uni_degree) +} + +/// Internal function to generate +/// - zero check proof +/// - PCS openings and evaluations for witness and selector polynomials at zero +/// check point +#[allow(clippy::type_complexity)] +pub(crate) fn zero_check_prover_subroutine( + pk: &HyperPlonkProvingKey, + witness_polys: &[PCS::Polynomial], + transcript: &mut IOPTranscript, +) -> Result< + ( + IOPProof, + Vec, + Vec, + Vec, + Vec, + ), + HyperPlonkErrors, +> +where + E: PairingEngine, + PCS: PolynomialCommitmentScheme< + E, + Polynomial = Rc>, + Point = Vec, + Evaluation = E::Fr, + >, +{ + let step = start_timer!(|| "ZeroCheck on f"); + + let fx = build_f( + &pk.params.gate_func, + pk.params.nv, + &pk.selector_oracles, + &witness_polys, + )?; + + let zero_check_proof = as ZeroCheck>::prove(&fx, transcript)?; + + let mut witness_zero_check_evals = vec![]; + let mut witness_zero_check_openings = vec![]; + + // ======================================================================= + // 4. Generate evaluations and corresponding proofs + // + // - zero check evaluations and proofs + // - wi_poly(r_zero_check) where r_zero_check is from zero_check_proof + // - selector_poly(r_zero_check) + // + // ======================================================================= + // 4.2 open zero check proof + // TODO: batch opening + for wire_poly in witness_polys { + // Open zero check proof + let (zero_proof, zero_eval) = + PCS::open(&pk.pcs_param, &wire_poly, &zero_check_proof.point)?; + { + let eval = wire_poly.evaluate(&zero_check_proof.point).ok_or_else(|| { + HyperPlonkErrors::InvalidParameters( + "evaluation dimension does not match".to_string(), + ) + })?; + if eval != zero_eval { + return Err(HyperPlonkErrors::InvalidProver( + "Evaluation is different from PCS opening".to_string(), + )); + } + } + witness_zero_check_evals.push(zero_eval); + witness_zero_check_openings.push(zero_proof); + } + + // Open selector polynomial at zero_check_point + let mut selector_oracle_openings = vec![]; + let mut selector_oracle_evals = vec![]; + + // TODO: parallelization + for selector_poly in pk.selector_oracles.iter() { + // Open zero check proof + // during verification, use this eval against subclaim + let (zero_proof, zero_eval) = + PCS::open(&pk.pcs_param, selector_poly, &zero_check_proof.point)?; + + #[cfg(feature = "extensive_sanity_checks")] + { + let eval = selector_poly + .evaluate(&zero_check_proof.point) + .ok_or_else(|| { + HyperPlonkErrors::InvalidParameters( + "evaluation dimension does not match".to_string(), + ) + })?; + if eval != zero_eval { + return Err(HyperPlonkErrors::InvalidProver( + "Evaluation is different from PCS opening".to_string(), + )); + } + } + selector_oracle_openings.push(zero_proof); + selector_oracle_evals.push(zero_eval); + } + + end_timer!(step); + + Ok(( + zero_check_proof, + witness_zero_check_openings, + witness_zero_check_evals, + selector_oracle_openings, + selector_oracle_evals, + )) +} + +/// Internal function to verify the zero check component +/// is correct in the proof. +pub(crate) fn zero_check_verifier_subroutine( + vk: &HyperPlonkVerifyingKey, + proof: &HyperPlonkProof, + transcript: &mut IOPTranscript, +) -> Result +where + E: PairingEngine, + PCS: PolynomialCommitmentScheme< + E, + Polynomial = Rc>, + Point = Vec, + Evaluation = E::Fr, + >, + ZC: ZeroCheck>, + PC: PermutationCheck, +{ + // ======================================================================= + // 1. Verify zero_check_proof on + // `f(q_0(x),...q_l(x), w_0(x),...w_d(x))` + // + // where `f` is the constraint polynomial i.e., + // + // f(q_l, q_r, q_m, q_o, w_a, w_b, w_c) + // = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x) + // + // ======================================================================= + let step = start_timer!(|| "verify zero check"); + + let num_var = vk.params.nv; + + // Zero check and sum check have different AuxInfo because `w_merged` and + // `Prod(x)` have degree and num_vars + let zero_check_aux_info = VPAuxInfo:: { + // TODO: get the real max degree from gate_func + // Here we use 6 is because the test has q[0] * w[0]^5 which is + // degree 6 + max_degree: 6, + num_variables: num_var, + phantom: PhantomData::default(), + }; + + // push witness to transcript + transcript.append_serializable_element(b"w", &proof.w_merged_com)?; + + let zero_check_sub_claim = as ZeroCheck>::verify( + &proof.zero_check_proof, + &zero_check_aux_info, + transcript, + )?; + + let zero_check_point = &zero_check_sub_claim.sum_check_sub_claim.point; + + // check zero check subclaim + let f_eval = eval_f( + &vk.params.gate_func, + &proof.selector_oracle_evals, + &proof.witness_zero_check_evals, + )?; + if f_eval != zero_check_sub_claim.expected_evaluation { + return Err(HyperPlonkErrors::InvalidProof( + "zero check evaluation failed".to_string(), + )); + } + + // ======================================================================= + // 3.2 check zero check evaluations + // ======================================================================= + // witness for zero check + // TODO: batch verification + for (commitment, (opening, eval)) in proof.witness_commits.iter().zip( + proof + .witness_zero_check_openings + .iter() + .zip(proof.witness_zero_check_evals.iter()), + ) { + if !PCS::verify(&vk.pcs_param, commitment, zero_check_point, eval, opening)? { + return Err(HyperPlonkErrors::InvalidProof( + "pcs verification failed".to_string(), + )); + } + } + + // selector for zero check + // TODO: for now we only support a single selector polynomial + for (opening, eval) in proof + .selector_oracle_openings + .iter() + .zip(proof.selector_oracle_evals.iter()) + { + if !PCS::verify( + &vk.pcs_param, + &vk.selector_com[0], + zero_check_point, + eval, + opening, + )? { + return Err(HyperPlonkErrors::InvalidProof( + "pcs verification failed".to_string(), + )); + } + } + + end_timer!(step); + Ok(true) +} diff --git a/pcs/src/lib.rs b/pcs/src/lib.rs index 3c817ea7..80a0a36f 100644 --- a/pcs/src/lib.rs +++ b/pcs/src/lib.rs @@ -23,7 +23,6 @@ pub trait PolynomialCommitmentScheme { type Evaluation; // Commitments and proofs type Commitment: CanonicalSerialize; - type BatchCommitment: CanonicalSerialize; type Proof; type BatchProof; @@ -58,7 +57,7 @@ pub trait PolynomialCommitmentScheme { fn multi_commit( prover_param: &Self::ProverParam, polys: &[Self::Polynomial], - ) -> Result; + ) -> Result; /// On input a polynomial `p` and a point `point`, outputs a proof for the /// same. @@ -89,13 +88,12 @@ pub trait PolynomialCommitmentScheme { /// Verifies that `value_i` is the evaluation at `x_i` of the polynomial /// `poly_i` committed inside `comm`. - fn batch_verify( + fn batch_verify( verifier_param: &Self::VerifierParam, - multi_commitment: &Self::BatchCommitment, + multi_commitment: &Self::Commitment, points: &[Self::Point], values: &[E::Fr], batch_proof: &Self::BatchProof, - rng: &mut R, ) -> Result; } diff --git a/pcs/src/multilinear_kzg/batching.rs b/pcs/src/multilinear_kzg/batching.rs index 8d459c99..835842c2 100644 --- a/pcs/src/multilinear_kzg/batching.rs +++ b/pcs/src/multilinear_kzg/batching.rs @@ -46,6 +46,7 @@ use transcript::IOPTranscript; /// 7. get a point `p := l(r)` /// 8. output an opening of `w` over point `p` /// 9. output `w(p)` +/// 10. output MLEs evaluated at points pub(super) fn multi_open_internal( uni_prover_param: &UnivariateProverParam, ml_prover_param: &MultilinearProverParam, @@ -153,6 +154,22 @@ pub(super) fn multi_open_internal( "Q(r) does not match W(l(r))".to_string(), )); } + + // 10. output MLEs evaluated at points (this is different from q(omega^i)) + let mle_evals: Vec = polynomials + .iter() + .zip(points.iter()) + .map( + |(poly, point)| poly.evaluate(point).unwrap(), // todo + ) + .collect(); + + for i in 0..mle_evals.len() { + println!("{}", i); + println!("mle eval: {}", mle_evals[i]); + println!("ex eval: {}", q_x_evals[i]) + } + end_timer!(open_timer); Ok(( @@ -160,8 +177,9 @@ pub(super) fn multi_open_internal( proof: mle_opening, q_x_commit, q_x_opens, + q_x_evals, }, - q_x_evals, + mle_evals, )) } @@ -184,7 +202,7 @@ pub(super) fn batch_verify_internal( ml_verifier_param: &MultilinearVerifierParam, multi_commitment: &Commitment, points: &[Vec], - values: &[E::Fr], + _values: &[E::Fr], batch_proof: &BatchProof, ) -> Result { let verify_timer = start_timer!(|| "batch verify"); @@ -204,7 +222,7 @@ pub(super) fn batch_verify_internal( )); } - if points_len + 1 != values.len() { + if points_len + 1 != batch_proof.q_x_evals.len() { return Err(PCSErrors::InvalidParameters( "values length does not match point length".to_string(), )); @@ -237,7 +255,7 @@ pub(super) fn batch_verify_internal( // 3. check `q(r) == batch_proof.q_x_value.last` and `q(omega^i) = // batch_proof.q_x_value[i]` - for (i, value) in values.iter().enumerate().take(points_len) { + for (i, value) in batch_proof.q_x_evals.iter().enumerate().take(points_len) { if !KZGUnivariatePCS::verify( uni_verifier_param, &batch_proof.q_x_commit, @@ -255,7 +273,7 @@ pub(super) fn batch_verify_internal( uni_verifier_param, &batch_proof.q_x_commit, &r, - &values[points_len], + &batch_proof.q_x_evals[points_len], &batch_proof.q_x_opens[points_len], )? { #[cfg(debug_assertion)] @@ -275,7 +293,7 @@ pub(super) fn batch_verify_internal( ml_verifier_param, multi_commitment, &point, - &values[points_len], + &batch_proof.q_x_evals[points_len], &batch_proof.proof, )?; @@ -333,7 +351,7 @@ mod tests { let (batch_proof, evaluations) = multi_open_internal(&uni_ck, &ml_ck, polys, &com, &points)?; - for (a, b) in evals.iter().zip(evaluations.iter()) { + for (a, b) in evals.iter().zip(batch_proof.q_x_evals.iter()) { assert_eq!(a, b) } @@ -377,20 +395,21 @@ mod tests { commitment: ::G1Affine::default() }, q_x_opens: vec![], + q_x_evals: vec![], }, ) .is_err()); // bad value - let mut wrong_evals = evaluations.clone(); - wrong_evals[0] = Fr::default(); + let mut bad_proof = batch_proof.clone(); + bad_proof.q_x_evals[0] = Fr::default(); assert!(!batch_verify_internal( &uni_vk, &ml_vk, &com, &points, - &wrong_evals, - &batch_proof + &evaluations, + &bad_proof )?); // bad q(x) commit diff --git a/pcs/src/multilinear_kzg/mod.rs b/pcs/src/multilinear_kzg/mod.rs index 4a54a006..38be8ac7 100644 --- a/pcs/src/multilinear_kzg/mod.rs +++ b/pcs/src/multilinear_kzg/mod.rs @@ -48,6 +48,8 @@ pub struct BatchProof { pub q_x_commit: Commitment, /// openings of q(x) at 1, omega, ..., and r pub q_x_opens: Vec>, + /// evaluations of q(x) at 1, omega, ..., and r + pub q_x_evals: Vec, } impl PolynomialCommitmentScheme for KZGMultilinearPCS { @@ -64,7 +66,6 @@ impl PolynomialCommitmentScheme for KZGMultilinearPCS { type Evaluation = E::Fr; // Commitments and proofs type Commitment = Commitment; - type BatchCommitment = Commitment; type Proof = Proof; type BatchProof = BatchProof; @@ -255,13 +256,12 @@ impl PolynomialCommitmentScheme for KZGMultilinearPCS { /// through the points /// 5. get a point `p := l(r)` /// 6. verifies `p` is verifies against proof - fn batch_verify( + fn batch_verify( verifier_param: &Self::VerifierParam, - multi_commitment: &Self::BatchCommitment, + multi_commitment: &Self::Commitment, points: &[Self::Point], values: &[E::Fr], batch_proof: &Self::BatchProof, - _rng: &mut R, ) -> Result { batch_verify_internal( &verifier_param.1, diff --git a/pcs/src/multilinear_kzg/util.rs b/pcs/src/multilinear_kzg/util.rs index 83dfe640..83738e66 100644 --- a/pcs/src/multilinear_kzg/util.rs +++ b/pcs/src/multilinear_kzg/util.rs @@ -8,18 +8,6 @@ use ark_poly::{ }; use ark_std::{end_timer, log2, rc::Rc, start_timer}; -/// Decompose an integer into a binary vector in little endian. -#[allow(dead_code)] -pub(crate) fn bit_decompose(input: u64, num_var: usize) -> Vec { - let mut res = Vec::with_capacity(num_var); - let mut i = input; - for _ in 0..num_var { - res.push(i & 1 == 1); - i >>= 1; - } - res -} - /// For an MLE w with `mle_num_vars` variables, and `point_len` number of /// points, compute the degree of the univariate polynomial `q(x):= w(l(x))` /// where l(x) is a list of polynomials that go through all points. @@ -107,7 +95,7 @@ pub fn get_batched_nv(num_var: usize, polynomials_len: usize) -> usize { /// polynomials do not share a same number of nvs. pub fn merge_polynomials( polynomials: &[Rc>], -) -> Result, PCSErrors> { +) -> Result>, PCSErrors> { let nv = polynomials[0].num_vars(); for poly in polynomials.iter() { if nv != poly.num_vars() { @@ -123,9 +111,9 @@ pub fn merge_polynomials( scalars.extend_from_slice(poly.to_evaluations().as_slice()); } scalars.extend_from_slice(vec![F::zero(); (1 << merged_nv) - scalars.len()].as_ref()); - Ok(DenseMultilinearExtension::from_evaluations_vec( + Ok(Rc::new(DenseMultilinearExtension::from_evaluations_vec( merged_nv, scalars, - )) + ))) } /// Given a list of points, build `l(points)` which is a list of univariate @@ -337,7 +325,7 @@ mod test { F::from(1u64), F::from(2u64), ]; - let w_rec = DenseMultilinearExtension::from_evaluations_vec(3, w_eval); + let w_rec = Rc::new(DenseMultilinearExtension::from_evaluations_vec(3, w_eval)); assert_eq!(w, w_rec); } @@ -387,7 +375,7 @@ mod test { F::zero(), F::zero(), ]; - let w_rec = DenseMultilinearExtension::from_evaluations_vec(4, w_eval); + let w_rec = Rc::new(DenseMultilinearExtension::from_evaluations_vec(4, w_eval)); assert_eq!(w, w_rec); } diff --git a/pcs/src/univariate_kzg/mod.rs b/pcs/src/univariate_kzg/mod.rs index 321785b7..c65842e6 100644 --- a/pcs/src/univariate_kzg/mod.rs +++ b/pcs/src/univariate_kzg/mod.rs @@ -8,7 +8,7 @@ use ark_ec::{msm::VariableBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve}; use ark_ff::PrimeField; use ark_poly::{univariate::DensePolynomial, Polynomial, UVPolynomial}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; -use ark_std::{end_timer, rand::RngCore, start_timer, One, UniformRand, Zero}; +use ark_std::{end_timer, rand::RngCore, start_timer, One}; use srs::{UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam}; use std::marker::PhantomData; @@ -38,7 +38,6 @@ impl PolynomialCommitmentScheme for KZGUnivariatePCS { type Evaluation = E::Fr; // Polynomial and its associated types type Commitment = Commitment; - type BatchCommitment = Vec; type Proof = KZGUnivariateOpening; type BatchProof = Vec; @@ -105,17 +104,10 @@ impl PolynomialCommitmentScheme for KZGUnivariatePCS { /// Generate a commitment for a list of polynomials fn multi_commit( - prover_param: &Self::ProverParam, - polys: &[Self::Polynomial], - ) -> Result { - let commit_time = start_timer!(|| format!("batch commit {} polynomials", polys.len())); - let res = polys - .iter() - .map(|poly| Self::commit(prover_param, poly)) - .collect::, PCSErrors>>()?; - - end_timer!(commit_time); - Ok(res) + _prover_param: &Self::ProverParam, + _polys: &[Self::Polynomial], + ) -> Result { + unimplemented!() } /// On input a polynomial `p` and a point `point`, outputs a proof for the @@ -211,59 +203,14 @@ impl PolynomialCommitmentScheme for KZGUnivariatePCS { // This is a naive approach // TODO: to implement the more efficient batch verification algorithm // (e.g., the appendix C.4 in https://eprint.iacr.org/2020/1536.pdf) - fn batch_verify( - verifier_param: &Self::VerifierParam, - multi_commitment: &Self::BatchCommitment, - points: &[Self::Point], - values: &[E::Fr], - batch_proof: &Self::BatchProof, - rng: &mut R, + fn batch_verify( + _verifier_param: &Self::VerifierParam, + _multi_commitment: &Self::Commitment, + _points: &[Self::Point], + _values: &[E::Fr], + _batch_proof: &Self::BatchProof, ) -> Result { - let check_time = - start_timer!(|| format!("Checking {} evaluation proofs", multi_commitment.len())); - - let mut total_c = ::zero(); - let mut total_w = ::zero(); - - let combination_time = start_timer!(|| "Combining commitments and proofs"); - let mut randomizer = E::Fr::one(); - // Instead of multiplying g and gamma_g in each turn, we simply accumulate - // their coefficients and perform a final multiplication at the end. - let mut g_multiplier = E::Fr::zero(); - for (((c, z), v), proof) in multi_commitment - .iter() - .zip(points) - .zip(values) - .zip(batch_proof) - { - let w = proof.proof; - let mut temp = w.mul(*z); - temp.add_assign_mixed(&c.commitment); - let c = temp; - g_multiplier += &(randomizer * v); - total_c += &c.mul(randomizer.into_repr()); - total_w += &w.mul(randomizer.into_repr()); - // We don't need to sample randomizers from the full field, - // only from 128-bit strings. - randomizer = u128::rand(rng).into(); - } - total_c -= &verifier_param.g.mul(g_multiplier); - end_timer!(combination_time); - - let to_affine_time = start_timer!(|| "Converting results to affine for pairing"); - let affine_points = E::G1Projective::batch_normalization_into_affine(&[-total_w, total_c]); - let (total_w, total_c) = (affine_points[0], affine_points[1]); - end_timer!(to_affine_time); - - let pairing_time = start_timer!(|| "Performing product of pairings"); - let result = E::product_of_pairings(&[ - (total_w.into(), verifier_param.beta_h.into()), - (total_c.into(), verifier_param.h.into()), - ]) - .is_one(); - end_timer!(pairing_time); - end_timer!(check_time, || format!("Result: {}", result)); - Ok(result) + unimplemented!() } } @@ -346,44 +293,6 @@ mod tests { Ok(()) } - fn batch_check_test_template() -> Result<(), PCSErrors> - where - E: PairingEngine, - { - let rng = &mut test_rng(); - for _ in 0..10 { - let mut degree = 0; - while degree <= 1 { - degree = usize::rand(rng) % 20; - } - let log_degree = log2(degree) as usize; - let pp = KZGUnivariatePCS::::gen_srs_for_testing(rng, log_degree)?; - let (ck, vk) = KZGUnivariatePCS::::trim(&pp, log_degree, None)?; - let mut comms = Vec::new(); - let mut values = Vec::new(); - let mut points = Vec::new(); - let mut proofs = Vec::new(); - for _ in 0..10 { - let p = as UVPolynomial>::rand(degree, rng); - let comm = KZGUnivariatePCS::::commit(&ck, &p)?; - let point = E::Fr::rand(rng); - let (proof, value) = KZGUnivariatePCS::::open(&ck, &p, &point)?; - - assert!(KZGUnivariatePCS::::verify( - &vk, &comm, &point, &value, &proof - )?); - comms.push(comm); - values.push(value); - points.push(point); - proofs.push(proof); - } - assert!(KZGUnivariatePCS::::batch_verify( - &vk, &comms, &points, &values, &proofs, rng - )?); - } - Ok(()) - } - #[test] fn end_to_end_test() { end_to_end_test_template::().expect("test failed for bls12-381"); @@ -393,8 +302,4 @@ mod tests { fn linear_polynomial_test() { linear_polynomial_test_template::().expect("test failed for bls12-381"); } - #[test] - fn batch_check_test() { - batch_check_test_template::().expect("test failed for bls12-381"); - } } diff --git a/poly-iop/benches/bench.rs b/poly-iop/benches/bench.rs index e0c487d6..f99f0dc7 100644 --- a/poly-iop/benches/bench.rs +++ b/poly-iop/benches/bench.rs @@ -5,7 +5,7 @@ use ark_std::{test_rng, UniformRand}; use poly_iop::prelude::{ identity_permutation_mle, PermutationCheck, PolyIOP, PolyIOPErrors, SumCheck, ZeroCheck, }; -use std::{marker::PhantomData, time::Instant}; +use std::{marker::PhantomData, rc::Rc, time::Instant}; fn main() -> Result<(), PolyIOPErrors> { bench_permutation_check()?; @@ -143,7 +143,7 @@ fn bench_permutation_check() -> Result<(), PolyIOPErrors> { 10 }; - let w = DenseMultilinearExtension::rand(nv, &mut rng); + let w = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); // s_perm is the identity map let s_perm = identity_permutation_mle(nv); diff --git a/poly-iop/src/perm_check/mod.rs b/poly-iop/src/perm_check/mod.rs index f65531c2..013f1417 100644 --- a/poly-iop/src/perm_check/mod.rs +++ b/poly-iop/src/perm_check/mod.rs @@ -48,7 +48,7 @@ pub trait PermutationCheck: ZeroCheck { fn preprocess( permutation: &[F], aux_info: &Self::VPAuxInfo, - ) -> Result, PolyIOPErrors>; + ) -> Result; /// Initialize the system with a transcript /// @@ -93,10 +93,10 @@ pub trait PermutationCheck: ZeroCheck { /// TODO: replace argument `s_perm` with the merged polynomial `s`. fn compute_prod_evals( challenge: &Self::PermutationChallenge, - fx: &DenseMultilinearExtension, - gx: &DenseMultilinearExtension, - s_perm: &DenseMultilinearExtension, - ) -> Result<[DenseMultilinearExtension; 3], PolyIOPErrors>; + fx: &Self::MLE, + gx: &Self::MLE, + s_perm: &Self::MLE, + ) -> Result<[Self::MLE; 3], PolyIOPErrors>; /// Step 3 of the IOP. /// push a commitment of `prod(x)` to the transcript @@ -125,7 +125,7 @@ pub trait PermutationCheck: ZeroCheck { /// and gamma /// Cost: O(N) fn prove( - prod_x_and_aux_info: &[DenseMultilinearExtension; 3], + prod_x_and_aux_info: &[Self::MLE; 3], challenge: &Self::PermutationChallenge, transcript: &mut IOPTranscript, ) -> Result; @@ -200,7 +200,7 @@ impl PermutationCheck for PolyIOP { fn preprocess( _permutation: &[F], _aux_info: &Self::VPAuxInfo, - ) -> Result, PolyIOPErrors> { + ) -> Result { unimplemented!(); } @@ -255,10 +255,10 @@ impl PermutationCheck for PolyIOP { /// TODO: replace argument `s_perm` with the merged polynomial `s`. fn compute_prod_evals( challenge: &Self::PermutationChallenge, - fx: &DenseMultilinearExtension, - gx: &DenseMultilinearExtension, - s_perm: &DenseMultilinearExtension, - ) -> Result<[DenseMultilinearExtension; 3], PolyIOPErrors> { + fx: &Self::MLE, + gx: &Self::MLE, + s_perm: &Self::MLE, + ) -> Result<[Self::MLE; 3], PolyIOPErrors> { let start = start_timer!(|| "compute evaluations of prod polynomial"); if challenge.alpha.is_some() { @@ -314,10 +314,18 @@ impl PermutationCheck for PolyIOP { // prod(x)'s evaluation is indeed `e := [eval_0x[..], eval_1x[..]].concat()` let eval = [prod_0x_eval.as_slice(), prod_1x_eval.as_slice()].concat(); - let fx = DenseMultilinearExtension::from_evaluations_vec(num_vars + 1, eval); - let numerator = DenseMultilinearExtension::from_evaluations_vec(num_vars, numerator_eval); - let denominator = - DenseMultilinearExtension::from_evaluations_vec(num_vars, denominator_eval); + let fx = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars + 1, + eval, + )); + let numerator = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + numerator_eval, + )); + let denominator = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + denominator_eval, + )); end_timer!(start); Ok([fx, numerator, denominator]) @@ -352,7 +360,7 @@ impl PermutationCheck for PolyIOP { /// and gamma /// Cost: O(N) fn prove( - prod_x_and_aux_info: &[DenseMultilinearExtension; 3], + prod_x_and_aux_info: &[Rc>; 3], challenge: &Self::PermutationChallenge, transcript: &mut IOPTranscript, ) -> Result { @@ -407,19 +415,19 @@ impl PermutationCheck for PolyIOP { /// /// Cost: O(N) fn prove_internal( - prod_x_and_aux_info: &[DenseMultilinearExtension; 3], + prod_x_and_aux_info: &[Rc>; 3], alpha: &F, transcript: &mut IOPTranscript, ) -> Result<(IOPProof, VirtualPolynomial), PolyIOPErrors> { let start = start_timer!(|| "Permutation check prove"); let prod_partial_evals = build_prod_partial_eval(&prod_x_and_aux_info[0])?; - let prod_0x = Rc::new(prod_partial_evals[0].clone()); - let prod_1x = Rc::new(prod_partial_evals[1].clone()); - let prod_x0 = Rc::new(prod_partial_evals[2].clone()); - let prod_x1 = Rc::new(prod_partial_evals[3].clone()); - let numerator = Rc::new(prod_x_and_aux_info[1].clone()); - let denominator = Rc::new(prod_x_and_aux_info[2].clone()); + let prod_0x = prod_partial_evals[0].clone(); + let prod_1x = prod_partial_evals[1].clone(); + let prod_x0 = prod_partial_evals[2].clone(); + let prod_x1 = prod_partial_evals[3].clone(); + let numerator = prod_x_and_aux_info[1].clone(); + let denominator = prod_x_and_aux_info[2].clone(); // compute (g(x) + beta * s_perm(x) + gamma) * prod(0, x) * alpha // which is prods[6] * prod[1] * alpha @@ -460,7 +468,7 @@ mod test { use ark_ff::{UniformRand, Zero}; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_std::test_rng; - use std::marker::PhantomData; + use std::{marker::PhantomData, rc::Rc}; /// This is a mock function to generate some commitment element for testing. fn mock_commit(_f: &DenseMultilinearExtension) -> G { @@ -469,9 +477,9 @@ mod test { } fn test_permutation_check_helper( - f: &DenseMultilinearExtension, - g: &DenseMultilinearExtension, - s_perm: &DenseMultilinearExtension, + f: &Rc>, + g: &Rc>, + s_perm: &Rc>, ) -> Result<(IOPProof, VirtualPolynomial), PolyIOPErrors> { let mut transcript = as PermutationCheck>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; @@ -505,7 +513,7 @@ mod test { { // good path: w is a permutation of w itself under the identify map - let w = DenseMultilinearExtension::rand(nv, &mut rng); + let w = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); // s_perm is the identity map let s_perm = identity_permutation_mle(nv); @@ -534,7 +542,7 @@ mod test { { // bad path 1: w is a not permutation of w itself under a random map - let w = DenseMultilinearExtension::rand(nv, &mut rng); + let w = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); // s_perm is a random map let s_perm = random_permutation_mle(nv, &mut rng); @@ -572,8 +580,8 @@ mod test { { // bad path 2: f is a not permutation of g under a identity map - let f = DenseMultilinearExtension::rand(nv, &mut rng); - let g = DenseMultilinearExtension::rand(nv, &mut rng); + let f = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); + let g = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); // s_perm is the identity map let s_perm = identity_permutation_mle(nv); @@ -632,8 +640,8 @@ mod test { let mut rng = test_rng(); for num_vars in 2..6 { - let f = DenseMultilinearExtension::rand(num_vars, &mut rng); - let g = DenseMultilinearExtension::rand(num_vars, &mut rng); + let f = Rc::new(DenseMultilinearExtension::rand(num_vars, &mut rng)); + let g = Rc::new(DenseMultilinearExtension::rand(num_vars, &mut rng)); let s_id = identity_permutation_mle::(num_vars); let s_perm = random_permutation_mle(num_vars, &mut rng); diff --git a/poly-iop/src/perm_check/util.rs b/poly-iop/src/perm_check/util.rs index d9265c82..7dbb8d7a 100644 --- a/poly-iop/src/perm_check/util.rs +++ b/poly-iop/src/perm_check/util.rs @@ -1,5 +1,7 @@ //! This module implements useful functions for the permutation check protocol. +use std::rc::Rc; + use crate::PolyIOPErrors; use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; @@ -42,7 +44,6 @@ pub(super) fn compute_prod_0( let mut numerator_evals = vec![]; let mut denominator_evals = vec![]; - // TODO: remove this line after replacing `s_perm` with `s`. let s_id = identity_permutation_mle::(num_vars); for (&fi, (&gi, (&s_id_i, &s_perm_i))) in @@ -61,16 +62,20 @@ pub(super) fn compute_prod_0( } /// An MLE that represent an identity permutation: `f(index) \mapto index` -pub fn identity_permutation_mle(num_vars: usize) -> DenseMultilinearExtension { +pub fn identity_permutation_mle( + num_vars: usize, +) -> Rc> { let s_id_vec = (0..1u64 << num_vars).map(F::from).collect(); - DenseMultilinearExtension::from_evaluations_vec(num_vars, s_id_vec) + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, s_id_vec, + )) } /// An MLE that represent a random permutation pub fn random_permutation_mle( num_vars: usize, rng: &mut R, -) -> DenseMultilinearExtension { +) -> Rc> { let len = 1u64 << num_vars; let mut s_id_vec: Vec = (0..len).map(F::from).collect(); let mut s_perm_vec = vec![]; @@ -78,7 +83,9 @@ pub fn random_permutation_mle( let index = rng.next_u64() as usize % s_id_vec.len(); s_perm_vec.push(s_id_vec.remove(index)); } - DenseMultilinearExtension::from_evaluations_vec(num_vars, s_perm_vec) + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, s_perm_vec, + )) } /// Helper function of the IOP. @@ -91,22 +98,24 @@ pub fn random_permutation_mle( /// - prod(1, x) /// - prod(x, 0) /// - prod(x, 1) -pub(super) fn build_prod_partial_eval( - prod_x: &DenseMultilinearExtension, -) -> Result<[DenseMultilinearExtension; 4], PolyIOPErrors> { +pub fn build_prod_partial_eval( + prod_x: &Rc>, +) -> Result<[Rc>; 4], PolyIOPErrors> { let start = start_timer!(|| "build prod polynomial"); let prod_x_eval = &prod_x.evaluations; let num_vars = prod_x.num_vars - 1; // prod(0, x) - let prod_0_x = - DenseMultilinearExtension::from_evaluations_slice(num_vars, &prod_x_eval[0..1 << num_vars]); + let prod_0_x = Rc::new(DenseMultilinearExtension::from_evaluations_slice( + num_vars, + &prod_x_eval[0..1 << num_vars], + )); // prod(1, x) - let prod_1_x = DenseMultilinearExtension::from_evaluations_slice( + let prod_1_x = Rc::new(DenseMultilinearExtension::from_evaluations_slice( num_vars, &prod_x_eval[1 << num_vars..1 << (num_vars + 1)], - ); + )); // =================================== // prod(x, 0) and prod(x, 1) @@ -124,8 +133,12 @@ pub(super) fn build_prod_partial_eval( eval_x1.push(prod_x); } } - let prod_x_0 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x0); - let prod_x_1 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x1); + let prod_x_0 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, eval_x0, + )); + let prod_x_1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, eval_x1, + )); end_timer!(start); diff --git a/poly-iop/src/prelude.rs b/poly-iop/src/prelude.rs index 0d13d05b..cb836589 100644 --- a/poly-iop/src/prelude.rs +++ b/poly-iop/src/prelude.rs @@ -1,9 +1,10 @@ pub use crate::{ errors::PolyIOPErrors, perm_check::{ - util::{identity_permutation_mle, random_permutation_mle}, + util::{build_prod_partial_eval, identity_permutation_mle, random_permutation_mle}, PermutationCheck, }, + structs::IOPProof, sum_check::SumCheck, utils::*, zero_check::ZeroCheck, diff --git a/poly-iop/src/sum_check/mod.rs b/poly-iop/src/sum_check/mod.rs index 1c43838d..d3aa731a 100644 --- a/poly-iop/src/sum_check/mod.rs +++ b/poly-iop/src/sum_check/mod.rs @@ -1,5 +1,7 @@ //! This module implements the sum check protocol. +use std::rc::Rc; + use crate::{ errors::PolyIOPErrors, structs::{IOPProof, IOPProverState, IOPVerifierState}, @@ -16,6 +18,7 @@ mod verifier; /// Trait for doing sum check protocols. pub trait SumCheck { + type MLE; type VirtualPolynomial; type VPAuxInfo; type MultilinearExtension; @@ -124,6 +127,7 @@ pub struct SumCheckSubClaim { impl SumCheck for PolyIOP { type SumCheckProof = IOPProof; + type MLE = Rc>; type VirtualPolynomial = VirtualPolynomial; type VPAuxInfo = VPAuxInfo; type MultilinearExtension = DenseMultilinearExtension;