enabling batch opening and mock tests (#80)

- add mock circuits
- add vanilla and jellyfish plonk gates
- performance tuning
This commit is contained in:
zhenfei
2022-09-27 14:51:30 -04:00
committed by GitHub
parent 3160ef17f2
commit baaa06b07b
51 changed files with 5637 additions and 1388 deletions

View File

@@ -6,7 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
poly-iop = { path = "../poly-iop" }
ark-std = { version = "^0.3.0", default-features = false }
ark-ec = { version = "^0.3.0", default-features = false }
@@ -15,33 +14,49 @@ ark-poly = { version = "^0.3.0", default-features = false }
ark-serialize = { version = "^0.3.0", default-features = false, features = [ "derive" ] }
displaydoc = { version = "0.2.3", default-features = false }
poly-iop = { path = "../poly-iop" }
pcs = { path = "../pcs" }
transcript = { path = "../transcript" }
arithmetic = { path = "../arithmetic" }
util = { path = "../util" }
jf-primitives = { git = "https://github.com/EspressoSystems/jellyfish", rev = "ff43209" }
rayon = { version = "1.5.2", default-features = false, optional = true }
[dev-dependencies]
ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "curve" ] }
# Benchmarks
[[bench]]
name = "hyperplonk-benches"
path = "benches/bench.rs"
harness = false
[features]
# default = [ "parallel", "print-trace", "extensive_sanity_checks" ]
default = [ "parallel", "extensive_sanity_checks" ]
# default = [ ]
default = [ "parallel" ]
# default = [ "parallel", "print-trace" ]
# default = [ "parallel", "extensive_sanity_checks" ]
bench = [ "parallel" ]
# extensive sanity checks that are useful for debugging
extensive_sanity_checks = [ ]
extensive_sanity_checks = [
"poly-iop/extensive_sanity_checks",
"pcs/extensive_sanity_checks",
]
parallel = [
"rayon",
"ark-std/parallel",
"ark-ff/parallel",
"ark-poly/parallel",
"ark-ec/parallel",
"poly-iop/parallel",
"arithmetic/parallel",
"jf-primitives/parallel",
"pcs/parallel",
"util/parallel"
]
print-trace = [
"ark-std/print-trace",
"poly-iop/print-trace",
"arithmetic/print-trace",
"jf-primitives/print-trace",
]

149
hyperplonk/benches/bench.rs Normal file
View File

