arnaucube
arnaucube/hyperplonkPublic Self-hosted repository
Refactor HyperPlonk SNARKs (#73)

chancharles92 chancharles92@gmail.com
Fri, 02 Sep 2022 14:11:32 -0400
commit

32cc0c4fd9ed116d56acd1ad7f4c2abf0a511572

parent

2af479ee84a491aee5168cb7ef3a0e951acbedaa

M hyperplonk/src/errors.rs → 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}
M hyperplonk/src/lib.rs → 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<E, PCS>: PermutationCheck<E, PCS> where E: PairingEngine, PCS: PolynomialCommitmentScheme<E>, { - type Parameters; + type Index; type ProvingKey; type VerifyingKey; type Proof;

@@ -39,17 +38,16 @@

/// 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<E::Fr>], ) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors>; /// Generate HyperPlonk SNARK proof.

@@ -69,13 +67,13 @@

/// 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<bool, HyperPlonkErrors>;

@@ -94,28 +92,17 @@ Point = Vec<E::Fr>,

Evaluation = E::Fr, >, { - type Parameters = HyperPlonkParams; + type Index = HyperPlonkIndex<E::Fr>; type ProvingKey = HyperPlonkProvingKey<E, PCS>; type VerifyingKey = HyperPlonkVerifyingKey<E, PCS>; type Proof = HyperPlonkProof<E, Self, PCS>; - /// 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<E::Fr>], ) -> 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 @@ Some(merged_nv + 1),

)?; // 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<Rc<DenseMultilinearExtension<E::Fr>>> = selectors + let selector_oracles: Vec<Rc<DenseMultilinearExtension<E::Fr>>> = index + .selectors .iter() .map(|s| Rc::new(DenseMultilinearExtension::from(s))) .collect();

@@ -149,13 +137,13 @@ .collect::<Result<Vec<PCS::Commitment>, PCSErrors>>()?;

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 @@ /// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)

/// ``` /// 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 @@ ) -> Result<Self::Proof, HyperPlonkErrors> {

let start = start_timer!(|| "hyperplonk proving"); let mut transcript = IOPTranscript::<E::Fr>::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;

@@ -225,53 +215,14 @@ 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 @@ let zero_check_proof = <Self as ZeroCheck<E::Fr>>::prove(&fx, &mut transcript)?;

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 @@ let (perm_check_proof, prod_x) = <Self as PermutationCheck<E, PCS>>::prove(

&pk.pcs_param, &w_merged, &w_merged, - &pk.permutation_oracles, + &pk.permutation_oracle, &mut transcript, )?;

@@ -331,12 +282,12 @@ {

// 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 @@ {

// 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 @@ {

// 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 @@ {

// 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 @@ let eval = w_merged

.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 @@

// 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 @@ #[cfg(feature = "extensive_sanity_checks")]

{ // 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 @@ let eval = selector_poly

.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 @@ }

// 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 @@ Ok(HyperPlonkProof {

// ======================================================================= // PCS components: common // ======================================================================= - witness_commits, w_merged_com, // ======================================================================= // PCS components: permutation check

@@ -567,6 +535,7 @@ prod_0_x_opening,

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 @@ /// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)

/// ``` /// 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. Verify the opening against the commitment: + /// 3. check subclaim validity + /// + /// 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 @@ let start = start_timer!(|| "hyperplonk verification");

let mut transcript = IOPTranscript::<E::Fr>::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 @@ pub_input.len(),

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 @@ // = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)

// // ======================================================================= let step = start_timer!(|| "verify zero check"); - // Zero check and sum check have different AuxInfo because `w_merged` and - // `Prod(x)` have degree and num_vars + // Zero check and perm check have different AuxInfo let zero_check_aux_info = VPAuxInfo::<E::Fr> { - // 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 @@ }

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::<E::Fr> { // Prod(x) has a max degree of 2 max_degree: 2,

@@ -777,7 +764,7 @@ &proof.witness_perm_check_eval,

&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 @@ &proof.perm_oracle_eval,

&proof.perm_oracle_opening, )? { return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), + "perm oracle pcs verification failed".to_string(), )); }

@@ -805,7 +792,7 @@ &proof.prod_evals[0],

&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 @@ &proof.prod_evals[1],

&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 @@ &proof.prod_evals[2],

&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 @@ &proof.prod_evals[3],

&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 @@ // 3.2 check zero check evaluations

// ======================================================================= // witness for zero check // TODO: batch verification - for (commitment, (opening, eval)) in proof.witness_commits.iter().zip( - proof - .witness_zero_check_openings - .iter() - .zip(proof.witness_zero_check_evals.iter()), - ) { - if !PCS::verify(&vk.pcs_param, commitment, zero_check_point, eval, opening)? { - return Err(HyperPlonkErrors::InvalidProof( - "pcs verification failed".to_string(), - )); - } - } - - // selector for zero check - // TODO: for now we only support a single selector polynomial - for (opening, eval) in proof - .selector_oracle_openings + 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 @@ let mut r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?;

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 @@

