Browse Source

initial integration of hyperplonk snark(#39)

main
zhenfei 2 years ago
committed by GitHub
parent
commit
a6ea6ac26b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1943 additions and 441 deletions
  1. +1
    -0
      Cargo.toml
  2. +34
    -0
      arithmetic/Cargo.toml
  3. +21
    -0
      arithmetic/src/errors.rs
  4. +7
    -0
      arithmetic/src/lib.rs
  5. +33
    -0
      arithmetic/src/multilinear_polynomial.rs
  6. +44
    -48
      arithmetic/src/virtual_polynomial.rs
  7. +36
    -0
      hyperplonk/Cargo.toml
  8. +64
    -0
      hyperplonk/src/errors.rs
  9. +846
    -3
      hyperplonk/src/lib.rs
  10. +46
    -0
      hyperplonk/src/selectors.rs
  11. +156
    -0
      hyperplonk/src/structs.rs
  12. +210
    -0
      hyperplonk/src/utils.rs
  13. +67
    -0
      hyperplonk/src/witness.rs
  14. +1
    -0
      pcs/Cargo.toml
  15. +2
    -2
      pcs/benches/bench.rs
  16. +4
    -4
      pcs/src/errors.rs
  17. +12
    -2
      pcs/src/lib.rs
  18. +5
    -5
      pcs/src/multilinear_kzg/batching.rs
  19. +68
    -29
      pcs/src/multilinear_kzg/mod.rs
  20. +11
    -27
      pcs/src/multilinear_kzg/util.rs
  21. +0
    -3
      pcs/src/param.rs
  22. +1
    -1
      pcs/src/prelude.rs
  23. +18
    -2
      pcs/src/univariate_kzg/mod.rs
  24. +6
    -1
      poly-iop/Cargo.toml
  25. +4
    -4
      poly-iop/benches/bench.rs
  26. +13
    -4
      poly-iop/src/errors.rs
  27. +0
    -126
      poly-iop/src/hyperplonk/mod.rs
  28. +1
    -4
      poly-iop/src/lib.rs
  29. +113
    -132
      poly-iop/src/perm_check/mod.rs
  30. +58
    -14
      poly-iop/src/perm_check/util.rs
  31. +11
    -0
      poly-iop/src/prelude.rs
  32. +5
    -3
      poly-iop/src/prod_check/mod.rs
  33. +7
    -4
      poly-iop/src/structs.rs
  34. +16
    -10
      poly-iop/src/sum_check/mod.rs
  35. +2
    -2
      poly-iop/src/sum_check/prover.rs
  36. +7
    -1
      poly-iop/src/sum_check/verifier.rs
  37. +12
    -8
      poly-iop/src/zero_check/mod.rs
  38. +1
    -2
      transcript/src/lib.rs

+ 1
- 0
Cargo.toml

@ -1,5 +1,6 @@
[workspace]
members = [
"arithmetic",
"hyperplonk",
"pcs",
"poly-iop",

+ 34
- 0
arithmetic/Cargo.toml

@ -0,0 +1,34 @@
[package]
name = "arithmetic"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ark-ff = { version = "^0.3.0", default-features = false }
ark-std = { version = "^0.3.0", default-features = false }
ark-poly = { version = "^0.3.0", default-features = false }
ark-serialize = { version = "^0.3.0", default-features = false }
ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "curve" ] }
rand_chacha = { version = "0.3.0", default-features = false }
displaydoc = { version = "0.2.3", default-features = false }
rayon = { version = "1.5.2", default-features = false, optional = true }
[dev-dependencies]
ark-ec = { version = "^0.3.0", default-features = false }
[features]
# default = [ "parallel", "print-trace" ]
default = [ "parallel" ]
parallel = [
"rayon",
"ark-std/parallel",
"ark-ff/parallel",
"ark-poly/parallel"
]
print-trace = [
"ark-std/print-trace"
]

+ 21
- 0
arithmetic/src/errors.rs

@ -0,0 +1,21 @@
//! Error module.
use ark_std::string::String;
use displaydoc::Display;
/// A `enum` specifying the possible failure modes of the arithmetics.
#[derive(Display, Debug)]
pub enum ArithErrors {
/// Invalid parameters: {0}
InvalidParameters(String),
/// Should not arrive to this point
ShouldNotArrive,
/// An error during (de)serialization: {0}
SerializationErrors(ark_serialize::SerializationError),
}
impl From<ark_serialize::SerializationError> for ArithErrors {
fn from(e: ark_serialize::SerializationError) -> Self {
Self::SerializationErrors(e)
}
}

+ 7
- 0
arithmetic/src/lib.rs

@ -0,0 +1,7 @@
mod errors;
mod multilinear_polynomial;
mod virtual_polynomial;
pub use errors::ArithErrors;
pub use multilinear_polynomial::{random_zero_mle_list, DenseMultilinearExtension};
pub use virtual_polynomial::{build_eq_x_r, VPAuxInfo, VirtualPolynomial};

+ 33
- 0
arithmetic/src/multilinear_polynomial.rs

@ -0,0 +1,33 @@
use ark_ff::PrimeField;
use ark_std::{end_timer, rand::RngCore, start_timer};
use std::rc::Rc;
pub use ark_poly::DenseMultilinearExtension;
// Build a randomize list of mle-s whose sum is zero.
pub fn random_zero_mle_list<F: PrimeField, R: RngCore>(
nv: usize,
degree: usize,
rng: &mut R,
) -> Vec<Rc<DenseMultilinearExtension<F>>> {
let start = start_timer!(|| "sample random zero mle list");
let mut multiplicands = Vec::with_capacity(degree);
for _ in 0..degree {
multiplicands.push(Vec::with_capacity(1 << nv))
}
for _ in 0..(1 << nv) {
multiplicands[0].push(F::zero());
for e in multiplicands.iter_mut().skip(1) {
e.push(F::rand(rng));
}
}
let list = multiplicands
.into_iter()
.map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x)))
.collect();
end_timer!(start);
list
}

poly-iop/src/virtual_poly.rs → arithmetic/src/virtual_polynomial.rs

