diff --git a/arithmetic/src/lib.rs b/arithmetic/src/lib.rs index 69109087..2fc2b5f6 100644 --- a/arithmetic/src/lib.rs +++ b/arithmetic/src/lib.rs @@ -7,8 +7,8 @@ mod virtual_polynomial; pub use errors::ArithErrors; pub use multilinear_polynomial::{ evaluate_no_par, evaluate_opt, fix_last_variables, fix_last_variables_no_par, fix_variables, - identity_permutation_mle, merge_polynomials, random_mle_list, random_permutation_mle, - random_zero_mle_list, DenseMultilinearExtension, + identity_permutation, identity_permutation_mles, merge_polynomials, random_mle_list, + random_permutation, random_permutation_mles, random_zero_mle_list, DenseMultilinearExtension, }; pub use univariate_polynomial::{build_l, get_uni_domain}; pub use util::{bit_decompose, gen_eval_point, get_batched_nv, get_index}; diff --git a/arithmetic/src/multilinear_polynomial.rs b/arithmetic/src/multilinear_polynomial.rs index a0738192..3b7a4be2 100644 --- a/arithmetic/src/multilinear_polynomial.rs +++ b/arithmetic/src/multilinear_polynomial.rs @@ -72,31 +72,58 @@ pub fn random_zero_mle_list( list } -/// An MLE that represent an identity permutation: `f(index) \mapto index` -pub fn identity_permutation_mle( +pub fn identity_permutation(num_vars: usize, num_chunks: usize) -> Vec { + let len = (num_chunks as u64) * (1u64 << num_vars); + (0..len).map(F::from).collect() +} + +/// A list of MLEs that represents an identity permutation +pub fn identity_permutation_mles( num_vars: usize, -) -> Rc> { - let s_id_vec = (0..1u64 << num_vars).map(F::from).collect(); - Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, s_id_vec, - )) + num_chunks: usize, +) -> Vec>> { + let mut res = vec![]; + for i in 0..num_chunks { + let shift = (i * (1 << num_vars)) as u64; + let s_id_vec = (shift..shift + (1u64 << num_vars)).map(F::from).collect(); + res.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, s_id_vec, + ))); + } + res } -/// An MLE that represent a random permutation -pub fn random_permutation_mle( +pub fn random_permutation( num_vars: usize, + num_chunks: usize, rng: &mut R, -) -> Rc> { - let len = 1u64 << num_vars; +) -> Vec { + let len = (num_chunks as u64) * (1u64 << num_vars); let mut s_id_vec: Vec = (0..len).map(F::from).collect(); let mut s_perm_vec = vec![]; for _ in 0..len { let index = rng.next_u64() as usize % s_id_vec.len(); s_perm_vec.push(s_id_vec.remove(index)); } - Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, s_perm_vec, - )) + s_perm_vec +} + +/// A list of MLEs that represent a random permutation +pub fn random_permutation_mles( + num_vars: usize, + num_chunks: usize, + rng: &mut R, +) -> Vec>> { + let s_perm_vec = random_permutation(num_vars, num_chunks, rng); + let mut res = vec![]; + let n = 1 << num_vars; + for i in 0..num_chunks { + res.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + s_perm_vec[i * n..i * n + n].to_vec(), + ))); + } + res } pub fn evaluate_opt(poly: &DenseMultilinearExtension, point: &[F]) -> F { diff --git a/hyperplonk/src/mock.rs b/hyperplonk/src/mock.rs index 0833ff26..6af98a2c 100644 --- a/hyperplonk/src/mock.rs +++ b/hyperplonk/src/mock.rs @@ -1,6 +1,5 @@ -use arithmetic::identity_permutation_mle; +use arithmetic::identity_permutation; use ark_ff::PrimeField; -use ark_poly::MultilinearExtension; use ark_std::{log2, test_rng}; use crate::{ @@ -93,7 +92,7 @@ impl MockCircuit { gate_func: gate.clone(), }; - let permutation = identity_permutation_mle(merged_nv as usize).to_evaluations(); + let permutation = identity_permutation(merged_nv as usize, 1); let index = HyperPlonkIndex { params, permutation, diff --git a/hyperplonk/src/snark.rs b/hyperplonk/src/snark.rs index 7f46a1c6..4318d999 100644 --- a/hyperplonk/src/snark.rs +++ b/hyperplonk/src/snark.rs @@ -1,11 +1,11 @@ use crate::{ errors::HyperPlonkErrors, structs::{HyperPlonkIndex, HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkVerifyingKey}, - utils::{build_f, eval_f, prover_sanity_check, PcsAccumulator}, + utils::{build_f, eval_f, eval_perm_gate, prover_sanity_check, PcsAccumulator}, witness::WitnessColumn, HyperPlonkSNARK, }; -use arithmetic::{evaluate_opt, identity_permutation_mle, merge_polynomials, VPAuxInfo}; +use arithmetic::{evaluate_opt, identity_permutation_mles, VPAuxInfo}; use ark_ec::PairingEngine; use ark_poly::DenseMultilinearExtension; use ark_std::{end_timer, log2, start_timer, One, Zero}; @@ -45,25 +45,32 @@ where pcs_srs: &PCS::SRS, ) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors> { let num_vars = index.num_variables(); - - let log_num_witness_polys = log2(index.num_witness_columns()) as usize; - let witness_merged_nv = num_vars + log_num_witness_polys; - - let log_chunk_size = log_num_witness_polys + 1; - let prod_x_nv = num_vars + log_chunk_size; - - let supported_ml_degree = prod_x_nv; + let supported_ml_degree = num_vars; // extract PCS prover and verifier keys from SRS let (pcs_prover_param, pcs_verifier_param) = PCS::trim(pcs_srs, None, Some(supported_ml_degree))?; + // build identity oracles + let id_oracles = identity_permutation_mles(num_vars, index.num_witness_columns()); + let mut id_comms = vec![]; + for id_oracle in id_oracles.iter() { + id_comms.push(PCS::commit(&pcs_prover_param, id_oracle)?); + } + // build permutation oracles - let permutation_oracle = Rc::new(DenseMultilinearExtension::from_evaluations_slice( - witness_merged_nv, - &index.permutation, - )); - let perm_com = PCS::commit(&pcs_prover_param, &permutation_oracle)?; + let mut permutation_oracles = vec![]; + let mut perm_comms = vec![]; + let chunk_size = 1 << num_vars; + for i in 0..index.num_witness_columns() { + let perm_oracle = Rc::new(DenseMultilinearExtension::from_evaluations_slice( + num_vars, + &index.permutation[i * chunk_size..(i + 1) * chunk_size], + )); + let perm_comm = PCS::commit(&pcs_prover_param, &perm_oracle)?; + permutation_oracles.push(perm_oracle); + perm_comms.push(perm_comm); + } // build selector oracles and commit to it let selector_oracles: Vec>> = index @@ -77,23 +84,23 @@ where .map(|poly| PCS::commit(&pcs_prover_param, poly)) .collect::, _>>()?; - // let selector_merged = merge_polynomials(&selector_oracles)?; - // let selector_com = PCS::commit(&pcs_prover_param, &selector_merged)?; - Ok(( Self::ProvingKey { params: index.params.clone(), - permutation_oracle: permutation_oracle.clone(), + id_oracles, + permutation_oracles, selector_oracles, selector_commitments: selector_commitments.clone(), + permutation_commitments: perm_comms.clone(), + id_commitments: id_comms.clone(), pcs_param: pcs_prover_param, }, Self::VerifyingKey { params: index.params.clone(), - permutation_oracle, pcs_param: pcs_verifier_param, selector_commitments, - perm_com, + perm_commitments: perm_comms, + id_commitments: id_comms, }, )) } @@ -157,30 +164,13 @@ where // witness assignment of length 2^n let num_vars = pk.params.num_variables(); - let log_num_witness_polys = log2(pk.params.num_witness_columns()) as usize; - let merged_nv = num_vars + log_num_witness_polys; - - // number of nv in prod(x) which is supposed to be the cap - // so each chunk we we store maximum 1 << (prod_x_nv - num_var) selectors - let log_chunk_size = log_num_witness_polys + 1; - let prod_x_nv = num_vars + log_chunk_size; // online public input of length 2^\ell let ell = log2(pk.params.num_pub_input) as usize; // We use accumulators to store the polynomials and their eval points. // They are batch opened at a later stage. - // This includes - // - witnesses - // - prod(x) - // - selectors - // - // Accumulator's nv is bounded by prod(x) that means - // we need to split the selectors into multiple chunks if - // #selectors > chunk_size - // let mut pcs_acc = PcsAccumulator::::new(prod_x_nv); - let mut prod_x_pcs_acc = PcsAccumulator::::new(prod_x_nv); - let mut witness_and_selector_x_pcs_acc = PcsAccumulator::::new(num_vars); + let mut pcs_acc = PcsAccumulator::::new(num_vars); // ======================================================================= // 1. Commit Witness polynomials `w_i(x)` and append commitment to @@ -197,21 +187,10 @@ where .iter() .map(|x| PCS::commit(&pk.pcs_param, x).unwrap()) .collect::>(); - - // merge all witness into a single MLE - we will run perm check on it - // to obtain prod(x) - let w_merged = merge_polynomials(&witness_polys)?; - if w_merged.num_vars != merged_nv { - return Err(HyperPlonkErrors::InvalidParameters(format!( - "merged witness poly has a different num_vars ({}) from expected ({})", - w_merged.num_vars, merged_nv - ))); + for w_com in witness_commits.iter() { + transcript.append_serializable_element(b"w", w_com)?; } - // TODO: we'll remove one of witness_merged_commit and witness_commits later. - let witness_merged_commit = PCS::commit(&pk.pcs_param, &w_merged)?; - transcript.append_serializable_element(b"w", &witness_merged_commit)?; - end_timer!(step); // ======================================================================= // 2 Run ZeroCheck on @@ -242,11 +221,11 @@ where // ======================================================================= let step = start_timer!(|| "Permutation check on w_i(x)"); - let (perm_check_proof, prod_x) = >::prove( + let (perm_check_proof, prod_x, frac_poly) = >::prove( &pk.pcs_param, - &w_merged, - &w_merged, - &pk.permutation_oracle, + &witness_polys, + &witness_polys, + &pk.permutation_oracles, &mut transcript, )?; let perm_check_point = &perm_check_proof.zero_check_proof.point; @@ -254,17 +233,24 @@ where end_timer!(step); // ======================================================================= // 4. Generate evaluations and corresponding proofs - // - 4.1. (deferred) batch opening prod(x) at - // - [0, perm_check_point] - // - [1, perm_check_point] - // - [perm_check_point, 0] - // - [perm_check_point, 1] + // - permcheck + // 1. (deferred) batch opening prod(x) at + // - [perm_check_point] + // - [perm_check_point[2..n], 0] + // - [perm_check_point[2..n], 1] // - [1,...1, 0] + // 2. (deferred) batch opening frac(x) at + // - [perm_check_point] + // - [perm_check_point[2..n], 0] + // - [perm_check_point[2..n], 1] + // 3. (deferred) batch opening s_id(x) at + // - [perm_check_point] + // 4. (deferred) batch opening perms(x) at + // - [perm_check_point] + // 5. (deferred) batch opening witness_i(x) at + // - [perm_check_point] // - // - 4.2. permutation check evaluations and proofs - // - 4.2.1. (deferred) wi_poly(perm_check_point) - // - // - 4.3. zero check evaluations and proofs + // - zero check evaluations and proofs // - 4.3.1. (deferred) wi_poly(zero_check_point) // - 4.3.2. (deferred) selector_poly(zero_check_point) // @@ -273,38 +259,58 @@ where // ======================================================================= let step = start_timer!(|| "opening and evaluations"); - // 4.1 (deferred) open prod(0,x), prod(1, x), prod(x, 0), prod(x, 1) - // perm_check_point - // prod(0, x) - let tmp_point1 = [perm_check_point.as_slice(), &[E::Fr::zero()]].concat(); - // prod(1, x) - let tmp_point2 = [perm_check_point.as_slice(), &[E::Fr::one()]].concat(); - // prod(x, 0) - let tmp_point3 = [&[E::Fr::zero()], perm_check_point.as_slice()].concat(); - // prod(x, 1) - let tmp_point4 = [&[E::Fr::one()], perm_check_point.as_slice()].concat(); - // // prod(1, ..., 1, 0) - // let tmp_point5 = [vec![E::Fr::zero()], vec![E::Fr::one(); - // merged_nv]].concat(); - - prod_x_pcs_acc.insert_poly_and_points(&prod_x, &perm_check_proof.prod_x_comm, &tmp_point1); - prod_x_pcs_acc.insert_poly_and_points(&prod_x, &perm_check_proof.prod_x_comm, &tmp_point2); - prod_x_pcs_acc.insert_poly_and_points(&prod_x, &perm_check_proof.prod_x_comm, &tmp_point3); - prod_x_pcs_acc.insert_poly_and_points(&prod_x, &perm_check_proof.prod_x_comm, &tmp_point4); - - // 4.2 permutation check - // - 4.2.1. wi_poly(perm_check_point) - let (perm_check_opening, perm_check_eval) = - PCS::open(&pk.pcs_param, &w_merged, perm_check_point)?; - - // - 4.3. zero check evaluations and proofs - // - 4.3.1 (deferred) wi_poly(zero_check_point) - for i in 0..witness_polys.len() { - witness_and_selector_x_pcs_acc.insert_poly_and_points( - &witness_polys[i], - &witness_commits[i], - &zero_check_proof.point, - ); + // (perm_check_point[2..n], 0) + let perm_check_point_0 = [&[E::Fr::zero()], &perm_check_point[0..num_vars - 1]].concat(); + // (perm_check_point[2..n], 1) + let perm_check_point_1 = [&[E::Fr::one()], &perm_check_point[0..num_vars - 1]].concat(); + // (1, ..., 1, 0) + let prod_final_query_point = + [vec![E::Fr::zero()], vec![E::Fr::one(); num_vars - 1]].concat(); + + // prod(x)'s points + pcs_acc.insert_poly_and_points(&prod_x, &perm_check_proof.prod_x_comm, perm_check_point); + pcs_acc.insert_poly_and_points(&prod_x, &perm_check_proof.prod_x_comm, &perm_check_point_0); + pcs_acc.insert_poly_and_points(&prod_x, &perm_check_proof.prod_x_comm, &perm_check_point_1); + pcs_acc.insert_poly_and_points( + &prod_x, + &perm_check_proof.prod_x_comm, + &prod_final_query_point, + ); + + // frac(x)'s points + pcs_acc.insert_poly_and_points(&frac_poly, &perm_check_proof.frac_comm, perm_check_point); + pcs_acc.insert_poly_and_points( + &frac_poly, + &perm_check_proof.frac_comm, + &perm_check_point_0, + ); + pcs_acc.insert_poly_and_points( + &frac_poly, + &perm_check_proof.frac_comm, + &perm_check_point_1, + ); + + // s_id(x)'s points + for (s_id, s_com) in pk.id_oracles.iter().zip(pk.id_commitments.iter()) { + pcs_acc.insert_poly_and_points(s_id, s_com, perm_check_point); + } + + // perms(x)'s points + for (perm, pcom) in pk + .permutation_oracles + .iter() + .zip(pk.permutation_commitments.iter()) + { + pcs_acc.insert_poly_and_points(perm, pcom, perm_check_point); + } + + // witnesses' points + // TODO: refactor so it remains correct even if the order changed + for (wpoly, wcom) in witness_polys.iter().zip(witness_commits.iter()) { + pcs_acc.insert_poly_and_points(wpoly, wcom, perm_check_point); + } + for (wpoly, wcom) in witness_polys.iter().zip(witness_commits.iter()) { + pcs_acc.insert_poly_and_points(wpoly, wcom, &zero_check_proof.point); } // - 4.3.2. (deferred) selector_poly(zero_check_point) @@ -312,48 +318,30 @@ where .iter() .zip(pk.selector_commitments.iter()) .for_each(|(poly, com)| { - witness_and_selector_x_pcs_acc.insert_poly_and_points( - poly, - com, - &zero_check_proof.point, - ) + pcs_acc.insert_poly_and_points(poly, com, &zero_check_proof.point) }); // - 4.4. public input consistency checks // - pi_poly(r_pi) where r_pi is sampled from transcript let r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?; let tmp_point = [vec![E::Fr::zero(); num_vars - ell], r_pi].concat(); - witness_and_selector_x_pcs_acc.insert_poly_and_points( - &witness_polys[0], - &witness_commits[0], - &tmp_point, - ); + pcs_acc.insert_poly_and_points(&witness_polys[0], &witness_commits[0], &tmp_point); end_timer!(step); // ======================================================================= // 5. deferred batch opening // ======================================================================= let step = start_timer!(|| "deferred batch openings prod(x)"); - let batch_prod_x_openings = prod_x_pcs_acc.multi_open(&pk.pcs_param, &mut transcript)?; - end_timer!(step); - - let step = start_timer!(|| "deferred batch openings witness and selectors"); - let batch_witness_and_selector_openings = - witness_and_selector_x_pcs_acc.multi_open(&pk.pcs_param, &mut transcript)?; + let batch_openings = pcs_acc.multi_open(&pk.pcs_param, &mut transcript)?; end_timer!(step); end_timer!(start); Ok(HyperPlonkProof { // PCS commit for witnesses - witness_merged_commit, witness_commits, // batch_openings, - batch_prod_x_openings, - batch_witness_and_selector_openings, - // perm check openings - perm_check_opening, - perm_check_eval, + batch_openings, // ======================================================================= // IOP proofs // ======================================================================= @@ -384,7 +372,7 @@ where /// ``` /// in vanilla plonk, and obtain a ZeroCheckSubClaim /// - /// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracle` + /// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracles` /// /// 3. check subclaim validity /// @@ -403,37 +391,11 @@ where let num_selectors = vk.params.num_selector_columns(); let num_witnesses = vk.params.num_witness_columns(); - - // witness assignment of length 2^n - let log_num_witness_polys = log2(num_witnesses) as usize; let num_vars = vk.params.num_variables(); - // number of variables in merged polynomial for Multilinear-KZG - let merged_nv = num_vars + log_num_witness_polys; // online public input of length 2^\ell let ell = log2(vk.params.num_pub_input) as usize; - // sequence: - // - prod(x) at 5 points - // - w_merged at perm check point - // - w_merged at zero check points (#witness points) - // - selector_merged at zero check points (#selector points) - // - w[0] at r_pi - let selector_evals = &proof - .batch_witness_and_selector_openings - .f_i_eval_at_point_i[num_witnesses..num_witnesses + num_selectors]; - let witness_evals = &proof - .batch_witness_and_selector_openings - .f_i_eval_at_point_i[..num_witnesses]; - let prod_evals = &proof.batch_prod_x_openings.f_i_eval_at_point_i[0..4]; - let pi_eval = proof - .batch_witness_and_selector_openings - .f_i_eval_at_point_i - .last() - .unwrap(); - - let pi_poly = DenseMultilinearExtension::from_evaluations_slice(ell as usize, pub_input); - // ======================================================================= // 0. sanity checks // ======================================================================= @@ -446,6 +408,20 @@ where ))); } + // Extract evaluations from openings + let prod_evals = &proof.batch_openings.f_i_eval_at_point_i[0..4]; + let frac_evals = &proof.batch_openings.f_i_eval_at_point_i[4..7]; + let id_evals = &proof.batch_openings.f_i_eval_at_point_i[7..7 + num_witnesses]; + let perm_evals = + &proof.batch_openings.f_i_eval_at_point_i[7 + num_witnesses..7 + 2 * num_witnesses]; + let witness_perm_evals = + &proof.batch_openings.f_i_eval_at_point_i[7 + 2 * num_witnesses..7 + 3 * num_witnesses]; + let witness_gate_evals = + &proof.batch_openings.f_i_eval_at_point_i[7 + 3 * num_witnesses..7 + 4 * num_witnesses]; + let selector_evals = &proof.batch_openings.f_i_eval_at_point_i + [7 + 4 * num_witnesses..7 + 4 * num_witnesses + num_selectors]; + let pi_eval = proof.batch_openings.f_i_eval_at_point_i.last().unwrap(); + // ======================================================================= // 1. Verify zero_check_proof on // `f(q_0(x),...q_l(x), w_0(x),...w_d(x))` @@ -464,7 +440,9 @@ where phantom: PhantomData::default(), }; // push witness to transcript - transcript.append_serializable_element(b"w", &proof.witness_merged_commit)?; + for w_com in proof.witness_commits.iter() { + transcript.append_serializable_element(b"w", w_com)?; + } let zero_check_sub_claim = >::verify( &proof.zero_check_proof, @@ -472,10 +450,10 @@ where &mut transcript, )?; - let zero_check_point = &zero_check_sub_claim.point; + let zero_check_point = zero_check_sub_claim.point.clone(); // check zero check subclaim - let f_eval = eval_f(&vk.params.gate_func, selector_evals, witness_evals)?; + let f_eval = eval_f(&vk.params.gate_func, selector_evals, witness_gate_evals)?; if f_eval != zero_check_sub_claim.expected_evaluation { return Err(HyperPlonkErrors::InvalidProof( "zero check evaluation failed".to_string(), @@ -490,10 +468,9 @@ where // Zero check and perm check have different AuxInfo 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, + // Prod(x) has a max degree of witnesses.len() + 1 + max_degree: proof.witness_commits.len() + 1, + num_variables: num_vars, phantom: PhantomData::default(), }; let perm_check_sub_claim = >::verify( @@ -502,40 +479,28 @@ where &mut transcript, )?; - let perm_check_point = &perm_check_sub_claim + let perm_check_point = perm_check_sub_claim .product_check_sub_claim .zero_check_sub_claim - .point; + .point + .clone(); let alpha = perm_check_sub_claim.product_check_sub_claim.alpha; let (beta, gamma) = perm_check_sub_claim.challenges; - // 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 - - // we evaluate MLE directly instead of using s_id/s_perm PCS verify - // Verification takes n pairings while evaluate takes 2^n field ops. - let s_id = identity_permutation_mle::(perm_check_point.len()); - let s_id_eval = evaluate_opt(&s_id, perm_check_point); - let s_perm_eval = evaluate_opt(&vk.permutation_oracle, perm_check_point); - - let q_x_rec = prod_evals[1] - prod_evals[2] * prod_evals[3] - + alpha - * ((prod_evals[0] + beta * s_perm_eval + gamma) * prod_evals[0] - - (prod_evals[0] + beta * s_id_eval + gamma)); - - if q_x_rec + // check evaluation subclaim + let perm_gate_eval = eval_perm_gate( + prod_evals, + frac_evals, + witness_perm_evals, + id_evals, + perm_evals, + alpha, + beta, + gamma, + *perm_check_point.last().unwrap(), + )?; + if perm_gate_eval != perm_check_sub_claim .product_check_sub_claim .zero_check_sub_claim @@ -552,73 +517,91 @@ where // ======================================================================= let step = start_timer!(|| "verify commitments"); - // ======================================================================= - // 3.1 open prod(x)' evaluations - // ======================================================================= - // TODO: Check prod(x) at (1,...,1,0) - let _prod_final_query = perm_check_sub_claim.product_check_sub_claim.final_query; - let prod_points = [ - [perm_check_point.as_slice(), &[E::Fr::zero()]].concat(), - [perm_check_point.as_slice(), &[E::Fr::one()]].concat(), - [&[E::Fr::zero()], perm_check_point.as_slice()].concat(), - [&[E::Fr::one()], perm_check_point.as_slice()].concat(), - // prod_final_query.0, - ]; - - let mut r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?; - - let res = PCS::batch_verify( - &vk.pcs_param, - [proof.perm_check_proof.prod_x_comm; 4].as_ref(), - prod_points.as_ref(), - &proof.batch_prod_x_openings, - &mut transcript, - )?; - assert!(res); + // generate evaluation points and commitments + let mut comms = vec![]; + let mut points = vec![]; + + let perm_check_point_0 = [&[E::Fr::zero()], &perm_check_point[0..num_vars - 1]].concat(); + let perm_check_point_1 = [&[E::Fr::one()], &perm_check_point[0..num_vars - 1]].concat(); + let prod_final_query_point = + [vec![E::Fr::zero()], vec![E::Fr::one(); num_vars - 1]].concat(); + + // prod(x)'s points + comms.push(proof.perm_check_proof.prod_x_comm); + comms.push(proof.perm_check_proof.prod_x_comm); + comms.push(proof.perm_check_proof.prod_x_comm); + comms.push(proof.perm_check_proof.prod_x_comm); + points.push(perm_check_point.clone()); + points.push(perm_check_point_0.clone()); + points.push(perm_check_point_1.clone()); + points.push(prod_final_query_point); + + // frac(x)'s points + comms.push(proof.perm_check_proof.frac_comm); + comms.push(proof.perm_check_proof.frac_comm); + comms.push(proof.perm_check_proof.frac_comm); + points.push(perm_check_point.clone()); + points.push(perm_check_point_0); + points.push(perm_check_point_1); + + // s_id's points + for &id_com in vk.id_commitments.iter() { + comms.push(id_com); + points.push(perm_check_point.clone()); + } - // ======================================================================= - // 3.3 open witnesses' and selectors evaluations - // ======================================================================= + // perms' points + for &pcom in vk.perm_commitments.iter() { + comms.push(pcom); + points.push(perm_check_point.clone()); + } - let res = PCS::verify( - &vk.pcs_param, - &proof.witness_merged_commit, - perm_check_point, - &proof.perm_check_eval, - &proof.perm_check_opening, - )?; - assert!(res); + // witnesses' points + for &wcom in proof.witness_commits.iter() { + comms.push(wcom); + points.push(perm_check_point.clone()); + } + for &wcom in proof.witness_commits.iter() { + comms.push(wcom); + points.push(zero_check_point.clone()); + } - let pi_eval_rec = evaluate_opt(&pi_poly, &r_pi); - assert_eq!(&pi_eval_rec, pi_eval); + // selector_poly(zero_check_point) + for &com in vk.selector_commitments.iter() { + comms.push(com); + points.push(zero_check_point.clone()); + } - r_pi = [vec![E::Fr::zero(); num_vars - ell], r_pi].concat(); - let commitments = [ - proof.witness_commits.as_slice(), - vk.selector_commitments.as_slice(), - &[proof.witness_commits[0]], - ] - .concat(); + // - 4.4. public input consistency checks + // - pi_poly(r_pi) where r_pi is sampled from transcript + let r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?; + let tmp_point = [vec![E::Fr::zero(); num_vars - ell], r_pi].concat(); + // check public evaluation + let pi_poly = DenseMultilinearExtension::from_evaluations_slice(ell as usize, pub_input); + let expect_pi_eval = evaluate_opt(&pi_poly, &tmp_point[..]); + if expect_pi_eval != *pi_eval { + return Err(HyperPlonkErrors::InvalidProver(format!( + "Public input eval mismatch: got {}, expect {}", + pi_eval, expect_pi_eval, + ))); + } + comms.push(proof.witness_commits[0]); + points.push(tmp_point); - let points = [ - vec![zero_check_point.clone(); num_witnesses + num_selectors].as_slice(), - &[r_pi], - ] - .concat(); + assert_eq!(comms.len(), proof.batch_openings.f_i_eval_at_point_i.len()); + // check proof let res = PCS::batch_verify( &vk.pcs_param, - commitments.as_ref(), - points.as_ref(), - &proof.batch_witness_and_selector_openings, + &comms, + &points, + &proof.batch_openings, &mut transcript, )?; - assert!(res); - end_timer!(step); end_timer!(start); - Ok(true) + Ok(res) } } @@ -629,7 +612,7 @@ mod tests { custom_gate::CustomizedGates, selectors::SelectorColumn, structs::HyperPlonkParams, witness::WitnessColumn, }; - use arithmetic::random_permutation_mle; + use arithmetic::{identity_permutation, random_permutation}; use ark_bls12_381::Bls12_381; use ark_std::test_rng; use subroutines::pcs::prelude::MultilinearKzgPCS; @@ -664,7 +647,7 @@ mod tests { let num_constraints = 4; let num_pub_input = 4; let nv = log2(num_constraints) as usize; - let merged_nv = nv + log2(gate_func.num_witness_columns()) as usize; + let num_witnesses = 2; // generate index let params = HyperPlonkParams { @@ -672,7 +655,7 @@ mod tests { num_pub_input, gate_func, }; - let permutation = identity_permutation_mle(merged_nv).evaluations.clone(); + let permutation = identity_permutation(nv, num_witnesses); let q1 = SelectorColumn(vec![E::Fr::one(), E::Fr::one(), E::Fr::one(), E::Fr::one()]); let index = HyperPlonkIndex { params, @@ -714,20 +697,18 @@ mod tests { )?; // bad path 1: wrong permutation - let rand_perm: Vec = random_permutation_mle(merged_nv, &mut rng) - .evaluations - .clone(); + let rand_perm: Vec = random_permutation(nv, num_witnesses, &mut rng); let mut bad_index = index.clone(); bad_index.permutation = rand_perm; // generate pk and vks let (_, bad_vk) = as HyperPlonkSNARK>>::preprocess( &bad_index, &pcs_srs, )?; - assert!( + assert_eq!( as HyperPlonkSNARK>>::verify( &bad_vk, &pi.0, &proof, - ) - .is_err() + )?, + false ); // bad path 2: wrong witness diff --git a/hyperplonk/src/structs.rs b/hyperplonk/src/structs.rs index 8fa10ce2..c381a162 100644 --- a/hyperplonk/src/structs.rs +++ b/hyperplonk/src/structs.rs @@ -12,7 +12,7 @@ use subroutines::{ }; /// The proof for the HyperPlonk PolyIOP, consists of the following: -/// - a batch commitment to all the witness MLEs +/// - the commitments to all witness MLEs /// - a batch opening to all the MLEs at certain index /// - the zero-check proof for checking custom gate-satisfiability /// - the permutation-check proof for checking the copy constraints @@ -24,12 +24,8 @@ where PCS: PolynomialCommitmentScheme, { // PCS commit for witnesses - pub witness_merged_commit: PCS::Commitment, pub witness_commits: Vec, - pub batch_prod_x_openings: PCS::BatchProof, - pub batch_witness_and_selector_openings: PCS::BatchProof, - pub perm_check_opening: PCS::Proof, - pub perm_check_eval: PCS::Evaluation, + pub batch_openings: PCS::BatchProof, // ======================================================================= // IOP proofs // ======================================================================= @@ -103,18 +99,24 @@ impl HyperPlonkIndex { /// The HyperPlonk proving key, consists of the following: /// - the hyperplonk instance parameters /// - the preprocessed polynomials output by the indexer -/// - the commitment to the selectors +/// - the commitment to the selectors and permutations /// - the parameters for polynomial commitment #[derive(Clone, Debug, Default, PartialEq)] pub struct HyperPlonkProvingKey> { /// Hyperplonk instance parameters pub params: HyperPlonkParams, /// The preprocessed permutation polynomials - pub permutation_oracle: Rc>, + pub permutation_oracles: Vec>>, + /// The preprocessed identity polynomials + pub id_oracles: Vec>>, /// The preprocessed selector polynomials pub selector_oracles: Vec>>, - /// A commitment to the preprocessed selector polynomials + /// Commitments to the preprocessed selector polynomials pub selector_commitments: Vec, + /// Commitments to the preprocessed permutation polynomials + pub permutation_commitments: Vec, + /// Commitments to the preprocessed identity polynomials + pub id_commitments: Vec, /// The parameters for PCS commitment pub pcs_param: PCS::ProverParam, } @@ -127,12 +129,12 @@ pub struct HyperPlonkProvingKey> { /// Hyperplonk instance parameters pub params: HyperPlonkParams, - /// The preprocessed permutation polynomials - pub permutation_oracle: Rc>, /// The parameters for PCS commitment pub pcs_param: PCS::VerifierParam, /// A commitment to the preprocessed selector polynomials pub selector_commitments: Vec, - /// Permutation oracle's commitment - pub perm_com: PCS::Commitment, + /// Permutation oracles' commitments + pub perm_commitments: Vec, + /// Commitments to the preprocessed identity polynomials + pub id_commitments: Vec, } diff --git a/hyperplonk/src/utils.rs b/hyperplonk/src/utils.rs index 2dcdf737..da11f13a 100644 --- a/hyperplonk/src/utils.rs +++ b/hyperplonk/src/utils.rs @@ -240,6 +240,45 @@ pub(crate) fn eval_f( Ok(res) } +// check perm check subclaim: +// proof.witness_perm_check_eval ?= perm_check_sub_claim.expected_eval +// Q(x) := prod(x) - p1(x) * p2(x) +// + alpha * frac(x) * g1(x) * ... * gk(x) +// - alpha * f1(x) * ... * fk(x) +// +// where p1(x) = (1-x1) * frac(x2, ..., xn, 0) +// + x1 * prod(x2, ..., xn, 0), +// and p2(x) = (1-x1) * frac(x2, ..., xn, 1) +// + x1 * prod(x2, ..., xn, 1) +// and gi(x) = (wi(x) + beta * perms_i(x) + gamma) +// and fi(x) = (wi(x) + beta * s_id_i(x) + gamma) +#[allow(clippy::too_many_arguments)] +pub(crate) fn eval_perm_gate( + prod_evals: &[F], + frac_evals: &[F], + witness_perm_evals: &[F], + id_evals: &[F], + perm_evals: &[F], + alpha: F, + beta: F, + gamma: F, + x1: F, +) -> Result { + let p1_eval = frac_evals[1] + x1 * (prod_evals[1] - frac_evals[1]); + let p2_eval = frac_evals[2] + x1 * (prod_evals[2] - frac_evals[2]); + let mut f_prod_eval = F::one(); + for (&w_eval, &id_eval) in witness_perm_evals.iter().zip(id_evals.iter()) { + f_prod_eval *= w_eval + beta * id_eval + gamma; + } + let mut g_prod_eval = F::one(); + for (&w_eval, &p_eval) in witness_perm_evals.iter().zip(perm_evals.iter()) { + g_prod_eval *= w_eval + beta * p_eval + gamma; + } + let res = + prod_evals[0] - p1_eval * p2_eval + alpha * (frac_evals[0] * g_prod_eval - f_prod_eval); + Ok(res) +} + #[cfg(test)] mod test { use super::*; diff --git a/subroutines/benches/iop_bench.rs b/subroutines/benches/iop_bench.rs index 7547aac8..0f70477d 100644 --- a/subroutines/benches/iop_bench.rs +++ b/subroutines/benches/iop_bench.rs @@ -1,4 +1,4 @@ -use arithmetic::{identity_permutation_mle, VPAuxInfo, VirtualPolynomial}; +use arithmetic::{identity_permutation_mles, VPAuxInfo, VirtualPolynomial}; use ark_bls12_381::{Bls12_381, Fr}; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_std::test_rng; @@ -144,32 +144,32 @@ fn bench_permutation_check() -> Result<(), PolyIOPErrors> { 10 }; - let w = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); + let ws = vec![Rc::new(DenseMultilinearExtension::rand(nv, &mut rng))]; - // s_perm is the identity map - let s_perm = identity_permutation_mle(nv); + // identity map + let perms = identity_permutation_mles(nv, 1); - let proof = { - let start = Instant::now(); - let mut transcript = - as PermutationCheck>::init_transcript(); - transcript.append_message(b"testing", b"initializing transcript for testing")?; + let proof = + { + let start = Instant::now(); + let mut transcript = + as PermutationCheck>::init_transcript(); + transcript.append_message(b"testing", b"initializing transcript for testing")?; - let (proof, _q_x) = as PermutationCheck>::prove( - &pcs_param, - &w, - &w, - &s_perm, - &mut transcript, - )?; + let (proof, _q_x, _frac_poly) = as PermutationCheck< + Bls12_381, + KZG, + >>::prove( + &pcs_param, &ws, &ws, &perms, &mut transcript + )?; - println!( - "permutation check proving time for {} variables: {} ns", - nv, - start.elapsed().as_nanos() / repetition as u128 - ); - proof - }; + println!( + "permutation check proving time for {} variables: {} ns", + nv, + start.elapsed().as_nanos() / repetition as u128 + ); + proof + }; { let poly_info = VPAuxInfo { @@ -218,20 +218,21 @@ fn bench_prod_check() -> Result<(), PolyIOPErrors> { let f: DenseMultilinearExtension = DenseMultilinearExtension::rand(nv, &mut rng); let mut g = f.clone(); g.evaluations.reverse(); - let f = Rc::new(f); - let g = Rc::new(g); + let fs = vec![Rc::new(f)]; + let gs = vec![Rc::new(g)]; let proof = { let start = Instant::now(); let mut transcript = as ProductCheck>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; - let (proof, _prod_x) = as ProductCheck>::prove( - &pcs_param, - &f, - &g, - &mut transcript, - )?; + let (proof, _prod_x, _frac_poly) = + as ProductCheck>::prove( + &pcs_param, + &fs, + &gs, + &mut transcript, + )?; println!( "product check proving time for {} variables: {} ns", diff --git a/subroutines/src/poly_iop/perm_check/mod.rs b/subroutines/src/poly_iop/perm_check/mod.rs index abbf1098..a26e0295 100644 --- a/subroutines/src/poly_iop/perm_check/mod.rs +++ b/subroutines/src/poly_iop/perm_check/mod.rs @@ -1,6 +1,6 @@ //! Main module for the Permutation Check protocol -use self::util::computer_num_and_denom; +use self::util::computer_nums_and_denoms; use crate::{ pcs::PolynomialCommitmentScheme, poly_iop::{errors::PolyIOPErrors, prelude::ProductCheck, PolyIOP}, @@ -29,17 +29,17 @@ where pub mod util; -/// A PermutationCheck w.r.t. `(f, g, perm)` -/// proves that g is a permutation of f under -/// permutation `perm` +/// A PermutationCheck w.r.t. `(fs, gs, perms)` +/// proves that (g1, ..., gk) is a permutation of (f1, ..., fk) under +/// permutation `(p1, ..., pk)` /// It is derived from ProductCheck. /// /// A Permutation Check IOP takes the following steps: /// /// Inputs: -/// - f(x) -/// - g(x) -/// - permutation s_perm(x) +/// - fs = (f1, ..., fk) +/// - gs = (g1, ..., gk) +/// - permutation oracles = (p1, ..., pk) pub trait PermutationCheck: ProductCheck where E: PairingEngine, @@ -57,25 +57,34 @@ where fn init_transcript() -> Self::Transcript; /// Inputs: - /// - f(x) - /// - g(x) - /// - permutation s_perm(x) + /// - fs = (f1, ..., fk) + /// - gs = (g1, ..., gk) + /// - permutation oracles = (p1, ..., pk) /// Outputs: - /// - a permutation check proof proving that g is a permutation of f under - /// s_perm - /// - the product polynomial build during product check + /// - a permutation check proof proving that gs is a permutation of fs under + /// permutation + /// - the product polynomial built during product check + /// - the fractional polynomial built during product check /// /// Cost: O(N) + #[allow(clippy::type_complexity)] fn prove( pcs_param: &PCS::ProverParam, - fx: &Self::MultilinearExtension, - gx: &Self::MultilinearExtension, - s_perm: &Self::MultilinearExtension, + fxs: &[Self::MultilinearExtension], + gxs: &[Self::MultilinearExtension], + perms: &[Self::MultilinearExtension], transcript: &mut IOPTranscript, - ) -> Result<(Self::PermutationProof, Self::MultilinearExtension), PolyIOPErrors>; - - /// Verify that an MLE g(x) is a permutation of - /// MLE f(x) over a permutation given by s_perm. + ) -> Result< + ( + Self::PermutationProof, + Self::MultilinearExtension, + Self::MultilinearExtension, + ), + PolyIOPErrors, + >; + + /// Verify that (g1, ..., gk) is a permutation of + /// (f1, ..., fk) over the permutation oracles (perm1, ..., permk) fn verify( proof: &Self::PermutationProof, aux_info: &Self::VPAuxInfo, @@ -97,39 +106,58 @@ where fn prove( pcs_param: &PCS::ProverParam, - fx: &Self::MultilinearExtension, - gx: &Self::MultilinearExtension, - s_perm: &Self::MultilinearExtension, + fxs: &[Self::MultilinearExtension], + gxs: &[Self::MultilinearExtension], + perms: &[Self::MultilinearExtension], transcript: &mut IOPTranscript, - ) -> Result<(Self::PermutationProof, Self::MultilinearExtension), PolyIOPErrors> { + ) -> Result< + ( + Self::PermutationProof, + Self::MultilinearExtension, + Self::MultilinearExtension, + ), + PolyIOPErrors, + > { let start = start_timer!(|| "Permutation check prove"); - if fx.num_vars != gx.num_vars { - return Err(PolyIOPErrors::InvalidParameters( - "fx and gx have different number of variables".to_string(), - )); + if fxs.is_empty() { + return Err(PolyIOPErrors::InvalidParameters("fxs is empty".to_string())); + } + if (fxs.len() != gxs.len()) || (fxs.len() != perms.len()) { + return Err(PolyIOPErrors::InvalidProof(format!( + "fxs.len() = {}, gxs.len() = {}, perms.len() = {}", + fxs.len(), + gxs.len(), + perms.len(), + ))); } - if fx.num_vars != s_perm.num_vars { - return Err(PolyIOPErrors::InvalidParameters( - "fx and s_perm have different number of variables".to_string(), - )); + let num_vars = fxs[0].num_vars; + for ((fx, gx), perm) in fxs.iter().zip(gxs.iter()).zip(perms.iter()) { + if (fx.num_vars != num_vars) || (gx.num_vars != num_vars) || (perm.num_vars != num_vars) + { + return Err(PolyIOPErrors::InvalidParameters( + "number of variables unmatched".to_string(), + )); + } } // generate challenge `beta` and `gamma` from current transcript let beta = transcript.get_and_append_challenge(b"beta")?; let gamma = transcript.get_and_append_challenge(b"gamma")?; - let (numerator, denominator) = computer_num_and_denom(&beta, &gamma, fx, gx, s_perm)?; + let (numerators, denominators) = computer_nums_and_denoms(&beta, &gamma, fxs, gxs, perms)?; // invoke product check on numerator and denominator - let (proof, prod_poly) = - >::prove(pcs_param, &numerator, &denominator, transcript)?; + let (proof, prod_poly, frac_poly) = >::prove( + pcs_param, + &numerators, + &denominators, + transcript, + )?; end_timer!(start); - Ok((proof, prod_poly)) + Ok((proof, prod_poly, frac_poly)) } - /// Verify that an MLE g(x) is a permutation of an - /// MLE f(x) over a permutation given by s_perm. fn verify( proof: &Self::PermutationProof, aux_info: &Self::VPAuxInfo, @@ -159,7 +187,7 @@ mod test { pcs::{prelude::MultilinearKzgPCS, PolynomialCommitmentScheme}, poly_iop::{errors::PolyIOPErrors, PolyIOP}, }; - use arithmetic::{evaluate_opt, identity_permutation_mle, random_permutation_mle, VPAuxInfo}; + use arithmetic::{evaluate_opt, identity_permutation_mles, random_permutation_mles, VPAuxInfo}; use ark_bls12_381::Bls12_381; use ark_ec::PairingEngine; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; @@ -170,17 +198,18 @@ mod test { fn test_permutation_check_helper( pcs_param: &PCS::ProverParam, - fx: &Rc>, - gx: &Rc>, - s_perm: &Rc>, + fxs: &[Rc>], + gxs: &[Rc>], + perms: &[Rc>], ) -> Result<(), PolyIOPErrors> where E: PairingEngine, PCS: PolynomialCommitmentScheme>>, { - let nv = fx.num_vars; + let nv = fxs[0].num_vars; + // what's AuxInfo used for? let poly_info = VPAuxInfo { - max_degree: 2, + max_degree: fxs.len() + 1, num_variables: nv, phantom: PhantomData::default(), }; @@ -188,11 +217,11 @@ mod test { // prover let mut transcript = as PermutationCheck>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; - let (proof, prod_x) = as PermutationCheck>::prove( + let (proof, prod_x, _frac_poly) = as PermutationCheck>::prove( pcs_param, - fx, - gx, - s_perm, + fxs, + gxs, + perms, &mut transcript, )?; @@ -220,40 +249,66 @@ mod test { fn test_permutation_check(nv: usize) -> Result<(), PolyIOPErrors> { let mut rng = test_rng(); - let srs = MultilinearKzgPCS::::gen_srs_for_testing(&mut rng, nv + 1)?; - let (pcs_param, _) = MultilinearKzgPCS::::trim(&srs, None, Some(nv + 1))?; + let srs = MultilinearKzgPCS::::gen_srs_for_testing(&mut rng, nv)?; + let (pcs_param, _) = MultilinearKzgPCS::::trim(&srs, None, Some(nv))?; + let id_perms = identity_permutation_mles(nv, 2); { - // good path: w is a permutation of w itself under the identify map - let w = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); - // s_perm is the identity map - let s_perm = identity_permutation_mle(nv); - test_permutation_check_helper::(&pcs_param, &w, &w, &s_perm)?; + // good path: (w1, w2) is a permutation of (w1, w2) itself under the identify + // map + let ws = vec![ + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + ]; + // perms is the identity map + test_permutation_check_helper::(&pcs_param, &ws, &ws, &id_perms)?; + } + + { + // good path: f = (w1, w2) is a permutation of g = (w2, w1) itself under a map + let mut fs = vec![ + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + ]; + let gs = fs.clone(); + fs.reverse(); + // perms is the reverse identity map + let mut perms = id_perms.clone(); + perms.reverse(); + test_permutation_check_helper::(&pcs_param, &fs, &gs, &perms)?; } { // bad path 1: w is a not permutation of w itself under a random map - let w = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); - // s_perm is a random map - let s_perm = random_permutation_mle(nv, &mut rng); + let ws = vec![ + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + ]; + // perms is a random map + let perms = random_permutation_mles(nv, 2, &mut rng); assert!( - test_permutation_check_helper::(&pcs_param, &w, &w, &s_perm) + test_permutation_check_helper::(&pcs_param, &ws, &ws, &perms) .is_err() ); } { // bad path 2: f is a not permutation of g under a identity map - let f = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); - let g = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)); + let fs = vec![ + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + ]; + let gs = vec![ + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)), + ]; // s_perm is the identity map - let s_perm = identity_permutation_mle(nv); - assert!( - test_permutation_check_helper::(&pcs_param, &f, &g, &s_perm) - .is_err() - ); + assert!(test_permutation_check_helper::( + &pcs_param, &fs, &gs, &id_perms + ) + .is_err()); } Ok(()) diff --git a/subroutines/src/poly_iop/perm_check/util.rs b/subroutines/src/poly_iop/perm_check/util.rs index cdf2f629..3feee685 100644 --- a/subroutines/src/poly_iop/perm_check/util.rs +++ b/subroutines/src/poly_iop/perm_check/util.rs @@ -1,61 +1,69 @@ //! This module implements useful functions for the permutation check protocol. use crate::poly_iop::errors::PolyIOPErrors; -use arithmetic::identity_permutation_mle; +use arithmetic::identity_permutation_mles; use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; use ark_std::{end_timer, start_timer}; use std::rc::Rc; -/// Returns the evaluations of two MLEs: -/// - numerator -/// - denominator +/// Returns the evaluations of two list of MLEs: +/// - numerators = (a1, ..., ak) +/// - denominators = (b1, ..., bk) /// /// where /// - beta and gamma are challenges -/// - f(x), g(x), s_id(x), s_perm(x) are mle-s +/// - (f1, ..., fk), (g1, ..., gk), +/// - (s_id1, ..., s_idk), (perm1, ..., permk) are mle-s /// -/// - numerator is the MLE for `f(x) + \beta s_id(x) + \gamma` -/// - denominator is the MLE for `g(x) + \beta s_perm(x) + \gamma` +/// - ai(x) is the MLE for `fi(x) + \beta s_id_i(x) + \gamma` +/// - bi(x) is the MLE for `gi(x) + \beta perm_i(x) + \gamma` +/// +/// The caller is responsible for sanity-check #[allow(clippy::type_complexity)] -pub(super) fn computer_num_and_denom( +pub(super) fn computer_nums_and_denoms( beta: &F, gamma: &F, - fx: &DenseMultilinearExtension, - gx: &DenseMultilinearExtension, - s_perm: &DenseMultilinearExtension, + fxs: &[Rc>], + gxs: &[Rc>], + perms: &[Rc>], ) -> Result< ( - Rc>, - Rc>, + Vec>>, + Vec>>, ), PolyIOPErrors, > { - let start = start_timer!(|| "compute numerator and denominator"); + let start = start_timer!(|| "compute numerators and denominators"); - let num_vars = fx.num_vars; - let mut numerator_evals = vec![]; - let mut denominator_evals = vec![]; - let s_id = identity_permutation_mle::(num_vars); + let num_vars = fxs[0].num_vars; + let mut numerators = vec![]; + let mut denominators = vec![]; + let s_ids = identity_permutation_mles::(num_vars, fxs.len()); + for l in 0..fxs.len() { + let mut numerator_evals = vec![]; + let mut denominator_evals = vec![]; - for (&fi, (&gi, (&s_id_i, &s_perm_i))) in - fx.iter().zip(gx.iter().zip(s_id.iter().zip(s_perm.iter()))) - { - let numerator = fi + *beta * s_id_i + gamma; - let denominator = gi + *beta * s_perm_i + gamma; + for (&f_ev, (&g_ev, (&s_id_ev, &perm_ev))) in fxs[l] + .iter() + .zip(gxs[l].iter().zip(s_ids[l].iter().zip(perms[l].iter()))) + { + let numerator = f_ev + *beta * s_id_ev + gamma; + let denominator = g_ev + *beta * perm_ev + gamma; - numerator_evals.push(numerator); - denominator_evals.push(denominator); + numerator_evals.push(numerator); + denominator_evals.push(denominator); + } + numerators.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + numerator_evals, + ))); + denominators.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + denominator_evals, + ))); } - let numerator = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - numerator_evals, - )); - let denominator = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - denominator_evals, - )); end_timer!(start); - Ok((numerator, denominator)) + Ok((numerators, denominators)) } diff --git a/subroutines/src/poly_iop/prod_check/mod.rs b/subroutines/src/poly_iop/prod_check/mod.rs index 0e0b69f1..27f55edd 100644 --- a/subroutines/src/poly_iop/prod_check/mod.rs +++ b/subroutines/src/poly_iop/prod_check/mod.rs @@ -4,7 +4,7 @@ use crate::{ pcs::PolynomialCommitmentScheme, poly_iop::{ errors::PolyIOPErrors, - prod_check::util::{compute_product_poly, prove_zero_check}, + prod_check::util::{compute_frac_poly, compute_product_poly, prove_zero_check}, zero_check::ZeroCheck, PolyIOP, }, @@ -19,22 +19,29 @@ use transcript::IOPTranscript; mod util; -/// A product-check proves that two n-variate multilinear polynomials `f(x), -/// g(x)` satisfy: -/// \prod_{x \in {0,1}^n} f(x) = \prod_{x \in {0,1}^n} g(x) +/// A product-check proves that two lists of n-variate multilinear polynomials +/// `(f1, f2, ..., fk)` and `(g1, ..., gk)` satisfy: +/// \prod_{x \in {0,1}^n} f1(x) * ... * fk(x) = \prod_{x \in {0,1}^n} g1(x) * +/// ... * gk(x) /// /// A ProductCheck is derived from ZeroCheck. /// /// Prover steps: -/// 1. build `prod(x0, ..., x_n)` from f and g, -/// such that `prod(0, x1, ..., xn)` equals `f/g` over domain {0,1}^n -/// 2. push commitments of `prod(x)` to the transcript, -/// and `generate_challenge` from current transcript (generate alpha) -/// 3. generate the zerocheck proof for the virtual polynomial -/// prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0, x) * g(x)) +/// 1. build MLE `frac(x)` s.t. `frac(x) = f1(x) * ... * fk(x) / (g1(x) * ... * +/// gk(x))` for all x \in {0,1}^n 2. build `prod(x)` from `frac(x)`, where +/// `prod(x)` equals to `v(1,x)` in the paper 2. push commitments of `frac(x)` +/// and `prod(x)` to the transcript, and `generate_challenge` from current +/// transcript (generate alpha) 3. generate the zerocheck proof for the virtual +/// polynomial Q(x): prod(x) - p1(x) * p2(x) +/// + alpha * frac(x) * g1(x) * ... * gk(x) +/// - alpha * f1(x) * ... * fk(x) +/// where p1(x) = (1-x1) * frac(x2, ..., xn, 0) +/// + x1 * prod(x2, ..., xn, 0), +/// and p2(x) = (1-x1) * frac(x2, ..., xn, 1) +/// + x1 * prod(x2, ..., xn, 1) /// /// Verifier steps: -/// 1. Extract commitments of `prod(x)` from the proof, push +/// 1. Extract commitments of `frac(x)` and `prod(x)` from the proof, push /// them to the transcript /// 2. `generate_challenge` from current transcript (generate alpha) /// 3. `verify` to verify the zerocheck proof and generate the subclaim for @@ -55,30 +62,42 @@ where /// ProductCheck prover/verifier. fn init_transcript() -> Self::Transcript; - /// Generate a proof for product check, showing that witness multilinear - /// polynomials f(x), g(x) satisfy `\prod_{x \in {0,1}^n} f(x) = - /// \prod_{x \in {0,1}^n} g(x)` + /// Proves that two lists of n-variate multilinear polynomials `(f1, f2, + /// ..., fk)` and `(g1, ..., gk)` satisfy: + /// \prod_{x \in {0,1}^n} f1(x) * ... * fk(x) + /// = \prod_{x \in {0,1}^n} g1(x) * ... * gk(x) /// /// Inputs: - /// - fx: the numerator multilinear polynomial - /// - gx: the denominator multilinear polynomial + /// - fxs: the list of numerator multilinear polynomial + /// - gxs: the list of denominator multilinear polynomial /// - transcript: the IOP transcript /// - pk: PCS committing key /// /// Outputs /// - the product check proof /// - the product polynomial (used for testing) + /// - the fractional polynomial (used for testing) /// /// Cost: O(N) + #[allow(clippy::type_complexity)] fn prove( pcs_param: &PCS::ProverParam, - fx: &Self::MultilinearExtension, - gx: &Self::MultilinearExtension, + fxs: &[Self::MultilinearExtension], + gxs: &[Self::MultilinearExtension], transcript: &mut IOPTranscript, - ) -> Result<(Self::ProductCheckProof, Self::MultilinearExtension), PolyIOPErrors>; - - /// Verify that for witness multilinear polynomials f(x), g(x) - /// it holds that `\prod_{x \in {0,1}^n} f(x) = \prod_{x \in {0,1}^n} g(x)` + ) -> Result< + ( + Self::ProductCheckProof, + Self::MultilinearExtension, + Self::MultilinearExtension, + ), + PolyIOPErrors, + >; + + /// Verify that for witness multilinear polynomials (f1, ..., fk, g1, ..., + /// gk) it holds that + /// `\prod_{x \in {0,1}^n} f1(x) * ... * fk(x) + /// = \prod_{x \in {0,1}^n} g1(x) * ... * gk(x)` fn verify( proof: &Self::ProductCheckProof, aux_info: &VPAuxInfo, @@ -87,9 +106,7 @@ where } /// A product check subclaim consists of -/// - A zero check IOP subclaim for -/// `Q(x) = prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0, -/// x) * g(x)) = 0` +/// - A zero check IOP subclaim for the virtual polynomial /// - The random challenge `alpha` /// - A final query for `prod(1, ..., 1, 0) = 1`. // Note that this final query is in fact a constant that @@ -110,6 +127,7 @@ pub struct ProductCheckSubClaim> { /// A product check proof consists of /// - a zerocheck proof /// - a product polynomial commitment +/// - a polynomial commitment for the fractional polynomial #[derive(Clone, Debug, Default, PartialEq)] pub struct ProductCheckProof< E: PairingEngine, @@ -118,6 +136,7 @@ pub struct ProductCheckProof< > { pub zero_check_proof: ZC::ZeroCheckProof, pub prod_x_comm: PCS::Commitment, + pub frac_comm: PCS::Commitment, } impl ProductCheck for PolyIOP @@ -134,28 +153,51 @@ where fn prove( pcs_param: &PCS::ProverParam, - fx: &Self::MultilinearExtension, - gx: &Self::MultilinearExtension, + fxs: &[Self::MultilinearExtension], + gxs: &[Self::MultilinearExtension], transcript: &mut IOPTranscript, - ) -> Result<(Self::ProductCheckProof, Self::MultilinearExtension), PolyIOPErrors> { + ) -> Result< + ( + Self::ProductCheckProof, + Self::MultilinearExtension, + Self::MultilinearExtension, + ), + PolyIOPErrors, + > { let start = start_timer!(|| "prod_check prove"); - if fx.num_vars != gx.num_vars { + if fxs.is_empty() { + return Err(PolyIOPErrors::InvalidParameters("fxs is empty".to_string())); + } + if fxs.len() != gxs.len() { return Err(PolyIOPErrors::InvalidParameters( - "fx and gx have different number of variables".to_string(), + "fxs and gxs have different number of polynomials".to_string(), )); } + for poly in fxs.iter().chain(gxs.iter()) { + if poly.num_vars != fxs[0].num_vars { + return Err(PolyIOPErrors::InvalidParameters( + "fx and gx have different number of variables".to_string(), + )); + } + } + // compute the fractional polynomial frac_p s.t. + // frac_p(x) = f1(x) * ... * fk(x) / (g1(x) * ... * gk(x)) + let frac_poly = compute_frac_poly(fxs, gxs)?; // compute the product polynomial - let prod_x = compute_product_poly(fx, gx)?; + let prod_x = compute_product_poly(&frac_poly)?; // generate challenge + let frac_comm = PCS::commit(pcs_param, &frac_poly)?; let prod_x_comm = PCS::commit(pcs_param, &prod_x)?; + transcript.append_serializable_element(b"frac(x)", &frac_comm)?; transcript.append_serializable_element(b"prod(x)", &prod_x_comm)?; let alpha = transcript.get_and_append_challenge(b"alpha")?; // build the zero-check proof - let (zero_check_proof, _) = prove_zero_check(fx, gx, &prod_x, &alpha, transcript)?; + let (zero_check_proof, _) = + prove_zero_check(fxs, gxs, &frac_poly, &prod_x, &alpha, transcript)?; end_timer!(start); @@ -163,8 +205,10 @@ where ProductCheckProof { zero_check_proof, prod_x_comm, + frac_comm, }, prod_x, + frac_poly, )) } @@ -176,6 +220,7 @@ where let start = start_timer!(|| "prod_check verify"); // update transcript and generate challenge + transcript.append_serializable_element(b"frac(x)", &proof.frac_comm)?; transcript.append_serializable_element(b"prod(x)", &proof.prod_x_comm)?; let alpha = transcript.get_and_append_challenge(b"alpha")?; @@ -184,8 +229,8 @@ where let zero_check_sub_claim = >::verify(&proof.zero_check_proof, aux_info, transcript)?; - // the final query is on prod_x, hence has length `num_vars` + 1 - let mut final_query = vec![E::Fr::one(); aux_info.num_variables + 1]; + // the final query is on prod_x + let mut final_query = vec![E::Fr::one(); aux_info.num_variables]; // the point has to be reversed because Arkworks uses big-endian. final_query[0] = E::Fr::zero(); let final_eval = E::Fr::one(); @@ -214,10 +259,35 @@ mod test { use ark_std::test_rng; use std::{marker::PhantomData, rc::Rc}; - // f and g are guaranteed to have the same product + fn check_frac_poly( + frac_poly: &Rc>, + fs: &[Rc>], + gs: &[Rc>], + ) where + E: PairingEngine, + { + let mut flag = true; + let num_vars = frac_poly.num_vars; + for i in 0..1 << num_vars { + let nom = fs + .iter() + .fold(E::Fr::from(1u8), |acc, f| acc * f.evaluations[i]); + let denom = gs + .iter() + .fold(E::Fr::from(1u8), |acc, g| acc * g.evaluations[i]); + if denom * frac_poly.evaluations[i] != nom { + flag = false; + break; + } + } + assert_eq!(flag, true); + } + // fs and gs are guaranteed to have the same product + // fs and hs doesn't have the same product fn test_product_check_helper( - f: &DenseMultilinearExtension, - g: &DenseMultilinearExtension, + fs: &[Rc>], + gs: &[Rc>], + hs: &[Rc>], pcs_param: &PCS::ProverParam, ) -> Result<(), PolyIOPErrors> where @@ -227,19 +297,16 @@ mod test { let mut transcript = as ProductCheck>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; - let (proof, prod_x) = as ProductCheck>::prove( - pcs_param, - &Rc::new(f.clone()), - &Rc::new(g.clone()), - &mut transcript, - )?; + let (proof, prod_x, frac_poly) = + as ProductCheck>::prove(pcs_param, fs, gs, &mut transcript)?; let mut transcript = as ProductCheck>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; + // what's aux_info for? let aux_info = VPAuxInfo { - max_degree: 2, - num_variables: f.num_vars, + max_degree: fs.len() + 1, + num_variables: fs[0].num_vars, phantom: PhantomData::default(), }; let prod_subclaim = @@ -249,18 +316,14 @@ mod test { prod_subclaim.final_query.1, "different product" ); + check_frac_poly::(&frac_poly, fs, gs); // bad path let mut transcript = as ProductCheck>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; - let h = f + g; - let (bad_proof, prod_x_bad) = as ProductCheck>::prove( - pcs_param, - &Rc::new(f.clone()), - &Rc::new(h), - &mut transcript, - )?; + let (bad_proof, prod_x_bad, frac_poly) = + as ProductCheck>::prove(pcs_param, fs, hs, &mut transcript)?; let mut transcript = as ProductCheck>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; @@ -274,6 +337,8 @@ mod test { bad_subclaim.final_query.1, "can't detect wrong proof" ); + // the frac_poly should still be computed correctly + check_frac_poly::(&frac_poly, fs, hs); Ok(()) } @@ -281,14 +346,28 @@ mod test { fn test_product_check(nv: usize) -> Result<(), PolyIOPErrors> { let mut rng = test_rng(); - let f: DenseMultilinearExtension = DenseMultilinearExtension::rand(nv, &mut rng); - let mut g = f.clone(); - g.evaluations.reverse(); + let f1: DenseMultilinearExtension = DenseMultilinearExtension::rand(nv, &mut rng); + let mut g1 = f1.clone(); + g1.evaluations.reverse(); + let f2: DenseMultilinearExtension = DenseMultilinearExtension::rand(nv, &mut rng); + let mut g2 = f2.clone(); + g2.evaluations.reverse(); + let fs = vec![Rc::new(f1), Rc::new(f2)]; + let gs = vec![Rc::new(g2), Rc::new(g1)]; + let mut hs = vec![]; + for _ in 0..fs.len() { + hs.push(Rc::new(DenseMultilinearExtension::rand( + fs[0].num_vars, + &mut rng, + ))); + } - let srs = MultilinearKzgPCS::::gen_srs_for_testing(&mut rng, nv + 1)?; - let (pcs_param, _) = MultilinearKzgPCS::::trim(&srs, None, Some(nv + 1))?; + let srs = MultilinearKzgPCS::::gen_srs_for_testing(&mut rng, nv)?; + let (pcs_param, _) = MultilinearKzgPCS::::trim(&srs, None, Some(nv))?; - test_product_check_helper::>(&f, &g, &pcs_param)?; + test_product_check_helper::>( + &fs, &gs, &hs, &pcs_param, + )?; Ok(()) } diff --git a/subroutines/src/poly_iop/prod_check/util.rs b/subroutines/src/poly_iop/prod_check/util.rs index d21032ae..1f7e909b 100644 --- a/subroutines/src/poly_iop/prod_check/util.rs +++ b/subroutines/src/poly_iop/prod_check/util.rs @@ -5,198 +5,166 @@ use arithmetic::{get_index, VirtualPolynomial}; use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; use ark_std::{end_timer, start_timer}; -use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use std::rc::Rc; use transcript::IOPTranscript; -/// Compute the product polynomial `prod(x)` where +/// Compute multilinear fractional polynomial s.t. frac(x) = f1(x) * ... * fk(x) +/// / (g1(x) * ... * gk(x)) for all x \in {0,1}^n /// -/// - `prod(0,x) := prod(0, x1, …, xn)` is the MLE over the -/// evaluations of `f(x)/g(x)` on the boolean hypercube {0,1}^n -/// -/// - `prod(1,x)` is a MLE over the evaluations of `prod(x, 0) * prod(x, 1)` -/// on the boolean hypercube {0,1}^n +/// The caller needs to sanity-check that the number of polynomials and +/// variables match in fxs and gxs; and gi(x) has no zero entries. +pub(super) fn compute_frac_poly( + fxs: &[Rc>], + gxs: &[Rc>], +) -> Result>, PolyIOPErrors> { + let start = start_timer!(|| "compute frac(x)"); + + let mut f_evals = vec![F::one(); 1 << fxs[0].num_vars]; + for fx in fxs.iter() { + for (f_eval, fi) in f_evals.iter_mut().zip(fx.iter()) { + *f_eval *= fi; + } + } + let mut g_evals = vec![F::one(); 1 << gxs[0].num_vars]; + for gx in gxs.iter() { + for (g_eval, gi) in g_evals.iter_mut().zip(gx.iter()) { + *g_eval *= gi; + } + } + for (f_eval, g_eval) in f_evals.iter_mut().zip(g_evals.iter()) { + if *g_eval == F::zero() { + return Err(PolyIOPErrors::InvalidParameters( + "gxs has zero entries in the boolean hypercube".to_string(), + )); + } + *f_eval /= g_eval; + } + + end_timer!(start); + Ok(Rc::new(DenseMultilinearExtension::from_evaluations_vec( + fxs[0].num_vars, + f_evals, + ))) +} + +/// Compute the product polynomial `prod(x)` such that +/// `prod(x) = [(1-x1)*frac(x2, ..., xn, 0) + x1*prod(x2, ..., xn, 0)] * +/// [(1-x1)*frac(x2, ..., xn, 1) + x1*prod(x2, ..., xn, 1)]` on the boolean +/// hypercube {0,1}^n /// /// The caller needs to check num_vars matches in f and g /// Cost: linear in N. pub(super) fn compute_product_poly( - fx: &Rc>, - gx: &Rc>, + frac_poly: &Rc>, ) -> Result>, PolyIOPErrors> { let start = start_timer!(|| "compute evaluations of prod polynomial"); - let num_vars = fx.num_vars; - - // =================================== - // prod(0, x) - // =================================== - let prod_0x_eval = compute_prod_0(fx, gx)?; + let num_vars = frac_poly.num_vars; + let frac_evals = &frac_poly.evaluations; // =================================== - // prod(1, x) + // prod(x) // =================================== // - // `prod(1, x)` can be computed via recursing the following formula for 2^n-1 + // `prod(x)` can be computed via recursing the following formula for 2^n-1 // times // - // `prod(1, x_1, ..., x_n) := - // prod(x_1, x_2, ..., x_n, 0) * prod(x_1, x_2, ..., x_n, 1)` + // `prod(x_1, ..., x_n) := + // [(1-x1)*frac(x2, ..., xn, 0) + x1*prod(x2, ..., xn, 0)] * + // [(1-x1)*frac(x2, ..., xn, 1) + x1*prod(x2, ..., xn, 1)]` // // At any given step, the right hand side of the equation - // is available via either eval_0x or the current view of eval_1x - let mut prod_1x_eval = vec![]; + // is available via either frac_x or the current view of prod_x + let mut prod_x_evals = vec![]; for x in 0..(1 << num_vars) - 1 { - // sign will decide if the evaluation should be looked up from eval_0x or - // eval_1x; x_zero_index is the index for the evaluation (x_2, ..., x_n, + // sign will decide if the evaluation should be looked up from frac_x or + // prod_x; x_zero_index is the index for the evaluation (x_2, ..., x_n, // 0); x_one_index is the index for the evaluation (x_2, ..., x_n, 1); let (x_zero_index, x_one_index, sign) = get_index(x, num_vars); if !sign { - prod_1x_eval.push(prod_0x_eval[x_zero_index] * prod_0x_eval[x_one_index]); + prod_x_evals.push(frac_evals[x_zero_index] * frac_evals[x_one_index]); } else { - // sanity check: if we are trying to look up from the eval_1x table, + // sanity check: if we are trying to look up from the prod_x_evals table, // then the target index must already exist - if x_zero_index >= prod_1x_eval.len() || x_one_index >= prod_1x_eval.len() { + if x_zero_index >= prod_x_evals.len() || x_one_index >= prod_x_evals.len() { return Err(PolyIOPErrors::ShouldNotArrive); } - prod_1x_eval.push(prod_1x_eval[x_zero_index] * prod_1x_eval[x_one_index]); + prod_x_evals.push(prod_x_evals[x_zero_index] * prod_x_evals[x_one_index]); } } // prod(1, 1, ..., 1) := 0 - prod_1x_eval.push(F::zero()); - - // =================================== - // prod(x) - // =================================== - // 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 prod_x = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars + 1, - eval, - )); - + prod_x_evals.push(F::zero()); end_timer!(start); - Ok(prod_x) + + Ok(Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + prod_x_evals, + ))) } /// generate the zerocheck proof for the virtual polynomial -/// prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0, x) * g(x)) -/// -/// Returns proof and Q(x) for testing purpose. +/// prod(x) - p1(x) * p2(x) + alpha * [frac(x) * g1(x) * ... * gk(x) - f1(x) +/// * ... * fk(x)] where p1(x) = (1-x1) * frac(x2, ..., xn, 0) + x1 * prod(x2, +/// ..., xn, 0), p2(x) = (1-x1) * frac(x2, ..., xn, 1) + x1 * prod(x2, ..., +/// xn, 1) +/// Returns proof. /// /// Cost: O(N) pub(super) fn prove_zero_check( - fx: &Rc>, - gx: &Rc>, + fxs: &[Rc>], + gxs: &[Rc>], + frac_poly: &Rc>, prod_x: &Rc>, alpha: &F, transcript: &mut IOPTranscript, ) -> Result<(IOPProof, VirtualPolynomial), PolyIOPErrors> { let start = start_timer!(|| "zerocheck in product check"); - - let prod_partial_evals = build_prod_partial_eval(prod_x)?; - 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(); - - // compute g(x) * prod(0, x) * alpha - let mut q_x = VirtualPolynomial::new_from_mle(gx, F::one()); - q_x.mul_by_mle(prod_0x, *alpha)?; - - // g(x) * prod(0, x) * alpha - // - f(x) * alpha - q_x.add_mle_list([fx.clone()], -*alpha)?; - - // Q(x) := prod(1,x) - prod(x, 0) * prod(x, 1) - // + alpha * ( - // g(x) * prod(0, x) - // - f(x)) - q_x.add_mle_list([prod_x0, prod_x1], -F::one())?; - q_x.add_mle_list([prod_1x], F::one())?; - - let iop_proof = as ZeroCheck>::prove(&q_x, transcript)?; - - end_timer!(start); - Ok((iop_proof, q_x)) -} - -/// Helper function of the IOP. -/// -/// Input: -/// - prod(x) -/// -/// Output: the following 4 polynomials -/// - prod(0, x) -/// - prod(1, x) -/// - prod(x, 0) -/// - prod(x, 1) -fn build_prod_partial_eval( - prod_x: &Rc>, -) -> Result<[Rc>; 4], PolyIOPErrors> { - let start = start_timer!(|| "build partial prod polynomial"); - - let prod_x_eval = &prod_x.evaluations; - let num_vars = prod_x.num_vars - 1; - - // prod(0, x) - 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 = 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) - // =================================== - // - // now we compute eval_x0 and eval_x1 - // eval_0x will be the odd coefficients of eval - // and eval_1x will be the even coefficients of eval - let mut eval_x0 = vec![]; - let mut eval_x1 = vec![]; - for (x, &prod_x) in prod_x_eval.iter().enumerate() { - if x & 1 == 0 { - eval_x0.push(prod_x); + let num_vars = frac_poly.num_vars; + + // compute p1(x) = (1-x1) * frac(x2, ..., xn, 0) + x1 * prod(x2, ..., xn, 0) + // compute p2(x) = (1-x1) * frac(x2, ..., xn, 1) + x1 * prod(x2, ..., xn, 1) + let mut p1_evals = vec![F::zero(); 1 << num_vars]; + let mut p2_evals = vec![F::zero(); 1 << num_vars]; + for x in 0..1 << num_vars { + let (x0, x1, sign) = get_index(x, num_vars); + if !sign { + p1_evals[x] = frac_poly.evaluations[x0]; + p2_evals[x] = frac_poly.evaluations[x1]; } else { - eval_x1.push(prod_x); + p1_evals[x] = prod_x.evaluations[x0]; + p2_evals[x] = prod_x.evaluations[x1]; } } - let prod_x_0 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, eval_x0, + let p1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, p1_evals, )); - let prod_x_1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, eval_x1, + let p2 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, p2_evals, )); - end_timer!(start); + // compute Q(x) + // prod(x) + let mut q_x = VirtualPolynomial::new_from_mle(prod_x, F::one()); - Ok([prod_0_x, prod_1_x, prod_x_0, prod_x_1]) -} + // prod(x) + // - p1(x) * p2(x) + q_x.add_mle_list([p1, p2], -F::one())?; -/// Returns the evaluations of -/// - `prod(0,x) := prod(0, x1, …, xn)` which is the MLE over the -/// evaluations of f(x)/g(x) on the boolean hypercube {0,1}^n: -/// -/// The caller needs to check num_vars matches in f/g -/// Cost: linear in N. -fn compute_prod_0( - fx: &DenseMultilinearExtension, - gx: &DenseMultilinearExtension, -) -> Result, PolyIOPErrors> { - let start = start_timer!(|| "compute prod(0,x)"); - - let input = fx - .iter() - .zip(gx.iter()) - .map(|(&fi, &gi)| (fi, gi)) - .collect::>(); - let prod_0x_evals = input.par_iter().map(|(x, y)| *x / *y).collect::>(); + // prod(x) + // - p1(x) * p2(x) + // + alpha * frac(x) * g1(x) * ... * gk(x) + let mut mle_list = gxs.to_vec(); + mle_list.push(frac_poly.clone()); + q_x.add_mle_list(mle_list, *alpha)?; + + // prod(x) + // - p1(x) * p2(x) + // + alpha * frac(x) * g1(x) * ... * gk(x) + // - alpha * f1(x) * ... * fk(x)] + q_x.add_mle_list(fxs.to_vec(), -*alpha)?; + + let iop_proof = as ZeroCheck>::prove(&q_x, transcript)?; end_timer!(start); - Ok(prod_0x_evals) + Ok((iop_proof, q_x)) }