mirror of
https://github.com/arnaucube/hyperplonk.git
synced 2026-01-12 08:51:33 +01:00
enabling batch opening and mock tests (#80)
- add mock circuits - add vanilla and jellyfish plonk gates - performance tuning
This commit is contained in:
@@ -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
149
hyperplonk/benches/bench.rs
Normal 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(())
|
||||
}
|
||||
158
hyperplonk/src/custom_gate.rs
Normal file
158
hyperplonk/src/custom_gate.rs
Normal 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 }
|
||||
}
|
||||
}
|
||||
@@ -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
235
hyperplonk/src/mock.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
4
hyperplonk/src/prelude.rs
Normal file
4
hyperplonk/src/prelude.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub use crate::{
|
||||
custom_gate::CustomizedGates, errors::HyperPlonkErrors, mock::MockCircuit,
|
||||
selectors::SelectorColumn, witness::WitnessColumn, HyperPlonkSNARK,
|
||||
};
|
||||
@@ -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
829
hyperplonk/src/snark.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user