@ -1,7 +1,7 @@
//! This module defines our main mathematical object `VirtualPolynomial`; and
//! various functions associated with it.
use crate::errors::PolyIOPErrors;
use crate::{errors::ArithErrors, multilinear_polynomial::random_zero_mle_list};
use ark_ff::PrimeField;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_serialize::{CanonicalSerialize, SerializationError, Write};
@ -84,6 +84,7 @@ impl Add for &VirtualPolynomial {
}
}
// TODO: convert this into a trait
impl<F: PrimeField> VirtualPolynomial<F> {
/// Creates an empty virtual polynomial with `num_variables`.
pub fn new(num_variables: usize) -> Self {
@ -129,12 +130,12 @@ impl VirtualPolynomial {
&mut self,
mle_list: impl IntoIterator<Item = Rc<DenseMultilinearExtension<F>>>,
coefficient: F,
) -> Result<(), PolyIOPErrors> {
) -> Result<(), ArithErrors> {
let mle_list: Vec<Rc<DenseMultilinearExtension<F>>> = mle_list.into_iter().collect();
let mut indexed_product = Vec::with_capacity(mle_list.len());
if mle_list.is_empty() {
return Err(PolyIOPErrors::InvalidParameters(
return Err(ArithErrors::InvalidParameters(
"input mle_list is empty".to_string(),
));
}
@ -143,7 +144,7 @@ impl VirtualPolynomial {
for mle in mle_list {
if mle.num_vars != self.aux_info.num_variables {
return Err(PolyIOPErrors::InvalidParameters(format!(
return Err(ArithErrors::InvalidParameters(format!(
"product has a multiplicand with wrong number of variables {} vs {}",
mle.num_vars, self.aux_info.num_variables
)));
@ -171,11 +172,11 @@ impl VirtualPolynomial {
&mut self,
mle: Rc<DenseMultilinearExtension<F>>,
coefficient: F,
) -> Result<(), PolyIOPErrors> {
) -> Result<(), ArithErrors> {
let start = start_timer!(|| "mul by mle");
if mle.num_vars != self.aux_info.num_variables {
return Err(PolyIOPErrors::InvalidParameters(format!(
return Err(ArithErrors::InvalidParameters(format!(
"product has a multiplicand with wrong number of variables {} vs {}",
mle.num_vars, self.aux_info.num_variables
)));
@ -209,11 +210,11 @@ impl VirtualPolynomial {
/// Evaluate the virtual polynomial at point `point`.
/// Returns an error is point.len() does not match `num_variables`.
pub fn evaluate(&self, point: &[F]) -> Result<F, PolyIOPErrors> {
pub fn evaluate(&self, point: &[F]) -> Result<F, ArithErrors> {
let start = start_timer!(|| "evaluation");
if self.aux_info.num_variables != point.len() {
return Err(PolyIOPErrors::InvalidParameters(format!(
return Err(ArithErrors::InvalidParameters(format!(
"wrong number of variables {} vs {}",
self.aux_info.num_variables,
point.len()
@ -246,7 +247,7 @@ impl VirtualPolynomial {
num_multiplicands_range: (usize, usize),
num_products: usize,
rng: &mut R,
) -> Result<(Self, F), PolyIOPErrors> {
) -> Result<(Self, F), ArithErrors> {
let start = start_timer!(|| "sample random virtual polynomial");
let mut sum = F::zero();
@ -271,7 +272,7 @@ impl VirtualPolynomial {
num_multiplicands_range: (usize, usize),
num_products: usize,
rng: &mut R,
) -> Result<Self, PolyIOPErrors> {
) -> Result<Self, ArithErrors> {
let mut poly = VirtualPolynomial::new(nv);
for _ in 0..num_products {
let num_multiplicands =
@ -290,11 +291,11 @@ impl VirtualPolynomial {
// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
//
// This function is used in ZeroCheck.
pub(crate) fn build_f_hat(&self, r: &[F]) -> Result<Self, PolyIOPErrors> {
pub fn build_f_hat(&self, r: &[F]) -> Result<Self, ArithErrors> {
let start = start_timer!(|| "zero check build hat f");
if self.aux_info.num_variables != r.len() {
return Err(PolyIOPErrors::InvalidParameters(format!(
return Err(ArithErrors::InvalidParameters(format!(
"r.len() is different from number of variables: {} vs {}",
r.len(),
self.aux_info.num_variables
@ -308,6 +309,19 @@ impl VirtualPolynomial {
end_timer!(start);
Ok(res)
}
/// Print out the evaluation map for testing. Panic if the num_vars > 5.
pub fn print_evals(&self) {
if self.aux_info.num_variables > 5 {
panic!("this function is used for testing only. cannot print more than 5 num_vars")
}
for i in 0..1 << self.aux_info.num_variables {
let point = bit_decompose(i, self.aux_info.num_variables);
let point_fr: Vec<F> = point.iter().map(|&x| F::from(x)).collect();
println!("{} {}", i, self.evaluate(point_fr.as_ref()).unwrap())
}
println!()
}
}
/// Sample a random list of multilinear polynomials.
@ -346,41 +360,15 @@ fn random_mle_list(
(list, sum)
}
// Build a randomize list of mle-s whose sum is zero.
pub fn random_zero_mle_list<F: PrimeField, R: RngCore>(
nv: usize,
degree: usize,
rng: &mut R,
) -> Vec<Rc<DenseMultilinearExtension<F>>> {
let start = start_timer!(|| "sample random zero mle list");
let mut multiplicands = Vec::with_capacity(degree);
for _ in 0..degree {
multiplicands.push(Vec::with_capacity(1 << nv))
}
for _ in 0..(1 << nv) {
multiplicands[0].push(F::zero());
for e in multiplicands.iter_mut().skip(1) {
e.push(F::rand(rng));
}
}
let list = multiplicands
.into_iter()
.map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x)))
.collect();
end_timer!(start);
list
}
// This function build the eq(x, r) polynomial for any given r.
//
// Evaluate
// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
// over r, which is
// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i))
fn build_eq_x_r<F: PrimeField>(r: &[F]) -> Result<Rc<DenseMultilinearExtension<F>>, PolyIOPErrors> {
pub fn build_eq_x_r<F: PrimeField>(
r: &[F],
) -> Result<Rc<DenseMultilinearExtension<F>>, ArithErrors> {
let start = start_timer!(|| "zero check build eq_x_r");
// we build eq(x,r) from its evaluations
@ -407,11 +395,9 @@ fn build_eq_x_r(r: &[F]) -> Result
/// A helper function to build eq(x, r) recursively.
/// This function takes `r.len()` steps, and for each step it requires a maximum
/// `r.len()-1` multiplications.
fn build_eq_x_r_helper<F: PrimeField>(r: &[F], buf: &mut Vec<F>) -> Result<(), PolyIOPErrors> {
fn build_eq_x_r_helper<F: PrimeField>(r: &[F], buf: &mut Vec<F>) -> Result<(), ArithErrors> {
if r.is_empty() {
return Err(PolyIOPErrors::InvalidParameters(
"r length is 0".to_string(),
));
return Err(ArithErrors::InvalidParameters("r length is 0".to_string()));
} else if r.len() == 1 {
// initializing the buffer with [1-r_0, r_0]
buf.push(F::one() - r[0]);
@ -436,16 +422,26 @@ fn build_eq_x_r_helper(r: &[F], buf: &mut Vec) -> Result<(), P
Ok(())
}
/// Decompose an integer into a binary vector in little endian.
pub fn bit_decompose(input: u64, num_var: usize) -> Vec<bool> {
let mut res = Vec::with_capacity(num_var);
let mut i = input;
for _ in 0..num_var {
res.push(i & 1 == 1);
i >>= 1;
}
res
}
#[cfg(test)]
mod test {
use super::*;
use crate::utils::bit_decompose;
use ark_bls12_381::Fr;
use ark_ff::UniformRand;
use ark_std::test_rng;
#[test]
fn test_virtual_polynomial_additions() -> Result<(), PolyIOPErrors> {
fn test_virtual_polynomial_additions() -> Result<(), ArithErrors> {
let mut rng = test_rng();
for nv in 2..5 {
for num_products in 2..5 {
@ -468,7 +464,7 @@ mod test {
}
#[test]
fn test_virtual_polynomial_mul_by_mle() -> Result<(), PolyIOPErrors> {
fn test_virtual_polynomial_mul_by_mle() -> Result<(), ArithErrors> {
let mut rng = test_rng();
for nv in 2..5 {
for num_products in 2..5 {

+ 36
- 0
hyperplonk/Cargo.toml

@ -6,3 +6,39 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
poly-iop = { path = "../poly-iop" }
pcs = { path = "../pcs" }
ark-std = { version = "^0.3.0", default-features = false }
ark-ec = { version = "^0.3.0", default-features = false }
ark-ff = { version = "^0.3.0", default-features = false }
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 }
transcript = { path = "../transcript" }
arithmetic = { path = "../arithmetic" }
[dev-dependencies]
ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "curve" ] }
[features]
default = [ "parallel", "print-trace" ]
# default = [ "parallel" ]
parallel = [
"ark-std/parallel",
"ark-ff/parallel",
"ark-poly/parallel",
"ark-ec/parallel",
"poly-iop/parallel",
"pcs/parallel",
"arithmetic/parallel",
]
print-trace = [
"ark-std/print-trace",
"poly-iop/print-trace",
"pcs/print-trace",
"arithmetic/print-trace",
]

+ 64
- 0
hyperplonk/src/errors.rs

@ -0,0 +1,64 @@
//! Error module.
use arithmetic::ArithErrors;
use ark_serialize::SerializationError;
use ark_std::string::String;
use displaydoc::Display;
use pcs::prelude::PCSErrors;
use poly_iop::prelude::PolyIOPErrors;
use transcript::TranscriptErrors;
/// A `enum` specifying the possible failure modes of hyperplonk.
#[allow(dead_code)]
// todo: REMOVE
#[derive(Display, Debug)]
pub enum HyperPlonkErrors {
/// Invalid Prover: {0}
InvalidProver(String),
/// Invalid Verifier: {0}
InvalidVerifier(String),
/// Invalid Proof: {0}
InvalidProof(String),
/// Invalid parameters: {0}
InvalidParameters(String),
/// An error during (de)serialization: {0}
SerializationError(SerializationError),
/// PolyIOP error {0}
PolyIOPErrors(PolyIOPErrors),
/// PCS error {0}
PCSErrors(PCSErrors),
/// Transcript error {0}
TranscriptError(TranscriptErrors),
/// Arithmetic Error: {0}
ArithmeticErrors(ArithErrors),
}
impl From<SerializationError> for HyperPlonkErrors {
fn from(e: ark_serialize::SerializationError) -> Self {
Self::SerializationError(e)
}
}
impl From<PolyIOPErrors> for HyperPlonkErrors {
fn from(e: PolyIOPErrors) -> Self {
Self::PolyIOPErrors(e)
}
}
impl From<PCSErrors> for HyperPlonkErrors {
fn from(e: PCSErrors) -> Self {
Self::PCSErrors(e)
}
}
impl From<TranscriptErrors> for HyperPlonkErrors {
fn from(e: TranscriptErrors) -> Self {
Self::TranscriptError(e)
}
}
impl From<ArithErrors> for HyperPlonkErrors {
fn from(e: ArithErrors) -> Self {
Self::ArithmeticErrors(e)
}
}

+ 846
- 3
hyperplonk/src/lib.rs

@ -1,8 +1,851 @@
//! Main module for the HyperPlonk PolyIOP.
use arithmetic::VPAuxInfo;
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::{end_timer, log2, start_timer, One, Zero};
use errors::HyperPlonkErrors;
use pcs::prelude::{compute_qx_degree, merge_polynomials, PCSErrors, PolynomialCommitmentScheme};
use poly_iop::{
prelude::{PermutationCheck, SumCheck, ZeroCheck},
PolyIOP,
};
use selectors::SelectorColumn;
use std::{marker::PhantomData, rc::Rc};
use structs::{
HyperPlonkParams, HyperPlonkProof, HyperPlonkProvingKey, HyperPlonkSubClaim,
HyperPlonkVerifyingKey,
};
use utils::build_f;
use witness::WitnessColumn;
mod errors;
mod selectors;
mod structs;
mod utils;
mod witness;
/// A trait for HyperPlonk Poly-IOPs.
/// A HyperPlonk is derived from SumChecks, ZeroChecks and PermutationChecks.
pub trait HyperPlonkSNARK<E, PCS>:
SumCheck<E::Fr> + ZeroCheck<E::Fr> + PermutationCheck<E::Fr>
where
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E>,
{
type Parameters;
type ProvingKey;
type VerifyingKey;
type Proof;
type SubClaim;
/// Generate the preprocessed polynomials output by the indexer.
///
/// Inputs:
/// - `params`: HyperPlonk instance parameters
/// - `permutation`: the permutation for the copy constraints
/// - `selectors`: the list of selector vectors for custom gates
/// Outputs:
/// - The HyperPlonk proving key, which includes the preprocessed
/// polynomials.
fn preprocess(
params: &Self::Parameters,
pcs_srs: &PCS::SRS,
permutation: &[E::Fr],
selectors: &[SelectorColumn<E::Fr>],
) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors>;
/// Generate HyperPlonk SNARK proof.
///
/// Inputs:
/// - `pk`: circuit proving key
/// - `pub_input`: online public input
/// - `witness`: witness assignment
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - The HyperPlonk SNARK proof.
fn prove(
pk: &Self::ProvingKey,
pub_input: &[E::Fr],
witnesses: &[WitnessColumn<E::Fr>],
transcript: &mut Self::Transcript,
) -> Result<Self::Proof, HyperPlonkErrors>;
/// Verify the HyperPlonk proof and generate the evaluation subclaims to be
/// checked later by the SNARK verifier.
///
/// Inputs:
/// - `params`: instance parameter
/// - `pub_input`: online public input
/// - `proof`: HyperPlonk SNARK proof
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - Return error if the verification fails, otherwise return the
/// evaluation subclaim
fn verify(
params: &Self::VerifyingKey,
pub_input: &[E::Fr],
proof: &Self::Proof,
transcript: &mut Self::Transcript,
) -> Result<Self::SubClaim, HyperPlonkErrors>;
}
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 Parameters = HyperPlonkParams;
type ProvingKey = HyperPlonkProvingKey<E, PCS>;
type VerifyingKey = HyperPlonkVerifyingKey<E, PCS>;
type Proof = HyperPlonkProof<E, PCS, Self, Self>;
type SubClaim = HyperPlonkSubClaim<E::Fr, Self, Self>;
/// Generate the preprocessed polynomials output by the indexer.
///
/// Inputs:
/// - `params`: HyperPlonk instance parameters
/// - `permutation`: the permutation for the copy constraints
/// - `selectors`: the list of selector vectors for custom gates
/// Outputs:
/// - The HyperPlonk proving key, which includes the preprocessed
/// polynomials.
fn preprocess(
params: &Self::Parameters,
pcs_srs: &PCS::SRS,
permutation: &[E::Fr],
selectors: &[SelectorColumn<E::Fr>],
) -> Result<(Self::ProvingKey, Self::VerifyingKey), HyperPlonkErrors> {
let num_vars = params.nv;
let log_num_witness_polys = params.log_n_wires;
// number of variables in merged polynomial for Multilinear-KZG
let merged_nv = num_vars + log_num_witness_polys;
// degree of q(x) for Univariate-KZG
let supported_uni_degree = compute_qx_degree(num_vars, 1 << log_num_witness_polys);
// extract PCS prover and verifier keys from SRS
let (pcs_prover_param, pcs_verifier_param) = PCS::trim(
pcs_srs,
log2(supported_uni_degree) as usize,
Some(merged_nv + 1),
)?;
// build permutation oracles
let permutation_oracles = Rc::new(DenseMultilinearExtension::from_evaluations_slice(
// num_vars = merged_nv + 1 because this oracle encodes both s_id and s_perm
merged_nv + 1,
permutation,
));
let perm_com = PCS::commit(&pcs_prover_param, &permutation_oracles)?;
// build selector oracles and commit to it
let selector_oracles: Vec<Rc<DenseMultilinearExtension<E::Fr>>> = selectors
.iter()
.map(|s| Rc::new(DenseMultilinearExtension::from(s)))
.collect();
let selector_com = selector_oracles
.iter()
.map(|poly| PCS::commit(&pcs_prover_param, poly))
.collect::<Result<Vec<PCS::Commitment>, PCSErrors>>()?;
Ok((
Self::ProvingKey {
params: params.clone(),
permutation_oracles,
selector_oracles,
pcs_param: pcs_prover_param,
},
Self::VerifyingKey {
params: params.clone(),
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
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// 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_oracles`, and
/// obtain a PermCheckSubClaim.
///
/// 4. Generate evaluations and corresponding proofs
/// - permutation check evaluations and proofs
/// - zero check evaluations and proofs
/// - public input consistency checks
fn prove(
pk: &Self::ProvingKey,
pub_input: &[E::Fr],
witnesses: &[WitnessColumn<E::Fr>],
transcript: &mut Self::Transcript,
) -> Result<Self::Proof, HyperPlonkErrors> {
let start = start_timer!(|| "hyperplonk proving");
// witness assignment of length 2^n
let num_vars = pk.params.nv;
let log_num_witness_polys = pk.params.log_n_wires;
// number of variables in merged polynomial for Multilinear-KZG
let merged_nv = num_vars + log_num_witness_polys;
// degree of q(x) for Univariate-KZG
let _supported_uni_degree = compute_qx_degree(num_vars, 1 << log_num_witness_polys);
// online public input of length 2^\ell
let ell = pk.params.log_pub_input_len;
let witness_polys: Vec<Rc<DenseMultilinearExtension<E::Fr>>> = witnesses
.iter()
.map(|w| Rc::new(DenseMultilinearExtension::from(w)))
.collect();
let pi_poly = Rc::new(DenseMultilinearExtension::from_evaluations_slice(
ell as usize,
pub_input,
));
// =======================================================================
// 0. sanity checks
// =======================================================================
// public input length
if pub_input.len() != 1 << ell {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input length is not correct: got {}, expect {}",
pub_input.len(),
1 << ell
)));
}
// witnesses length
for (i, w) in witnesses.iter().enumerate() {
if w.0.len() != 1 << num_vars {
return Err(HyperPlonkErrors::InvalidProver(format!(
"{}-th witness length is not correct: got {}, expect {}",
i,
pub_input.len(),
1 << ell
)));
}
}
// check public input matches witness[0]'s first 2^ell elements
let pi_in_w0 =
Rc::new(witness_polys[0].fix_variables(vec![E::Fr::zero(); num_vars - ell].as_ref()));
if pi_in_w0 != pi_poly {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input {:?} does not match witness[0] {:?}",
pi_poly, pi_in_w0,
)));
}
// =======================================================================
// 1. Commit Witness polynomials `w_i(x)` and append commitment to
// transcript
// =======================================================================
let step = start_timer!(|| "commit witnesses");
let mut witness_commits = vec![];
// TODO: batch commit
for wi_poly in witness_polys.iter() {
let wi_com = PCS::commit(&pk.pcs_param, wi_poly)?;
witness_commits.push(wi_com);
}
let w_merged = merge_polynomials(&witness_polys)?;
if w_merged.num_vars != merged_nv {
return Err(HyperPlonkErrors::InvalidParameters(format!(
"merged witness poly has a different num_var ({}) from expected ({})",
w_merged.num_vars, merged_nv
)));
}
let w_merged_com = PCS::commit(&pk.pcs_param, &Rc::new(w_merged.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.nv,
&pk.selector_oracles,
&witness_polys,
)?;
let zero_check_proof = <Self as ZeroCheck<E::Fr>>::prove(&fx, transcript)?;
end_timer!(step);
// =======================================================================
// 3. Run permutation check on `\{w_i(x)\}` and `permutation_oracles`, and
// obtain a PermCheckSubClaim.
//
// 3.1. `generate_challenge` from current transcript (generate beta, gamma)
// 3.2. `compute_product` to build `prod(x)` etc. from f, g and s_perm
// 3.3. push a commitment of `prod(x)` to the transcript
// 3.4. `update_challenge` with the updated transcript
// 3.5. `prove` to generate the proof
// =======================================================================
let step = start_timer!(|| "Permutation check on w_i(x)");
// 3.1 `generate_challenge` from current transcript (generate beta, gamma)
let mut permutation_challenge = Self::generate_challenge(transcript)?;
// 3.2. `compute_product` to build `prod(x)` etc. from f, g and s_perm
// s_perm is the second half of permutation oracle
let s_perm = pk.permutation_oracles.fix_variables(&[E::Fr::one()]);
// This function returns 3 MLEs:
// - prod(x)
// - numerator
// - denominator
// See function signature for details.
let prod_x_and_aux_info =
Self::compute_prod_evals(&permutation_challenge, &w_merged, &w_merged, &s_perm)?;
// 3.3 push a commitment of `prod(x)` to the transcript
let prod_com = PCS::commit(&pk.pcs_param, &Rc::new(prod_x_and_aux_info[0].clone()))?;
// 3.4. `update_challenge` with the updated transcript
Self::update_challenge(&mut permutation_challenge, transcript, &prod_com)?;
// 3.5. `prove` to generate the proof
let perm_check_proof = <Self as PermutationCheck<E::Fr>>::prove(
&prod_x_and_aux_info,
&permutation_challenge,
transcript,
)?;
end_timer!(step);
// =======================================================================
// 4. Generate evaluations and corresponding proofs
// - permutation check evaluations and proofs
// - wi_poly(r_perm_check) where r_perm_check is from perm_check_proof
// - selector_poly(r_perm_check)
//
// - zero check evaluations and proofs
// - wi_poly(r_zero_check) where r_zero_check is from zero_check_proof
// - selector_poly(r_zero_check)
//
// - public input consistency checks
// - pi_poly(r_pi) where r_pi is sampled from transcript
// =======================================================================
let step = start_timer!(|| "opening and evaluations");
// 4.1 permutation check
let mut witness_zero_check_evals = vec![];
let mut witness_zero_check_openings = vec![];
// TODO: parallelization
// TODO: Batch opening
// open permutation check proof
let (witness_perm_check_opening, witness_perm_check_eval) = PCS::open(
&pk.pcs_param,
&Rc::new(w_merged.clone()),
&perm_check_proof.point,
)?;
// sanity checks
if w_merged.evaluate(&perm_check_proof.point).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string())
})? != witness_perm_check_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS opening".to_string(),
));
}
// 4.2 open zero check proof
// TODO: batch opening
for wire_poly in witness_polys {
// Open zero check proof
let (zero_proof, zero_eval) =
PCS::open(&pk.pcs_param, &wire_poly, &zero_check_proof.point)?;
{
if wire_poly.evaluate(&zero_check_proof.point).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"evaluation dimension does not match".to_string(),
)
})? != zero_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS opening".to_string(),
));
}
}
witness_zero_check_evals.push(zero_eval);
witness_zero_check_openings.push(zero_proof);
}
// Open permutation check proof
let (perm_oracle_opening, perm_oracle_eval) = PCS::open(
&pk.pcs_param,
&pk.permutation_oracles,
&[&[E::Fr::one()], perm_check_proof.point.as_slice()].concat(),
)?;
{
// sanity check
if s_perm.evaluate(&perm_check_proof.point).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"evaluation dimension does not match".to_string(),
)
})? != perm_oracle_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS opening".to_string(),
));
}
}
let mut selector_oracle_openings = vec![];
let mut selector_oracle_evals = vec![];
// TODO: parallelization
for selector_poly in pk.selector_oracles.iter() {
// Open zero check proof
// during verification, use this eval against subclaim
let (zero_proof, zero_eval) =
PCS::open(&pk.pcs_param, selector_poly, &zero_check_proof.point)?;
{
if selector_poly
.evaluate(&zero_check_proof.point)
.ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"evaluation dimension does not match".to_string(),
)
})?
!= zero_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS
opening"
.to_string(),
));
}
}
selector_oracle_openings.push(zero_proof);
selector_oracle_evals.push(zero_eval);
}
// 4.3 public input consistency checks
let r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?;
let (pi_opening, pi_eval) = PCS::open(&pk.pcs_param, &pi_in_w0, &r_pi)?;
{
// sanity check
if pi_poly.evaluate(&r_pi).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters(
"evaluation dimension does not match".to_string(),
)
})? != pi_eval
{
return Err(HyperPlonkErrors::InvalidProver(
"Evaluation is different from PCS opening".to_string(),
));
}
}
end_timer!(step);
end_timer!(start);
Ok(HyperPlonkProof {
// PCS components
witness_commits,
w_merged_com,
// We do not validate prod(x), this is checked by subclaim
prod_commit: prod_com,
witness_perm_check_opening,
witness_zero_check_openings,
witness_perm_check_eval,
witness_zero_check_evals,
perm_oracle_opening,
perm_oracle_eval,
selector_oracle_openings,
selector_oracle_evals,
pi_eval,
pi_opening,
// IOP components
zero_check_proof,
perm_check_proof,
})
}
/// Verify the HyperPlonk proof and generate the evaluation subclaims to be
/// checked later by the SNARK verifier.
///
/// Inputs:
/// - `params`: instance parameter
/// - `pub_input`: online public input
/// - `proof`: HyperPlonk SNARK proof
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - Return error if the verification fails, otherwise return the
/// evaluation subclaim
///
/// 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_oracles`
///
/// 3. Verify the opening against the commitment:
/// - check permutation check evaluations
/// - check zero check evaluations
/// - public input consistency checks
/// 4. check subclaim validity // todo
fn verify(
vk: &Self::VerifyingKey,
pub_input: &[E::Fr],
proof: &Self::Proof,
transcript: &mut Self::Transcript,
) -> Result<Self::SubClaim, HyperPlonkErrors> {
let start = start_timer!(|| "hyperplonk verification");
// witness assignment of length 2^n
let num_var = vk.params.nv;
let log_num_witness_polys = vk.params.log_n_wires;
// number of variables in merged polynomial for Multilinear-KZG
let merged_nv = num_var + log_num_witness_polys;
// online public input of length 2^\ell
let ell = vk.params.log_pub_input_len;
let pi_poly = DenseMultilinearExtension::from_evaluations_slice(ell as usize, pub_input);
// =======================================================================
// 0. sanity checks
// =======================================================================
// public input length
if pub_input.len() != 1 << ell {
return Err(HyperPlonkErrors::InvalidProver(format!(
"Public input length is not correct: got {}, expect {}",
pub_input.len(),
1 << ell
)));
}
// =======================================================================
// 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 sum check have different AuxInfo because `w_merged` and
// `Prod(x)` have degree and num_vars
let zero_check_aux_info = VPAuxInfo::<E::Fr> {
// TODO: get the real max degree from gate_func
// Here we use 6 is because the test has q[0] * w[0]^5 which is degree 6
max_degree: 6,
num_variables: num_var,
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,
transcript,
)?;
end_timer!(step);
// =======================================================================
// 2. Verify perm_check_proof on `\{w_i(x)\}` and `permutation_oracles`
// =======================================================================
// Zero check and sum check have different AuxInfo because `w_merged` and
// `Prod(x)` have degree and num_vars
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 mut challenge = <Self as PermutationCheck<E::Fr>>::generate_challenge(transcript)?;
<Self as PermutationCheck<E::Fr>>::update_challenge(
&mut challenge,
transcript,
&proof.prod_commit,
)?;
let perm_check_sub_claim = <Self as PermutationCheck<E::Fr>>::verify(
&proof.perm_check_proof,
&perm_check_aux_info,
transcript,
)?;
end_timer!(step);
// =======================================================================
// 3. Verify the opening against the commitment
// =======================================================================
let step = start_timer!(|| "verify commitments");
let perm_point = &perm_check_sub_claim
.zero_check_sub_claim
.sum_check_sub_claim
.point;
let zero_point = &zero_check_sub_claim.sum_check_sub_claim.point;
// =======================================================================
// 3.1 check permutation check evaluations
// =======================================================================
// witness for permutation check
if !PCS::verify(
&vk.pcs_param,
&proof.w_merged_com,
perm_point,
&proof.witness_perm_check_eval,
&proof.witness_perm_check_opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
// perm for permutation check
if !PCS::verify(
&vk.pcs_param,
&vk.perm_com,
&[&[E::Fr::one()], perm_point.as_slice()].concat(),
&proof.perm_oracle_eval,
&proof.perm_oracle_opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
// =======================================================================
// 3.2 check zero check evaluations
// =======================================================================
// witness for zero check
// TODO: batch verification
for (commitment, (opening, eval)) in proof.witness_commits.iter().zip(
proof
.witness_zero_check_openings
.iter()
.zip(proof.witness_zero_check_evals.iter()),
) {
if !PCS::verify(&vk.pcs_param, commitment, zero_point, eval, opening)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
}
// selector for zero check
// TODO: for now we only support a single selector polynomial
for (opening, eval) in proof
.selector_oracle_openings
.iter()
.zip(proof.selector_oracle_evals.iter())
{
if !PCS::verify(
&vk.pcs_param,
&vk.selector_com[0],
perm_point,
eval,
opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
}
// let f_eval = eval_f(
// &vk.params.gate_func,
// &proof.selector_oracle_evals,
// &proof.witness_zero_check_evals,
// )?;
// if f_eval != zero_check_sub_claim.sum_check_sub_claim.expected_evaluation {
// return Err(HyperPlonkErrors::InvalidProof(
// "zero check evaluation failed".to_string(),
// ));
// }
// =======================================================================
// 3.3 public input consistency checks
// =======================================================================
let mut r_pi = transcript.get_and_append_challenge_vectors(b"r_pi", ell)?;
let pi_eval = pi_poly.evaluate(&r_pi).ok_or_else(|| {
HyperPlonkErrors::InvalidParameters("evaluation dimension does not match".to_string())
})?;
r_pi = [vec![E::Fr::zero(); num_var - ell], r_pi].concat();
if !PCS::verify(
&vk.pcs_param,
&proof.witness_commits[0],
&r_pi,
&pi_eval,
&proof.pi_opening,
)? {
return Err(HyperPlonkErrors::InvalidProof(
"pcs verification failed".to_string(),
));
}
end_timer!(step);
end_timer!(start);
// todo: verify the subclaim within snark
Ok(HyperPlonkSubClaim {
zero_check_sub_claim,
perm_check_sub_claim,
pub_input_sub_claim: (vec![], E::Fr::default()), // FIXME
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{selectors::SelectorColumn, structs::CustomizedGates, witness::WitnessColumn};
use ark_bls12_381::Bls12_381;
use ark_std::test_rng;
use pcs::prelude::KZGMultilinearPCS;
use poly_iop::{identity_permutation_mle, random_permutation_mle};
use transcript::IOPTranscript;
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
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>(2, 2, 0, 1, gates)
}
fn test_hyperplonk_helper<E: PairingEngine>(
nv: usize,
log_pub_input_len: usize,
log_n_selectors: usize,
log_n_wires: usize,
gate_func: CustomizedGates,
) -> Result<(), HyperPlonkErrors> {
let mut rng = test_rng();
// system parameters
let params = HyperPlonkParams {
nv,
log_pub_input_len,
log_n_selectors,
log_n_wires,
gate_func,
};
let pcs_srs = KZGMultilinearPCS::<E>::gen_srs_for_testing(&mut rng, 15)?;
let merged_nv = nv + log_n_wires;
let s_perm = random_permutation_mle(merged_nv, &mut rng);
let s_id = identity_permutation_mle(merged_nv);
let perm: Vec<E::Fr> = [s_id.evaluations, s_perm.evaluations].concat();
let q1 = SelectorColumn(vec![E::Fr::one(), E::Fr::one(), E::Fr::one(), E::Fr::one()]);
// w1 := [0, 1, 2, 3]
let w1 = WitnessColumn(vec![
E::Fr::zero(),
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 pk and vks
let (pk, vk) = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::preprocess(
&params,
&pcs_srs,
&perm,
&[q1],
)?;
// generate a proof and verify
let mut transcript = IOPTranscript::<E::Fr>::new(b"test hyperplonk");
let proof = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::prove(
&pk,
&pi.0,
&[w1, w2],
&mut transcript,
)?;
let mut transcript = IOPTranscript::<E::Fr>::new(b"test hyperplonk");
let _sub_claim = <PolyIOP<E::Fr> as HyperPlonkSNARK<E, KZGMultilinearPCS<E>>>::verify(
&vk,
&pi.0,
&proof,
&mut transcript,
)?;
Ok(())
}
}

+ 46
- 0
hyperplonk/src/selectors.rs

@ -0,0 +1,46 @@
use crate::errors::HyperPlonkErrors;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::log2;
/// A column of selectors of length `#constraints`
#[derive(Debug, Clone)]
pub struct SelectorColumn<F: PrimeField>(pub(crate) Vec<F>);
impl<F: PrimeField> SelectorColumn<F> {
/// the number of variables for MLE to present a column.
pub fn get_nv(&self) -> usize {
log2(self.0.len()) as usize
}
/// Build selector columns from rows
pub fn from_selector_rows(
selector_rows: &[SelectorColumn<F>],
) -> Result<Vec<Self>, HyperPlonkErrors> {
if selector_rows.is_empty() {
return Err(HyperPlonkErrors::InvalidParameters(
"empty witness rows".to_string(),
));
}
let mut res = Vec::with_capacity(selector_rows.len());
let num_wires = selector_rows[0].0.len();
for i in 0..num_wires {
let mut cur_column = Vec::new();
for row in selector_rows.iter() {
cur_column.push(row.0[i])
}
res.push(Self(cur_column))
}
Ok(res)
}
}
impl<F: PrimeField> From<&SelectorColumn<F>> for DenseMultilinearExtension<F> {
fn from(witness: &SelectorColumn<F>) -> Self {
let nv = witness.get_nv();
Self::from_evaluations_slice(nv, witness.0.as_ref())
}
}

+ 156
- 0
hyperplonk/src/structs.rs

@ -0,0 +1,156 @@
//! Main module for the HyperPlonk PolyIOP.
use ark_ec::PairingEngine;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use pcs::PolynomialCommitmentScheme;
use poly_iop::prelude::{PermutationCheck, ZeroCheck};
use std::rc::Rc;
/// The sub-claim for the HyperPlonk PolyIOP, consists of the following:
/// - the SubClaim for the zero-check PIOP
/// - the SubClaim for the permutation-check PIOP
/// - the SubClaim for public input consistency
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkSubClaim<F: PrimeField, ZC: ZeroCheck<F>, PC: PermutationCheck<F>> {
/// the SubClaim for the custom gate zerocheck
pub zero_check_sub_claim: ZC::ZeroCheckSubClaim,
/// the SubClaim for the permutation check
pub perm_check_sub_claim: PC::PermutationCheckSubClaim,
/// the public input consistency check
pub pub_input_sub_claim: (Vec<F>, F), // (point, expected_eval)
}
/// The proof for the HyperPlonk PolyIOP, consists of the following:
/// - a batch commitment to all the witness MLEs
/// - a batch opening to all the MLEs at certain index
/// - the zero-check proof for checking custom gate-satisfiability
/// - the permutation-check proof for checking the copy constraints
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkProof<
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E>,
ZC: ZeroCheck<E::Fr>,
PC: PermutationCheck<E::Fr>,
> {
// =======================================================================
// PCS components
// =======================================================================
/// PCS commit for witnesses
// TODO: replace me with a batch commitment
pub witness_commits: Vec<PCS::Commitment>,
pub w_merged_com: PCS::Commitment,
/// PCS commit for prod(x)
// TODO: replace me with a batch commitment
pub prod_commit: PCS::Commitment,
/// PCS openings for witness on permutation check point
// TODO: replace me with a batch opening
pub witness_perm_check_opening: PCS::Proof,
/// 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 permutation check point
pub witness_perm_check_eval: E::Fr,
/// Evaluates of witnesses on zero check point
pub witness_zero_check_evals: Vec<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,
/// 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>,
/// 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
pub zero_check_proof: ZC::ZeroCheckProof,
/// 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 customized gate function
#[derive(Clone, Debug, Default, PartialEq)]
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,
/// customized gate function
pub gate_func: CustomizedGates,
}
/// The HyperPlonk proving key, consists of the following:
/// - the hyperplonk instance parameters
/// - the preprocessed polynomials output by the indexer
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkProvingKey<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> {
/// hyperplonk instance parameters
pub params: HyperPlonkParams,
/// the preprocessed permutation polynomials
pub permutation_oracles: Rc<DenseMultilinearExtension<E::Fr>>,
/// the preprocessed selector polynomials
// TODO: merge the list into a single MLE
pub selector_oracles: Vec<Rc<DenseMultilinearExtension<E::Fr>>>,
/// 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
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkVerifyingKey<E: PairingEngine, PCS: PolynomialCommitmentScheme<E>> {
/// hyperplonk instance parameters
pub params: HyperPlonkParams,
/// 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>,
/// 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)]
pub struct CustomizedGates {
pub(crate) gates: Vec<(i64, Option<usize>, Vec<usize>)>,
}

+ 210
- 0
hyperplonk/src/utils.rs

@ -0,0 +1,210 @@
use std::rc::Rc;
use arithmetic::VirtualPolynomial;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use crate::{errors::HyperPlonkErrors, structs::CustomizedGates};
/// Build MLE from matrix of witnesses.
///
/// Given a matrix := [row1, row2, ...] where
/// row1:= (a1, a2, ...)
/// row2:= (b1, b2, ...)
/// row3:= (c1, c2, ...)
///
/// output mle(a1,b1,c1, ...), mle(a2,b2,c2, ...), ...
#[macro_export]
macro_rules! build_mle {
($rows:expr) => {{
let mut res = Vec::with_capacity($rows.len());
let num_vars = log2($rows.len()) as usize;
let num_mles = $rows[0].0.len();
for i in 0..num_mles {
let mut cur_coeffs = Vec::new();
for row in $rows.iter() {
cur_coeffs.push(row.0[i])
}
res.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars, cur_coeffs,
)))
}
Ok(res)
}};
}
/// build `f(w_0(x),...w_d(x))` where `f` is the constraint polynomial
/// i.e., `f(a, b, c) = q_l a(x) + q_r b(x) + q_m a(x)b(x) - q_o c(x)` in
/// vanilla plonk
pub(crate) fn build_f<F: PrimeField>(
gates: &CustomizedGates,
num_vars: usize,
selector_mles: &[Rc<DenseMultilinearExtension<F>>],
witness_mles: &[Rc<DenseMultilinearExtension<F>>],
) -> Result<VirtualPolynomial<F>, HyperPlonkErrors> {
// TODO: check that selector and witness lengths match what is in
// the gate definition
for selector_mle in selector_mles.iter() {
if selector_mle.num_vars != num_vars {
return Err(HyperPlonkErrors::InvalidParameters(format!(
"selector has different number of vars: {} vs {}",
selector_mle.num_vars, num_vars
)));
}
}
for witness_mle in witness_mles.iter() {
if witness_mle.num_vars != num_vars {
return Err(HyperPlonkErrors::InvalidParameters(format!(
"selector has different number of vars: {} vs {}",
witness_mle.num_vars, num_vars
)));
}
}
let mut res = VirtualPolynomial::<F>::new(num_vars);
for (coeff, selector, witnesses) in gates.gates.iter() {
let coeff_fr = if *coeff < 0 {
-F::from(-*coeff as u64)
} else {
F::from(*coeff as u64)
};
let mut mle_list = vec![];
match *selector {
Some(s) => mle_list.push(selector_mles[s].clone()),
None => (),
};
for &witness in witnesses.iter() {
mle_list.push(witness_mles[witness].clone())
}
res.add_mle_list(mle_list, coeff_fr)?;
}
Ok(res)
}
#[allow(dead_code)]
pub(crate) fn eval_f<F: PrimeField>(
gates: &CustomizedGates,
selector_evals: &[F],
witness_evals: &[F],
) -> Result<F, HyperPlonkErrors> {
let mut res = F::zero();
for (coeff, selector, witnesses) in gates.gates.iter() {
let mut cur_value = if *coeff < 0 {
-F::from(-*coeff as u64)
} else {
F::from(*coeff as u64)
};
cur_value *= match selector {
Some(s) => selector_evals[*s],
None => F::one(),
};
for &witness in witnesses.iter() {
cur_value *= witness_evals[witness]
}
res += cur_value;
}
Ok(res)
}
#[cfg(test)]
mod test {
use super::*;
use ark_bls12_381::Fr;
use ark_ff::PrimeField;
use ark_poly::MultilinearExtension;
#[test]
fn test_build_gate() -> Result<(), HyperPlonkErrors> {
test_build_gate_helper::<Fr>()
}
fn test_build_gate_helper<F: PrimeField>() -> Result<(), HyperPlonkErrors> {
let num_vars = 2;
// ql = 3x1x2 + 2x2 whose evaluations are
// 0, 0 |-> 0
// 0, 1 |-> 2
// 1, 0 |-> 0
// 1, 1 |-> 5
let ql_eval = vec![F::zero(), F::from(2u64), F::zero(), F::from(5u64)];
let ql = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, ql_eval));
// W1 = x1x2 + x1 whose evaluations are
// 0, 0 |-> 0
// 0, 1 |-> 0
// 1, 0 |-> 1
// 1, 1 |-> 2
let w_eval = vec![F::zero(), F::zero(), F::from(1u64), F::from(2u64)];
let w1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W2 = x1 + x2 whose evaluations are
// 0, 0 |-> 0
// 0, 1 |-> 1
// 1, 0 |-> 1
// 1, 1 |-> 2
let w_eval = vec![F::zero(), F::one(), F::from(1u64), F::from(2u64)];
let w2 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// 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])
// ]
let gates = CustomizedGates {
gates: vec![(1, Some(0), vec![0, 0, 0, 0, 0]), (-1, None, vec![1])],
};
let f = build_f(&gates, num_vars, &[ql.clone()], &[w1.clone(), w2.clone()])?;
// Sanity check on build_f
// f(0, 0) = 0
assert_eq!(f.evaluate(&[F::zero(), F::zero()])?, F::zero());
// f(0, 1) = 2 * 0^5 + (-1) * 1 = -1
assert_eq!(f.evaluate(&[F::zero(), F::one()])?, -F::one());
// f(1, 0) = 0 * 1^5 + (-1) * 1 = -1
assert_eq!(f.evaluate(&[F::one(), F::zero()])?, -F::one());
// f(1, 1) = 5 * 2^5 + (-1) * 2 = 158
assert_eq!(f.evaluate(&[F::one(), F::one()])?, F::from(158u64));
// test eval_f
{
let point = [F::zero(), F::zero()];
let selector_evals = ql.evaluate(&point).unwrap();
let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()];
let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?;
// f(0, 0) = 0
assert_eq!(eval_f, F::zero());
}
{
let point = [F::zero(), F::one()];
let selector_evals = ql.evaluate(&point).unwrap();
let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()];
let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?;
// f(0, 1) = 2 * 0^5 + (-1) * 1 = -1
assert_eq!(eval_f, -F::one());
}
{
let point = [F::one(), F::zero()];
let selector_evals = ql.evaluate(&point).unwrap();
let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()];
let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?;
// f(1, 0) = 0 * 1^5 + (-1) * 1 = -1
assert_eq!(eval_f, -F::one());
}
{
let point = [F::one(), F::one()];
let selector_evals = ql.evaluate(&point).unwrap();
let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()];
let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?;
// f(1, 1) = 5 * 2^5 + (-1) * 2 = 158
assert_eq!(eval_f, F::from(158u64));
}
Ok(())
}
}

+ 67
- 0
hyperplonk/src/witness.rs

@ -0,0 +1,67 @@
use crate::{build_mle, errors::HyperPlonkErrors};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::log2;
use std::rc::Rc;
/// A row of witnesses of width `#wires`
#[derive(Debug, Clone)]
pub struct WitnessRow<F: PrimeField>(pub(crate) Vec<F>);
/// A column of witnesses of length `#constraints`
#[derive(Debug, Clone)]
pub struct WitnessColumn<F: PrimeField>(pub(crate) Vec<F>);
impl<F: PrimeField> WitnessColumn<F> {
/// the number of variables for MLE to present a column.
pub fn get_nv(&self) -> usize {
log2(self.0.len()) as usize
}
/// Build witness columns from rows
pub fn from_witness_rows(
witness_rows: &[WitnessRow<F>],
) -> Result<Vec<Self>, HyperPlonkErrors> {
if witness_rows.is_empty() {
return Err(HyperPlonkErrors::InvalidParameters(
"empty witness rows".to_string(),
));
}
let mut res = Vec::with_capacity(witness_rows.len());
let num_wires = witness_rows[0].0.len();
for i in 0..num_wires {
let mut cur_column = Vec::new();
for row in witness_rows.iter() {
cur_column.push(row.0[i])
}
res.push(Self(cur_column))
}
Ok(res)
}
}
impl<F: PrimeField> From<&WitnessColumn<F>> for DenseMultilinearExtension<F> {
fn from(witness: &WitnessColumn<F>) -> Self {
let nv = witness.get_nv();
Self::from_evaluations_slice(nv, witness.0.as_ref())
}
}
impl<F: PrimeField> WitnessRow<F> {
/// Build MLE from matrix of witnesses.
///
/// Given a matrix := [row1, row2, ...] where
/// row1:= (a1, a2, ...)
/// row2:= (b1, b2, ...)
/// row3:= (c1, c2, ...)
///
/// output mle(a1,b1,c1, ...), mle(a2,b2,c2, ...), ...
pub fn build_mles(
matrix: &[Self],
) -> Result<Vec<Rc<DenseMultilinearExtension<F>>>, HyperPlonkErrors> {
build_mle!(matrix)
}
}

+ 1
- 0
pcs/Cargo.toml

@ -16,6 +16,7 @@ ark-sponge = {version = "^0.3.0", default-features = false}
ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "curve" ] }
displaydoc = { version = "0.2.3", default-features = false }
derivative = { version = "2", features = ["use_core"] }
transcript = { path = "../transcript" }

+ 2
- 2
pcs/benches/bench.rs

@ -1,7 +1,7 @@
use ark_bls12_381::{Bls12_381, Fr};
use ark_ff::UniformRand;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::test_rng;
use ark_std::{rc::Rc, test_rng};
use pcs::{
prelude::{KZGMultilinearPCS, PCSErrors, PolynomialCommitmentScheme},
StructuredReferenceString,
@ -27,7 +27,7 @@ fn bench_pcs() -> Result<(), PCSErrors> {
10
};
let poly = DenseMultilinearExtension::rand(nv, &mut rng);
let poly = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
let (ml_ck, ml_vk) = uni_params.0.trim(nv)?;
let (uni_ck, uni_vk) = uni_params.1.trim(nv)?;
let ck = (ml_ck, uni_ck);

+ 4
- 4
pcs/src/errors.rs

@ -17,19 +17,19 @@ pub enum PCSErrors {
/// Invalid parameters: {0}
InvalidParameters(String),
/// An error during (de)serialization: {0}
SerializationError(SerializationError),
SerializationErrors(SerializationError),
/// Transcript error {0}
TranscriptError(TranscriptErrors),
TranscriptErrors(TranscriptErrors),
}
impl From<SerializationError> for PCSErrors {
fn from(e: ark_serialize::SerializationError) -> Self {
Self::SerializationError(e)
Self::SerializationErrors(e)
}
}
impl From<TranscriptErrors> for PCSErrors {
fn from(e: TranscriptErrors) -> Self {
Self::TranscriptError(e)
Self::TranscriptErrors(e)
}
}

+ 12
- 2
pcs/src/lib.rs

@ -6,6 +6,7 @@ mod univariate_kzg;
pub mod prelude;
use ark_ec::PairingEngine;
use ark_serialize::CanonicalSerialize;
use ark_std::rand::RngCore;
use errors::PCSErrors;
@ -21,10 +22,10 @@ pub trait PolynomialCommitmentScheme {
type Point;
type Evaluation;
// Commitments and proofs
type Commitment;
type Commitment: CanonicalSerialize;
type BatchCommitment: CanonicalSerialize;
type Proof;
type BatchProof;
type BatchCommitment;
/// Build SRS for testing.
///
@ -38,6 +39,15 @@ pub trait PolynomialCommitmentScheme {
log_size: usize,
) -> Result<Self::SRS, PCSErrors>;
/// Trim the universal parameters to specialize the public parameters.
/// Input both `supported_log_degree` for univariate and
/// `supported_num_vars` for multilinear.
fn trim(
srs: &Self::SRS,
supported_log_degree: usize,
supported_num_vars: Option<usize>,
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSErrors>;
/// Generate a commitment for a polynomial
fn commit(
prover_param: &Self::ProverParam,

+ 5
- 5
pcs/src/multilinear_kzg/batching.rs

@ -12,7 +12,7 @@ use crate::{
};
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, EvaluationDomain, MultilinearExtension, Polynomial};
use ark_std::{end_timer, start_timer, vec::Vec};
use ark_std::{end_timer, rc::Rc, start_timer, vec::Vec};
use transcript::IOPTranscript;
/// Input
@ -49,7 +49,7 @@ use transcript::IOPTranscript;
pub(super) fn multi_open_internal<E: PairingEngine>(
uni_prover_param: &UnivariateProverParam<E::G1Affine>,
ml_prover_param: &MultilinearProverParam<E>,
polynomials: &[DenseMultilinearExtension<E::Fr>],
polynomials: &[Rc<DenseMultilinearExtension<E::Fr>>],
multi_commitment: &Commitment<E>,
points: &[Vec<E::Fr>],
) -> Result<(BatchProof<E>, Vec<E::Fr>), PCSErrors> {
@ -309,7 +309,7 @@ mod tests {
fn test_multi_commit_helper<R: RngCore>(
uni_params: &UnivariateUniversalParams<E>,
ml_params: &MultilinearUniversalParams<E>,
polys: &[DenseMultilinearExtension<Fr>],
polys: &[Rc<DenseMultilinearExtension<Fr>>],
rng: &mut R,
) -> Result<(), PCSErrors> {
let merged_nv = get_batched_nv(polys[0].num_vars(), polys.len());
@ -418,13 +418,13 @@ mod tests {
// normal polynomials
let polys1: Vec<_> = (0..5)
.map(|_| DenseMultilinearExtension::rand(4, &mut rng))
.map(|_| Rc::new(DenseMultilinearExtension::rand(4, &mut rng)))
.collect();
test_multi_commit_helper(&uni_params, &ml_params, &polys1, &mut rng)?;
// single-variate polynomials
let polys1: Vec<_> = (0..5)
.map(|_| DenseMultilinearExtension::rand(1, &mut rng))
.map(|_| Rc::new(DenseMultilinearExtension::rand(1, &mut rng)))
.collect();
test_multi_commit_helper(&uni_params, &ml_params, &polys1, &mut rng)?;

+ 68
- 29
pcs/src/multilinear_kzg/mod.rs

@ -18,7 +18,7 @@ use ark_ec::{
use ark_ff::PrimeField;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
use ark_std::{end_timer, rand::RngCore, start_timer, vec::Vec, One, Zero};
use ark_std::{end_timer, rand::RngCore, rc::Rc, start_timer, vec::Vec, One, Zero};
use batching::{batch_verify_internal, multi_open_internal};
use srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam};
use std::marker::PhantomData;
@ -59,14 +59,14 @@ impl PolynomialCommitmentScheme for KZGMultilinearPCS {
type VerifierParam = (MultilinearVerifierParam<E>, UnivariateVerifierParam<E>);
type SRS = (MultilinearUniversalParams<E>, UnivariateUniversalParams<E>);
// Polynomial and its associated types
type Polynomial = DenseMultilinearExtension<E::Fr>;
type Polynomial = Rc<DenseMultilinearExtension<E::Fr>>;
type Point = Vec<E::Fr>;
type Evaluation = E::Fr;
// Commitments and proofs
type Commitment = Commitment<E>;
type BatchCommitment = Commitment<E>;
type Proof = Proof<E>;
type BatchProof = BatchProof<E>;
type BatchCommitment = Commitment<E>;
/// Build SRS for testing.
///
@ -85,6 +85,28 @@ impl PolynomialCommitmentScheme for KZGMultilinearPCS {
))
}
/// Trim the universal parameters to specialize the public parameters.
/// Input both `supported_log_degree` for univariate and
/// `supported_num_vars` for multilinear.
fn trim(
srs: &Self::SRS,
supported_log_degree: usize,
supported_num_vars: Option<usize>,
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSErrors> {
let supported_num_vars = match supported_num_vars {
Some(p) => p,
None => {
return Err(PCSErrors::InvalidParameters(
"multilinear should receive a num_var param".to_string(),
))
},
};
let (uni_ck, uni_vk) = srs.1.trim(supported_log_degree)?;
let (ml_ck, ml_vk) = srs.0.trim(supported_num_vars)?;
Ok(((ml_ck, uni_ck), (ml_vk, uni_vk)))
}
/// Generate a commitment for a polynomial.
///
/// This function takes `2^num_vars` number of scalar multiplications over
@ -94,14 +116,20 @@ impl PolynomialCommitmentScheme for KZGMultilinearPCS {
poly: &Self::Polynomial,
) -> Result<Self::Commitment, PCSErrors> {
let commit_timer = start_timer!(|| "commit");
if prover_param.0.num_vars < poly.num_vars {
return Err(PCSErrors::InvalidParameters(format!(
"Poly length ({}) exceeds param limit ({})",
poly.num_vars, prover_param.0.num_vars
)));
}
let ignored = prover_param.0.num_vars - poly.num_vars;
let scalars: Vec<_> = poly
.to_evaluations()
.into_iter()
.map(|x| x.into_repr())
.collect();
let commitment = VariableBaseMSM::multi_scalar_mul(
&prover_param.0.powers_of_g[0].evals,
&prover_param.0.powers_of_g[ignored].evals,
scalars.as_slice(),
)
.into_affine();
@ -262,12 +290,23 @@ fn open_internal(
) -> Result<(Proof<E>, E::Fr), PCSErrors> {
let open_timer = start_timer!(|| format!("open mle with {} variable", polynomial.num_vars));
assert_eq!(
polynomial.num_vars(),
prover_param.num_vars,
"Invalid size of polynomial"
);
if polynomial.num_vars() > prover_param.num_vars {
return Err(PCSErrors::InvalidParameters(format!(
"Polynomial num_vars {} exceed the limit {}",
polynomial.num_vars, prover_param.num_vars
)));
}
if polynomial.num_vars() != point.len() {
return Err(PCSErrors::InvalidParameters(format!(
"Polynomial num_vars {} does not match point len {}",
polynomial.num_vars,
point.len()
)));
}
let nv = polynomial.num_vars();
let ignored = prover_param.num_vars - nv;
let mut r: Vec<Vec<E::Fr>> = (0..nv + 1).map(|_| Vec::new()).collect();
let mut q: Vec<Vec<E::Fr>> = (0..nv + 1).map(|_| Vec::new()).collect();
@ -277,7 +316,7 @@ fn open_internal(
for (i, (&point_at_k, gi)) in point
.iter()
.zip(prover_param.powers_of_g.iter())
.zip(prover_param.powers_of_g[ignored..].iter())
.take(nv)
.enumerate()
{
@ -327,10 +366,20 @@ fn verify_internal(
proof: &Proof<E>,
) -> Result<bool, PCSErrors> {
let verify_timer = start_timer!(|| "verify");
let num_var = point.len();
if num_var > verifier_param.num_vars {
return Err(PCSErrors::InvalidParameters(format!(
"point length ({}) exceeds param limit ({})",
num_var, verifier_param.num_vars
)));
}
let ignored = verifier_param.num_vars - num_var;
let prepare_inputs_timer = start_timer!(|| "prepare pairing inputs");
let scalar_size = E::Fr::size_in_bits();
let window_size = FixedBaseMSM::get_mul_window_size(verifier_param.num_vars);
let window_size = FixedBaseMSM::get_mul_window_size(num_var);
let h_table = FixedBaseMSM::get_window_table(
scalar_size,
@ -340,8 +389,8 @@ fn verify_internal(
let h_mul: Vec<E::G2Projective> =
FixedBaseMSM::multi_scalar_mul(scalar_size, window_size, &h_table, point);
let h_vec: Vec<_> = (0..verifier_param.num_vars)
.map(|i| verifier_param.h_mask[i].into_projective() - h_mul[i])
let h_vec: Vec<_> = (0..num_var)
.map(|i| verifier_param.h_mask[ignored + i].into_projective() - h_mul[i])
.collect();
let h_vec: Vec<E::G2Affine> = E::G2Projective::batch_normalization_into_affine(&h_vec);
end_timer!(prepare_inputs_timer);
@ -352,12 +401,7 @@ fn verify_internal(
.proofs
.iter()
.map(|&x| E::G1Prepared::from(x))
.zip(
h_vec
.into_iter()
.take(verifier_param.num_vars)
.map(E::G2Prepared::from),
)
.zip(h_vec.into_iter().take(num_var).map(E::G2Prepared::from))
.collect();
pairings.push((
@ -377,7 +421,6 @@ fn verify_internal(
#[cfg(test)]
mod tests {
use super::*;
use crate::StructuredReferenceString;
use ark_bls12_381::Bls12_381;
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
@ -387,17 +430,13 @@ mod tests {
fn test_single_helper<R: RngCore>(
params: &(MultilinearUniversalParams<E>, UnivariateUniversalParams<E>),
poly: &DenseMultilinearExtension<Fr>,
poly: &Rc<DenseMultilinearExtension<Fr>>,
rng: &mut R,
) -> Result<(), PCSErrors> {
let nv = poly.num_vars();
assert_ne!(nv, 0);
let uni_degree = 1;
let (uni_ck, uni_vk) = params.1.trim(uni_degree)?;
let (ml_ck, ml_vk) = params.0.trim(nv)?;
let ck = (ml_ck, uni_ck);
let vk = (ml_vk, uni_vk);
let (ck, vk) = KZGMultilinearPCS::trim(params, uni_degree, Some(nv + 1))?;
let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
let com = KZGMultilinearPCS::commit(&ck, poly)?;
let (proof, value) = KZGMultilinearPCS::open(&ck, poly, &point)?;
@ -421,11 +460,11 @@ mod tests {
let params = KZGMultilinearPCS::<E>::gen_srs_for_testing(&mut rng, 10)?;
// normal polynomials
let poly1 = DenseMultilinearExtension::rand(8, &mut rng);
let poly1 = Rc::new(DenseMultilinearExtension::rand(8, &mut rng));
test_single_helper(&params, &poly1, &mut rng)?;
// single-variate polynomials
let poly2 = DenseMultilinearExtension::rand(1, &mut rng);
let poly2 = Rc::new(DenseMultilinearExtension::rand(1, &mut rng));
test_single_helper(&params, &poly2, &mut rng)?;
Ok(())

+ 11
- 27
pcs/src/multilinear_kzg/util.rs

@ -6,8 +6,7 @@ use ark_poly::{
univariate::DensePolynomial, DenseMultilinearExtension, EvaluationDomain, Evaluations,
MultilinearExtension, Polynomial, Radix2EvaluationDomain,
};
use ark_std::{end_timer, log2, start_timer};
use std::cmp::max;
use ark_std::{end_timer, log2, rc::Rc, start_timer};
/// Decompose an integer into a binary vector in little endian.
#[allow(dead_code)]
@ -29,8 +28,7 @@ pub(crate) fn bit_decompose(input: u64, num_var: usize) -> Vec {
// - mle has degree one
// - worst case is `\prod_{i=0}^{mle_num_vars-1} l_i(x) < point_len * mle_num_vars`
#[inline]
#[allow(dead_code)]
pub(crate) fn compute_qx_degree(mle_num_vars: usize, point_len: usize) -> usize {
pub fn compute_qx_degree(mle_num_vars: usize, point_len: usize) -> usize {
mle_num_vars * point_len
}
@ -101,28 +99,14 @@ pub(crate) fn compute_w_circ_l(
/// Return the number of variables that one need for an MLE to
/// batch the list of MLEs
#[inline]
pub(crate) fn get_batched_nv(num_var: usize, polynomials_len: usize) -> usize {
pub fn get_batched_nv(num_var: usize, polynomials_len: usize) -> usize {
num_var + log2(polynomials_len) as usize
}
/// Return the SRS size
// We require an SRS that is the greater of the two:
// - Multilinear srs is bounded by the merged MLS size which is `get_batched_nv(num_var,
// num_witnesses)`
// - Univariate srs is bounded by q_x's degree which is `compute_uni_degree(num_vars,
// num_witnesses)`
#[inline]
#[allow(dead_code)]
pub(crate) fn get_srs_size(num_var: usize, num_wires: usize) -> usize {
max(
num_var + log2(num_wires) as usize,
(log2(num_var) as usize + 2) * num_wires,
)
}
/// merge a set of polynomials. Returns an error if the
/// polynomials do not share a same number of nvs.
pub fn merge_polynomials<F: PrimeField>(
polynomials: &[impl MultilinearExtension<F>],
polynomials: &[Rc<DenseMultilinearExtension<F>>],
) -> Result<DenseMultilinearExtension<F>, PCSErrors> {
let nv = polynomials[0].num_vars();
for poly in polynomials.iter() {
@ -184,7 +168,7 @@ pub(crate) fn build_l(
// are included in the `batch_proof`.
#[cfg(test)]
pub(crate) fn generate_evaluations<F: PrimeField>(
polynomials: &[DenseMultilinearExtension<F>],
polynomials: &[Rc<DenseMultilinearExtension<F>>],
points: &[Vec<F>],
) -> Result<Vec<F>, PCSErrors> {
if polynomials.len() != points.len() {
@ -308,7 +292,7 @@ mod test {
// 1, 0 |-> 0
// 1, 1 |-> 5
let w_eval = vec![F::zero(), F::from(2u64), F::zero(), F::from(5u64)];
let w1 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
let w1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W2 = x1x2 + x1 whose evaluations are
// 0, 0 |-> 0
@ -316,7 +300,7 @@ mod test {
// 1, 0 |-> 1
// 1, 1 |-> 2
let w_eval = vec![F::zero(), F::zero(), F::from(1u64), F::from(2u64)];
let w2 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
let w2 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W3 = x1 + x2 whose evaluations are
// 0, 0 |-> 0
@ -324,7 +308,7 @@ mod test {
// 1, 0 |-> 1
// 1, 1 |-> 2
let w_eval = vec![F::zero(), F::one(), F::from(1u64), F::from(2u64)];
let w3 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
let w3 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
{
// W = (3x1x2 + 2x2)(1-x0) + (x1x2 + x1)x0
@ -577,15 +561,15 @@ mod test {
// Example from page 53:
// W1 = 3x1x2 + 2x2
let w_eval = vec![Fr::zero(), Fr::from(2u64), Fr::zero(), Fr::from(5u64)];
let w1 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
let w1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W2 = x1x2 + x1
let w_eval = vec![Fr::zero(), Fr::zero(), Fr::from(1u64), Fr::from(2u64)];
let w2 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
let w2 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W3 = x1 + x2
let w_eval = vec![Fr::zero(), Fr::one(), Fr::from(1u64), Fr::from(2u64)];
let w3 = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
let w3 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
let r = Fr::from(42u64);

+ 0
- 3
pcs/src/param.rs

@ -1,3 +0,0 @@
use ark_ec::{AffineCurve, PairingEngine};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
use ark_std::vec::Vec;

+ 1
- 1
pcs/src/prelude.rs

@ -2,7 +2,7 @@ pub use crate::{
errors::PCSErrors,
multilinear_kzg::{
srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam},
util::merge_polynomials,
util::{compute_qx_degree, get_batched_nv, merge_polynomials},
BatchProof, KZGMultilinearPCS, Proof,
},
structs::Commitment,

+ 18
- 2
pcs/src/univariate_kzg/mod.rs

@ -38,9 +38,9 @@ impl PolynomialCommitmentScheme for KZGUnivariatePCS {
type Evaluation = E::Fr;
// Polynomial and its associated types
type Commitment = Commitment<E>;
type BatchCommitment = Vec<Self::Commitment>;
type Proof = KZGUnivariateOpening<E>;
type BatchProof = Vec<Self::Proof>;
type BatchCommitment = Vec<Self::Commitment>;
/// Build SRS for testing.
///
@ -56,6 +56,22 @@ impl PolynomialCommitmentScheme for KZGUnivariatePCS {
Self::SRS::gen_srs_for_testing(rng, log_size)
}
/// Trim the universal parameters to specialize the public parameters.
/// Input `supported_log_degree` for univariate.
/// `supported_num_vars` must be None or an error is returned.
fn trim(
srs: &Self::SRS,
supported_log_degree: usize,
supported_num_vars: Option<usize>,
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSErrors> {
if supported_num_vars.is_some() {
return Err(PCSErrors::InvalidParameters(
"univariate should not receive a num_var param".to_string(),
));
}
srs.trim(supported_log_degree)
}
/// Generate a commitment for a polynomial
/// Note that the scheme is not hidding
fn commit(
@ -342,7 +358,7 @@ mod tests {
}
let log_degree = log2(degree) as usize;
let pp = KZGUnivariatePCS::<E>::gen_srs_for_testing(rng, log_degree)?;
let (ck, vk) = pp.trim(log_degree)?;
let (ck, vk) = KZGUnivariatePCS::<E>::trim(&pp, log_degree, None)?;
let mut comms = Vec::new();
let mut values = Vec::new();
let mut points = Vec::new();

+ 6
- 1
poly-iop/Cargo.toml

@ -15,10 +15,13 @@ ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "cur
rand_chacha = { version = "0.3.0", default-features = false }
displaydoc = { version = "0.2.3", default-features = false }
rayon = { version = "1.5.2", default-features = false, optional = true }
transcript = { path = "../transcript" }
arithmetic = { path = "../arithmetic" }
[dev-dependencies]
ark-ec = { version = "^0.3.0", default-features = false }
# Benchmarks
[[bench]]
@ -31,10 +34,12 @@ harness = false
default = [ "parallel" ]
parallel = [
"rayon",
"arithmetic/parallel",
"ark-std/parallel",
"ark-ff/parallel",
"ark-poly/parallel"
]
print-trace = [
"arithmetic/print-trace",
"ark-std/print-trace"
]

+ 4
- 4
poly-iop/benches/bench.rs

@ -1,9 +1,9 @@
use arithmetic::{VPAuxInfo, VirtualPolynomial};
use ark_bls12_381::Fr;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::{test_rng, UniformRand};
use poly_iop::{
identity_permutation_mle, PermutationCheck, PolyIOP, PolyIOPErrors, SumCheck, VPAuxInfo,
VirtualPolynomial, ZeroCheck,
use poly_iop::prelude::{
identity_permutation_mle, PermutationCheck, PolyIOP, PolyIOPErrors, SumCheck, ZeroCheck,
};
use std::{marker::PhantomData, time::Instant};
@ -156,7 +156,7 @@ fn bench_permutation_check() -> Result<(), PolyIOPErrors> {
let mut challenge =
<PolyIOP<Fr> as PermutationCheck<Fr>>::generate_challenge(&mut transcript)?;
let prod_x_and_aux = <PolyIOP<Fr> as PermutationCheck<Fr>>::compute_products(
let prod_x_and_aux = <PolyIOP<Fr> as PermutationCheck<Fr>>::compute_prod_evals(
&challenge, &w, &w, &s_perm,
)?;

+ 13
- 4
poly-iop/src/errors.rs

@ -1,5 +1,6 @@
//! Error module.
use arithmetic::ArithErrors;
use ark_std::string::String;
use displaydoc::Display;
use transcript::TranscriptErrors;
@ -20,19 +21,27 @@ pub enum PolyIOPErrors {
/// Should not arrive to this point
ShouldNotArrive,
/// An error during (de)serialization: {0}
SerializationError(ark_serialize::SerializationError),
SerializationErrors(ark_serialize::SerializationError),
/// Transcript Error: {0}
TranscriptError(TranscriptErrors),
TranscriptErrors(TranscriptErrors),
/// Arithmetic Error: {0}
ArithmeticErrors(ArithErrors),
}
impl From<ark_serialize::SerializationError> for PolyIOPErrors {
fn from(e: ark_serialize::SerializationError) -> Self {
Self::SerializationError(e)
Self::SerializationErrors(e)
}
}
impl From<TranscriptErrors> for PolyIOPErrors {
fn from(e: TranscriptErrors) -> Self {
Self::TranscriptError(e)
Self::TranscriptErrors(e)
}
}
impl From<ArithErrors> for PolyIOPErrors {
fn from(e: ArithErrors) -> Self {
Self::ArithmeticErrors(e)
}
}

+ 0
- 126
poly-iop/src/hyperplonk/mod.rs

@ -1,126 +0,0 @@
//! Main module for the HyperPlonk PolyIOP.
use crate::{errors::PolyIOPErrors, perm_check::PermutationCheck, zero_check::ZeroCheck};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use std::rc::Rc;
use transcript::IOPTranscript;
/// A trait for HyperPlonk Poly-IOPs
pub trait HyperPlonkPIOP<F: PrimeField> {
type Parameters;
type ProvingKey;
type Proof;
type SubClaim;
/// Generate the preprocessed polynomials output by the indexer.
///
/// Inputs:
/// - `params`: HyperPlonk instance parameters
/// - `permutation`: the permutation for the copy constraints
/// - `selectors`: the list of selector vectors for custom gates
/// Outputs:
/// - The HyperPlonk proving key, which includes the preprocessed
/// polynomials.
fn preprocess(
params: &Self::Parameters,
permutation: &[F],
selectors: &[&[F]],
) -> Result<Self::ProvingKey, PolyIOPErrors>;
/// Generate HyperPlonk PIOP proof.
///
/// Inputs:
/// - `pk`: circuit proving key
/// - `pub_input`: online public input
/// - `witness`: witness assignement
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - The HyperPlonk PIOP proof.
fn prove(
pk: &Self::ProvingKey,
pub_input: &[F],
witness: &[&[F]],
transcript: &mut IOPTranscript<F>,
) -> Result<Self::Proof, PolyIOPErrors>;
/// Verify the HyperPlonk proof and generate the evaluation subclaims to be
/// checked later by the SNARK verifier.
///
/// Inputs:
/// - `params`: instance parameter
/// - `pub_input`: online public input
/// - `proof`: HyperPlonk PIOP proof
/// - `transcript`: the transcript used for generating pseudorandom
/// challenges
/// Outputs:
/// - Return error if the verification fails, otherwise return the
/// evaluation subclaim
fn verify(
params: &Self::Parameters,
pub_input: &[F],
proof: &Self::Proof,
transcript: &mut IOPTranscript<F>,
) -> Result<Self::SubClaim, PolyIOPErrors>;
}
/// The sub-claim for the HyperPlonk PolyIOP, consists of the following:
/// - the SubClaim for the zero-check PIOP
/// - the SubClaim for the permutation-check PIOP
/// - the SubClaim for public input consistency
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkSubClaim<F: PrimeField, ZC: ZeroCheck<F>, PC: PermutationCheck<F>> {
/// the SubClaim for the custom gate zerocheck
pub zero_check_sub_claim: ZC::ZeroCheckSubClaim,
/// the SubClaim for the permutation check
pub perm_check_sub_claim: PC::PermutationCheckSubClaim,
/// the public input consistency check
pub pub_input_sub_claim: (Vec<F>, F), // (point, expected_eval)
}
/// The proof for the HyperPlonk PolyIOP, consists of the following:
/// - the zero-check proof for checking custom gate-satisfiability
/// - the permutation-check proof for checking the copy constraints
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkProof<F: PrimeField, ZC: ZeroCheck<F>, PC: PermutationCheck<F>> {
/// the custom gate zerocheck proof
pub zero_check_proof: ZC::Proof,
/// the permutation check proof for copy constraints
pub perm_check_proof: PC::Proof,
}
/// 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 customized gate function
#[derive(Clone, Debug, Default, PartialEq)]
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,
/// customized gate function
// TODO: define a struct for it.
pub gate_func: Vec<Vec<usize>>,
}
/// The HyperPlonk proving key, consists of the following:
/// - the hyperplonk instance parameters
/// - the preprocessed polynomials output by the indexer
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HyperPlonkProvingKey<F: PrimeField> {
/// hyperplonk instance parameters
pub params: HyperPlonkParams,
/// the preprocessed index polynomials
pub index_oracles: Vec<Rc<DenseMultilinearExtension<F>>>,
}
#[cfg(test)]
mod test {}

+ 1
- 4
poly-iop/src/lib.rs

@ -2,17 +2,15 @@ use ark_ff::PrimeField;
use std::marker::PhantomData;
mod errors;
mod hyperplonk;
mod perm_check;
pub mod prelude;
mod prod_check;
mod structs;
mod sum_check;
mod utils;
mod virtual_poly;
mod zero_check;
pub use errors::PolyIOPErrors;
pub use hyperplonk::HyperPlonkPIOP;
pub use perm_check::{
util::{identity_permutation_mle, random_permutation_mle},
PermutationCheck,
@ -20,7 +18,6 @@ pub use perm_check::{
pub use prod_check::ProductCheck;
pub use sum_check::SumCheck;
pub use utils::*;
pub use virtual_poly::{VPAuxInfo, VirtualPolynomial};
pub use zero_check::ZeroCheck;
#[derive(Clone, Debug, Default, Copy)]

+ 113
- 132
poly-iop/src/perm_check/mod.rs

@ -1,11 +1,16 @@
//! Main module for the Permutation Check protocol
use crate::{
errors::PolyIOPErrors, perm_check::util::compute_prod_0, structs::IOPProof, utils::get_index,
PolyIOP, VirtualPolynomial, ZeroCheck,
errors::PolyIOPErrors,
perm_check::util::{build_prod_partial_eval, compute_prod_0},
structs::IOPProof,
utils::get_index,
PolyIOP, ZeroCheck,
};
use arithmetic::VirtualPolynomial;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_serialize::CanonicalSerialize;
use ark_std::{end_timer, start_timer};
use std::rc::Rc;
use transcript::IOPTranscript;
@ -31,6 +36,7 @@ pub mod util;
pub trait PermutationCheck<F: PrimeField>: ZeroCheck<F> {
type PermutationCheckSubClaim;
type PermutationChallenge;
type PermutationProof;
/// Generate the preprocessed polynomial for the permutation check.
///
@ -58,22 +64,14 @@ pub trait PermutationCheck: ZeroCheck {
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationChallenge, PolyIOPErrors>;
/// Step 4 of the IOP.
/// Update the challenge with alpha; returns an error if
/// alpha already exists.
fn update_challenge(
challenge: &mut Self::PermutationChallenge,
transcript: &mut Self::Transcript,
prod_x_binding: &F,
) -> Result<(), PolyIOPErrors>;
/// Step 2 of the IOP.
/// Compute the following 7 polynomials
///
/// Input:
/// - f(x), g(x), s_perm(x) are mle-s
/// - challenges, consists of beta and gamma
///
/// Output: the evaluations for the following 3 polynomials
/// - prod(x)
/// - prod(0, x)
/// - prod(1, x)
/// - prod(x, 0)
/// - prod(x, 1)
/// - numerator
/// - denominator
///
@ -84,11 +82,8 @@ pub trait PermutationCheck: ZeroCheck {
///
/// (f(x) + \beta s_id(x) + \gamma)/(g(x) + \beta s_perm(x) + \gamma)
///
/// where
/// - beta and gamma are challenges
/// - f(x), g(x), s_id(x), s_perm(x) are mle-s
/// where s_id(x) is an identity permutation
///
/// - `prod(1,x) := prod(x, 0) * prod(x, 1)`
/// - numerator is the MLE for `f(x) + \beta s_id(x) + \gamma`
/// - denominator is the MLE for `g(x) + \beta s_perm(x) + \gamma`
///
@ -96,33 +91,49 @@ pub trait PermutationCheck: ZeroCheck {
/// Cost: linear in N.
///
/// TODO: replace argument `s_perm` with the merged polynomial `s`.
fn compute_products(
fn compute_prod_evals(
challenge: &Self::PermutationChallenge,
fx: &DenseMultilinearExtension<F>,
gx: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<[DenseMultilinearExtension<F>; 7], PolyIOPErrors>;
) -> Result<[DenseMultilinearExtension<F>; 3], PolyIOPErrors>;
/// Step 3 of the IOP.
/// push a commitment of `prod(x)` to the transcript
/// IMPORTANT: this step is done by the snark caller
fn commit_prod_x() {
unimplemented!()
}
/// Step 4 of the IOP.
/// Update the challenge with alpha; returns an error if
/// alpha already exists.
fn update_challenge(
challenge: &mut Self::PermutationChallenge,
transcript: &mut Self::Transcript,
prod_x_binding: &impl CanonicalSerialize,
) -> Result<(), PolyIOPErrors>;
/// Step 5 of the IOP.
///
/// Initialize the prover to argue that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
/// Inputs:
/// - 7 MLEs from `Self::compute_products`
/// - 3 MLEs from the second step
/// - challenge: `Self::Challenge` that has been updated
/// - transcript: a transcript that is used to generate the challenges beta
/// and gamma
/// Cost: O(N)
fn prove(
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 7],
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 3],
challenge: &Self::PermutationChallenge,
transcript: &mut IOPTranscript<F>,
) -> Result<Self::Proof, PolyIOPErrors>;
) -> Result<Self::PermutationProof, PolyIOPErrors>;
/// Verify that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
fn verify(
proof: &Self::Proof,
proof: &Self::PermutationProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationCheckSubClaim, PolyIOPErrors>;
@ -140,13 +151,14 @@ pub trait PermutationCheck: ZeroCheck {
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PermutationCheckSubClaim<F: PrimeField, ZC: ZeroCheck<F>> {
// the SubClaim from the ZeroCheck
zero_check_sub_claim: ZC::ZeroCheckSubClaim,
pub zero_check_sub_claim: ZC::ZeroCheckSubClaim,
// final query which consists of
// - the vector `(1, ..., 1, 0)`
// - the evaluation `1`
final_query: (Vec<F>, F),
}
#[derive(Debug, Clone)]
pub struct PermutationChallenge<F: PrimeField> {
alpha: Option<F>,
beta: F,
@ -175,6 +187,7 @@ impl PermutationCheck for PolyIOP {
/// - the initial challenge r which is used to build eq(x, r)
type PermutationCheckSubClaim = PermutationCheckSubClaim<F, Self>;
type PermutationProof = Self::SumCheckProof;
type PermutationChallenge = PermutationChallenge<F>;
/// Generate the preprocessed polynomial for the permutation check.
@ -213,31 +226,14 @@ impl PermutationCheck for PolyIOP {
})
}
/// Step 4 of the IOP.
/// Update the challenge with alpha; returns an error if
/// alpha already exists.
fn update_challenge(
challenge: &mut Self::PermutationChallenge,
transcript: &mut Self::Transcript,
prod_x_binding: &F,
) -> Result<(), PolyIOPErrors> {
if challenge.alpha.is_some() {
return Err(PolyIOPErrors::InvalidChallenge(
"alpha should not be sampled at the current stage".to_string(),
));
}
transcript.append_field_element(b"prod(x)", prod_x_binding)?;
challenge.alpha = Some(transcript.get_and_append_challenge(b"alpha")?);
Ok(())
}
/// Step 2 of the IOP.
/// Compute the following 7 polynomials
///
/// Input:
/// - f(x), g(x), s_perm(x) are mle-s
/// - challenges, consists of beta and gamma
///
/// Output: the evaluations for the following 3 polynomials
/// - prod(x)
/// - prod(0, x)
/// - prod(1, x)
/// - prod(x, 0)
/// - prod(x, 1)
/// - numerator
/// - denominator
///
@ -248,11 +244,8 @@ impl PermutationCheck for PolyIOP {
///
/// (f(x) + \beta s_id(x) + \gamma)/(g(x) + \beta s_perm(x) + \gamma)
///
/// where
/// - beta and gamma are challenges
/// - f(x), g(x), s_id(x), s_perm(x) are mle-s
/// where s_id(x) is an identity permutation
///
/// - `prod(1,x) := prod(x, 0) * prod(x, 1)`
/// - numerator is the MLE for `f(x) + \beta s_id(x) + \gamma`
/// - denominator is the MLE for `g(x) + \beta s_perm(x) + \gamma`
///
@ -260,13 +253,13 @@ impl PermutationCheck for PolyIOP {
/// Cost: linear in N.
///
/// TODO: replace argument `s_perm` with the merged polynomial `s`.
fn compute_products(
fn compute_prod_evals(
challenge: &Self::PermutationChallenge,
fx: &DenseMultilinearExtension<F>,
gx: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<[DenseMultilinearExtension<F>; 7], PolyIOPErrors> {
let start = start_timer!(|| "compute all prod polynomial");
) -> Result<[DenseMultilinearExtension<F>; 3], PolyIOPErrors> {
let start = start_timer!(|| "compute evaluations of prod polynomial");
if challenge.alpha.is_some() {
return Err(PolyIOPErrors::InvalidChallenge(
@ -279,7 +272,7 @@ impl PermutationCheck for PolyIOP {
// ===================================
// prod(0, x)
// ===================================
let (prod_0x, numerator, denominator) =
let (prod_0x_eval, numerator_eval, denominator_eval) =
compute_prod_0(&challenge.beta, &challenge.gamma, fx, gx, s_perm)?;
// ===================================
@ -294,82 +287,75 @@ impl PermutationCheck for PolyIOP {
//
// At any given step, the right hand side of the equation
// is available via either eval_0x or the current view of eval_1x
let eval_0x = &prod_0x.evaluations;
let mut eval_1x = vec![];
let mut prod_1x_eval = vec![];
for x in 0..(1 << num_vars) - 1 {
// sign will decide if the evaluation should be looked up from eval_0x or
// eval_1x; x_zero_index is the index for the evaluation (x_2, ..., x_n,
// 0); x_one_index is the index for the evaluation (x_2, ..., x_n, 1);
let (x_zero_index, x_one_index, sign) = get_index(x, num_vars);
if !sign {
eval_1x.push(eval_0x[x_zero_index] * eval_0x[x_one_index]);
prod_1x_eval.push(prod_0x_eval[x_zero_index] * prod_0x_eval[x_one_index]);
} else {
// sanity check: if we are trying to look up from the eval_1x table,
// then the target index must already exist
if x_zero_index >= eval_1x.len() || x_one_index >= eval_1x.len() {
if x_zero_index >= prod_1x_eval.len() || x_one_index >= prod_1x_eval.len() {
return Err(PolyIOPErrors::ShouldNotArrive);
}
eval_1x.push(eval_1x[x_zero_index] * eval_1x[x_one_index]);
prod_1x_eval.push(prod_1x_eval[x_zero_index] * prod_1x_eval[x_one_index]);
}
}
// prod(1, 1, ..., 1) := 0
eval_1x.push(F::zero());
prod_1x_eval.push(F::zero());
// ===================================
// prod(x)
// ===================================
// prod(x)'s evaluation is indeed `e := [eval_0x[..], eval_1x[..]].concat()`
let eval = [eval_0x.as_slice(), eval_1x.as_slice()].concat();
let eval = [prod_0x_eval.as_slice(), prod_1x_eval.as_slice()].concat();
// ===================================
// prod(x, 0) and prod(x, 1)
// ===================================
//
// now we compute eval_x0 and eval_x1
// eval_0x will be the odd coefficients of eval
// and eval_1x will be the even coefficients of eval
let mut eval_x0 = vec![];
let mut eval_x1 = vec![];
for (x, &prod_x) in eval.iter().enumerate() {
if x & 1 == 0 {
eval_x0.push(prod_x);
} else {
eval_x1.push(prod_x);
}
}
let prod_1x = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_1x);
let prod_x0 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x0);
let prod_x1 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x1);
let prod = DenseMultilinearExtension::from_evaluations_vec(num_vars + 1, eval);
let fx = DenseMultilinearExtension::from_evaluations_vec(num_vars + 1, eval);
let numerator = DenseMultilinearExtension::from_evaluations_vec(num_vars, numerator_eval);
let denominator =
DenseMultilinearExtension::from_evaluations_vec(num_vars, denominator_eval);
end_timer!(start);
Ok([
prod,
prod_0x,
prod_1x,
prod_x0,
prod_x1,
numerator,
denominator,
])
Ok([fx, numerator, denominator])
}
/// Step 4 of the IOP.
/// Update the challenge with alpha; returns an error if
/// alpha already exists.
fn update_challenge(
challenge: &mut Self::PermutationChallenge,
transcript: &mut Self::Transcript,
prod_x_binding: &impl CanonicalSerialize,
) -> Result<(), PolyIOPErrors> {
if challenge.alpha.is_some() {
return Err(PolyIOPErrors::InvalidChallenge(
"alpha should not be sampled at the current stage".to_string(),
));
}
transcript.append_serializable_element(b"prod(x)", prod_x_binding)?;
challenge.alpha = Some(transcript.get_and_append_challenge(b"alpha")?);
Ok(())
}
/// Step 5 of the IOP.
///
/// Generate a proof to argue that an MLE g(x) is a permutation of
/// Initialize the prover to argue that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
/// Inputs:
/// - 7 MLEs from `Self::compute_products(*, f, g, s_perm)`
/// - 3 MLEs from the second step
/// - challenge: `Self::Challenge` that has been updated
/// - transcript: a transcript that is used to generate the challenges beta
/// and gamma
/// Cost: O(N)
fn prove(
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 7],
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 3],
challenge: &Self::PermutationChallenge,
transcript: &mut IOPTranscript<F>,
) -> Result<Self::Proof, PolyIOPErrors> {
) -> Result<Self::PermutationProof, PolyIOPErrors> {
let alpha = match challenge.alpha {
Some(p) => p,
None => {
@ -386,7 +372,7 @@ impl PermutationCheck for PolyIOP {
/// Verify that an MLE g(x) is a permutation of an
/// MLE f(x) over a permutation given by s_perm.
fn verify(
proof: &Self::Proof,
proof: &Self::PermutationProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationCheckSubClaim, PolyIOPErrors> {
@ -394,7 +380,6 @@ impl PermutationCheck for PolyIOP {
// invoke the zero check on the iop_proof
let zero_check_sub_claim = <Self as ZeroCheck<F>>::verify(proof, aux_info, transcript)?;
let mut final_query = vec![F::one(); aux_info.num_variables];
final_query[aux_info.num_variables - 1] = F::zero();
let final_eval = F::one();
@ -413,8 +398,8 @@ impl PermutationCheck for PolyIOP {
/// Generate a proof to argue that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
/// Inputs:
/// - 7 MLEs from `Self::compute_products(*, f, g, s_perm)`
/// - challenge: `Self::Challenge` that has been updated
/// - 3 MLEs from the second step
/// - challenge alpha
/// - transcript: a transcript that is used to generate the challenges beta and
/// gamma
///
@ -422,26 +407,19 @@ impl PermutationCheck for PolyIOP {
///
/// Cost: O(N)
fn prove_internal<F: PrimeField>(
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 7],
prod_x_and_aux_info: &[DenseMultilinearExtension<F>; 3],
alpha: &F,
transcript: &mut IOPTranscript<F>,
) -> Result<(IOPProof<F>, VirtualPolynomial<F>), PolyIOPErrors> {
let start = start_timer!(|| "Permutation check prove");
// prods consists of the following:
// - prod(x)
// - prod(0, x)
// - prod(1, x)
// - prod(x, 0)
// - prod(x, 1)
// - numerator
// - denominator
let prod_0x = Rc::new(prod_x_and_aux_info[1].clone());
let prod_1x = Rc::new(prod_x_and_aux_info[2].clone());
let prod_x1 = Rc::new(prod_x_and_aux_info[3].clone());
let prod_x0 = Rc::new(prod_x_and_aux_info[4].clone());
let numerator = Rc::new(prod_x_and_aux_info[5].clone());
let denominator = Rc::new(prod_x_and_aux_info[6].clone());
let prod_partial_evals = build_prod_partial_eval(&prod_x_and_aux_info[0])?;
let prod_0x = Rc::new(prod_partial_evals[0].clone());
let prod_1x = Rc::new(prod_partial_evals[1].clone());
let prod_x0 = Rc::new(prod_partial_evals[2].clone());
let prod_x1 = Rc::new(prod_partial_evals[3].clone());
let numerator = Rc::new(prod_x_and_aux_info[1].clone());
let denominator = Rc::new(prod_x_and_aux_info[2].clone());
// compute (g(x) + beta * s_perm(x) + gamma) * prod(0, x) * alpha
// which is prods[6] * prod[1] * alpha
@ -458,6 +436,7 @@ fn prove_internal(
// - (f(x) + beta * s_id(x) + gamma))
q_x.add_mle_list([prod_x0, prod_x1], -F::one())?;
q_x.add_mle_list([prod_1x], F::one())?;
let iop_proof = <PolyIOP<F> as ZeroCheck<F>>::prove(&q_x, transcript)?;
end_timer!(start);
@ -466,26 +445,27 @@ fn prove_internal(
#[cfg(test)]
mod test {
use super::PermutationCheck;
use super::{util::build_prod_partial_eval, PermutationCheck};
use crate::{
errors::PolyIOPErrors,
perm_check::{prove_internal, util::identity_permutation_mle},
random_permutation_mle,
perm_check::prove_internal,
prelude::{identity_permutation_mle, random_permutation_mle},
structs::IOPProof,
utils::bit_decompose,
virtual_poly::VPAuxInfo,
PolyIOP, VirtualPolynomial,
PolyIOP,
};
use ark_bls12_381::Fr;
use ark_ff::{PrimeField, Zero};
use arithmetic::{VPAuxInfo, VirtualPolynomial};
use ark_bls12_381::{Fr, G1Affine};
use ark_ec::{AffineCurve, ProjectiveCurve};
use ark_ff::{UniformRand, Zero};
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::test_rng;
use std::marker::PhantomData;
fn mock_commit<F: PrimeField>(_f: &DenseMultilinearExtension<F>) -> F {
/// This is a mock function to generate some commitment element for testing.
fn mock_commit<G: AffineCurve>(_f: &DenseMultilinearExtension<G::ScalarField>) -> G {
let mut rng = test_rng();
F::rand(&mut rng)
G::Projective::rand(&mut rng).into_affine()
}
fn test_permutation_check_helper(
@ -500,9 +480,9 @@ mod test {
<PolyIOP<Fr> as PermutationCheck<Fr>>::generate_challenge(&mut transcript)?;
let prod_x_and_aux =
<PolyIOP<Fr> as PermutationCheck<Fr>>::compute_products(&challenge, f, g, s_perm)?;
<PolyIOP<Fr> as PermutationCheck<Fr>>::compute_prod_evals(&challenge, f, g, s_perm)?;
let prod_x_binding = mock_commit(&prod_x_and_aux[0]);
let prod_x_binding: G1Affine = mock_commit(&prod_x_and_aux[0]);
<PolyIOP<Fr> as PermutationCheck<Fr>>::update_challenge(
&mut challenge,
@ -663,9 +643,10 @@ mod test {
let challenge =
<PolyIOP<Fr> as PermutationCheck<Fr>>::generate_challenge(&mut transcript)?;
let res = <PolyIOP<Fr> as PermutationCheck<Fr>>::compute_products(
let prod_and_aux = <PolyIOP<Fr> as PermutationCheck<Fr>>::compute_prod_evals(
&challenge, &f, &g, &s_perm,
)?;
let prod_partial_eval = build_prod_partial_eval(&prod_and_aux[0])?;
for i in 0..1 << num_vars {
let r: Vec<Fr> = bit_decompose(i, num_vars)
@ -673,7 +654,7 @@ mod test {
.map(|&x| Fr::from(x))
.collect();
let eval = res[1].evaluate(&r).unwrap();
let eval = prod_partial_eval[0].evaluate(&r).unwrap();
let f_eval = f.evaluate(&r).unwrap();
let g_eval = g.evaluate(&r).unwrap();

+ 58
- 14
poly-iop/src/perm_check/util.rs

@ -5,7 +5,7 @@ use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, rand::RngCore, start_timer};
/// Returns three MLEs:
/// Returns the evaluations of three MLEs:
/// - prod(0,x)
/// - numerator
/// - denominator
@ -34,14 +34,7 @@ pub(super) fn compute_prod_0(
fx: &DenseMultilinearExtension<F>,
gx: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<
(
DenseMultilinearExtension<F>,
DenseMultilinearExtension<F>,
DenseMultilinearExtension<F>,
),
PolyIOPErrors,
> {
) -> Result<(Vec<F>, Vec<F>, Vec<F>), PolyIOPErrors> {
let start = start_timer!(|| "compute prod(1,x)");
let num_vars = fx.num_vars;
@ -63,12 +56,8 @@ pub(super) fn compute_prod_0(
denominator_evals.push(denominator);
}
let prod_0x = DenseMultilinearExtension::from_evaluations_vec(num_vars, prod_0x_evals);
let numerator = DenseMultilinearExtension::from_evaluations_vec(num_vars, numerator_evals);
let denominator = DenseMultilinearExtension::from_evaluations_vec(num_vars, denominator_evals);
end_timer!(start);
Ok((prod_0x, numerator, denominator))
Ok((prod_0x_evals, numerator_evals, denominator_evals))
}
/// An MLE that represent an identity permutation: `f(index) \mapto index`
@ -92,6 +81,57 @@ pub fn random_permutation_mle(
DenseMultilinearExtension::from_evaluations_vec(num_vars, s_perm_vec)
}
/// Helper function of the IOP.
///
/// Input:
/// - prod(x)
///
/// Output: the following 4 polynomials
/// - prod(0, x)
/// - prod(1, x)
/// - prod(x, 0)
/// - prod(x, 1)
pub(super) fn build_prod_partial_eval<F: PrimeField>(
prod_x: &DenseMultilinearExtension<F>,
) -> Result<[DenseMultilinearExtension<F>; 4], PolyIOPErrors> {
let start = start_timer!(|| "build prod polynomial");
let prod_x_eval = &prod_x.evaluations;
let num_vars = prod_x.num_vars - 1;
// prod(0, x)
let prod_0_x =
DenseMultilinearExtension::from_evaluations_slice(num_vars, &prod_x_eval[0..1 << num_vars]);
// prod(1, x)
let prod_1_x = DenseMultilinearExtension::from_evaluations_slice(
num_vars,
&prod_x_eval[1 << num_vars..1 << (num_vars + 1)],
);
// ===================================
// prod(x, 0) and prod(x, 1)
// ===================================
//
// now we compute eval_x0 and eval_x1
// eval_0x will be the odd coefficients of eval
// and eval_1x will be the even coefficients of eval
let mut eval_x0 = vec![];
let mut eval_x1 = vec![];
for (x, &prod_x) in prod_x_eval.iter().enumerate() {
if x & 1 == 0 {
eval_x0.push(prod_x);
} else {
eval_x1.push(prod_x);
}
}
let prod_x_0 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x0);
let prod_x_1 = DenseMultilinearExtension::from_evaluations_vec(num_vars, eval_x1);
end_timer!(start);
Ok([prod_0_x, prod_1_x, prod_x_0, prod_x_1])
}
#[cfg(test)]
mod test {
use super::*;
@ -116,6 +156,10 @@ mod test {
let gamma = Fr::rand(&mut rng);
let (prod_0, numerator, denominator) = compute_prod_0(&beta, &gamma, &f, &g, &s_perm)?;
let prod_0 = DenseMultilinearExtension::from_evaluations_vec(num_vars, prod_0);
let numerator = DenseMultilinearExtension::from_evaluations_vec(num_vars, numerator);
let denominator =
DenseMultilinearExtension::from_evaluations_vec(num_vars, denominator);
for i in 0..1 << num_vars {
let r: Vec<Fr> = bit_decompose(i, num_vars)

+ 11
- 0
poly-iop/src/prelude.rs

@ -0,0 +1,11 @@
pub use crate::{
errors::PolyIOPErrors,
perm_check::{
util::{identity_permutation_mle, random_permutation_mle},
PermutationCheck,
},
sum_check::SumCheck,
utils::*,
zero_check::ZeroCheck,
PolyIOP,
};

+ 5
- 3
poly-iop/src/prod_check/mod.rs

@ -1,6 +1,7 @@
//! Main module for the Permutation Check protocol
use crate::{errors::PolyIOPErrors, VirtualPolynomial, ZeroCheck};
use crate::{errors::PolyIOPErrors, ZeroCheck};
use arithmetic::VirtualPolynomial;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use transcript::IOPTranscript;
@ -30,6 +31,7 @@ use transcript::IOPTranscript;
pub trait ProductCheck<F: PrimeField>: ZeroCheck<F> {
type ProductCheckSubClaim;
type ProductCheckChallenge;
type ProductProof;
/// Initialize the system with a transcript
///
@ -73,12 +75,12 @@ pub trait ProductCheck: ZeroCheck {
prod_x: &DenseMultilinearExtension<F>,
transcript: &mut IOPTranscript<F>,
claimed_product: F,
) -> Result<Self::Proof, PolyIOPErrors>;
) -> Result<Self::ProductProof, PolyIOPErrors>;
/// Verify that for a witness virtual polynomial f(x),
/// it holds that `s = \prod_{x \in {0,1}^n} f(x)`
fn verify(
proof: &Self::Proof,
proof: &Self::ProductProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
claimed_product: F,

+ 7
- 4
poly-iop/src/structs.rs

@ -1,13 +1,16 @@
//! This module defines structs that are shared by all sub protocols.
use crate::VirtualPolynomial;
use arithmetic::VirtualPolynomial;
use ark_ff::PrimeField;
use ark_serialize::{CanonicalSerialize, SerializationError, Write};
/// An IOP proof is a collections of messages from prover to verifier at each
/// round through the interactive protocol.
#[derive(Clone, Debug, Default, PartialEq, CanonicalSerialize)]
/// An IOP proof is a collections of
/// - messages from prover to verifier at each round through the interactive
/// protocol.
/// - a point that is generated by the transcript for evaluation
#[derive(Clone, Debug, Default, PartialEq)]
pub struct IOPProof<F: PrimeField> {
pub point: Vec<F>,
pub proofs: Vec<IOPProverMessage<F>>,
}

+ 16
- 10
poly-iop/src/sum_check/mod.rs

@ -3,9 +3,9 @@
use crate::{
errors::PolyIOPErrors,
structs::{IOPProof, IOPProverState, IOPVerifierState},
virtual_poly::{VPAuxInfo, VirtualPolynomial},
PolyIOP,
};
use arithmetic::{VPAuxInfo, VirtualPolynomial};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
@ -20,12 +20,12 @@ pub trait SumCheck {
type VPAuxInfo;
type MultilinearExtension;
type Proof;
type SumCheckProof;
type Transcript;
type SumCheckSubClaim;
/// Extract sum from the proof
fn extract_sum(proof: &Self::Proof) -> F;
fn extract_sum(proof: &Self::SumCheckProof) -> F;
/// Initialize the system with a transcript
///
@ -41,12 +41,12 @@ pub trait SumCheck {
fn prove(
poly: &Self::VirtualPolynomial,
transcript: &mut Self::Transcript,
) -> Result<Self::Proof, PolyIOPErrors>;
) -> Result<Self::SumCheckProof, PolyIOPErrors>;
/// Verify the claimed sum using the proof
fn verify(
sum: F,
proof: &Self::Proof,
proof: &Self::SumCheckProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors>;
@ -123,7 +123,7 @@ pub struct SumCheckSubClaim {
}
impl<F: PrimeField> SumCheck<F> for PolyIOP<F> {
type Proof = IOPProof<F>;
type SumCheckProof = IOPProof<F>;
type VirtualPolynomial = VirtualPolynomial<F>;
type VPAuxInfo = VPAuxInfo<F>;
type MultilinearExtension = DenseMultilinearExtension<F>;
@ -131,7 +131,7 @@ impl SumCheck for PolyIOP {
type Transcript = IOPTranscript<F>;
/// Extract sum from the proof
fn extract_sum(proof: &Self::Proof) -> F {
fn extract_sum(proof: &Self::SumCheckProof) -> F {
let start = start_timer!(|| "extract sum");
let res = proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1];
end_timer!(start);
@ -157,7 +157,7 @@ impl SumCheck for PolyIOP {
fn prove(
poly: &Self::VirtualPolynomial,
transcript: &mut Self::Transcript,
) -> Result<Self::Proof, PolyIOPErrors> {
) -> Result<Self::SumCheckProof, PolyIOPErrors> {
let start = start_timer!(|| "sum check prove");
transcript.append_serializable_element(b"aux info", &poly.aux_info)?;
@ -172,9 +172,15 @@ impl SumCheck for PolyIOP {
prover_msgs.push(prover_msg);
challenge = Some(transcript.get_and_append_challenge(b"Internal round")?);
}
// pushing the last challenge point to the state
match challenge {
Some(p) => prover_state.challenges.push(p),
None => (),
};
end_timer!(start);
Ok(IOPProof {
point: prover_state.challenges,
proofs: prover_msgs,
})
}
@ -182,7 +188,7 @@ impl SumCheck for PolyIOP {
/// Verify the claimed sum using the proof
fn verify(
claimed_sum: F,
proof: &Self::Proof,
proof: &Self::SumCheckProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors> {
@ -367,7 +373,7 @@ mod test {
assert_eq!(poly.flattened_ml_extensions.len(), 5);
// test memory usage for prover
let prover = IOPProverState::prover_init(&poly).unwrap();
let prover = IOPProverState::<Fr>::prover_init(&poly).unwrap();
assert_eq!(prover.poly.flattened_ml_extensions.len(), 5);
drop(prover);

+ 2
- 2
poly-iop/src/sum_check/prover.rs

@ -4,8 +4,8 @@ use super::SumCheckProver;
use crate::{
errors::PolyIOPErrors,
structs::{IOPProverMessage, IOPProverState},
virtual_poly::VirtualPolynomial,
};
use arithmetic::VirtualPolynomial;
use ark_ff::PrimeField;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::{end_timer, start_timer, vec::Vec};
@ -60,7 +60,7 @@ impl SumCheckProver for IOPProverState {
// for the current round, and m is the round number, indexed from 1
//
// i.e.:
// at round m <=n, for each mle g(x_1, ... x_n) within the flattened_mle
// at round m <= n, for each mle g(x_1, ... x_n) within the flattened_mle
// which has already been evaluated to
//
// g(r_1, ..., r_{m-1}, x_m ... x_n)

+ 7
- 1
poly-iop/src/sum_check/verifier.rs

@ -4,8 +4,8 @@ use super::{SumCheckSubClaim, SumCheckVerifier};
use crate::{
errors::PolyIOPErrors,
structs::{IOPProverMessage, IOPVerifierState},
virtual_poly::VPAuxInfo,
};
use arithmetic::VPAuxInfo;
use ark_ff::PrimeField;
use ark_std::{end_timer, start_timer};
use transcript::IOPTranscript;
@ -105,6 +105,12 @@ impl SumCheckVerifier for IOPVerifierState {
));
}
println!(
"eval len {} max degree {}",
self.polynomials_received[0].len(),
self.max_degree + 1
);
// the deferred check during the interactive phase:
// 2. set `expected` to P(r)`
#[cfg(feature = "parallel")]

+ 12
- 8
poly-iop/src/zero_check/mod.rs

@ -19,6 +19,7 @@ pub struct ZeroCheckSubClaim> {
/// A ZeroCheck is derived from SumCheck.
pub trait ZeroCheck<F: PrimeField>: SumCheck<F> {
type ZeroCheckSubClaim;
type ZeroCheckProof;
/// Initialize the system with a transcript
///
@ -33,11 +34,11 @@ pub trait ZeroCheck: SumCheck {
fn prove(
poly: &Self::VirtualPolynomial,
transcript: &mut Self::Transcript,
) -> Result<Self::Proof, PolyIOPErrors>;
) -> Result<Self::ZeroCheckProof, PolyIOPErrors>;
/// verify the claimed sum using the proof
fn verify(
proof: &Self::Proof,
proof: &Self::ZeroCheckProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::ZeroCheckSubClaim, PolyIOPErrors>;
@ -48,6 +49,8 @@ impl ZeroCheck for PolyIOP {
/// - the SubClaim from the SumCheck
/// - the initial challenge r which is used to build eq(x, r)
type ZeroCheckSubClaim = ZeroCheckSubClaim<F, Self>;
/// A ZeroCheckProof is a SumCheckProof
type ZeroCheckProof = Self::SumCheckProof;
/// Initialize the system with a transcript
///
@ -70,11 +73,11 @@ impl ZeroCheck for PolyIOP {
fn prove(
poly: &Self::VirtualPolynomial,
transcript: &mut Self::Transcript,
) -> Result<Self::Proof, PolyIOPErrors> {
) -> Result<Self::ZeroCheckProof, PolyIOPErrors> {
let start = start_timer!(|| "zero check prove");
let length = poly.aux_info.num_variables;
let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?;
let r = transcript.get_and_append_challenge_vectors(b"0check r", length)?;
let f_hat = poly.build_f_hat(r.as_ref())?;
let res = <Self as SumCheck<F>>::prove(&f_hat, transcript);
@ -92,7 +95,7 @@ impl ZeroCheck for PolyIOP {
/// `\hat f(x)` is build correctly. The caller needs to makes sure that
/// `\hat f(x) = f(x) * eq(x, r)`
fn verify(
proof: &Self::Proof,
proof: &Self::ZeroCheckProof,
fx_aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::ZeroCheckSubClaim, PolyIOPErrors> {
@ -108,7 +111,7 @@ impl ZeroCheck for PolyIOP {
// generate `r` and pass it to the caller for correctness check
let length = fx_aux_info.num_variables;
let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?;
let r = transcript.get_and_append_challenge_vectors(b"0check r", length)?;
// hat_fx's max degree is increased by eq(x, r).degree() which is 1
let mut hat_fx_aux_info = fx_aux_info.clone();
@ -128,7 +131,8 @@ impl ZeroCheck for PolyIOP {
mod test {
use super::ZeroCheck;
use crate::{errors::PolyIOPErrors, PolyIOP, VirtualPolynomial};
use crate::{errors::PolyIOPErrors, PolyIOP};
use arithmetic::VirtualPolynomial;
use ark_bls12_381::Fr;
use ark_std::test_rng;
@ -163,7 +167,7 @@ mod test {
{
// bad path: random virtual poly whose sum is not zero
let (poly, _sum) =
VirtualPolynomial::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
VirtualPolynomial::<Fr>::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;

+ 1
- 2
transcript/src/lib.rs

@ -87,8 +87,7 @@ impl IOPTranscript {
let mut buf = [0u8; 64];
self.transcript.challenge_bytes(label, &mut buf);
let challenge = F::from_le_bytes_mod_order(&buf);
self.transcript
.append_message(label, &to_bytes!(&challenge)?);
self.append_serializable_element(label, &challenge)?;
Ok(challenge)
}

Loading…
Cancel
Save