You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

829 lines
32 KiB

use crate::{
errors::HyperPlonkErrors,
structs::{HyperPlonkIndex, HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkVerifyingKey},
utils::{build_f, eval_f, prover_sanity_check, PcsAccumulator},
witness::WitnessColumn,
HyperPlonkSNARK,
};
use arithmetic::{
evaluate_opt, gen_eval_point, identity_permutation_mle, merge_polynomials, VPAuxInfo,
};
use ark_ec::PairingEngine;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, log2, start_timer, One, Zero};
use pcs::prelude::{compute_qx_degree, PolynomialCommitmentScheme};
use poly_iop::{
prelude::{PermutationCheck, ZeroCheck},
PolyIOP,
};
use std::{cmp::max, marker::PhantomData, rc::Rc};
use transcript::IOPTranscript;
impl<E, PCS> HyperPlonkSNARK<E, PCS> for PolyIOP<E::Fr>
where
E: PairingEngine,
// Ideally we want to access polynomial as PCS::Polynomial, instead of instantiating it here.
// But since PCS::Polynomial can be both univariate or multivariate in our implementation
// we cannot bound PCS::Polynomial with a property trait bound.
PCS: PolynomialCommitmentScheme<
E,
Polynomial = Rc<DenseMultilinearExtension<E::Fr>>,
Point = Vec<E::Fr>,
Evaluation = E::Fr,
>,
{
type Index = HyperPlonkIndex<E::Fr>;
type ProvingKey = HyperPlonkProvingKey<E, PCS>;
type VerifyingKey = HyperPlonkVerifyingKey<E, PCS>;
type Proof = HyperPlonkProof<E, Self, PCS>;
fn preprocess(
index: &Self::Index,
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 log_num_selector_polys = log2(index.num_selector_columns()) as usize;
let witness_merged_nv = num_vars + log_num_witness_polys;
let selector_merged_nv = num_vars + log_num_selector_polys;
let max_nv = max(witness_merged_nv + 1, selector_merged_nv);
let max_points = max(
// prod(x) has 5 points
5,
max(
// selector points
index.num_selector_columns(),
// witness points + public input point + perm point
index.num_witness_columns() + 2,
),
);
let supported_uni_degree = compute_qx_degree(max_nv, max_points);
let supported_ml_degree = max_nv;
// extract PCS prover and verifier keys from SRS
let (pcs_prover_param, pcs_verifier_param) =
PCS::trim(pcs_srs, supported_uni_degree, Some(supported_ml_degree))?;
// 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)?;
// build selector oracles and commit to it
let selector_oracles: Vec<Rc<DenseMultilinearExtension<E::Fr>>> = index
.selectors
.iter()
.map(|s| Rc::new(DenseMultilinearExtension::from(s)))
.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(),
selector_oracles,
selector_com: selector_com.clone(),
pcs_param: pcs_prover_param,
},
Self::VerifyingKey {
params: index.params.clone(),
permutation_oracle,
pcs_param: pcs_verifier_param,
selector_com,
perm_com,
},
))
}
/// Generate HyperPlonk SNARK proof.
///
/// Inputs:
/// - `pk`: circuit proving key
/// - `pub_input`: online public input of length 2^\ell
/// - `witness`: witness assignment of length 2^n
/// Outputs:
/// - The HyperPlonk SNARK proof.
///
/// Steps:
///
/// 1. Commit Witness polynomials `w_i(x)` and append commitment to
/// transcript
///
/// 2. Run ZeroCheck on
///
/// `f(q_0(x),...q_l(x), w_0(x),...w_d(x))`
///
/// where `f` is the constraint polynomial i.e.,
/// ```ignore
/// f(q_l, q_r, q_m, q_o, w_a, w_b, w_c)
/// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)
/// ```
/// in vanilla plonk, and obtain a ZeroCheckSubClaim
///
/// 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracle`, and
/// obtain a PermCheckSubClaim.
///
/// 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]
/// - [1,...1, 0]
///
/// - 4.2. permutation check evaluations and proofs
/// - 4.2.1. (deferred) wi_poly(perm_check_point)
///
/// - 4.3. zero check evaluations and proofs
/// - 4.3.1. (deferred) wi_poly(zero_check_point)
/// - 4.3.2. (deferred) selector_poly(zero_check_point)
///
/// - 4.4. public input consistency checks
/// - pi_poly(r_pi) where r_pi is sampled from transcript
///
/// - 5. deferred batch opening
fn prove(
pk: &Self::ProvingKey,
pub_input: &[E::Fr],
witnesses: &[WitnessColumn<E::Fr>],
) -> Result<Self::Proof, HyperPlonkErrors> {
let start = start_timer!(|| "hyperplonk proving");
let mut transcript = IOPTranscript::<E::Fr>::new(b"hyperplonk");
prover_sanity_check(&pk.params, pub_input, witnesses)?;
// 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 log_num_selector_polys = log2(pk.params.num_selector_columns()) as usize;
// 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(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 for w_merged and its points
let mut w_merged_pcs_acc = PcsAccumulator::<E, PCS>::new();
// Accumulator for prod(x) and its points
let mut prod_pcs_acc = PcsAccumulator::<E, PCS>::new();
// Accumulator for prod(x) and its points
let mut selector_pcs_acc = PcsAccumulator::<E, PCS>::new();
let witness_polys: Vec<Rc<DenseMultilinearExtension<E::Fr>>> = witnesses
.iter()
.map(|w| Rc::new(DenseMultilinearExtension::from(w)))
.collect();
// =======================================================================
// 1. Commit Witness polynomials `w_i(x)` and append commitment to
// transcript
// =======================================================================
let step = start_timer!(|| "commit witnesses");
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
)));
}
let w_merged_com = PCS::commit(&pk.pcs_param, &w_merged)?;
w_merged_pcs_acc.init_poly(w_merged.clone(), w_merged_com.clone())?;
transcript.append_serializable_element(b"w", &w_merged_com)?;
end_timer!(step);
// =======================================================================
// 2 Run ZeroCheck on
//
// `f(q_0(x),...q_l(x), w_0(x),...w_d(x))`
//
// where `f` is the constraint polynomial i.e.,
//
// f(q_l, q_r, q_m, q_o, w_a, w_b, w_c)
// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)
//
// in vanilla plonk, and obtain a ZeroCheckSubClaim
// =======================================================================
let step = start_timer!(|| "ZeroCheck on f");
let fx = build_f(
&pk.params.gate_func,
pk.params.num_variables(),
&pk.selector_oracles,
&witness_polys,
)?;
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_oracle`, and
// obtain a PermCheckSubClaim.
// =======================================================================
let step = start_timer!(|| "Permutation check on w_i(x)");
let (perm_check_proof, prod_x) = <Self as PermutationCheck<E, PCS>>::prove(
&pk.pcs_param,
&w_merged,
&w_merged,
&pk.permutation_oracle,
&mut transcript,
)?;
let perm_check_point = &perm_check_proof.zero_check_proof.point;
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]
// - [1,...1, 0]
//
// - 4.2. permutation check evaluations and proofs
// - 4.2.1. (deferred) wi_poly(perm_check_point)
//
// - 4.3. zero check evaluations and proofs
// - 4.3.1. (deferred) wi_poly(zero_check_point)
// - 4.3.2. (deferred) selector_poly(zero_check_point)
//
// - 4.4. (deferred) public input consistency checks
// - pi_poly(r_pi) where r_pi is sampled from transcript
// =======================================================================
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_pcs_acc.init_poly(prod_x, perm_check_proof.prod_x_comm.clone())?;
// 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_pcs_acc.insert_point(&tmp_point1);
prod_pcs_acc.insert_point(&tmp_point2);
prod_pcs_acc.insert_point(&tmp_point3);
prod_pcs_acc.insert_point(&tmp_point4);
prod_pcs_acc.insert_point(&tmp_point5);
// 4.2 permutation check
// - 4.2.1. (deferred) wi_poly(perm_check_point)
w_merged_pcs_acc.insert_point(perm_check_point);
#[cfg(feature = "extensive_sanity_checks")]
{
// sanity check
let eval = pk
.permutation_oracle
.evaluate(&perm_check_proof.zero_check_proof.point)
.ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"perm_oracle evaluation dimension does not match".to_string(),
)
})?;
if eval != perm_oracle_eval {
return Err(HyperPlonkErrors::InvalidProver(
"perm_oracle evaluation is different from PCS opening".to_string(),
));
}
}
// - 4.3. zero check evaluations and proofs
// - 4.3.1 (deferred) wi_poly(zero_check_point)
for i in 0..witness_polys.len() {
let tmp_point = gen_eval_point(i, log_num_witness_polys, &zero_check_proof.point);
// Deferred opening zero check proof
w_merged_pcs_acc.insert_point(&tmp_point);
}
// - 4.3.2. (deferred) selector_poly(zero_check_point)
let selector_merged = merge_polynomials(&pk.selector_oracles)?;
selector_pcs_acc.init_poly(selector_merged, pk.selector_com.clone())?;
for i in 0..pk.selector_oracles.len() {
let tmp_point = gen_eval_point(i, log_num_selector_polys, &zero_check_proof.point);
// Deferred opening zero check proof
selector_pcs_acc.insert_point(&tmp_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,
vec![E::Fr::zero(); log_num_witness_polys],
]
.concat();
w_merged_pcs_acc.insert_point(&tmp_point);
#[cfg(feature = "extensive_sanity_checks")]
{
// sanity check
let pi_poly = Rc::new(DenseMultilinearExtension::from_evaluations_slice(
ell, pub_input,
));
let eval = pi_poly.evaluate(&r_pi).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"public input evaluation dimension does not match".to_string(),
)
})?;
if eval != pi_eval {
return Err(HyperPlonkErrors::InvalidProver(
"public input evaluation is different from PCS opening".to_string(),
));
}
}
end_timer!(step);
// =======================================================================
// 5. deferred batch opening
// =======================================================================
let step = start_timer!(|| "deferred batch openings");
let sub_step = start_timer!(|| "open witness");
let (w_merged_batch_opening, w_merged_batch_evals) =
w_merged_pcs_acc.batch_open(&pk.pcs_param)?;
end_timer!(sub_step);
let sub_step = start_timer!(|| "open prod(x)");
let (prod_batch_openings, prod_batch_evals) = prod_pcs_acc.batch_open(&pk.pcs_param)?;
end_timer!(sub_step);
let sub_step = start_timer!(|| "open selector");
let (selector_batch_opening, selector_batch_evals) =
selector_pcs_acc.batch_open(&pk.pcs_param)?;
end_timer!(sub_step);
end_timer!(step);
end_timer!(start);
Ok(HyperPlonkProof {
// =======================================================================
// witness related
// =======================================================================
/// PCS commit for witnesses
w_merged_com,
// Batch opening for witness commitment
// - PermCheck eval: 1 point
// - ZeroCheck evals: #witness points
// - public input eval: 1 point
w_merged_batch_opening,
// Evaluations of Witness
// - PermCheck eval: 1 point
// - ZeroCheck evals: #witness points
// - public input eval: 1 point
w_merged_batch_evals,
// =======================================================================
// prod(x) related
// =======================================================================
// prod(x)'s openings
// - prod(0, x),
// - prod(1, x),
// - prod(x, 0),
// - prod(x, 1),
// - prod(1, ..., 1,0)
prod_batch_openings,
// prod(x)'s evaluations
// - prod(0, x),
// - prod(1, x),
// - prod(x, 0),
// - prod(x, 1),
// - prod(1, ..., 1,0)
prod_batch_evals,
// =======================================================================
// selectors related
// =======================================================================
// PCS openings for selectors on zero check point
selector_batch_opening,
// Evaluates of selectors on zero check point
selector_batch_evals,
// =======================================================================
// IOP proofs
// =======================================================================
// the custom gate zerocheck proof
zero_check_proof,
// the permutation check proof for copy constraints
perm_check_proof,
})
}
/// Verify the HyperPlonk proof.
///
/// Inputs:
/// - `vk`: verification key
/// - `pub_input`: online public input
/// - `proof`: HyperPlonk SNARK proof
/// Outputs:
/// - Return a boolean on whether the verification is successful
///
/// 1. Verify zero_check_proof on
///
/// `f(q_0(x),...q_l(x), w_0(x),...w_d(x))`
///
/// where `f` is the constraint polynomial i.e.,
/// ```ignore
/// f(q_l, q_r, q_m, q_o, w_a, w_b, w_c)
/// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)
/// ```
/// in vanilla plonk, and obtain a ZeroCheckSubClaim
///
/// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracle`
///
/// 3. check subclaim validity
///
/// 4. Verify the opening against the commitment:
/// - check permutation check evaluations
/// - check zero check evaluations
/// - public input consistency checks
fn verify(
vk: &Self::VerifyingKey,
pub_input: &[E::Fr],
proof: &Self::Proof,
) -> Result<bool, HyperPlonkErrors> {
let start = start_timer!(|| "hyperplonk verification");
let mut transcript = IOPTranscript::<E::Fr>::new(b"hyperplonk");
// witness assignment of length 2^n
let num_vars = vk.params.num_variables();
let log_num_witness_polys = log2(vk.params.num_witness_columns()) as usize;
// 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;
let pi_poly = DenseMultilinearExtension::from_evaluations_slice(ell as usize, pub_input);
// =======================================================================
// 0. sanity checks
// =======================================================================
// public input length
if pub_input.len() != vk.params.num_pub_input {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input length is not correct: got {}, expect {}",
pub_input.len(),
1 << ell
)));
}
if proof.selector_batch_evals.len() - 1 != vk.params.num_selector_columns() {
return Err(HyperPlonkErrors::InvalidVerifier(format!(
"Selector length is not correct: got {}, expect {}",
proof.selector_batch_evals.len() - 1,
1 << vk.params.num_selector_columns()
)));
}
if proof.w_merged_batch_evals.len() != vk.params.num_witness_columns() + 3 {
return Err(HyperPlonkErrors::InvalidVerifier(format!(
"Witness length is not correct: got {}, expect {}",
proof.w_merged_batch_evals.len() - 3,
vk.params.num_witness_columns()
)));
}
if proof.prod_batch_evals.len() - 1 != 5 {
return Err(HyperPlonkErrors::InvalidVerifier(format!(
"the number of product polynomial evaluations is not correct: got {}, expect {}",
proof.prod_batch_evals.len() - 1,
5
)));
}
// =======================================================================
// 1. Verify zero_check_proof on
// `f(q_0(x),...q_l(x), w_0(x),...w_d(x))`
//
// where `f` is the constraint polynomial i.e.,
//
// f(q_l, q_r, q_m, q_o, w_a, w_b, w_c)
// = q_l w_a(x) + q_r w_b(x) + q_m w_a(x)w_b(x) - q_o w_c(x)
//
// =======================================================================
let step = start_timer!(|| "verify zero check");
// Zero check and perm check have different AuxInfo
let zero_check_aux_info = VPAuxInfo::<E::Fr> {
max_degree: vk.params.gate_func.degree(),
num_variables: num_vars,
phantom: PhantomData::default(),
};
// push witness to transcript
transcript.append_serializable_element(b"w", &proof.w_merged_com)?;
let zero_check_sub_claim = <Self as ZeroCheck<E::Fr>>::verify(
&proof.zero_check_proof,
&zero_check_aux_info,
&mut transcript,
)?;
let zero_check_point = &zero_check_sub_claim.point;
// check zero check subclaim
let f_eval = eval_f(
&vk.params.gate_func,
&proof.selector_batch_evals[..vk.params.num_selector_columns()],
&proof.w_merged_batch_evals[1..],
)?;
if f_eval != zero_check_sub_claim.expected_evaluation {
return Err(HyperPlonkErrors::InvalidProof(
"zero check evaluation failed".to_string(),
));
}
end_timer!(step);
// =======================================================================
// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracle`
// =======================================================================
let step = start_timer!(|| "verify permutation check");
// 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,
// degree of merged poly
num_variables: merged_nv,
phantom: PhantomData::default(),
};
let perm_check_sub_claim = <Self as PermutationCheck<E, PCS>>::verify(
&proof.perm_check_proof,
&perm_check_aux_info,
&mut transcript,
)?;
let perm_check_point = &perm_check_sub_claim
.product_check_sub_claim
.zero_check_sub_claim
.point;
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::<E::Fr>(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 = proof.prod_batch_evals[1]
- proof.prod_batch_evals[2] * proof.prod_batch_evals[3]
+ alpha
* ((proof.w_merged_batch_evals[0] + beta * s_perm_eval + gamma)
* proof.prod_batch_evals[0]
- (proof.w_merged_batch_evals[0] + beta * s_id_eval + gamma));
if q_x_rec
!= perm_check_sub_claim
.product_check_sub_claim
.zero_check_sub_claim
.expected_evaluation
{
return Err(HyperPlonkErrors::InvalidVerifier(
"evaluation failed".to_string(),
));
}
end_timer!(step);
// =======================================================================
// 3. Verify the opening against the commitment
// =======================================================================
let step = start_timer!(|| "verify commitments");
// =======================================================================
// 3.1 open prod(x)' evaluations
// =======================================================================
let prod_final_query = perm_check_sub_claim.product_check_sub_claim.final_query;
let 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,
];
if !PCS::batch_verify_single_poly(
&vk.pcs_param,
&proof.perm_check_proof.prod_x_comm,
&points,
&proof.prod_batch_evals,
&proof.prod_batch_openings,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"prod(0, x) pcs verification failed".to_string(),
));
}
// =======================================================================
// 3.2 open selectors' evaluations
// =======================================================================
let log_num_selector_polys = log2(vk.params.num_selector_columns()) as usize;
let mut points = vec![];
for i in 0..vk.params.num_selector_columns() {
let tmp_point =
gen_eval_point(i, log_num_selector_polys, &proof.zero_check_proof.point);
points.push(tmp_point);
}
if !PCS::batch_verify_single_poly(
&vk.pcs_param,
&vk.selector_com,
&points,
&proof.selector_batch_evals,
&proof.selector_batch_opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"selector pcs verification failed".to_string(),
));
}
// =======================================================================
// 3.2 open witnesses' evaluations
// =======================================================================
let mut r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?;
let pi_eval = evaluate_opt(&pi_poly, &r_pi);
assert_eq!(
pi_eval,
proof.w_merged_batch_evals[proof.w_merged_batch_evals.len() - 2]
);
r_pi = [
vec![E::Fr::zero(); num_vars - ell],
r_pi,
vec![E::Fr::zero(); log_num_witness_polys],
]
.concat();
let mut points = vec![perm_check_point.clone()];
for i in 0..proof.w_merged_batch_evals.len() - 3 {
points.push(gen_eval_point(i, log_num_witness_polys, zero_check_point))
}
points.push(r_pi);
if !PCS::batch_verify_single_poly(
&vk.pcs_param,
&proof.w_merged_com,
&points,
&proof.w_merged_batch_evals,
&proof.w_merged_batch_opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"witness for permutation check pcs verification failed".to_string(),
));
}
end_timer!(step);
end_timer!(start);
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
custom_gate::CustomizedGates, selectors::SelectorColumn, structs::HyperPlonkParams,
witness::WitnessColumn,
};
use arithmetic::random_permutation_mle;
use ark_bls12_381::Bls12_381;
use ark_std::test_rng;
use pcs::prelude::MultilinearKzgPCS;
#[test]
fn test_hyperplonk_e2e() -> Result<(), HyperPlonkErrors> {
// Example:
// q_L(X) * W_1(X)^5 - W_2(X) = 0
// is represented as
// vec![
// ( 1, Some(id_qL), vec![id_W1, id_W1, id_W1, id_W1, id_W1]),
// (-1, None, vec![id_W2])
// ]
//
// 4 public input
// 1 selector,
// 2 witnesses,
// 2 variables for MLE,
// 4 wires,
let gates = CustomizedGates {
gates: vec![(1, Some(0), vec![0, 0, 0, 0, 0]), (-1, None, vec![1])],
};
test_hyperplonk_helper::<Bls12_381>(gates)
}
fn test_hyperplonk_helper<E: PairingEngine>(
gate_func: CustomizedGates,
) -> Result<(), HyperPlonkErrors> {
let mut rng = test_rng();
let pcs_srs = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 16)?;
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;
// generate index
let params = HyperPlonkParams {
num_constraints,
num_pub_input,
gate_func,
};
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],
};
// generate pk and vks
let (pk, vk) = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, MultilinearKzgPCS<E>>>::preprocess(
&index, &pcs_srs,
)?;
// w1 := [0, 1, 2, 3]
let w1 = WitnessColumn(vec![
E::Fr::zero(),
E::Fr::one(),
E::Fr::from(2u64),
E::Fr::from(3u64),
]);
// w2 := [0^5, 1^5, 2^5, 3^5]
let w2 = WitnessColumn(vec![
E::Fr::zero(),
E::Fr::one(),
E::Fr::from(32u64),
E::Fr::from(243u64),
]);
// public input = w1
let pi = w1.clone();
// generate a proof and verify
let proof = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, MultilinearKzgPCS<E>>>::prove(
&pk,
&pi.0,
&[w1.clone(), w2.clone()],
)?;
let _verify = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, MultilinearKzgPCS<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, MultilinearKzgPCS<E>>>::preprocess(
&bad_index, &pcs_srs,
)?;
assert!(
<PolyIOP<E::Fr> as HyperPlonkSNARK<E, MultilinearKzgPCS<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, MultilinearKzgPCS<E>>>::prove(
&pk,
&pi.0,
&[w1_bad, w2],
)
.is_err()
);
Ok(())
}
}