#[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 @@ log_n_wires: usize,

gate_func: CustomizedGates, ) -> Result<(), HyperPlonkErrors> { let mut rng = test_rng(); - // system parameters + let pcs_srs = KZGMultilinearPCS::<E>::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 @@ log_n_selectors,

log_n_wires, gate_func, }; - let pcs_srs = KZGMultilinearPCS::<E>::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) = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::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,24 +998,45 @@ ]);

// public input = w1 let pi = w1.clone(); - // generate pk and vks - let (pk, vk) = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::preprocess( - &params, - &pcs_srs, - &s_perm.evaluations, - &[q1], - )?; - // generate a proof and verify let proof = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::prove( &pk, &pi.0, - &[w1, w2], + &[w1.clone(), w2.clone()], )?; - let _sub_claim = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::verify( + let _verify = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::verify( &vk, &pi.0, &proof, )?; + + // bad path 1: wrong permutation + let rand_perm: Vec<E::Fr> = 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) = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::preprocess( + &bad_index, &pcs_srs, + )?; + assert!( + <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::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!( + <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::prove( + &pk, + &pi.0, + &[w1_bad, w2], + ) + .is_err() + ); Ok(()) }
M hyperplonk/src/selectors.rs → 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<F: PrimeField>(pub(crate) Vec<F>); /// A column of selectors of length `#constraints` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SelectorColumn<F: PrimeField>(pub(crate) Vec<F>); impl<F: PrimeField> SelectorColumn<F> { - /// 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<F>], + selector_rows: &[SelectorRow<F>], ) -> Result<Vec<Self>, HyperPlonkErrors> { if selector_rows.is_empty() { return Err(HyperPlonkErrors::InvalidParameters(

@@ -24,9 +30,9 @@ ));

} 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 @@ let nv = witness.get_nv();

Self::from_evaluations_slice(nv, witness.0.as_ref()) } } + +impl<F: PrimeField> SelectorRow<F> { + /// 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<Vec<Rc<DenseMultilinearExtension<F>>>, HyperPlonkErrors> { + build_mle!(matrix) + } +}
M hyperplonk/src/structs.rs → 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 @@ // =======================================================================

// PCS components: common // ======================================================================= /// PCS commit for witnesses - // TODO: replace me with a batch commitment - pub witness_commits: Vec<PCS::Commitment>, 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<E::Fr>, /// 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::Proof>, /// PCS openings for witness on permutation check point // TODO: replace me with a batch opening

@@ -93,6 +96,17 @@ /// customized gate function

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<F: PrimeField> { + pub params: HyperPlonkParams, + pub permutation: Vec<F>, + pub selectors: Vec<SelectorColumn<F>>, +} + /// 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<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> {

/// hyperplonk instance parameters pub params: HyperPlonkParams, /// the preprocessed permutation polynomials - pub permutation_oracles: Rc<DenseMultilinearExtension<E::Fr>>, + pub permutation_oracle: Rc<DenseMultilinearExtension<E::Fr>>, /// the preprocessed selector polynomials // TODO: merge the list into a single MLE pub selector_oracles: Vec<Rc<DenseMultilinearExtension<E::Fr>>>,

@@ -151,3 +165,14 @@ #[derive(Clone, Debug, Default, PartialEq, Eq)]

pub struct CustomizedGates { pub(crate) gates: Vec<(i64, Option<usize>, Vec<usize>)>, } + +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 + } +}
M hyperplonk/src/utils.rs → 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 @@ Ok(res)

}}; } +/// Sanity-check for HyperPlonk SNARK proving +pub(crate) fn prove_sanity_check<F: PrimeField>( + params: &HyperPlonkParams, + pub_input: &[F], + witnesses: &[WitnessColumn<F>], +) -> 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 @@

Ok(res) } -#[allow(dead_code)] pub(crate) fn eval_f<F: PrimeField>( gates: &CustomizedGates, selector_evals: &[F],

@@ -109,6 +159,17 @@ }

res += cur_value; } 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<F: PrimeField>(index: usize, index_len: usize, point: &[F]) -> Vec<F> { + let mut index_vec: Vec<F> = bit_decompose(index as u64, index_len) + .into_iter() + .map(|x| F::from(x)) + .collect(); + index_vec.reverse(); + [point, &index_vec].concat() } #[cfg(test)]
M hyperplonk/src/witness.rs → hyperplonk/src/witness.rs

@@ -13,7 +13,8 @@ #[derive(Debug, Clone)]

pub struct WitnessColumn<F: PrimeField>(pub(crate) Vec<F>); impl<F: PrimeField> WitnessColumn<F> { - /// 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 @@ ));

} 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])