@@ -0,0 +1,149 @@
use std::{env, fs::File, time::Instant};
use ark_bls12_381::{Bls12_381, Fr};
use ark_serialize::Write;
use ark_std::test_rng;
use hyperplonk::{
prelude::{CustomizedGates, HyperPlonkErrors, MockCircuit},
HyperPlonkSNARK,
};
use pcs::{
prelude::{MultilinearKzgPCS, MultilinearUniversalParams, UnivariateUniversalParams},
PolynomialCommitmentScheme,
};
use poly_iop::PolyIOP;
use rayon::ThreadPoolBuilder;
fn main() -> Result<(), HyperPlonkErrors> {
let args: Vec<String> = env::args().collect();
let thread = args[1].parse().unwrap_or(12);
ThreadPoolBuilder::new()
.num_threads(thread)
.build_global()
.unwrap();
bench_vanilla_plonk(thread)?;
for degree in [1, 2, 4, 8, 16, 32] {
bench_high_degree_plonk(degree, thread)?;
}
Ok(())
}
fn bench_vanilla_plonk(thread: usize) -> Result<(), HyperPlonkErrors> {
let mut rng = test_rng();
let pcs_srs = MultilinearKzgPCS::<Bls12_381>::gen_srs_for_testing(&mut rng, 22)?;
let filename = format!("vanilla nv {}.txt", thread);
let mut file = File::create(filename).unwrap();
for nv in 1..16 {
let vanilla_gate = CustomizedGates::vanilla_plonk_gate();
bench_mock_circuit_zkp_helper(&mut file, nv, &vanilla_gate, &pcs_srs)?;
}
Ok(())
}
fn bench_high_degree_plonk(degree: usize, thread: usize) -> Result<(), HyperPlonkErrors> {
let mut rng = test_rng();
let pcs_srs = MultilinearKzgPCS::<Bls12_381>::gen_srs_for_testing(&mut rng, 22)?;
let filename = format!("high degree {} thread {}.txt", degree, thread);
let mut file = File::create(filename).unwrap();
for nv in 1..16 {
let vanilla_gate = CustomizedGates::vanilla_plonk_gate();
bench_mock_circuit_zkp_helper(&mut file, nv, &vanilla_gate, &pcs_srs)?;
}
Ok(())
}
fn bench_mock_circuit_zkp_helper(
file: &mut File,
nv: usize,
gate: &CustomizedGates,
pcs_srs: &(
MultilinearUniversalParams<Bls12_381>,
UnivariateUniversalParams<Bls12_381>,
),
) -> Result<(), HyperPlonkErrors> {
let repetition = if nv < 10 {
10
} else if nv < 20 {
5
} else {
2
};
//==========================================================
let start = Instant::now();
for _ in 0..repetition {
let circuit = MockCircuit::<Fr>::new(1 << nv, gate);
assert!(circuit.is_satisfied());
}
println!(
"mock circuit gen for {} variables: {} ns",
nv,
start.elapsed().as_nanos() / repetition as u128
);
let circuit = MockCircuit::<Fr>::new(1 << nv, gate);
assert!(circuit.is_satisfied());
let index = circuit.index;
//==========================================================
// generate pk and vks
let start = Instant::now();
for _ in 0..repetition {
let (_pk, _vk) = <PolyIOP<Fr> as HyperPlonkSNARK<
Bls12_381,
MultilinearKzgPCS<Bls12_381>,
>>::preprocess(&index, &pcs_srs)?;
}
println!(
"key extraction for {} variables: {} us",
nv,
start.elapsed().as_micros() / repetition as u128
);
let (pk, vk) =
<PolyIOP<Fr> as HyperPlonkSNARK<Bls12_381, MultilinearKzgPCS<Bls12_381>>>::preprocess(
&index, &pcs_srs,
)?;
//==========================================================
// generate a proof
let start = Instant::now();
for _ in 0..repetition {
let _proof =
<PolyIOP<Fr> as HyperPlonkSNARK<Bls12_381, MultilinearKzgPCS<Bls12_381>>>::prove(
&pk,
&circuit.witnesses[0].coeff_ref(),
&circuit.witnesses,
)?;
}
let t = start.elapsed().as_micros() / repetition as u128;
println!("proving for {} variables: {} us", nv, t);
file.write_all(format!("{} {}\n", nv, t).as_ref()).unwrap();
let proof = <PolyIOP<Fr> as HyperPlonkSNARK<Bls12_381, MultilinearKzgPCS<Bls12_381>>>::prove(
&pk,
&circuit.witnesses[0].coeff_ref(),
&circuit.witnesses,
)?;
//==========================================================
// verify a proof
let start = Instant::now();
for _ in 0..repetition {
let verify =
<PolyIOP<Fr> as HyperPlonkSNARK<Bls12_381, MultilinearKzgPCS<Bls12_381>>>::verify(
&vk,
&circuit.witnesses[0].coeff_ref(),
&proof,
)?;
assert!(verify);
}
println!(
"verifying for {} variables: {} us",
nv,
start.elapsed().as_micros() / repetition as u128
);
Ok(())
}

View File

