From a6ea6ac26bc4fde004ac360b1a49ffb531ef45fe Mon Sep 17 00:00:00 2001 From: zhenfei Date: Mon, 1 Aug 2022 13:16:55 -0400 Subject: [PATCH] initial integration of hyperplonk snark(#39) --- Cargo.toml | 1 + arithmetic/Cargo.toml | 34 + arithmetic/src/errors.rs | 21 + arithmetic/src/lib.rs | 7 + arithmetic/src/multilinear_polynomial.rs | 33 + .../src/virtual_polynomial.rs | 92 +- hyperplonk/Cargo.toml | 36 + hyperplonk/src/errors.rs | 64 ++ hyperplonk/src/lib.rs | 849 +++++++++++++++++- hyperplonk/src/selectors.rs | 46 + hyperplonk/src/structs.rs | 156 ++++ hyperplonk/src/utils.rs | 210 +++++ hyperplonk/src/witness.rs | 67 ++ pcs/Cargo.toml | 1 + pcs/benches/bench.rs | 4 +- pcs/src/errors.rs | 8 +- pcs/src/lib.rs | 14 +- pcs/src/multilinear_kzg/batching.rs | 10 +- pcs/src/multilinear_kzg/mod.rs | 97 +- pcs/src/multilinear_kzg/util.rs | 38 +- pcs/src/param.rs | 3 - pcs/src/prelude.rs | 2 +- pcs/src/univariate_kzg/mod.rs | 20 +- poly-iop/Cargo.toml | 7 +- poly-iop/benches/bench.rs | 8 +- poly-iop/src/errors.rs | 17 +- poly-iop/src/hyperplonk/mod.rs | 126 --- poly-iop/src/lib.rs | 5 +- poly-iop/src/perm_check/mod.rs | 245 +++-- poly-iop/src/perm_check/util.rs | 72 +- poly-iop/src/prelude.rs | 11 + poly-iop/src/prod_check/mod.rs | 8 +- poly-iop/src/structs.rs | 11 +- poly-iop/src/sum_check/mod.rs | 26 +- poly-iop/src/sum_check/prover.rs | 4 +- poly-iop/src/sum_check/verifier.rs | 8 +- poly-iop/src/zero_check/mod.rs | 20 +- transcript/src/lib.rs | 3 +- 38 files changed, 1943 insertions(+), 441 deletions(-) create mode 100644 arithmetic/Cargo.toml create mode 100644 arithmetic/src/errors.rs create mode 100644 arithmetic/src/lib.rs create mode 100644 arithmetic/src/multilinear_polynomial.rs rename poly-iop/src/virtual_poly.rs => arithmetic/src/virtual_polynomial.rs (90%) create mode 100644 hyperplonk/src/errors.rs create mode 100644 hyperplonk/src/selectors.rs create mode 100644 hyperplonk/src/structs.rs create mode 100644 hyperplonk/src/utils.rs create mode 100644 hyperplonk/src/witness.rs delete mode 100644 pcs/src/param.rs delete mode 100644 poly-iop/src/hyperplonk/mod.rs create mode 100644 poly-iop/src/prelude.rs diff --git a/Cargo.toml b/Cargo.toml index 2300049..caf62a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "arithmetic", "hyperplonk", "pcs", "poly-iop", diff --git a/arithmetic/Cargo.toml b/arithmetic/Cargo.toml new file mode 100644 index 0000000..669ad28 --- /dev/null +++ b/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" + ] \ No newline at end of file diff --git a/arithmetic/src/errors.rs b/arithmetic/src/errors.rs new file mode 100644 index 0000000..3b06a7d --- /dev/null +++ b/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 for ArithErrors { + fn from(e: ark_serialize::SerializationError) -> Self { + Self::SerializationErrors(e) + } +} diff --git a/arithmetic/src/lib.rs b/arithmetic/src/lib.rs new file mode 100644 index 0000000..82bc047 --- /dev/null +++ b/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}; diff --git a/arithmetic/src/multilinear_polynomial.rs b/arithmetic/src/multilinear_polynomial.rs new file mode 100644 index 0000000..077edec --- /dev/null +++ b/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( + nv: usize, + degree: usize, + rng: &mut R, +) -> Vec>> { + 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 +} diff --git a/poly-iop/src/virtual_poly.rs b/arithmetic/src/virtual_polynomial.rs similarity index 90% rename from poly-iop/src/virtual_poly.rs rename to arithmetic/src/virtual_polynomial.rs index c16ced8..df7ef13 100644 --- a/poly-iop/src/virtual_poly.rs +++ b/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 VirtualPolynomial { /// 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>>, coefficient: F, - ) -> Result<(), PolyIOPErrors> { + ) -> Result<(), ArithErrors> { let mle_list: Vec>> = 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>, 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 { + pub fn evaluate(&self, point: &[F]) -> Result { 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 { + ) -> Result { 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 { + pub fn build_f_hat(&self, r: &[F]) -> Result { 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 = 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( - nv: usize, - degree: usize, - rng: &mut R, -) -> Vec>> { - 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(r: &[F]) -> Result>, PolyIOPErrors> { +pub fn build_eq_x_r( + r: &[F], +) -> Result>, 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(r: &[F], buf: &mut Vec) -> Result<(), PolyIOPErrors> { +fn build_eq_x_r_helper(r: &[F], buf: &mut Vec) -> 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 { + 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 { diff --git a/hyperplonk/Cargo.toml b/hyperplonk/Cargo.toml index 55988d4..772b24a 100644 --- a/hyperplonk/Cargo.toml +++ b/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", + ] \ No newline at end of file diff --git a/hyperplonk/src/errors.rs b/hyperplonk/src/errors.rs new file mode 100644 index 0000000..ac5e25b --- /dev/null +++ b/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 for HyperPlonkErrors { + fn from(e: ark_serialize::SerializationError) -> Self { + Self::SerializationError(e) + } +} + +impl From for HyperPlonkErrors { + fn from(e: PolyIOPErrors) -> Self { + Self::PolyIOPErrors(e) + } +} + +impl From for HyperPlonkErrors { + fn from(e: PCSErrors) -> Self { + Self::PCSErrors(e) + } +} + +impl From for HyperPlonkErrors { + fn from(e: TranscriptErrors) -> Self { + Self::TranscriptError(e) + } +} + +impl From for HyperPlonkErrors { + fn from(e: ArithErrors) -> Self { + Self::ArithmeticErrors(e) + } +} diff --git a/hyperplonk/src/lib.rs b/hyperplonk/src/lib.rs index 1b4a90c..a5c9e5f 100644 --- a/hyperplonk/src/lib.rs +++ b/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: + SumCheck + ZeroCheck + PermutationCheck +where + E: PairingEngine, + PCS: PolynomialCommitmentScheme, +{ + 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], + ) -> 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], + transcript: &mut Self::Transcript, + ) -> Result; + + /// 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; +} + +impl HyperPlonkSNARK for PolyIOP +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>, + Point = Vec, + Evaluation = E::Fr, + >, +{ + type Parameters = HyperPlonkParams; + type ProvingKey = HyperPlonkProvingKey; + type VerifyingKey = HyperPlonkVerifyingKey; + type Proof = HyperPlonkProof; + type SubClaim = HyperPlonkSubClaim; + + /// 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], + ) -> 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>> = 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::, 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], + transcript: &mut Self::Transcript, + ) -> Result { + 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>> = 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 = >::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 = >::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 { + 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:: { + // 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 = >::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:: { + // 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 = >::generate_challenge(transcript)?; + >::update_challenge( + &mut challenge, + transcript, + &proof.prod_commit, + )?; + + let perm_check_sub_claim = >::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::(2, 2, 0, 1, gates) + } + + fn test_hyperplonk_helper( + 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::::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 = [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) = as HyperPlonkSNARK>>::preprocess( + ¶ms, + &pcs_srs, + &perm, + &[q1], + )?; + + // generate a proof and verify + let mut transcript = IOPTranscript::::new(b"test hyperplonk"); + let proof = as HyperPlonkSNARK>>::prove( + &pk, + &pi.0, + &[w1, w2], + &mut transcript, + )?; + + let mut transcript = IOPTranscript::::new(b"test hyperplonk"); + let _sub_claim = as HyperPlonkSNARK>>::verify( + &vk, + &pi.0, + &proof, + &mut transcript, + )?; + + Ok(()) } } diff --git a/hyperplonk/src/selectors.rs b/hyperplonk/src/selectors.rs new file mode 100644 index 0000000..ae34a47 --- /dev/null +++ b/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(pub(crate) Vec); + +impl SelectorColumn { + /// 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], + ) -> Result, 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 From<&SelectorColumn> for DenseMultilinearExtension { + fn from(witness: &SelectorColumn) -> Self { + let nv = witness.get_nv(); + Self::from_evaluations_slice(nv, witness.0.as_ref()) + } +} diff --git a/hyperplonk/src/structs.rs b/hyperplonk/src/structs.rs new file mode 100644 index 0000000..a4833fe --- /dev/null +++ b/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, PC: PermutationCheck> { + /// 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), // (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, + ZC: ZeroCheck, + PC: PermutationCheck, +> { + // ======================================================================= + // PCS components + // ======================================================================= + /// PCS commit for witnesses + // TODO: replace me with a batch commitment + pub witness_commits: Vec, + 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, + /// 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, + /// 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, + /// Evaluates of selectors on zero check point + pub selector_oracle_evals: Vec, + /// 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> { + /// hyperplonk instance parameters + pub params: HyperPlonkParams, + /// the preprocessed permutation polynomials + pub permutation_oracles: Rc>, + /// the preprocessed selector polynomials + // TODO: merge the list into a single MLE + pub selector_oracles: Vec>>, + /// 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> { + /// 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, + /// 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, Vec)>, +} diff --git a/hyperplonk/src/utils.rs b/hyperplonk/src/utils.rs new file mode 100644 index 0000000..f73ae83 --- /dev/null +++ b/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( + gates: &CustomizedGates, + num_vars: usize, + selector_mles: &[Rc>], + witness_mles: &[Rc>], +) -> Result, 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::::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( + gates: &CustomizedGates, + selector_evals: &[F], + witness_evals: &[F], +) -> Result { + 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::() + } + + fn test_build_gate_helper() -> 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(()) + } +} diff --git a/hyperplonk/src/witness.rs b/hyperplonk/src/witness.rs new file mode 100644 index 0000000..3c0122a --- /dev/null +++ b/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(pub(crate) Vec); + +/// A column of witnesses of length `#constraints` +#[derive(Debug, Clone)] +pub struct WitnessColumn(pub(crate) Vec); + +impl WitnessColumn { + /// 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], + ) -> Result, 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 From<&WitnessColumn> for DenseMultilinearExtension { + fn from(witness: &WitnessColumn) -> Self { + let nv = witness.get_nv(); + Self::from_evaluations_slice(nv, witness.0.as_ref()) + } +} + +impl WitnessRow { + /// 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>>, HyperPlonkErrors> { + build_mle!(matrix) + } +} diff --git a/pcs/Cargo.toml b/pcs/Cargo.toml index e74ebc5..0347620 100644 --- a/pcs/Cargo.toml +++ b/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" } diff --git a/pcs/benches/bench.rs b/pcs/benches/bench.rs index 418ac94..4642765 100644 --- a/pcs/benches/bench.rs +++ b/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); diff --git a/pcs/src/errors.rs b/pcs/src/errors.rs index 804c8b9..b30d8c4 100644 --- a/pcs/src/errors.rs +++ b/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 for PCSErrors { fn from(e: ark_serialize::SerializationError) -> Self { - Self::SerializationError(e) + Self::SerializationErrors(e) } } impl From for PCSErrors { fn from(e: TranscriptErrors) -> Self { - Self::TranscriptError(e) + Self::TranscriptErrors(e) } } diff --git a/pcs/src/lib.rs b/pcs/src/lib.rs index 88bdd35..3c817ea 100644 --- a/pcs/src/lib.rs +++ b/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; + /// 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, + ) -> Result<(Self::ProverParam, Self::VerifierParam), PCSErrors>; + /// Generate a commitment for a polynomial fn commit( prover_param: &Self::ProverParam, diff --git a/pcs/src/multilinear_kzg/batching.rs b/pcs/src/multilinear_kzg/batching.rs index de26eef..44508dc 100644 --- a/pcs/src/multilinear_kzg/batching.rs +++ b/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( uni_prover_param: &UnivariateProverParam, ml_prover_param: &MultilinearProverParam, - polynomials: &[DenseMultilinearExtension], + polynomials: &[Rc>], multi_commitment: &Commitment, points: &[Vec], ) -> Result<(BatchProof, Vec), PCSErrors> { @@ -309,7 +309,7 @@ mod tests { fn test_multi_commit_helper( uni_params: &UnivariateUniversalParams, ml_params: &MultilinearUniversalParams, - polys: &[DenseMultilinearExtension], + polys: &[Rc>], 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)?; diff --git a/pcs/src/multilinear_kzg/mod.rs b/pcs/src/multilinear_kzg/mod.rs index 8a3d6ae..4a54a00 100644 --- a/pcs/src/multilinear_kzg/mod.rs +++ b/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, UnivariateVerifierParam); type SRS = (MultilinearUniversalParams, UnivariateUniversalParams); // Polynomial and its associated types - type Polynomial = DenseMultilinearExtension; + type Polynomial = Rc>; type Point = Vec; type Evaluation = E::Fr; // Commitments and proofs type Commitment = Commitment; + type BatchCommitment = Commitment; type Proof = Proof; type BatchProof = BatchProof; - type BatchCommitment = Commitment; /// 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, + ) -> 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 { 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::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> = (0..nv + 1).map(|_| Vec::new()).collect(); let mut q: Vec> = (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, ) -> Result { 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 = 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::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( params: &(MultilinearUniversalParams, UnivariateUniversalParams), - poly: &DenseMultilinearExtension, + poly: &Rc>, 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::::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(¶ms, &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(¶ms, &poly2, &mut rng)?; Ok(()) diff --git a/pcs/src/multilinear_kzg/util.rs b/pcs/src/multilinear_kzg/util.rs index 09d26de..83dfe64 100644 --- a/pcs/src/multilinear_kzg/util.rs +++ b/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( - polynomials: &[impl MultilinearExtension], + polynomials: &[Rc>], ) -> Result, 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( - polynomials: &[DenseMultilinearExtension], + polynomials: &[Rc>], points: &[Vec], ) -> Result, 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); diff --git a/pcs/src/param.rs b/pcs/src/param.rs deleted file mode 100644 index 47fc0f2..0000000 --- a/pcs/src/param.rs +++ /dev/null @@ -1,3 +0,0 @@ -use ark_ec::{AffineCurve, PairingEngine}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; -use ark_std::vec::Vec; diff --git a/pcs/src/prelude.rs b/pcs/src/prelude.rs index 71acec5..373b96d 100644 --- a/pcs/src/prelude.rs +++ b/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, diff --git a/pcs/src/univariate_kzg/mod.rs b/pcs/src/univariate_kzg/mod.rs index 5a18b52..321785b 100644 --- a/pcs/src/univariate_kzg/mod.rs +++ b/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; + type BatchCommitment = Vec; type Proof = KZGUnivariateOpening; type BatchProof = Vec; - type BatchCommitment = Vec; /// 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, + ) -> 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::::gen_srs_for_testing(rng, log_degree)?; - let (ck, vk) = pp.trim(log_degree)?; + let (ck, vk) = KZGUnivariatePCS::::trim(&pp, log_degree, None)?; let mut comms = Vec::new(); let mut values = Vec::new(); let mut points = Vec::new(); diff --git a/poly-iop/Cargo.toml b/poly-iop/Cargo.toml index f90f2dc..3388cde 100644 --- a/poly-iop/Cargo.toml +++ b/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" ] \ No newline at end of file diff --git a/poly-iop/benches/bench.rs b/poly-iop/benches/bench.rs index 12502df..e0c487d 100644 --- a/poly-iop/benches/bench.rs +++ b/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 = as PermutationCheck>::generate_challenge(&mut transcript)?; - let prod_x_and_aux = as PermutationCheck>::compute_products( + let prod_x_and_aux = as PermutationCheck>::compute_prod_evals( &challenge, &w, &w, &s_perm, )?; diff --git a/poly-iop/src/errors.rs b/poly-iop/src/errors.rs index 60ecdcd..bc71eec 100644 --- a/poly-iop/src/errors.rs +++ b/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 for PolyIOPErrors { fn from(e: ark_serialize::SerializationError) -> Self { - Self::SerializationError(e) + Self::SerializationErrors(e) } } impl From for PolyIOPErrors { fn from(e: TranscriptErrors) -> Self { - Self::TranscriptError(e) + Self::TranscriptErrors(e) + } +} + +impl From for PolyIOPErrors { + fn from(e: ArithErrors) -> Self { + Self::ArithmeticErrors(e) } } diff --git a/poly-iop/src/hyperplonk/mod.rs b/poly-iop/src/hyperplonk/mod.rs deleted file mode 100644 index 4eb3846..0000000 --- a/poly-iop/src/hyperplonk/mod.rs +++ /dev/null @@ -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 { - 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; - - /// 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, - ) -> Result; - - /// 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, - ) -> Result; -} - -/// 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, PC: PermutationCheck> { - /// 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), // (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, PC: PermutationCheck> { - /// 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>, -} - -/// 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 { - /// hyperplonk instance parameters - pub params: HyperPlonkParams, - /// the preprocessed index polynomials - pub index_oracles: Vec>>, -} - -#[cfg(test)] -mod test {} diff --git a/poly-iop/src/lib.rs b/poly-iop/src/lib.rs index 117f7c0..1331f65 100644 --- a/poly-iop/src/lib.rs +++ b/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)] diff --git a/poly-iop/src/perm_check/mod.rs b/poly-iop/src/perm_check/mod.rs index 1eb9399..1c23869 100644 --- a/poly-iop/src/perm_check/mod.rs +++ b/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: ZeroCheck { 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; - /// 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, gx: &DenseMultilinearExtension, s_perm: &DenseMultilinearExtension, - ) -> Result<[DenseMultilinearExtension; 7], PolyIOPErrors>; + ) -> Result<[DenseMultilinearExtension; 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; 7], + prod_x_and_aux_info: &[DenseMultilinearExtension; 3], challenge: &Self::PermutationChallenge, transcript: &mut IOPTranscript, - ) -> Result; + ) -> Result; /// 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; @@ -140,13 +151,14 @@ pub trait PermutationCheck: ZeroCheck { #[derive(Clone, Debug, Default, PartialEq)] pub struct PermutationCheckSubClaim> { // 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), } +#[derive(Debug, Clone)] pub struct PermutationChallenge { alpha: Option, 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; + type PermutationProof = Self::SumCheckProof; type PermutationChallenge = PermutationChallenge; /// 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, gx: &DenseMultilinearExtension, s_perm: &DenseMultilinearExtension, - ) -> Result<[DenseMultilinearExtension; 7], PolyIOPErrors> { - let start = start_timer!(|| "compute all prod polynomial"); + ) -> Result<[DenseMultilinearExtension; 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; 7], + prod_x_and_aux_info: &[DenseMultilinearExtension; 3], challenge: &Self::PermutationChallenge, transcript: &mut IOPTranscript, - ) -> Result { + ) -> Result { 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 { @@ -394,7 +380,6 @@ impl PermutationCheck for PolyIOP { // invoke the zero check on the iop_proof let zero_check_sub_claim = >::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( - prod_x_and_aux_info: &[DenseMultilinearExtension; 7], + prod_x_and_aux_info: &[DenseMultilinearExtension; 3], alpha: &F, transcript: &mut IOPTranscript, ) -> Result<(IOPProof, VirtualPolynomial), 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 = as ZeroCheck>::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: &DenseMultilinearExtension) -> F { + /// This is a mock function to generate some commitment element for testing. + fn mock_commit(_f: &DenseMultilinearExtension) -> 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 { as PermutationCheck>::generate_challenge(&mut transcript)?; let prod_x_and_aux = - as PermutationCheck>::compute_products(&challenge, f, g, s_perm)?; + as PermutationCheck>::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]); as PermutationCheck>::update_challenge( &mut challenge, @@ -663,9 +643,10 @@ mod test { let challenge = as PermutationCheck>::generate_challenge(&mut transcript)?; - let res = as PermutationCheck>::compute_products( + let prod_and_aux = as PermutationCheck>::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 = 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(); diff --git a/poly-iop/src/perm_check/util.rs b/poly-iop/src/perm_check/util.rs index 520080f..d9265c8 100644 --- a/poly-iop/src/perm_check/util.rs +++ b/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, gx: &DenseMultilinearExtension, s_perm: &DenseMultilinearExtension, -) -> Result< - ( - DenseMultilinearExtension, - DenseMultilinearExtension, - DenseMultilinearExtension, - ), - PolyIOPErrors, -> { +) -> Result<(Vec, Vec, Vec), 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( + prod_x: &DenseMultilinearExtension, +) -> Result<[DenseMultilinearExtension; 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 = bit_decompose(i, num_vars) diff --git a/poly-iop/src/prelude.rs b/poly-iop/src/prelude.rs new file mode 100644 index 0000000..0d13d05 --- /dev/null +++ b/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, +}; diff --git a/poly-iop/src/prod_check/mod.rs b/poly-iop/src/prod_check/mod.rs index 8fd377f..9c1d0d6 100644 --- a/poly-iop/src/prod_check/mod.rs +++ b/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: ZeroCheck { type ProductCheckSubClaim; type ProductCheckChallenge; + type ProductProof; /// Initialize the system with a transcript /// @@ -73,12 +75,12 @@ pub trait ProductCheck: ZeroCheck { prod_x: &DenseMultilinearExtension, transcript: &mut IOPTranscript, claimed_product: F, - ) -> Result; + ) -> Result; /// 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, diff --git a/poly-iop/src/structs.rs b/poly-iop/src/structs.rs index 459a30e..db336a5 100644 --- a/poly-iop/src/structs.rs +++ b/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 { + pub point: Vec, pub proofs: Vec>, } diff --git a/poly-iop/src/sum_check/mod.rs b/poly-iop/src/sum_check/mod.rs index a8f7d5b..fbfa69d 100644 --- a/poly-iop/src/sum_check/mod.rs +++ b/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; + ) -> Result; /// 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; @@ -123,7 +123,7 @@ pub struct SumCheckSubClaim { } impl SumCheck for PolyIOP { - type Proof = IOPProof; + type SumCheckProof = IOPProof; type VirtualPolynomial = VirtualPolynomial; type VPAuxInfo = VPAuxInfo; type MultilinearExtension = DenseMultilinearExtension; @@ -131,7 +131,7 @@ impl SumCheck for PolyIOP { type Transcript = IOPTranscript; /// 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 { + ) -> Result { 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 { @@ -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::::prover_init(&poly).unwrap(); assert_eq!(prover.poly.flattened_ml_extensions.len(), 5); drop(prover); diff --git a/poly-iop/src/sum_check/prover.rs b/poly-iop/src/sum_check/prover.rs index 81c1060..93d2022 100644 --- a/poly-iop/src/sum_check/prover.rs +++ b/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) diff --git a/poly-iop/src/sum_check/verifier.rs b/poly-iop/src/sum_check/verifier.rs index 24d90b7..5033d51 100644 --- a/poly-iop/src/sum_check/verifier.rs +++ b/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")] diff --git a/poly-iop/src/zero_check/mod.rs b/poly-iop/src/zero_check/mod.rs index 56b77f0..b15d523 100644 --- a/poly-iop/src/zero_check/mod.rs +++ b/poly-iop/src/zero_check/mod.rs @@ -19,6 +19,7 @@ pub struct ZeroCheckSubClaim> { /// A ZeroCheck is derived from SumCheck. pub trait ZeroCheck: SumCheck { 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; + ) -> Result; /// verify the claimed sum using the proof fn verify( - proof: &Self::Proof, + proof: &Self::ZeroCheckProof, aux_info: &Self::VPAuxInfo, transcript: &mut Self::Transcript, ) -> Result; @@ -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; + /// 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 { + ) -> Result { 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 = >::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 { @@ -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::::rand(nv, num_multiplicands_range, num_products, &mut rng)?; let mut transcript = as ZeroCheck>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; diff --git a/transcript/src/lib.rs b/transcript/src/lib.rs index 63ad852..4764b88 100644 --- a/transcript/src/lib.rs +++ b/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) }