diff --git a/hyperplonk/src/errors.rs b/hyperplonk/src/errors.rs index ac5e25b..168d6f6 100644 --- a/hyperplonk/src/errors.rs +++ b/hyperplonk/src/errors.rs @@ -9,8 +9,6 @@ use poly_iop::prelude::PolyIOPErrors; use transcript::TranscriptErrors; /// A `enum` specifying the possible failure modes of hyperplonk. -#[allow(dead_code)] -// todo: REMOVE #[derive(Display, Debug)] pub enum HyperPlonkErrors { /// Invalid Prover: {0} diff --git a/hyperplonk/src/lib.rs b/hyperplonk/src/lib.rs index 6bed05e..f031a4a 100644 --- a/hyperplonk/src/lib.rs +++ b/hyperplonk/src/lib.rs @@ -1,6 +1,6 @@ -//! Main module for the HyperPlonk PolyIOP. +//! Main module for the HyperPlonk SNARK. -use crate::utils::eval_f; +use crate::utils::{eval_f, prove_sanity_check}; use arithmetic::VPAuxInfo; use ark_ec::PairingEngine; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; @@ -11,11 +11,10 @@ use poly_iop::{ prelude::{identity_permutation_mle, PermutationCheck, ZeroCheck}, PolyIOP, }; -use selectors::SelectorColumn; use std::{marker::PhantomData, rc::Rc}; -use structs::{HyperPlonkParams, HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkVerifyingKey}; +use structs::{HyperPlonkIndex, HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkVerifyingKey}; use transcript::IOPTranscript; -use utils::build_f; +use utils::{build_f, gen_eval_point}; use witness::WitnessColumn; mod errors; @@ -25,13 +24,13 @@ mod utils; mod witness; /// A trait for HyperPlonk SNARKs. -/// A HyperPlonk is derived from SumChecks, ZeroChecks and PermutationChecks. +/// A HyperPlonk is derived from ZeroChecks and PermutationChecks. pub trait HyperPlonkSNARK: PermutationCheck where E: PairingEngine, PCS: PolynomialCommitmentScheme, { - type Parameters; + type Index; type ProvingKey; type VerifyingKey; type Proof; @@ -39,17 +38,16 @@ where /// Generate the preprocessed polynomials output by the indexer. /// /// Inputs: - /// - `params`: HyperPlonk instance parameters - /// - `permutation`: the permutation for the copy constraints - /// - `selectors`: the list of selector vectors for custom gates + /// - `index`: HyperPlonk index + /// - `pcs_srs`: Polynomial commitment structured reference string /// Outputs: /// - The HyperPlonk proving key, which includes the preprocessed /// polynomials. + /// - The HyperPlonk verifying key, which includes the preprocessed + /// polynomial commitments fn preprocess( - params: &Self::Parameters, + index: &Self::Index, pcs_srs: &PCS::SRS, - permutation: &[E::Fr], - selectors: &[SelectorColumn], ) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors>; /// Generate HyperPlonk SNARK proof. @@ -69,13 +67,13 @@ where /// Verify the HyperPlonk proof. /// /// Inputs: - /// - `params`: instance parameter + /// - `vk`: verifying key /// - `pub_input`: online public input /// - `proof`: HyperPlonk SNARK proof challenges /// Outputs: /// - Return a boolean on whether the verification is successful fn verify( - params: &Self::VerifyingKey, + vk: &Self::VerifyingKey, pub_input: &[E::Fr], proof: &Self::Proof, ) -> Result; @@ -94,28 +92,17 @@ where Evaluation = E::Fr, >, { - type Parameters = HyperPlonkParams; + type Index = HyperPlonkIndex; type ProvingKey = HyperPlonkProvingKey; type VerifyingKey = HyperPlonkVerifyingKey; type Proof = HyperPlonkProof; - /// Generate the preprocessed polynomials output by the indexer. - /// - /// Inputs: - /// - `params`: HyperPlonk instance parameters - /// - `permutation`: the permutation for the copy constraints - /// - `selectors`: the list of selector vectors for custom gates - /// Outputs: - /// - The HyperPlonk proving key, which includes the preprocessed - /// polynomials. fn preprocess( - params: &Self::Parameters, + index: &Self::Index, pcs_srs: &PCS::SRS, - permutation: &[E::Fr], - selectors: &[SelectorColumn], ) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors> { - let num_vars = params.nv; - let log_num_witness_polys = params.log_n_wires; + let num_vars = index.params.nv; + let log_num_witness_polys = index.params.log_n_wires; // number of variables in merged polynomial for Multilinear-KZG let merged_nv = num_vars + log_num_witness_polys; @@ -130,14 +117,15 @@ where )?; // build permutation oracles - let permutation_oracles = Rc::new(DenseMultilinearExtension::from_evaluations_slice( + let permutation_oracle = Rc::new(DenseMultilinearExtension::from_evaluations_slice( merged_nv, - permutation, + &index.permutation, )); - let perm_com = PCS::commit(&pcs_prover_param, &permutation_oracles)?; + let perm_com = PCS::commit(&pcs_prover_param, &permutation_oracle)?; // build selector oracles and commit to it - let selector_oracles: Vec>> = selectors + let selector_oracles: Vec>> = index + .selectors .iter() .map(|s| Rc::new(DenseMultilinearExtension::from(s))) .collect(); @@ -149,13 +137,13 @@ where Ok(( Self::ProvingKey { - params: params.clone(), - permutation_oracles, + params: index.params.clone(), + permutation_oracle, selector_oracles, pcs_param: pcs_prover_param, }, Self::VerifyingKey { - params: params.clone(), + params: index.params.clone(), pcs_param: pcs_verifier_param, selector_com, perm_com, @@ -188,7 +176,7 @@ where /// ``` /// in vanilla plonk, and obtain a ZeroCheckSubClaim /// - /// 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracles`, and + /// 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracle`, and /// obtain a PermCheckSubClaim. /// /// 4. Generate evaluations and corresponding proofs @@ -205,6 +193,8 @@ where let start = start_timer!(|| "hyperplonk proving"); let mut transcript = IOPTranscript::::new(b"hyperplonk"); + prove_sanity_check(&pk.params, pub_input, witnesses)?; + // witness assignment of length 2^n let num_vars = pk.params.nv; let log_num_witness_polys = pk.params.log_n_wires; @@ -224,54 +214,15 @@ where pub_input, )); - // ======================================================================= - // 0. sanity checks - // ======================================================================= - // public input length - if pub_input.len() != 1 << ell { - return Err(HyperPlonkErrors::InvalidProver(format!( - "Public input length is not correct: got {}, expect {}", - pub_input.len(), - 1 << ell - ))); - } - // witnesses length - for (i, w) in witnesses.iter().enumerate() { - if w.0.len() != 1 << num_vars { - return Err(HyperPlonkErrors::InvalidProver(format!( - "{}-th witness length is not correct: got {}, expect {}", - i, - pub_input.len(), - 1 << ell - ))); - } - } - // check public input matches witness[0]'s first 2^ell elements - let pi_in_w0 = - Rc::new(witness_polys[0].fix_variables(vec![E::Fr::zero(); num_vars - ell].as_ref())); - - if pi_in_w0 != pi_poly { - return Err(HyperPlonkErrors::InvalidProver(format!( - "Public input {:?} does not match witness[0] {:?}", - pi_poly, pi_in_w0, - ))); - } // ======================================================================= // 1. Commit Witness polynomials `w_i(x)` and append commitment to // transcript // ======================================================================= let step = start_timer!(|| "commit witnesses"); - let mut witness_commits = vec![]; - // TODO: batch commit - for wi_poly in witness_polys.iter() { - let wi_com = PCS::commit(&pk.pcs_param, wi_poly)?; - witness_commits.push(wi_com); - } - 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_var ({}) from expected ({})", + "merged witness poly has a different num_vars ({}) from expected ({})", w_merged.num_vars, merged_nv ))); } @@ -305,7 +256,7 @@ where end_timer!(step); // ======================================================================= - // 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracles`, and + // 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracle`, and // obtain a PermCheckSubClaim. // ======================================================================= let step = start_timer!(|| "Permutation check on w_i(x)"); @@ -314,7 +265,7 @@ where &pk.pcs_param, &w_merged, &w_merged, - &pk.permutation_oracles, + &pk.permutation_oracle, &mut transcript, )?; @@ -331,12 +282,12 @@ where // sanity check let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "prod_0_x 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_0_x evaluation is different from PCS opening".to_string(), )); } } @@ -352,12 +303,12 @@ where // sanity check let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "prod_1_x 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_1_x evaluation is different from PCS opening".to_string(), )); } } @@ -373,13 +324,13 @@ where // sanity check let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "prod_x_0 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_0 evaluation is different from PCS opening".to_string(), )); } } @@ -395,15 +346,28 @@ where // sanity check let eval = prod_x.evaluate(&tmp_point).ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "prod_x_1 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(), + "prod_x_1 evaluation is different from PCS opening".to_string(), )); } } + // prod(1, ..., 1, 0) + let tmp_point = [vec![E::Fr::zero()], vec![E::Fr::one(); merged_nv]].concat(); + let (prod_1_0_opening, prod_1_0_eval) = PCS::open(&pk.pcs_param, &prod_x, &tmp_point)?; + #[cfg(feature = "extensive_sanity_checks")] + { + // sanity check + if prod_1_0_eval != E::Fr::one() { + return Err(HyperPlonkErrors::InvalidProver(format!( + "prod_1_0 evaluation is not one: got {}", + prod_1_0_eval, + ))); + } + } end_timer!(step); // ======================================================================= @@ -441,31 +405,31 @@ where .evaluate(&perm_check_proof.zero_check_proof.point) .ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "witness_perm_check 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(), + "witness_perm_check evaluation is different from PCS opening".to_string(), )); } } // 4.2 open zero check proof // TODO: batch opening - for wire_poly in witness_polys { + for (i, wire_poly) in witness_polys.iter().enumerate() { + let tmp_point = gen_eval_point(i, log_num_witness_polys, &zero_check_proof.point); // Open zero check proof - let (zero_proof, zero_eval) = - PCS::open(&pk.pcs_param, &wire_poly, &zero_check_proof.point)?; + let (zero_proof, zero_eval) = PCS::open(&pk.pcs_param, &w_merged, &tmp_point)?; { let eval = wire_poly.evaluate(&zero_check_proof.point).ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "witness_zero_check 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 evaluation is different from PCS opening".to_string(), )); } } @@ -476,7 +440,7 @@ where // Open permutation polynomial at perm_check_point let (s_perm_opening, s_perm_eval) = PCS::open( &pk.pcs_param, - &pk.permutation_oracles, + &pk.permutation_oracle, &perm_check_proof.zero_check_proof.point, )?; @@ -484,16 +448,16 @@ where { // sanity check let eval = pk - .permutation_oracles + .permutation_oracle .evaluate(&perm_check_proof.zero_check_proof.point) .ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "perm_oracle evaluation dimension does not match".to_string(), ) })?; if eval != s_perm_eval { return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), + "perm_oracle evaluation is different from PCS opening".to_string(), )); } } @@ -515,12 +479,12 @@ where .evaluate(&zero_check_proof.point) .ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "selector evaluation dimension does not match".to_string(), ) })?; if eval != zero_eval { return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), + "selector evaluation is different from PCS opening".to_string(), )); } } @@ -530,20 +494,25 @@ where // 4.3 public input consistency checks let r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?; - - let (pi_opening, pi_eval) = PCS::open(&pk.pcs_param, &pi_in_w0, &r_pi)?; + let tmp_point = [ + vec![E::Fr::zero(); num_vars - ell], + r_pi.clone(), + vec![E::Fr::zero(); log_num_witness_polys], + ] + .concat(); + let (pi_opening, pi_eval) = PCS::open(&pk.pcs_param, &w_merged, &tmp_point)?; #[cfg(feature = "extensive_sanity_checks")] { // sanity check let eval = pi_poly.evaluate(&r_pi).ok_or_else(|| { HyperPlonkErrors::InvalidParameters( - "evaluation dimension does not match".to_string(), + "public input evaluation dimension does not match".to_string(), ) })?; if eval != pi_eval { return Err(HyperPlonkErrors::InvalidProver( - "Evaluation is different from PCS opening".to_string(), + "public input evaluation is different from PCS opening".to_string(), )); } } @@ -555,7 +524,6 @@ where // ======================================================================= // PCS components: common // ======================================================================= - witness_commits, w_merged_com, // ======================================================================= // PCS components: permutation check @@ -567,6 +535,7 @@ where prod_1_x_opening, prod_x_0_opening, prod_x_1_opening, + prod_1_0_opening, ], witness_perm_check_opening, witness_perm_check_eval, @@ -612,13 +581,14 @@ where /// ``` /// in vanilla plonk, and obtain a ZeroCheckSubClaim /// - /// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracles` + /// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracle` + /// + /// 3. check subclaim validity /// - /// 3. Verify the opening against the commitment: + /// 4. Verify the opening against the commitment: /// - 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], @@ -628,10 +598,10 @@ where let mut transcript = IOPTranscript::::new(b"hyperplonk"); // witness assignment of length 2^n - let num_var = vk.params.nv; + let num_vars = 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; + let merged_nv = num_vars + log_num_witness_polys; // online public input of length 2^\ell let ell = vk.params.log_pub_input_len; @@ -649,6 +619,27 @@ where 1 << ell ))); } + if proof.selector_oracle_evals.len() != 1 << vk.params.log_n_selectors { + return Err(HyperPlonkErrors::InvalidProver(format!( + "Selector length is not correct: got {}, expect {}", + proof.selector_oracle_evals.len(), + 1 << vk.params.log_n_selectors + ))); + } + if proof.witness_zero_check_evals.len() != 1 << log_num_witness_polys { + return Err(HyperPlonkErrors::InvalidProver(format!( + "Witness length is not correct: got {}, expect {}", + proof.witness_zero_check_evals.len(), + 1 << log_num_witness_polys + ))); + } + if proof.prod_openings.len() != 5 { + return Err(HyperPlonkErrors::InvalidProver(format!( + "the number of product polynomial evaluations is not correct: got {}, expect {}", + proof.prod_openings.len(), + 5 + ))); + } // ======================================================================= // 1. Verify zero_check_proof on @@ -661,13 +652,10 @@ where // // ======================================================================= 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 + // Zero check and perm check have different AuxInfo 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, + max_degree: vk.params.gate_func.degree(), + num_variables: num_vars, phantom: PhantomData::default(), }; @@ -696,12 +684,11 @@ where end_timer!(step); // ======================================================================= - // 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracles` + // 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracle` // ======================================================================= 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 + // 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, @@ -777,7 +764,7 @@ where &proof.witness_perm_check_opening, )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "witness for permutation check pcs verification failed".to_string(), )); } @@ -789,7 +776,7 @@ where &proof.perm_oracle_opening, )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "perm oracle pcs verification failed".to_string(), )); } @@ -805,7 +792,7 @@ where &proof.prod_openings[0], )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "prod(0, x) pcs verification failed".to_string(), )); } // prod(1, x) @@ -817,7 +804,7 @@ where &proof.prod_openings[1], )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "prod(1, x) pcs verification failed".to_string(), )); } // prod(x, 0) @@ -829,7 +816,7 @@ where &proof.prod_openings[2], )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "prod(x, 0) pcs verification failed".to_string(), )); } // prod(x, 1) @@ -841,7 +828,20 @@ where &proof.prod_openings[3], )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "prod(x, 1) pcs verification failed".to_string(), + )); + } + // prod(1, ..., 1, 0) = 1 + let prod_final_query = perm_check_sub_claim.product_check_sub_claim.final_query; + if !PCS::verify( + &vk.pcs_param, + &proof.perm_check_proof.prod_x_comm, + &prod_final_query.0, + &prod_final_query.1, + &proof.prod_openings[4], + )? { + return Err(HyperPlonkErrors::InvalidProof( + "prod(1, ..., 1, 0) pcs verification failed".to_string(), )); } @@ -850,35 +850,36 @@ where // ======================================================================= // 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 + for (i, (opening, eval)) in proof + .witness_zero_check_openings .iter() - .zip(proof.selector_oracle_evals.iter()) + .zip(proof.witness_zero_check_evals.iter()) + .enumerate() { + let tmp_point = gen_eval_point(i, log_num_witness_polys, zero_check_point); if !PCS::verify( &vk.pcs_param, - &vk.selector_com[0], - perm_check_point, + &proof.w_merged_com, + &tmp_point, eval, opening, )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "witness for zero_check pcs verification failed".to_string(), + )); + } + } + + // selector for zero check + for (commitment, (opening, eval)) in vk.selector_com.iter().zip( + proof + .selector_oracle_openings + .iter() + .zip(proof.selector_oracle_evals.iter()), + ) { + if !PCS::verify(&vk.pcs_param, commitment, perm_check_point, eval, opening)? { + return Err(HyperPlonkErrors::InvalidProof( + "selector pcs verification failed".to_string(), )); } } @@ -890,16 +891,21 @@ where let pi_eval = pi_poly.evaluate(&r_pi).ok_or_else(|| { HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string()) })?; - r_pi = [vec![E::Fr::zero(); num_var - ell], r_pi].concat(); + r_pi = [ + vec![E::Fr::zero(); num_vars - ell], + r_pi, + vec![E::Fr::zero(); log_num_witness_polys], + ] + .concat(); if !PCS::verify( &vk.pcs_param, - &proof.witness_commits[0], + &proof.w_merged_com, &r_pi, &pi_eval, &proof.pi_opening, )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "public input pcs verification failed".to_string(), )); } @@ -912,7 +918,11 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{selectors::SelectorColumn, structs::CustomizedGates, witness::WitnessColumn}; + use crate::{ + selectors::SelectorColumn, + structs::{CustomizedGates, HyperPlonkParams}, + witness::WitnessColumn, + }; use ark_bls12_381::Bls12_381; use ark_std::test_rng; use pcs::prelude::KZGMultilinearPCS; @@ -947,7 +957,10 @@ mod tests { gate_func: CustomizedGates, ) -> Result<(), HyperPlonkErrors> { let mut rng = test_rng(); - // system parameters + let pcs_srs = KZGMultilinearPCS::::gen_srs_for_testing(&mut rng, 15)?; + let merged_nv = nv + log_n_wires; + + // generate index let params = HyperPlonkParams { nv, log_pub_input_len, @@ -955,12 +968,19 @@ mod tests { log_n_wires, gate_func, }; - let pcs_srs = KZGMultilinearPCS::::gen_srs_for_testing(&mut rng, 15)?; - let merged_nv = nv + log_n_wires; + let permutation = identity_permutation_mle(merged_nv).evaluations.clone(); + let q1 = SelectorColumn(vec![E::Fr::one(), E::Fr::one(), E::Fr::one(), E::Fr::one()]); + let index = HyperPlonkIndex { + params, + permutation, + selectors: vec![q1], + }; - let s_perm = random_permutation_mle(merged_nv, &mut rng); + // generate pk and vks + let (pk, vk) = as HyperPlonkSNARK>>::preprocess( + &index, &pcs_srs, + )?; - let q1 = SelectorColumn(vec![E::Fr::one(), E::Fr::one(), E::Fr::one(), E::Fr::one()]); // w1 := [0, 1, 2, 3] let w1 = WitnessColumn(vec![ E::Fr::zero(), @@ -978,25 +998,46 @@ mod tests { // public input = w1 let pi = w1.clone(); - // generate pk and vks - let (pk, vk) = as HyperPlonkSNARK>>::preprocess( - ¶ms, - &pcs_srs, - &s_perm.evaluations, - &[q1], - )?; - // generate a proof and verify let proof = as HyperPlonkSNARK>>::prove( &pk, &pi.0, - &[w1, w2], + &[w1.clone(), w2.clone()], )?; - let _sub_claim = as HyperPlonkSNARK>>::verify( + let _verify = as HyperPlonkSNARK>>::verify( &vk, &pi.0, &proof, )?; + // bad path 1: wrong permutation + let rand_perm: Vec = random_permutation_mle(merged_nv, &mut rng) + .evaluations + .clone(); + 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!( + as HyperPlonkSNARK>>::verify( + &bad_vk, &pi.0, &proof, + ) + .is_err() + ); + + // bad path 2: wrong witness + let mut w1_bad = w1.clone(); + w1_bad.0[0] = E::Fr::one(); + assert!( + as HyperPlonkSNARK>>::prove( + &pk, + &pi.0, + &[w1_bad, w2], + ) + .is_err() + ); + Ok(()) } } diff --git a/hyperplonk/src/selectors.rs b/hyperplonk/src/selectors.rs index ae34a47..a08f4c5 100644 --- a/hyperplonk/src/selectors.rs +++ b/hyperplonk/src/selectors.rs @@ -1,21 +1,27 @@ -use crate::errors::HyperPlonkErrors; +use crate::{build_mle, errors::HyperPlonkErrors}; use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; use ark_std::log2; +use std::rc::Rc; + +/// A row of selector of width `#selectors` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SelectorRow(pub(crate) Vec); /// A column of selectors of length `#constraints` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SelectorColumn(pub(crate) Vec); impl SelectorColumn { - /// the number of variables for MLE to present a column. + /// the number of variables of the multilinear polynomial that presents a + /// column. pub fn get_nv(&self) -> usize { log2(self.0.len()) as usize } /// Build selector columns from rows pub fn from_selector_rows( - selector_rows: &[SelectorColumn], + selector_rows: &[SelectorRow], ) -> Result, HyperPlonkErrors> { if selector_rows.is_empty() { return Err(HyperPlonkErrors::InvalidParameters( @@ -24,9 +30,9 @@ impl SelectorColumn { } let mut res = Vec::with_capacity(selector_rows.len()); - let num_wires = selector_rows[0].0.len(); + let num_colnumns = selector_rows[0].0.len(); - for i in 0..num_wires { + for i in 0..num_colnumns { let mut cur_column = Vec::new(); for row in selector_rows.iter() { cur_column.push(row.0[i]) @@ -44,3 +50,19 @@ impl From<&SelectorColumn> for DenseMultilinearExtension { Self::from_evaluations_slice(nv, witness.0.as_ref()) } } + +impl SelectorRow { + /// Build MLE from matrix of selectors. + /// + /// Given a matrix := [row1, row2, ...] where + /// row1:= (a1, a2, ...) + /// row2:= (b1, b2, ...) + /// row3:= (c1, c2, ...) + /// + /// output mle(a1,b1,c1, ...), mle(a2,b2,c2, ...), ... + pub fn build_mles( + matrix: &[Self], + ) -> Result>>, HyperPlonkErrors> { + build_mle!(matrix) + } +} diff --git a/hyperplonk/src/structs.rs b/hyperplonk/src/structs.rs index 68ccca3..f22e48b 100644 --- a/hyperplonk/src/structs.rs +++ b/hyperplonk/src/structs.rs @@ -1,7 +1,10 @@ //! Main module for the HyperPlonk PolyIOP. +use crate::selectors::SelectorColumn; use ark_ec::PairingEngine; +use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; +use ark_std::cmp::max; use pcs::PolynomialCommitmentScheme; use poly_iop::prelude::{PermutationCheck, ZeroCheck}; use std::rc::Rc; @@ -22,17 +25,17 @@ where // PCS components: common // ======================================================================= /// PCS commit for witnesses - // TODO: replace me with a batch commitment - pub witness_commits: Vec, pub w_merged_com: PCS::Commitment, // ======================================================================= // PCS components: permutation check // ======================================================================= /// prod(x)'s evaluations - /// sequence: prod(0,x), prod(1, x), prod(x, 0), prod(x, 1) + /// sequence: prod(0,x), prod(1, x), prod(x, 0), prod(x, 1), prod(1, ..., 1, + /// 0) pub prod_evals: Vec, /// prod(x)'s openings - /// sequence: prod(0,x), prod(1, x), prod(x, 0), prod(x, 1) + /// sequence: prod(0,x), prod(1, x), prod(x, 0), prod(x, 1), prod(1, ..., 1, + /// 0) pub prod_openings: Vec, /// PCS openings for witness on permutation check point // TODO: replace me with a batch opening @@ -93,6 +96,17 @@ pub struct HyperPlonkParams { pub gate_func: CustomizedGates, } +/// The HyperPlonk index, consists of the following: +/// - HyperPlonk parameters +/// - the wire permutation +/// - the selector vectors +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct HyperPlonkIndex { + pub params: HyperPlonkParams, + pub permutation: Vec, + pub selectors: Vec>, +} + /// The HyperPlonk proving key, consists of the following: /// - the hyperplonk instance parameters /// - the preprocessed polynomials output by the indexer @@ -101,7 +115,7 @@ pub struct HyperPlonkProvingKey>, + pub permutation_oracle: Rc>, /// the preprocessed selector polynomials // TODO: merge the list into a single MLE pub selector_oracles: Vec>>, @@ -151,3 +165,14 @@ pub struct HyperPlonkVerifyingKey, Vec)>, } + +impl CustomizedGates { + /// The degree of the algebraic customized gate + pub fn degree(&self) -> usize { + let mut res = 0; + for x in self.gates.iter() { + res = max(res, x.2.len() + (x.1.is_some() as usize)) + } + res + } +} diff --git a/hyperplonk/src/utils.rs b/hyperplonk/src/utils.rs index 6700bad..c6bf8b9 100644 --- a/hyperplonk/src/utils.rs +++ b/hyperplonk/src/utils.rs @@ -4,7 +4,13 @@ use arithmetic::VirtualPolynomial; use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; -use crate::{errors::HyperPlonkErrors, structs::CustomizedGates}; +use crate::{ + errors::HyperPlonkErrors, + structs::{CustomizedGates, HyperPlonkParams}, + witness::WitnessColumn, +}; + +use poly_iop::prelude::bit_decompose; /// Build MLE from matrix of witnesses. /// @@ -35,6 +41,51 @@ macro_rules! build_mle { }}; } +/// Sanity-check for HyperPlonk SNARK proving +pub(crate) fn prove_sanity_check( + params: &HyperPlonkParams, + pub_input: &[F], + witnesses: &[WitnessColumn], +) -> Result<(), HyperPlonkErrors> { + let num_vars = params.nv; + let ell = params.log_pub_input_len; + + // public input length + if pub_input.len() != 1 << ell { + return Err(HyperPlonkErrors::InvalidProver(format!( + "Public input length is not correct: got {}, expect {}", + pub_input.len(), + 1 << ell + ))); + } + // witnesses length + for (i, w) in witnesses.iter().enumerate() { + if w.0.len() != 1 << num_vars { + return Err(HyperPlonkErrors::InvalidProver(format!( + "{}-th witness length is not correct: got {}, expect {}", + i, + pub_input.len(), + 1 << ell + ))); + } + } + // check public input matches witness[0]'s first 2^ell elements + for (i, (&pi, &w)) in pub_input + .iter() + .zip(witnesses[0].0.iter().take(pub_input.len())) + .enumerate() + { + if pi != w { + return Err(HyperPlonkErrors::InvalidProver(format!( + "The {:?}-th public input {:?} does not match witness[0] {:?}", + i, pi, w + ))); + } + } + + Ok(()) +} + /// build `f(w_0(x),...w_d(x))` where `f` is the constraint polynomial /// i.e., `f(a, b, c) = q_l a(x) + q_r b(x) + q_m a(x)b(x) - q_o c(x)` in /// vanilla plonk @@ -86,7 +137,6 @@ pub(crate) fn build_f( Ok(res) } -#[allow(dead_code)] pub(crate) fn eval_f( gates: &CustomizedGates, selector_evals: &[F], @@ -111,6 +161,17 @@ pub(crate) fn eval_f( Ok(res) } +/// given the evaluation input `point` of the `index`-th polynomial, +/// obtain the evaluation point in the merged polynomial +pub(crate) fn gen_eval_point(index: usize, index_len: usize, point: &[F]) -> Vec { + let mut index_vec: Vec = bit_decompose(index as u64, index_len) + .into_iter() + .map(|x| F::from(x)) + .collect(); + index_vec.reverse(); + [point, &index_vec].concat() +} + #[cfg(test)] mod test { use super::*; diff --git a/hyperplonk/src/witness.rs b/hyperplonk/src/witness.rs index 3c0122a..60145ea 100644 --- a/hyperplonk/src/witness.rs +++ b/hyperplonk/src/witness.rs @@ -13,7 +13,8 @@ pub struct WitnessRow(pub(crate) Vec); pub struct WitnessColumn(pub(crate) Vec); impl WitnessColumn { - /// the number of variables for MLE to present a column. + /// the number of variables of the multilinear polynomial that presents a + /// column. pub fn get_nv(&self) -> usize { log2(self.0.len()) as usize } @@ -29,9 +30,9 @@ impl WitnessColumn { } let mut res = Vec::with_capacity(witness_rows.len()); - let num_wires = witness_rows[0].0.len(); + let num_columns = witness_rows[0].0.len(); - for i in 0..num_wires { + for i in 0..num_columns { let mut cur_column = Vec::new(); for row in witness_rows.iter() { cur_column.push(row.0[i])