@@ -0,0 +1,158 @@
use ark_std::cmp::max;
/// Customized gate is a list of tuples of
/// (coefficient, selector_index, wire_indices)
///
/// 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])
/// ]
///
/// CustomizedGates {
/// gates: vec![
/// (1, Some(0), vec![0, 0, 0, 0, 0]),
/// (-1, None, vec![1])
/// ],
/// };
/// where id_qL = 0 // first selector
/// id_W1 = 0 // first witness
/// id_w2 = 1 // second witness
///
/// NOTE: here coeff is a signed integer, instead of a field element
#[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
}
/// The number of selectors in a customized gate
pub fn num_selector_columns(&self) -> usize {
let mut res = 0;
for (_coeff, q, _ws) in self.gates.iter() {
// a same selector must not be used for multiple monomials.
if q.is_some() {
res += 1;
}
}
res
}
/// The number of witnesses in a customized gate
pub fn num_witness_columns(&self) -> usize {
let mut res = 0;
for (_coeff, _q, ws) in self.gates.iter() {
// witness list must be ordered
// so we just need to compare with the last one
if let Some(&p) = ws.last() {
if res < p {
res = p
}
}
}
// add one here because index starts from 0
res + 1
}
/// Return a vanilla plonk gate:
/// ``` ignore
/// q_L w_1 + q_R w_2 + q_O w_3 + q_M w1w2 + q_C = 0
/// ```
/// which is
/// ``` ignore
/// (1, Some(id_qL), vec![id_W1]),
/// (1, Some(id_qR), vec![id_W2]),
/// (1, Some(id_qO), vec![id_W3]),
/// (1, Some(id_qM), vec![id_W1, id_w2]),
/// (1, Some(id_qC), vec![]),
/// ```
pub fn vanilla_plonk_gate() -> Self {
Self {
gates: vec![
(1, Some(0), vec![0]),
(1, Some(1), vec![1]),
(1, Some(2), vec![2]),
(1, Some(3), vec![0, 1]),
(1, Some(4), vec![]),
],
}
}
/// Return a jellyfish turbo plonk gate:
/// ```ignore
/// q_1 w_1 + q_2 w_2 + q_3 w_3 + q_4 w4
/// + q_M1 w1w2 + q_M2 w3w4
/// + q_H1 w1^5 + q_H2 w2^5 + q_H3 w1^5 + q_H4 w2^5
/// + q_E w1w2w3w4
/// + q_O w5
/// + q_C
/// = 0
/// ```
/// with
/// - w = [w1, w2, w3, w4, w5]
/// - q = [ q_1, q_2, q_3, q_4, q_M1, q_M2, q_H1, q_H2, q_H3, q_H4, q_E,
/// q_O, q_c ]
///
/// which is
/// ```ignore
/// (1, Some(q[0]), vec![w[0]]),
/// (1, Some(q[1]), vec![w[1]]),
/// (1, Some(q[2]), vec![w[2]]),
/// (1, Some(q[3]), vec![w[3]]),
/// (1, Some(q[4]), vec![w[0], w[1]]),
/// (1, Some(q[5]), vec![w[2], w[3]]),
/// (1, Some(q[6]), vec![w[0], w[0], w[0], w[0], w[0]]),
/// (1, Some(q[7]), vec![w[1], w[1], w[1], w[1], w[1]]),
/// (1, Some(q[8]), vec![w[2], w[2], w[2], w[2], w[2]]),
/// (1, Some(q[9]), vec![w[3], w[3], w[3], w[3], w[3]]),
/// (1, Some(q[10]), vec![w[0], w[1], w[2], w[3]]),
/// (1, Some(q[11]), vec![w[4]]),
/// (1, Some(q[12]), vec![]),
/// ```
pub fn jellyfish_turbo_plonk_gate() -> Self {
CustomizedGates {
gates: vec![
(1, Some(0), vec![0]),
(1, Some(1), vec![1]),
(1, Some(2), vec![2]),
(1, Some(3), vec![3]),
(1, Some(4), vec![0, 1]),
(1, Some(5), vec![2, 3]),
(1, Some(6), vec![0, 0, 0, 0, 0]),
(1, Some(7), vec![1, 1, 1, 1, 1]),
(1, Some(8), vec![2, 2, 2, 2, 2]),
(1, Some(9), vec![3, 3, 3, 3, 3]),
(1, Some(10), vec![0, 1, 2, 3]),
(1, Some(11), vec![4]),
(1, Some(12), vec![]),
],
}
}
/// Generate a random gate for `num_witness` with a highest degree =
/// `degree`
pub fn mock_gate(num_witness: usize, degree: usize) -> Self {
let mut gates = vec![];
let high_degree_term = vec![0; degree];
gates.push((1, Some(0), high_degree_term));
for i in 1..num_witness {
gates.push((1, Some(i), vec![i]))
}
gates.push((1, Some(num_witness), vec![]));
CustomizedGates { gates }
}
}

View File

@@ -4,9 +4,9 @@ use arithmetic::ArithErrors;
use ark_serialize::SerializationError;
use ark_std::string::String;
use displaydoc::Display;
use jf_primitives::pcs::prelude::PCSError;
use pcs::prelude::PCSError;
use poly_iop::prelude::PolyIOPErrors;
use transcript::TranscriptErrors;
use transcript::TranscriptError;
/// A `enum` specifying the possible failure modes of hyperplonk.
#[derive(Display, Debug)]
@@ -24,9 +24,9 @@ pub enum HyperPlonkErrors {
/// PolyIOP error {0}
PolyIOPErrors(PolyIOPErrors),
/// PCS error {0}
PCSError(PCSError),
PCSErrors(PCSError),
/// Transcript error {0}
TranscriptError(TranscriptErrors),
TranscriptError(TranscriptError),
/// Arithmetic Error: {0}
ArithmeticErrors(ArithErrors),
}
@@ -45,12 +45,12 @@ impl From<PolyIOPErrors> for HyperPlonkErrors {
impl From<PCSError> for HyperPlonkErrors {
fn from(e: PCSError) -> Self {
Self::PCSError(e)
Self::PCSErrors(e)
}
}
impl From<TranscriptErrors> for HyperPlonkErrors {
fn from(e: TranscriptErrors) -> Self {
impl From<TranscriptError> for HyperPlonkErrors {
fn from(e: TranscriptError) -> Self {
Self::TranscriptError(e)
}
}

File diff suppressed because it is too large Load Diff

235
hyperplonk/src/mock.rs Normal file
View File

@@ -0,0 +1,235 @@
use arithmetic::identity_permutation_mle;
use ark_ff::PrimeField;
use ark_poly::MultilinearExtension;
use ark_std::{log2, test_rng};
use crate::{
custom_gate::CustomizedGates,
selectors::SelectorColumn,
structs::{HyperPlonkIndex, HyperPlonkParams},
witness::WitnessColumn,
};
pub struct MockCircuit<F: PrimeField> {
pub witnesses: Vec<WitnessColumn<F>>,
pub index: HyperPlonkIndex<F>,
}
impl<F: PrimeField> MockCircuit<F> {
/// Number of variables in a multilinear system
pub fn num_variables(&self) -> usize {
self.index.num_variables()
}
/// number of selector columns
pub fn num_selector_columns(&self) -> usize {
self.index.num_selector_columns()
}
/// number of witness columns
pub fn num_witness_columns(&self) -> usize {
self.index.num_witness_columns()
}
}
impl<F: PrimeField> MockCircuit<F> {
/// Generate a mock plonk circuit for the input constraint size.
pub fn new(num_constraints: usize, gate: &CustomizedGates) -> MockCircuit<F> {
let mut rng = test_rng();
let nv = log2(num_constraints);
let num_selectors = gate.num_selector_columns();
let num_witnesses = gate.num_witness_columns();
let log_n_wires = log2(num_witnesses);
let merged_nv = nv + log_n_wires;
let mut selectors: Vec<SelectorColumn<F>> = vec![SelectorColumn::default(); num_selectors];
let mut witnesses: Vec<WitnessColumn<F>> = vec![WitnessColumn::default(); num_witnesses];
for _cs_counter in 0..num_constraints {
let mut cur_selectors: Vec<F> = (0..(num_selectors - 1))
.map(|_| F::rand(&mut rng))
.collect();
let cur_witness: Vec<F> = (0..num_witnesses).map(|_| F::rand(&mut rng)).collect();
let mut last_selector = F::zero();
for (index, (coeff, q, wit)) in gate.gates.iter().enumerate() {
if index != num_selectors - 1 {
let mut cur_monomial = if *coeff < 0 {
-F::from((-coeff) as u64)
} else {
F::from(*coeff as u64)
};
cur_monomial = match q {
Some(p) => cur_monomial * cur_selectors[*p],
None => cur_monomial,
};
for wit_index in wit.iter() {
cur_monomial *= cur_witness[*wit_index];
}
last_selector += cur_monomial;
} else {
let mut cur_monomial = if *coeff < 0 {
-F::from((-coeff) as u64)
} else {
F::from(*coeff as u64)
};
for wit_index in wit.iter() {
cur_monomial *= cur_witness[*wit_index];
}
last_selector /= -cur_monomial;
}
}
cur_selectors.push(last_selector);
for i in 0..num_selectors {
selectors[i].append(cur_selectors[i]);
}
for i in 0..num_witnesses {
witnesses[i].append(cur_witness[i]);
}
}
let params = HyperPlonkParams {
num_constraints,
num_pub_input: num_constraints,
gate_func: gate.clone(),
};
let permutation = identity_permutation_mle(merged_nv as usize).to_evaluations();
let index = HyperPlonkIndex {
params,
permutation,
selectors,
};
Self { witnesses, index }
}
pub fn is_satisfied(&self) -> bool {
for current_row in 0..self.num_variables() {
let mut cur = F::zero();
for (coeff, q, wit) in self.index.params.gate_func.gates.iter() {
let mut cur_monomial = if *coeff < 0 {
-F::from((-coeff) as u64)
} else {
F::from(*coeff as u64)
};
cur_monomial = match q {
Some(p) => cur_monomial * self.index.selectors[*p].0[current_row],
None => cur_monomial,
};
for wit_index in wit.iter() {
cur_monomial *= self.witnesses[*wit_index].0[current_row];
}
cur += cur_monomial;
}
if !cur.is_zero() {
return false;
}
}
true
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{errors::HyperPlonkErrors, HyperPlonkSNARK};
use ark_bls12_381::{Bls12_381, Fr};
use pcs::{
prelude::{MultilinearKzgPCS, MultilinearUniversalParams, UnivariateUniversalParams},
PolynomialCommitmentScheme,
};
use poly_iop::PolyIOP;
#[test]
fn test_mock_circuit_sat() {
for i in 1..10 {
let vanilla_gate = CustomizedGates::vanilla_plonk_gate();
let circuit = MockCircuit::<Fr>::new(1 << i, &vanilla_gate);
assert!(circuit.is_satisfied());
let jf_gate = CustomizedGates::jellyfish_turbo_plonk_gate();
let circuit = MockCircuit::<Fr>::new(1 << i, &jf_gate);
assert!(circuit.is_satisfied());
for num_witness in 2..10 {
for degree in 1..10 {
let mock_gate = CustomizedGates::mock_gate(num_witness, degree);
let circuit = MockCircuit::<Fr>::new(1 << i, &mock_gate);
assert!(circuit.is_satisfied());
}
}
}
}
fn test_mock_circuit_zkp_helper(
nv: usize,
gate: &CustomizedGates,
pcs_srs: &(
MultilinearUniversalParams<Bls12_381>,
UnivariateUniversalParams<Bls12_381>,
),
) -> Result<(), HyperPlonkErrors> {
let circuit = MockCircuit::<Fr>::new(1 << nv, gate);
assert!(circuit.is_satisfied());
let index = circuit.index;
// generate pk and vks
let (pk, vk) =
<PolyIOP<Fr> as HyperPlonkSNARK<Bls12_381, MultilinearKzgPCS<Bls12_381>>>::preprocess(
&index, &pcs_srs,
)?;
// generate a proof and verify
let proof =
<PolyIOP<Fr> as HyperPlonkSNARK<Bls12_381, MultilinearKzgPCS<Bls12_381>>>::prove(
&pk,
&circuit.witnesses[0].0,
&circuit.witnesses,
)?;
let verify =
<PolyIOP<Fr> as HyperPlonkSNARK<Bls12_381, MultilinearKzgPCS<Bls12_381>>>::verify(
&vk,
&circuit.witnesses[0].0,
&proof,
)?;
assert!(verify);
Ok(())
}
#[test]
fn test_mock_circuit_zkp() -> Result<(), HyperPlonkErrors> {
let mut rng = test_rng();
let pcs_srs = MultilinearKzgPCS::<Bls12_381>::gen_srs_for_testing(&mut rng, 16)?;
for nv in 1..10 {
let vanilla_gate = CustomizedGates::vanilla_plonk_gate();
test_mock_circuit_zkp_helper(nv, &vanilla_gate, &pcs_srs)?;
}
for nv in 1..10 {
let tubro_gate = CustomizedGates::jellyfish_turbo_plonk_gate();
test_mock_circuit_zkp_helper(nv, &tubro_gate, &pcs_srs)?;
}
let nv = 5;
for num_witness in 2..10 {
for degree in [1, 2, 4, 8, 16] {
let mock_gate = CustomizedGates::mock_gate(num_witness, degree);
test_mock_circuit_zkp_helper(nv, &mock_gate, &pcs_srs)?;
}
}
Ok(())
}
#[test]
fn test_mock_circuit_e2e() -> Result<(), HyperPlonkErrors> {
let mut rng = test_rng();
let pcs_srs = MultilinearKzgPCS::<Bls12_381>::gen_srs_for_testing(&mut rng, 23)?;
let nv = 18;
let vanilla_gate = CustomizedGates::vanilla_plonk_gate();
test_mock_circuit_zkp_helper(nv, &vanilla_gate, &pcs_srs)?;
Ok(())
}
}

View File

@@ -0,0 +1,4 @@
pub use crate::{
custom_gate::CustomizedGates, errors::HyperPlonkErrors, mock::MockCircuit,
selectors::SelectorColumn, witness::WitnessColumn, HyperPlonkSNARK,
};

View File

@@ -9,7 +9,7 @@ use std::rc::Rc;
pub struct SelectorRow<F: PrimeField>(pub(crate) Vec<F>);
/// A column of selectors of length `#constraints`
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SelectorColumn<F: PrimeField>(pub(crate) Vec<F>);
impl<F: PrimeField> SelectorColumn<F> {
@@ -19,6 +19,11 @@ impl<F: PrimeField> SelectorColumn<F> {
log2(self.0.len()) as usize
}
/// Append a new element to the selector column
pub fn append(&mut self, new_element: F) {
self.0.push(new_element)
}
/// Build selector columns from rows
pub fn from_selector_rows(
selector_rows: &[SelectorRow<F>],

829
hyperplonk/src/snark.rs Normal file
View File

@@ -0,0 +1,829 @@
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(())
}
}

View File

@@ -1,11 +1,11 @@
//! Main module for the HyperPlonk PolyIOP.
use crate::selectors::SelectorColumn;
use crate::{custom_gate::CustomizedGates, selectors::SelectorColumn};
use ark_ec::PairingEngine;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::cmp::max;
use jf_primitives::pcs::PolynomialCommitmentScheme;
use ark_std::log2;
use pcs::PolynomialCommitmentScheme;
use poly_iop::prelude::{PermutationCheck, ZeroCheck};
use std::rc::Rc;
@@ -22,80 +22,86 @@ where
PCS: PolynomialCommitmentScheme<E>,
{
// =======================================================================
// PCS components: common
// witness related
// =======================================================================
/// PCS commit for witnesses
// PCS commit for witnesses
pub w_merged_com: PCS::Commitment,
// Batch opening for witness commitment
// - PermCheck eval: 1 point
// - ZeroCheck evals: #witness points
// - public input eval: 1 point
pub w_merged_batch_opening: PCS::BatchProof,
// Evaluations of Witness
// - PermCheck eval: 1 point
// - ZeroCheck evals: #witness points
// - public input eval: 1 point
pub w_merged_batch_evals: Vec<E::Fr>,
// =======================================================================
// PCS components: permutation check
// prod(x) related
// =======================================================================
/// prod(x)'s evaluations
/// 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), 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
pub witness_perm_check_opening: PCS::Proof,
/// Evaluates of witnesses on permutation check point
pub witness_perm_check_eval: E::Fr,
/// PCS openings for selectors on permutation check point
// TODO: replace me with a batch opening
pub perm_oracle_opening: PCS::Proof,
/// Evaluates of selectors on permutation check point
pub perm_oracle_eval: E::Fr,
// prod(x)'s openings
// - prod(0, x),
// - prod(1, x),
// - prod(x, 0),
// - prod(x, 1),
// - prod(1, ..., 1,0)
pub prod_batch_openings: PCS::BatchProof,
// prod(x)'s evaluations
// - prod(0, x),
// - prod(1, x),
// - prod(x, 0),
// - prod(x, 1),
// - prod(1, ..., 1,0)
pub prod_batch_evals: Vec<E::Fr>,
// =======================================================================
// PCS components: zero check
// selectors related
// =======================================================================
/// PCS openings for witness on zero check point
// TODO: replace me with a batch opening
pub witness_zero_check_openings: Vec<PCS::Proof>,
/// Evaluates of witnesses on zero check point
pub witness_zero_check_evals: Vec<E::Fr>,
/// PCS openings for selectors on zero check point
// TODO: replace me with a batch opening
pub selector_oracle_openings: Vec<PCS::Proof>,
/// Evaluates of selectors on zero check point
pub selector_oracle_evals: Vec<E::Fr>,
// PCS openings for selectors on zero check point
pub selector_batch_opening: PCS::BatchProof,
// Evaluates of selectors on zero check point
pub selector_batch_evals: Vec<E::Fr>,
// =======================================================================
// PCS components: public inputs
// IOP proofs
// =======================================================================
/// Evaluates of public inputs on r_pi from transcript
pub pi_eval: E::Fr,
/// Opening of public inputs on r_pi from transcript
pub pi_opening: PCS::Proof,
// =======================================================================
// IOP components
// =======================================================================
/// the custom gate zerocheck proof
// the custom gate zerocheck proof
pub zero_check_proof: <PC as ZeroCheck<E::Fr>>::ZeroCheckProof,
/// the permutation check proof for copy constraints
// the permutation check proof for copy constraints
pub perm_check_proof: PC::PermutationProof,
}
/// The HyperPlonk instance parameters, consists of the following:
/// - the number of variables in the poly-IOP
/// - binary log of the number of public input variables
/// - binary log of the number of selectors
/// - binary log of the number of witness wires
/// - the number of constraints
/// - number of public input columns
/// - the customized gate function
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct HyperPlonkParams {
/// the number of variables in polys
pub nv: usize,
/// binary log of the public input length
pub log_pub_input_len: usize,
// binary log of the number of selectors
pub log_n_selectors: usize,
/// binary log of the number of witness wires
pub log_n_wires: usize,
/// the number of constraints
pub num_constraints: usize,
/// number of public input
// public input is only 1 column and is implicitly the first witness column.
// this size must not exceed number of constraints.
pub num_pub_input: usize,
/// customized gate function
pub gate_func: CustomizedGates,
}
impl HyperPlonkParams {
/// Number of variables in a multilinear system
pub fn num_variables(&self) -> usize {
log2(self.num_constraints) as usize
}
/// number of selector columns
pub fn num_selector_columns(&self) -> usize {
self.gate_func.num_selector_columns()
}
/// number of witness columns
pub fn num_witness_columns(&self) -> usize {
self.gate_func.num_witness_columns()
}
}
/// The HyperPlonk index, consists of the following:
/// - HyperPlonk parameters
/// - the wire permutation
@@ -107,72 +113,56 @@ pub struct HyperPlonkIndex<F: PrimeField> {
pub selectors: Vec<SelectorColumn<F>>,
}
impl<F: PrimeField> HyperPlonkIndex<F> {
/// Number of variables in a multilinear system
pub fn num_variables(&self) -> usize {
self.params.num_variables()
}
/// number of selector columns
pub fn num_selector_columns(&self) -> usize {
self.params.num_selector_columns()
}
/// number of witness columns
pub fn num_witness_columns(&self) -> usize {
self.params.num_witness_columns()
}
}
/// 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 parameters for polynomial commitment
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkProvingKey<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> {
/// hyperplonk instance parameters
/// Hyperplonk instance parameters
pub params: HyperPlonkParams,
/// the preprocessed permutation polynomials
/// The preprocessed permutation polynomials
pub permutation_oracle: Rc<DenseMultilinearExtension<E::Fr>>,
/// the preprocessed selector polynomials
// TODO: merge the list into a single MLE
/// The preprocessed selector polynomials
pub selector_oracles: Vec<Rc<DenseMultilinearExtension<E::Fr>>>,
/// the parameters for PCS commitment
/// A commitment to the preprocessed selector polynomials
pub selector_com: PCS::Commitment,
/// The parameters for PCS commitment
pub pcs_param: PCS::ProverParam,
}
/// The HyperPlonk verifying key, consists of the following:
/// - the hyperplonk instance parameters
/// - the preprocessed polynomials output by the indexer
/// - the commitments to the preprocessed polynomials output by the indexer
/// - the parameters for polynomial commitment
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkVerifyingKey<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> {
/// hyperplonk instance parameters
/// Hyperplonk instance parameters
pub params: HyperPlonkParams,
/// the parameters for PCS commitment
/// The preprocessed permutation polynomials
pub permutation_oracle: Rc<DenseMultilinearExtension<E::Fr>>,
/// The parameters for PCS commitment
pub pcs_param: PCS::VerifierParam,
/// Selector's commitment
// TODO: replace me with a batch commitment
pub selector_com: Vec<PCS::Commitment>,
/// A commitment to the preprocessed selector polynomials
pub selector_com: PCS::Commitment,
/// Permutation oracle's commitment
pub perm_com: PCS::Commitment,
}
/// Customized gate is a list of tuples of
/// (coefficient, selector_index, wire_indices)
///
/// Example:
/// q_L(X) * W_1(X)^5 - W_2(X)
/// is represented as
/// vec![
/// ( 1, Some(id_qL), vec![id_W1, id_W1, id_W1, id_W1, id_W1]),
/// (-1, None, vec![id_W2])
/// ]
///
/// CustomizedGates {
/// gates: vec![
/// (1, Some(0), vec![0, 0, 0, 0, 0]),
/// (-1, None, vec![1])
/// ],
/// };
/// where id_qL = 0 // first selector
/// id_W1 = 0 // first witness
/// id_w2 = 1 // second witness
///
/// NOTE: here coeff is a signed integer, instead of a field element
#[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
}
}

View File

@@ -1,16 +1,87 @@
use std::rc::Rc;
use arithmetic::VirtualPolynomial;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use crate::{
errors::HyperPlonkErrors,
structs::{CustomizedGates, HyperPlonkParams},
custom_gate::CustomizedGates, errors::HyperPlonkErrors, structs::HyperPlonkParams,
witness::WitnessColumn,
};
use arithmetic::VirtualPolynomial;
use ark_ec::PairingEngine;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use pcs::PolynomialCommitmentScheme;
use std::{borrow::Borrow, rc::Rc};
use poly_iop::prelude::bit_decompose;
/// An accumulator structure that holds a polynomial and
/// its opening points
#[derive(Debug)]
pub(super) struct PcsAccumulator<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> {
pub(crate) polynomial: Option<PCS::Polynomial>,
pub(crate) poly_commit: Option<PCS::Commitment>,
pub(crate) points: Vec<PCS::Point>,
}
impl<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> PcsAccumulator<E, PCS> {
/// Create an empty accumulator.
pub(super) fn new() -> Self {
Self {
polynomial: None,
poly_commit: None,
points: vec![],
}
}
/// Initialize the polynomial; requires both the polynomial
/// and its commitment.
pub(super) fn init_poly(
&mut self,
polynomial: PCS::Polynomial,
commitment: PCS::Commitment,
) -> Result<(), HyperPlonkErrors> {
if self.polynomial.is_some() || self.poly_commit.is_some() {
return Err(HyperPlonkErrors::InvalidProver(
"poly already set for accumulator".to_string(),
));
}
self.polynomial = Some(polynomial);
self.poly_commit = Some(commitment);
Ok(())
}
/// Push a new evaluation point into the accumulator
pub(super) fn insert_point(&mut self, point: &PCS::Point) {
self.points.push(point.clone())
}
/// Batch open all the points over a merged polynomial.
/// A simple wrapper of PCS::multi_open
pub(super) fn batch_open(
&self,
prover_param: impl Borrow<PCS::ProverParam>,
) -> Result<(PCS::BatchProof, Vec<PCS::Evaluation>), HyperPlonkErrors> {
let poly = match &self.polynomial {
Some(p) => p,
None => {
return Err(HyperPlonkErrors::InvalidProver(
"poly is set for accumulator".to_string(),
))
},
};
let commitment = match &self.poly_commit {
Some(p) => p,
None => {
return Err(HyperPlonkErrors::InvalidProver(
"poly is set for accumulator".to_string(),
))
},
};
Ok(PCS::multi_open_single_poly(
prover_param.borrow(),
commitment,
poly,
&self.points,
)?)
}
}
/// Build MLE from matrix of witnesses.
///
@@ -42,30 +113,37 @@ macro_rules! build_mle {
}
/// Sanity-check for HyperPlonk SNARK proving
pub(crate) fn prove_sanity_check<F: PrimeField>(
pub(crate) fn prover_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 must be no greater than num_constraints
if pub_input.len() > params.num_constraints {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input length {} is greater than num constraits {}",
pub_input.len(),
params.num_pub_input
)));
}
// public input length
if pub_input.len() != 1 << ell {
if pub_input.len() != params.num_pub_input {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input length is not correct: got {}, expect {}",
pub_input.len(),
1 << ell
params.num_pub_input
)));
}
// witnesses length
for (i, w) in witnesses.iter().enumerate() {
if w.0.len() != 1 << num_vars {
if w.0.len() != params.num_constraints {
return Err(HyperPlonkErrors::InvalidProver(format!(
"{}-th witness length is not correct: got {}, expect {}",
i,
pub_input.len(),
1 << ell
w.0.len(),
params.num_constraints
)));
}
}
@@ -161,17 +239,6 @@ pub(crate) fn eval_f<F: PrimeField>(
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)]
mod test {
use super::*;

View File

@@ -9,7 +9,7 @@ use std::rc::Rc;
pub struct WitnessRow<F: PrimeField>(pub(crate) Vec<F>);
/// A column of witnesses of length `#constraints`
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct WitnessColumn<F: PrimeField>(pub(crate) Vec<F>);
impl<F: PrimeField> WitnessColumn<F> {
@@ -19,6 +19,11 @@ impl<F: PrimeField> WitnessColumn<F> {
log2(self.0.len()) as usize
}
/// Append a new element to the witness column
pub fn append(&mut self, new_element: F) {
self.0.push(new_element)
}
/// Build witness columns from rows
pub fn from_witness_rows(
witness_rows: &[WitnessRow<F>],
@@ -42,6 +47,10 @@ impl<F: PrimeField> WitnessColumn<F> {
Ok(res)
}
pub fn coeff_ref(&self) -> &[F] {
self.0.as_ref()
}
}
impl<F: PrimeField> From<&WitnessColumn<F>> for DenseMultilinearExtension<F> {