Batch all (#89)

- use sumcheck to batch open PCS
- split Prod and witness into two batches
- benchmark code
This commit is contained in:
zhenfei
2022-10-13 23:21:30 -04:00
committed by GitHub
parent baaa06b07b
commit 719f595758
56 changed files with 1354 additions and 2515 deletions

View File

@@ -0,0 +1,56 @@
//! Error module.
use crate::pcs::prelude::PCSError;
use arithmetic::ArithErrors;
use ark_std::string::String;
use displaydoc::Display;
use transcript::TranscriptError;
/// A `enum` specifying the possible failure modes of the PolyIOP.
#[derive(Display, Debug)]
pub enum PolyIOPErrors {
/// Invalid Prover: {0}
InvalidProver(String),
/// Invalid Verifier: {0}
InvalidVerifier(String),
/// Invalid Proof: {0}
InvalidProof(String),
/// Invalid parameters: {0}
InvalidParameters(String),
/// Invalid challenge: {0}
InvalidChallenge(String),
/// Should not arrive to this point
ShouldNotArrive,
/// An error during (de)serialization: {0}
SerializationErrors(ark_serialize::SerializationError),
/// Transcript Error: {0}
TranscriptErrors(TranscriptError),
/// Arithmetic Error: {0}
ArithmeticErrors(ArithErrors),
/// PCS error {0}
PCSErrors(PCSError),
}
impl From<ark_serialize::SerializationError> for PolyIOPErrors {
fn from(e: ark_serialize::SerializationError) -> Self {
Self::SerializationErrors(e)
}
}
impl From<TranscriptError> for PolyIOPErrors {
fn from(e: TranscriptError) -> Self {
Self::TranscriptErrors(e)
}
}
impl From<ArithErrors> for PolyIOPErrors {
fn from(e: ArithErrors) -> Self {
Self::ArithmeticErrors(e)
}
}
impl From<PCSError> for PolyIOPErrors {
fn from(e: PCSError) -> Self {
Self::PCSErrors(e)
}
}

View File

@@ -0,0 +1,30 @@
use ark_ff::PrimeField;
use std::marker::PhantomData;
mod errors;
mod perm_check;
pub mod prelude;
mod prod_check;
mod structs;
mod sum_check;
mod utils;
mod zero_check;
#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)]
/// Struct for PolyIOP protocol.
/// It has an associated type `F` that defines the prime field the multi-variate
/// polynomial operates on.
///
/// An PolyIOP may be instantiated with one of the following:
/// - SumCheck protocol.
/// - ZeroCheck protocol.
/// - PermutationCheck protocol.
///
/// Those individual protocol may have similar or identical APIs.
/// The systematic way to invoke specific protocol is, for example
/// `<PolyIOP<F> as SumCheck<F>>::prove()`
pub struct PolyIOP<F: PrimeField> {
/// Associated field
#[doc(hidden)]
phantom: PhantomData<F>,
}

View File

@@ -0,0 +1,276 @@
//! Main module for the Permutation Check protocol
use self::util::computer_num_and_denom;
use crate::{
pcs::PolynomialCommitmentScheme,
poly_iop::{errors::PolyIOPErrors, prelude::ProductCheck, PolyIOP},
};
use ark_ec::PairingEngine;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
use std::rc::Rc;
use transcript::IOPTranscript;
/// A permutation subclaim consists of
/// - the SubClaim from the ProductCheck
/// - Challenges beta and gamma
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PermutationCheckSubClaim<E, PCS, PC>
where
E: PairingEngine,
PC: ProductCheck<E, PCS>,
PCS: PolynomialCommitmentScheme<E>,
{
/// the SubClaim from the ProductCheck
pub product_check_sub_claim: PC::ProductCheckSubClaim,
/// Challenges beta and gamma
pub challenges: (E::Fr, E::Fr),
}
pub mod util;
/// A PermutationCheck w.r.t. `(f, g, perm)`
/// proves that g is a permutation of f under
/// permutation `perm`
/// It is derived from ProductCheck.
///
/// A Permutation Check IOP takes the following steps:
///
/// Inputs:
/// - f(x)
/// - g(x)
/// - permutation s_perm(x)
pub trait PermutationCheck<E, PCS>: ProductCheck<E, PCS>
where
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E>,
{
type PermutationCheckSubClaim;
type PermutationProof;
/// Initialize the system with a transcript
///
/// This function is optional -- in the case where a PermutationCheck is
/// an building block for a more complex protocol, the transcript
/// may be initialized by this complex protocol, and passed to the
/// PermutationCheck prover/verifier.
fn init_transcript() -> Self::Transcript;
/// Inputs:
/// - f(x)
/// - g(x)
/// - permutation s_perm(x)
/// Outputs:
/// - a permutation check proof proving that g is a permutation of f under
/// s_perm
/// - the product polynomial build during product check
///
/// Cost: O(N)
fn prove(
pcs_param: &PCS::ProverParam,
fx: &Self::MultilinearExtension,
gx: &Self::MultilinearExtension,
s_perm: &Self::MultilinearExtension,
transcript: &mut IOPTranscript<E::Fr>,
) -> Result<(Self::PermutationProof, Self::MultilinearExtension), PolyIOPErrors>;
/// Verify that an MLE g(x) is a permutation of
/// MLE f(x) over a permutation given by s_perm.
fn verify(
proof: &Self::PermutationProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationCheckSubClaim, PolyIOPErrors>;
}
impl<E, PCS> PermutationCheck<E, PCS> for PolyIOP<E::Fr>
where
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E, Polynomial = Rc<DenseMultilinearExtension<E::Fr>>>,
{
type PermutationCheckSubClaim = PermutationCheckSubClaim<E, PCS, Self>;
type PermutationProof = Self::ProductCheckProof;
fn init_transcript() -> Self::Transcript {
IOPTranscript::<E::Fr>::new(b"Initializing PermutationCheck transcript")
}
fn prove(
pcs_param: &PCS::ProverParam,
fx: &Self::MultilinearExtension,
gx: &Self::MultilinearExtension,
s_perm: &Self::MultilinearExtension,
transcript: &mut IOPTranscript<E::Fr>,
) -> Result<(Self::PermutationProof, Self::MultilinearExtension), PolyIOPErrors> {
let start = start_timer!(|| "Permutation check prove");
if fx.num_vars != gx.num_vars {
return Err(PolyIOPErrors::InvalidParameters(
"fx and gx have different number of variables".to_string(),
));
}
if fx.num_vars != s_perm.num_vars {
return Err(PolyIOPErrors::InvalidParameters(
"fx and s_perm have different number of variables".to_string(),
));
}
// generate challenge `beta` and `gamma` from current transcript
let beta = transcript.get_and_append_challenge(b"beta")?;
let gamma = transcript.get_and_append_challenge(b"gamma")?;
let (numerator, denominator) = computer_num_and_denom(&beta, &gamma, fx, gx, s_perm)?;
// invoke product check on numerator and denominator
let (proof, prod_poly) =
<Self as ProductCheck<E, PCS>>::prove(pcs_param, &numerator, &denominator, transcript)?;
end_timer!(start);
Ok((proof, prod_poly))
}
/// 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::PermutationProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::PermutationCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "Permutation check verify");
let beta = transcript.get_and_append_challenge(b"beta")?;
let gamma = transcript.get_and_append_challenge(b"gamma")?;
// invoke the zero check on the iop_proof
let product_check_sub_claim =
<Self as ProductCheck<E, PCS>>::verify(proof, aux_info, transcript)?;
end_timer!(start);
Ok(PermutationCheckSubClaim {
product_check_sub_claim,
challenges: (beta, gamma),
})
}
}
#[cfg(test)]
mod test {
use super::PermutationCheck;
use crate::{
pcs::{prelude::MultilinearKzgPCS, PolynomialCommitmentScheme},
poly_iop::{errors::PolyIOPErrors, PolyIOP},
};
use arithmetic::{evaluate_opt, identity_permutation_mle, random_permutation_mle, VPAuxInfo};
use ark_bls12_381::Bls12_381;
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::test_rng;
use std::{marker::PhantomData, rc::Rc};
type KZG = MultilinearKzgPCS<Bls12_381>;
fn test_permutation_check_helper<E, PCS>(
pcs_param: &PCS::ProverParam,
fx: &Rc<DenseMultilinearExtension<E::Fr>>,
gx: &Rc<DenseMultilinearExtension<E::Fr>>,
s_perm: &Rc<DenseMultilinearExtension<E::Fr>>,
) -> Result<(), PolyIOPErrors>
where
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E, Polynomial = Rc<DenseMultilinearExtension<E::Fr>>>,
{
let nv = fx.num_vars;
let poly_info = VPAuxInfo {
max_degree: 2,
num_variables: nv,
phantom: PhantomData::default(),
};
// prover
let mut transcript = <PolyIOP<E::Fr> as PermutationCheck<E, PCS>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let (proof, prod_x) = <PolyIOP<E::Fr> as PermutationCheck<E, PCS>>::prove(
pcs_param,
fx,
gx,
s_perm,
&mut transcript,
)?;
// verifier
let mut transcript = <PolyIOP<E::Fr> as PermutationCheck<E, PCS>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let perm_check_sub_claim = <PolyIOP<E::Fr> as PermutationCheck<E, PCS>>::verify(
&proof,
&poly_info,
&mut transcript,
)?;
// check product subclaim
if evaluate_opt(
&prod_x,
&perm_check_sub_claim.product_check_sub_claim.final_query.0,
) != perm_check_sub_claim.product_check_sub_claim.final_query.1
{
return Err(PolyIOPErrors::InvalidVerifier("wrong subclaim".to_string()));
};
Ok(())
}
fn test_permutation_check(nv: usize) -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
let srs = MultilinearKzgPCS::<Bls12_381>::gen_srs_for_testing(&mut rng, nv + 1)?;
let (pcs_param, _) = MultilinearKzgPCS::<Bls12_381>::trim(&srs, None, Some(nv + 1))?;
{
// good path: w is a permutation of w itself under the identify map
let w = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
// s_perm is the identity map
let s_perm = identity_permutation_mle(nv);
test_permutation_check_helper::<Bls12_381, KZG>(&pcs_param, &w, &w, &s_perm)?;
}
{
// bad path 1: w is a not permutation of w itself under a random map
let w = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
// s_perm is a random map
let s_perm = random_permutation_mle(nv, &mut rng);
assert!(
test_permutation_check_helper::<Bls12_381, KZG>(&pcs_param, &w, &w, &s_perm)
.is_err()
);
}
{
// bad path 2: f is a not permutation of g under a identity map
let f = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
let g = Rc::new(DenseMultilinearExtension::rand(nv, &mut rng));
// s_perm is the identity map
let s_perm = identity_permutation_mle(nv);
assert!(
test_permutation_check_helper::<Bls12_381, KZG>(&pcs_param, &f, &g, &s_perm)
.is_err()
);
}
Ok(())
}
#[test]
fn test_trivial_polynomial() -> Result<(), PolyIOPErrors> {
test_permutation_check(1)
}
#[test]
fn test_normal_polynomial() -> Result<(), PolyIOPErrors> {
test_permutation_check(5)
}
#[test]
fn zero_polynomial_should_error() -> Result<(), PolyIOPErrors> {
assert!(test_permutation_check(0).is_err());
Ok(())
}
}

View File

@@ -0,0 +1,61 @@
//! This module implements useful functions for the permutation check protocol.
use crate::poly_iop::errors::PolyIOPErrors;
use arithmetic::identity_permutation_mle;
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
use std::rc::Rc;
/// Returns the evaluations of two MLEs:
/// - numerator
/// - denominator
///
/// where
/// - beta and gamma are challenges
/// - f(x), g(x), s_id(x), s_perm(x) are mle-s
///
/// - numerator is the MLE for `f(x) + \beta s_id(x) + \gamma`
/// - denominator is the MLE for `g(x) + \beta s_perm(x) + \gamma`
#[allow(clippy::type_complexity)]
pub(super) fn computer_num_and_denom<F: PrimeField>(
beta: &F,
gamma: &F,
fx: &DenseMultilinearExtension<F>,
gx: &DenseMultilinearExtension<F>,
s_perm: &DenseMultilinearExtension<F>,
) -> Result<
(
Rc<DenseMultilinearExtension<F>>,
Rc<DenseMultilinearExtension<F>>,
),
PolyIOPErrors,
> {
let start = start_timer!(|| "compute numerator and denominator");
let num_vars = fx.num_vars;
let mut numerator_evals = vec![];
let mut denominator_evals = vec![];
let s_id = identity_permutation_mle::<F>(num_vars);
for (&fi, (&gi, (&s_id_i, &s_perm_i))) in
fx.iter().zip(gx.iter().zip(s_id.iter().zip(s_perm.iter())))
{
let numerator = fi + *beta * s_id_i + gamma;
let denominator = gi + *beta * s_perm_i + gamma;
numerator_evals.push(numerator);
denominator_evals.push(denominator);
}
let numerator = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars,
numerator_evals,
));
let denominator = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars,
denominator_evals,
));
end_timer!(start);
Ok((numerator, denominator))
}

View File

@@ -0,0 +1,4 @@
pub use crate::poly_iop::{
errors::PolyIOPErrors, perm_check::PermutationCheck, prod_check::ProductCheck,
structs::IOPProof, sum_check::SumCheck, utils::*, zero_check::ZeroCheck, PolyIOP,
};

View File

@@ -0,0 +1,304 @@
//! Main module for the Product Check protocol
use crate::{
pcs::PolynomialCommitmentScheme,
poly_iop::{
errors::PolyIOPErrors,
prod_check::util::{compute_product_poly, prove_zero_check},
zero_check::ZeroCheck,
PolyIOP,
},
};
use arithmetic::VPAuxInfo;
use ark_ec::PairingEngine;
use ark_ff::{One, PrimeField, Zero};
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
use std::rc::Rc;
use transcript::IOPTranscript;
mod util;
/// A product-check proves that two n-variate multilinear polynomials `f(x),
/// g(x)` satisfy:
/// \prod_{x \in {0,1}^n} f(x) = \prod_{x \in {0,1}^n} g(x)
///
/// A ProductCheck is derived from ZeroCheck.
///
/// Prover steps:
/// 1. build `prod(x0, ..., x_n)` from f and g,
/// such that `prod(0, x1, ..., xn)` equals `f/g` over domain {0,1}^n
/// 2. push commitments of `prod(x)` to the transcript,
/// and `generate_challenge` from current transcript (generate alpha)
/// 3. generate the zerocheck proof for the virtual polynomial
/// prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0, x) * g(x))
///
/// Verifier steps:
/// 1. Extract commitments of `prod(x)` from the proof, push
/// them to the transcript
/// 2. `generate_challenge` from current transcript (generate alpha)
/// 3. `verify` to verify the zerocheck proof and generate the subclaim for
/// polynomial evaluations
pub trait ProductCheck<E, PCS>: ZeroCheck<E::Fr>
where
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E>,
{
type ProductCheckSubClaim;
type ProductCheckProof;
/// Initialize the system with a transcript
///
/// This function is optional -- in the case where a ProductCheck is
/// an building block for a more complex protocol, the transcript
/// may be initialized by this complex protocol, and passed to the
/// ProductCheck prover/verifier.
fn init_transcript() -> Self::Transcript;
/// Generate a proof for product check, showing that witness multilinear
/// polynomials f(x), g(x) satisfy `\prod_{x \in {0,1}^n} f(x) =
/// \prod_{x \in {0,1}^n} g(x)`
///
/// Inputs:
/// - fx: the numerator multilinear polynomial
/// - gx: the denominator multilinear polynomial
/// - transcript: the IOP transcript
/// - pk: PCS committing key
///
/// Outputs
/// - the product check proof
/// - the product polynomial (used for testing)
///
/// Cost: O(N)
fn prove(
pcs_param: &PCS::ProverParam,
fx: &Self::MultilinearExtension,
gx: &Self::MultilinearExtension,
transcript: &mut IOPTranscript<E::Fr>,
) -> Result<(Self::ProductCheckProof, Self::MultilinearExtension), PolyIOPErrors>;
/// Verify that for witness multilinear polynomials f(x), g(x)
/// it holds that `\prod_{x \in {0,1}^n} f(x) = \prod_{x \in {0,1}^n} g(x)`
fn verify(
proof: &Self::ProductCheckProof,
aux_info: &VPAuxInfo<E::Fr>,
transcript: &mut Self::Transcript,
) -> Result<Self::ProductCheckSubClaim, PolyIOPErrors>;
}
/// A product check subclaim consists of
/// - A zero check IOP subclaim for
/// `Q(x) = prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0,
/// x) * g(x)) = 0`
/// - The random challenge `alpha`
/// - A final query for `prod(1, ..., 1, 0) = 1`.
// Note that this final query is in fact a constant that
// is independent from the proof. So we should avoid
// (de)serialize it.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ProductCheckSubClaim<F: PrimeField, ZC: ZeroCheck<F>> {
// the SubClaim from the ZeroCheck
pub zero_check_sub_claim: ZC::ZeroCheckSubClaim,
// final query which consists of
// - the vector `(1, ..., 1, 0)` (needs to be reversed because Arkwork's MLE uses big-endian
// format for points)
// The expected final query evaluation is 1
pub final_query: (Vec<F>, F),
pub alpha: F,
}
/// A product check proof consists of
/// - a zerocheck proof
/// - a product polynomial commitment
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ProductCheckProof<
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E>,
ZC: ZeroCheck<E::Fr>,
> {
pub zero_check_proof: ZC::ZeroCheckProof,
pub prod_x_comm: PCS::Commitment,
}
impl<E, PCS> ProductCheck<E, PCS> for PolyIOP<E::Fr>
where
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E, Polynomial = Rc<DenseMultilinearExtension<E::Fr>>>,
{
type ProductCheckSubClaim = ProductCheckSubClaim<E::Fr, Self>;
type ProductCheckProof = ProductCheckProof<E, PCS, Self>;
fn init_transcript() -> Self::Transcript {
IOPTranscript::<E::Fr>::new(b"Initializing ProductCheck transcript")
}
fn prove(
pcs_param: &PCS::ProverParam,
fx: &Self::MultilinearExtension,
gx: &Self::MultilinearExtension,
transcript: &mut IOPTranscript<E::Fr>,
) -> Result<(Self::ProductCheckProof, Self::MultilinearExtension), PolyIOPErrors> {
let start = start_timer!(|| "prod_check prove");
if fx.num_vars != gx.num_vars {
return Err(PolyIOPErrors::InvalidParameters(
"fx and gx have different number of variables".to_string(),
));
}
// compute the product polynomial
let prod_x = compute_product_poly(fx, gx)?;
// generate challenge
let prod_x_comm = PCS::commit(pcs_param, &prod_x)?;
transcript.append_serializable_element(b"prod(x)", &prod_x_comm)?;
let alpha = transcript.get_and_append_challenge(b"alpha")?;
// build the zero-check proof
let (zero_check_proof, _) = prove_zero_check(fx, gx, &prod_x, &alpha, transcript)?;
end_timer!(start);
Ok((
ProductCheckProof {
zero_check_proof,
prod_x_comm,
},
prod_x,
))
}
fn verify(
proof: &Self::ProductCheckProof,
aux_info: &VPAuxInfo<E::Fr>,
transcript: &mut Self::Transcript,
) -> Result<Self::ProductCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "prod_check verify");
// update transcript and generate challenge
transcript.append_serializable_element(b"prod(x)", &proof.prod_x_comm)?;
let alpha = transcript.get_and_append_challenge(b"alpha")?;
// invoke the zero check on the iop_proof
// the virtual poly info for Q(x)
let zero_check_sub_claim =
<Self as ZeroCheck<E::Fr>>::verify(&proof.zero_check_proof, aux_info, transcript)?;
// the final query is on prod_x, hence has length `num_vars` + 1
let mut final_query = vec![E::Fr::one(); aux_info.num_variables + 1];
// the point has to be reversed because Arkworks uses big-endian.
final_query[0] = E::Fr::zero();
let final_eval = E::Fr::one();
end_timer!(start);
Ok(ProductCheckSubClaim {
zero_check_sub_claim,
final_query: (final_query, final_eval),
alpha,
})
}
}
#[cfg(test)]
mod test {
use super::ProductCheck;
use crate::{
pcs::{prelude::MultilinearKzgPCS, PolynomialCommitmentScheme},
poly_iop::{errors::PolyIOPErrors, PolyIOP},
};
use arithmetic::VPAuxInfo;
use ark_bls12_381::{Bls12_381, Fr};
use ark_ec::PairingEngine;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::test_rng;
use std::{marker::PhantomData, rc::Rc};
// f and g are guaranteed to have the same product
fn test_product_check_helper<E, PCS>(
f: &DenseMultilinearExtension<E::Fr>,
g: &DenseMultilinearExtension<E::Fr>,
pcs_param: &PCS::ProverParam,
) -> Result<(), PolyIOPErrors>
where
E: PairingEngine,
PCS: PolynomialCommitmentScheme<E, Polynomial = Rc<DenseMultilinearExtension<E::Fr>>>,
{
let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let (proof, prod_x) = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::prove(
pcs_param,
&Rc::new(f.clone()),
&Rc::new(g.clone()),
&mut transcript,
)?;
let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let aux_info = VPAuxInfo {
max_degree: 2,
num_variables: f.num_vars,
phantom: PhantomData::default(),
};
let prod_subclaim =
<PolyIOP<E::Fr> as ProductCheck<E, PCS>>::verify(&proof, &aux_info, &mut transcript)?;
assert_eq!(
prod_x.evaluate(&prod_subclaim.final_query.0).unwrap(),
prod_subclaim.final_query.1,
"different product"
);
// bad path
let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let h = f + g;
let (bad_proof, prod_x_bad) = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::prove(
pcs_param,
&Rc::new(f.clone()),
&Rc::new(h),
&mut transcript,
)?;
let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let bad_subclaim = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::verify(
&bad_proof,
&aux_info,
&mut transcript,
)?;
assert_ne!(
prod_x_bad.evaluate(&bad_subclaim.final_query.0).unwrap(),
bad_subclaim.final_query.1,
"can't detect wrong proof"
);
Ok(())
}
fn test_product_check(nv: usize) -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
let f: DenseMultilinearExtension<Fr> = DenseMultilinearExtension::rand(nv, &mut rng);
let mut g = f.clone();
g.evaluations.reverse();
let srs = MultilinearKzgPCS::<Bls12_381>::gen_srs_for_testing(&mut rng, nv + 1)?;
let (pcs_param, _) = MultilinearKzgPCS::<Bls12_381>::trim(&srs, None, Some(nv + 1))?;
test_product_check_helper::<Bls12_381, MultilinearKzgPCS<Bls12_381>>(&f, &g, &pcs_param)?;
Ok(())
}
#[test]
fn test_trivial_polynomial() -> Result<(), PolyIOPErrors> {
test_product_check(1)
}
#[test]
fn test_normal_polynomial() -> Result<(), PolyIOPErrors> {
test_product_check(10)
}
}

View File

@@ -0,0 +1,202 @@
//! This module implements useful functions for the product check protocol.
use crate::poly_iop::{errors::PolyIOPErrors, structs::IOPProof, zero_check::ZeroCheck, PolyIOP};
use arithmetic::{get_index, VirtualPolynomial};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use std::rc::Rc;
use transcript::IOPTranscript;
/// Compute the product polynomial `prod(x)` where
///
/// - `prod(0,x) := prod(0, x1, …, xn)` is the MLE over the
/// evaluations of `f(x)/g(x)` on the boolean hypercube {0,1}^n
///
/// - `prod(1,x)` is a MLE over the evaluations of `prod(x, 0) * prod(x, 1)`
/// on the boolean hypercube {0,1}^n
///
/// The caller needs to check num_vars matches in f and g
/// Cost: linear in N.
pub(super) fn compute_product_poly<F: PrimeField>(
fx: &Rc<DenseMultilinearExtension<F>>,
gx: &Rc<DenseMultilinearExtension<F>>,
) -> Result<Rc<DenseMultilinearExtension<F>>, PolyIOPErrors> {
let start = start_timer!(|| "compute evaluations of prod polynomial");
let num_vars = fx.num_vars;
// ===================================
// prod(0, x)
// ===================================
let prod_0x_eval = compute_prod_0(fx, gx)?;
// ===================================
// prod(1, x)
// ===================================
//
// `prod(1, x)` can be computed via recursing the following formula for 2^n-1
// times
//
// `prod(1, x_1, ..., x_n) :=
// prod(x_1, x_2, ..., x_n, 0) * prod(x_1, x_2, ..., x_n, 1)`
//
// 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 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 {
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 >= prod_1x_eval.len() || x_one_index >= prod_1x_eval.len() {
return Err(PolyIOPErrors::ShouldNotArrive);
}
prod_1x_eval.push(prod_1x_eval[x_zero_index] * prod_1x_eval[x_one_index]);
}
}
// prod(1, 1, ..., 1) := 0
prod_1x_eval.push(F::zero());
// ===================================
// prod(x)
// ===================================
// prod(x)'s evaluation is indeed `e := [eval_0x[..], eval_1x[..]].concat()`
let eval = [prod_0x_eval.as_slice(), prod_1x_eval.as_slice()].concat();
let prod_x = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars + 1,
eval,
));
end_timer!(start);
Ok(prod_x)
}
/// generate the zerocheck proof for the virtual polynomial
/// prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0, x) * g(x))
///
/// Returns proof and Q(x) for testing purpose.
///
/// Cost: O(N)
pub(super) fn prove_zero_check<F: PrimeField>(
fx: &Rc<DenseMultilinearExtension<F>>,
gx: &Rc<DenseMultilinearExtension<F>>,
prod_x: &Rc<DenseMultilinearExtension<F>>,
alpha: &F,
transcript: &mut IOPTranscript<F>,
) -> Result<(IOPProof<F>, VirtualPolynomial<F>), PolyIOPErrors> {
let start = start_timer!(|| "zerocheck in product check");
let prod_partial_evals = build_prod_partial_eval(prod_x)?;
let prod_0x = prod_partial_evals[0].clone();
let prod_1x = prod_partial_evals[1].clone();
let prod_x0 = prod_partial_evals[2].clone();
let prod_x1 = prod_partial_evals[3].clone();
// compute g(x) * prod(0, x) * alpha
let mut q_x = VirtualPolynomial::new_from_mle(gx, F::one());
q_x.mul_by_mle(prod_0x, *alpha)?;
// g(x) * prod(0, x) * alpha
// - f(x) * alpha
q_x.add_mle_list([fx.clone()], -*alpha)?;
// Q(x) := prod(1,x) - prod(x, 0) * prod(x, 1)
// + alpha * (
// g(x) * prod(0, x)
// - f(x))
q_x.add_mle_list([prod_x0, prod_x1], -F::one())?;
q_x.add_mle_list([prod_1x], F::one())?;
let iop_proof = <PolyIOP<F> as ZeroCheck<F>>::prove(&q_x, transcript)?;
end_timer!(start);
Ok((iop_proof, q_x))
}
/// 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)
fn build_prod_partial_eval<F: PrimeField>(
prod_x: &Rc<DenseMultilinearExtension<F>>,
) -> Result<[Rc<DenseMultilinearExtension<F>>; 4], PolyIOPErrors> {
let start = start_timer!(|| "build partial prod polynomial");
let prod_x_eval = &prod_x.evaluations;
let num_vars = prod_x.num_vars - 1;
// prod(0, x)
let prod_0_x = Rc::new(DenseMultilinearExtension::from_evaluations_slice(
num_vars,
&prod_x_eval[0..1 << num_vars],
));
// prod(1, x)
let prod_1_x = Rc::new(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 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars, eval_x0,
));
let prod_x_1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars, eval_x1,
));
end_timer!(start);
Ok([prod_0_x, prod_1_x, prod_x_0, prod_x_1])
}
/// Returns the evaluations of
/// - `prod(0,x) := prod(0, x1, …, xn)` which is the MLE over the
/// evaluations of f(x)/g(x) on the boolean hypercube {0,1}^n:
///
/// The caller needs to check num_vars matches in f/g
/// Cost: linear in N.
fn compute_prod_0<F: PrimeField>(
fx: &DenseMultilinearExtension<F>,
gx: &DenseMultilinearExtension<F>,
) -> Result<Vec<F>, PolyIOPErrors> {
let start = start_timer!(|| "compute prod(0,x)");
let input = fx
.iter()
.zip(gx.iter())
.map(|(&fi, &gi)| (fi, gi))
.collect::<Vec<_>>();
let prod_0x_evals = input.par_iter().map(|(x, y)| *x / *y).collect::<Vec<_>>();
end_timer!(start);
Ok(prod_0x_evals)
}

View File

@@ -0,0 +1,9 @@
Poly IOP
-----
Implements the following protocols
- sum checks
- zero checks
- product checks
- permutation checks

View File

@@ -0,0 +1,45 @@
//! This module defines structs that are shared by all sub protocols.
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.
/// - a point that is generated by the transcript for evaluation
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IOPProof<F: PrimeField> {
pub point: Vec<F>,
pub proofs: Vec<IOPProverMessage<F>>,
}
/// A message from the prover to the verifier at a given round
/// is a list of evaluations.
#[derive(Clone, Debug, Default, PartialEq, Eq, CanonicalSerialize)]
pub struct IOPProverMessage<F: PrimeField> {
pub(crate) evaluations: Vec<F>,
}
/// Prover State of a PolyIOP.
pub struct IOPProverState<F: PrimeField> {
/// sampled randomness given by the verifier
pub challenges: Vec<F>,
/// the current round number
pub(crate) round: usize,
/// pointer to the virtual polynomial
pub(crate) poly: VirtualPolynomial<F>,
}
/// Prover State of a PolyIOP
pub struct IOPVerifierState<F: PrimeField> {
pub(crate) round: usize,
pub(crate) num_vars: usize,
pub(crate) max_degree: usize,
pub(crate) finished: bool,
/// a list storing the univariate polynomial in evaluation form sent by the
/// prover at each round
pub(crate) polynomials_received: Vec<Vec<F>>,
/// a list storing the randomness sampled by the verifier at each round
pub(crate) challenges: Vec<F>,
}

View File

@@ -0,0 +1,387 @@
//! This module implements the sum check protocol.
use crate::poly_iop::{
errors::PolyIOPErrors,
structs::{IOPProof, IOPProverState, IOPVerifierState},
PolyIOP,
};
use arithmetic::{VPAuxInfo, VirtualPolynomial};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
use std::{fmt::Debug, rc::Rc};
use transcript::IOPTranscript;
mod prover;
mod verifier;
/// Trait for doing sum check protocols.
pub trait SumCheck<F: PrimeField> {
type VirtualPolynomial;
type VPAuxInfo;
type MultilinearExtension;
type SumCheckProof: Clone + Debug + Default + PartialEq;
type Transcript;
type SumCheckSubClaim: Clone + Debug + Default + PartialEq;
/// Extract sum from the proof
fn extract_sum(proof: &Self::SumCheckProof) -> F;
/// Initialize the system with a transcript
///
/// This function is optional -- in the case where a SumCheck is
/// an building block for a more complex protocol, the transcript
/// may be initialized by this complex protocol, and passed to the
/// SumCheck prover/verifier.
fn init_transcript() -> Self::Transcript;
/// Generate proof of the sum of polynomial over {0,1}^`num_vars`
///
/// The polynomial is represented in the form of a VirtualPolynomial.
fn prove(
poly: &Self::VirtualPolynomial,
transcript: &mut Self::Transcript,
) -> Result<Self::SumCheckProof, PolyIOPErrors>;
/// Verify the claimed sum using the proof
fn verify(
sum: F,
proof: &Self::SumCheckProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors>;
}
/// Trait for sum check protocol prover side APIs.
pub trait SumCheckProver<F: PrimeField>
where
Self: Sized,
{
type VirtualPolynomial;
type ProverMessage;
/// Initialize the prover state to argue for the sum of the input polynomial
/// over {0,1}^`num_vars`.
fn prover_init(polynomial: &Self::VirtualPolynomial) -> Result<Self, PolyIOPErrors>;
/// Receive message from verifier, generate prover message, and proceed to
/// next round.
///
/// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2).
fn prove_round_and_update_state(
&mut self,
challenge: &Option<F>,
) -> Result<Self::ProverMessage, PolyIOPErrors>;
}
/// Trait for sum check protocol verifier side APIs.
pub trait SumCheckVerifier<F: PrimeField> {
type VPAuxInfo;
type ProverMessage;
type Challenge;
type Transcript;
type SumCheckSubClaim;
/// Initialize the verifier's state.
fn verifier_init(index_info: &Self::VPAuxInfo) -> Self;
/// Run verifier for the current round, given a prover message.
///
/// Note that `verify_round_and_update_state` only samples and stores
/// challenges; and update the verifier's state accordingly. The actual
/// verifications are deferred (in batch) to `check_and_generate_subclaim`
/// at the last step.
fn verify_round_and_update_state(
&mut self,
prover_msg: &Self::ProverMessage,
transcript: &mut Self::Transcript,
) -> Result<Self::Challenge, PolyIOPErrors>;
/// This function verifies the deferred checks in the interactive version of
/// the protocol; and generate the subclaim. Returns an error if the
/// proof failed to verify.
///
/// If the asserted sum is correct, then the multilinear polynomial
/// evaluated at `subclaim.point` will be `subclaim.expected_evaluation`.
/// Otherwise, it is highly unlikely that those two will be equal.
/// Larger field size guarantees smaller soundness error.
fn check_and_generate_subclaim(
&self,
asserted_sum: &F,
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors>;
}
/// A SumCheckSubClaim is a claim generated by the verifier at the end of
/// verification when it is convinced.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SumCheckSubClaim<F: PrimeField> {
/// the multi-dimensional point that this multilinear extension is evaluated
/// to
pub point: Vec<F>,
/// the expected evaluation
pub expected_evaluation: F,
}
impl<F: PrimeField> SumCheck<F> for PolyIOP<F> {
type SumCheckProof = IOPProof<F>;
type VirtualPolynomial = VirtualPolynomial<F>;
type VPAuxInfo = VPAuxInfo<F>;
type MultilinearExtension = Rc<DenseMultilinearExtension<F>>;
type SumCheckSubClaim = SumCheckSubClaim<F>;
type Transcript = IOPTranscript<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);
res
}
fn init_transcript() -> Self::Transcript {
let start = start_timer!(|| "init transcript");
let res = IOPTranscript::<F>::new(b"Initializing SumCheck transcript");
end_timer!(start);
res
}
fn prove(
poly: &Self::VirtualPolynomial,
transcript: &mut Self::Transcript,
) -> Result<Self::SumCheckProof, PolyIOPErrors> {
let start = start_timer!(|| "sum check prove");
transcript.append_serializable_element(b"aux info", &poly.aux_info)?;
let mut prover_state = IOPProverState::prover_init(poly)?;
let mut challenge = None;
let mut prover_msgs = Vec::with_capacity(poly.aux_info.num_variables);
for _ in 0..poly.aux_info.num_variables {
let prover_msg =
IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)?;
transcript.append_serializable_element(b"prover msg", &prover_msg)?;
prover_msgs.push(prover_msg);
challenge = Some(transcript.get_and_append_challenge(b"Internal round")?);
}
// pushing the last challenge point to the state
if let Some(p) = challenge {
prover_state.challenges.push(p)
};
end_timer!(start);
Ok(IOPProof {
point: prover_state.challenges,
proofs: prover_msgs,
})
}
fn verify(
claimed_sum: F,
proof: &Self::SumCheckProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "sum check verify");
transcript.append_serializable_element(b"aux info", aux_info)?;
let mut verifier_state = IOPVerifierState::verifier_init(aux_info);
for i in 0..aux_info.num_variables {
let prover_msg = proof.proofs.get(i).expect("proof is incomplete");
transcript.append_serializable_element(b"prover msg", prover_msg)?;
IOPVerifierState::verify_round_and_update_state(
&mut verifier_state,
prover_msg,
transcript,
)?;
}
let res = IOPVerifierState::check_and_generate_subclaim(&verifier_state, &claimed_sum);
end_timer!(start);
res
}
}
#[cfg(test)]
mod test {
use super::*;
use ark_bls12_381::Fr;
use ark_ff::UniformRand;
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
use ark_std::test_rng;
use std::rc::Rc;
fn test_sumcheck(
nv: usize,
num_multiplicands_range: (usize, usize),
num_products: usize,
) -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
let (poly, asserted_sum) =
VirtualPolynomial::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?;
let poly_info = poly.aux_info.clone();
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
let subclaim = <PolyIOP<Fr> as SumCheck<Fr>>::verify(
asserted_sum,
&proof,
&poly_info,
&mut transcript,
)?;
assert!(
poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation,
"wrong subclaim"
);
Ok(())
}
fn test_sumcheck_internal(
nv: usize,
num_multiplicands_range: (usize, usize),
num_products: usize,
) -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
let (poly, asserted_sum) =
VirtualPolynomial::<Fr>::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
let poly_info = poly.aux_info.clone();
let mut prover_state = IOPProverState::prover_init(&poly)?;
let mut verifier_state = IOPVerifierState::verifier_init(&poly_info);
let mut challenge = None;
let mut transcript = IOPTranscript::new(b"a test transcript");
transcript
.append_message(b"testing", b"initializing transcript for testing")
.unwrap();
for _ in 0..poly.aux_info.num_variables {
let prover_message =
IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)
.unwrap();
challenge = Some(
IOPVerifierState::verify_round_and_update_state(
&mut verifier_state,
&prover_message,
&mut transcript,
)
.unwrap(),
);
}
let subclaim =
IOPVerifierState::check_and_generate_subclaim(&verifier_state, &asserted_sum)
.expect("fail to generate subclaim");
assert!(
poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation,
"wrong subclaim"
);
Ok(())
}
#[test]
fn test_trivial_polynomial() -> Result<(), PolyIOPErrors> {
let nv = 1;
let num_multiplicands_range = (4, 13);
let num_products = 5;
test_sumcheck(nv, num_multiplicands_range, num_products)?;
test_sumcheck_internal(nv, num_multiplicands_range, num_products)
}
#[test]
fn test_normal_polynomial() -> Result<(), PolyIOPErrors> {
let nv = 12;
let num_multiplicands_range = (4, 9);
let num_products = 5;
test_sumcheck(nv, num_multiplicands_range, num_products)?;
test_sumcheck_internal(nv, num_multiplicands_range, num_products)
}
#[test]
fn zero_polynomial_should_error() {
let nv = 0;
let num_multiplicands_range = (4, 13);
let num_products = 5;
assert!(test_sumcheck(nv, num_multiplicands_range, num_products).is_err());
assert!(test_sumcheck_internal(nv, num_multiplicands_range, num_products).is_err());
}
#[test]
fn test_extract_sum() -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
let (poly, asserted_sum) = VirtualPolynomial::<Fr>::rand(8, (3, 4), 3, &mut rng)?;
let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?;
assert_eq!(
<PolyIOP<Fr> as SumCheck<Fr>>::extract_sum(&proof),
asserted_sum
);
Ok(())
}
#[test]
/// Test that the memory usage of shared-reference is linear to number of
/// unique MLExtensions instead of total number of multiplicands.
fn test_shared_reference() -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
let ml_extensions: Vec<_> = (0..5)
.map(|_| Rc::new(DenseMultilinearExtension::<Fr>::rand(8, &mut rng)))
.collect();
let mut poly = VirtualPolynomial::new(8);
poly.add_mle_list(
vec![
ml_extensions[2].clone(),
ml_extensions[3].clone(),
ml_extensions[0].clone(),
],
Fr::rand(&mut rng),
)?;
poly.add_mle_list(
vec![
ml_extensions[1].clone(),
ml_extensions[4].clone(),
ml_extensions[4].clone(),
],
Fr::rand(&mut rng),
)?;
poly.add_mle_list(
vec![
ml_extensions[3].clone(),
ml_extensions[2].clone(),
ml_extensions[1].clone(),
],
Fr::rand(&mut rng),
)?;
poly.add_mle_list(
vec![ml_extensions[0].clone(), ml_extensions[0].clone()],
Fr::rand(&mut rng),
)?;
poly.add_mle_list(vec![ml_extensions[4].clone()], Fr::rand(&mut rng))?;
assert_eq!(poly.flattened_ml_extensions.len(), 5);
// test memory usage for prover
let prover = IOPProverState::<Fr>::prover_init(&poly).unwrap();
assert_eq!(prover.poly.flattened_ml_extensions.len(), 5);
drop(prover);
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
let poly_info = poly.aux_info.clone();
let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?;
let asserted_sum = <PolyIOP<Fr> as SumCheck<Fr>>::extract_sum(&proof);
let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript();
let subclaim = <PolyIOP<Fr> as SumCheck<Fr>>::verify(
asserted_sum,
&proof,
&poly_info,
&mut transcript,
)?;
assert!(
poly.evaluate(&subclaim.point)? == subclaim.expected_evaluation,
"wrong subclaim"
);
Ok(())
}
}

View File

@@ -0,0 +1,200 @@
//! Prover subroutines for a SumCheck protocol.
use super::SumCheckProver;
use crate::poly_iop::{
errors::PolyIOPErrors,
structs::{IOPProverMessage, IOPProverState},
};
use arithmetic::{fix_variables, VirtualPolynomial};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer, vec::Vec};
use rayon::prelude::IntoParallelIterator;
use std::rc::Rc;
#[cfg(feature = "parallel")]
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
type VirtualPolynomial = VirtualPolynomial<F>;
type ProverMessage = IOPProverMessage<F>;
/// Initialize the prover state to argue for the sum of the input polynomial
/// over {0,1}^`num_vars`.
fn prover_init(polynomial: &Self::VirtualPolynomial) -> Result<Self, PolyIOPErrors> {
let start = start_timer!(|| "sum check prover init");
if polynomial.aux_info.num_variables == 0 {
return Err(PolyIOPErrors::InvalidParameters(
"Attempt to prove a constant.".to_string(),
));
}
end_timer!(start);
Ok(Self {
challenges: Vec::with_capacity(polynomial.aux_info.num_variables),
round: 0,
poly: polynomial.clone(),
})
}
/// Receive message from verifier, generate prover message, and proceed to
/// next round.
///
/// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2).
fn prove_round_and_update_state(
&mut self,
challenge: &Option<F>,
) -> Result<Self::ProverMessage, PolyIOPErrors> {
// let start =
// start_timer!(|| format!("sum check prove {}-th round and update state",
// self.round));
if self.round >= self.poly.aux_info.num_variables {
return Err(PolyIOPErrors::InvalidProver(
"Prover is not active".to_string(),
));
}
// let fix_argument = start_timer!(|| "fix argument");
// Step 1:
// fix argument and evaluate f(x) over x_m = r; where r is the challenge
// 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
// which has already been evaluated to
//
// g(r_1, ..., r_{m-1}, x_m ... x_n)
//
// eval g over r_m, and mutate g to g(r_1, ... r_m,, x_{m+1}... x_n)
let mut flattened_ml_extensions: Vec<DenseMultilinearExtension<F>> = self
.poly
.flattened_ml_extensions
.iter()
.map(|x| x.as_ref().clone())
.collect();
if let Some(chal) = challenge {
if self.round == 0 {
return Err(PolyIOPErrors::InvalidProver(
"first round should be prover first.".to_string(),
));
}
self.challenges.push(*chal);
let r = self.challenges[self.round - 1];
#[cfg(feature = "parallel")]
flattened_ml_extensions
.par_iter_mut()
.for_each(|mle| *mle = fix_variables(mle, &[r]));
#[cfg(not(feature = "parallel"))]
flattened_ml_extensions
.iter_mut()
.for_each(|mle| *mle = fix_variables(mle, &[r]));
} else if self.round > 0 {
return Err(PolyIOPErrors::InvalidProver(
"verifier message is empty".to_string(),
));
}
// end_timer!(fix_argument);
self.round += 1;
let products_list = self.poly.products.clone();
let mut products_sum = vec![F::zero(); self.poly.aux_info.max_degree + 1];
// let compute_sum = start_timer!(|| "compute sum");
// Step 2: generate sum for the partial evaluated polynomial:
// f(r_1, ... r_m,, x_{m+1}... x_n)
#[cfg(feature = "parallel")]
{
let flag = (self.poly.aux_info.max_degree == 2)
&& (products_list.len() == 1)
&& (products_list[0].0 == F::one());
if flag {
for (t, e) in products_sum.iter_mut().enumerate() {
let evals = (0..1 << (self.poly.aux_info.num_variables - self.round))
.into_par_iter()
.map(|b| {
// evaluate P_round(t)
let table0 = &flattened_ml_extensions[products_list[0].1[0]];
let table1 = &flattened_ml_extensions[products_list[0].1[1]];
if t == 0 {
table0[b << 1] * table1[b << 1]
} else if t == 1 {
table0[(b << 1) + 1] * table1[(b << 1) + 1]
} else {
(table0[(b << 1) + 1] + table0[(b << 1) + 1] - table0[b << 1])
* (table1[(b << 1) + 1] + table1[(b << 1) + 1] - table1[b << 1])
}
})
.collect::<Vec<F>>();
for val in evals.iter() {
*e += val
}
}
} else {
for (t, e) in products_sum.iter_mut().enumerate() {
let t = F::from(t as u64);
let products = (0..1 << (self.poly.aux_info.num_variables - self.round))
.into_par_iter()
.map(|b| {
// evaluate P_round(t)
let mut tmp = F::zero();
products_list.iter().for_each(|(coefficient, products)| {
let num_mles = products.len();
let mut product = *coefficient;
for &f in products.iter().take(num_mles) {
let table = &flattened_ml_extensions[f]; // f's range is checked in init
product *=
table[b << 1] + (table[(b << 1) + 1] - table[b << 1]) * t;
}
tmp += product;
});
tmp
})
.collect::<Vec<F>>();
for i in products.iter() {
*e += i
}
}
}
}
#[cfg(not(feature = "parallel"))]
products_sum.iter_mut().enumerate().for_each(|(t, e)| {
let t = F::from(t as u64);
let one_minus_t = F::one() - t;
for b in 0..1 << (self.poly.aux_info.num_variables - self.round) {
// evaluate P_round(t)
for (coefficient, products) in products_list.iter() {
let num_mles = products.len();
let mut product = *coefficient;
for &f in products.iter().take(num_mles) {
let table = &flattened_ml_extensions[f]; // f's range is checked in init
product *= table[b << 1] + (table[(b << 1) + 1] - table[b << 1]) * t;
}
*e += product;
}
}
});
// update prover's state to the partial evaluated polynomial
self.poly.flattened_ml_extensions = flattened_ml_extensions
.iter()
.map(|x| Rc::new(x.clone()))
.collect();
// end_timer!(compute_sum);
// end_timer!(start);
Ok(IOPProverMessage {
evaluations: products_sum,
})
}
}

View File

@@ -0,0 +1,352 @@
//! Verifier subroutines for a SumCheck protocol.
use super::{SumCheckSubClaim, SumCheckVerifier};
use crate::poly_iop::{
errors::PolyIOPErrors,
structs::{IOPProverMessage, IOPVerifierState},
};
use arithmetic::VPAuxInfo;
use ark_ff::PrimeField;
use ark_std::{end_timer, start_timer};
use transcript::IOPTranscript;
#[cfg(feature = "parallel")]
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
type VPAuxInfo = VPAuxInfo<F>;
type ProverMessage = IOPProverMessage<F>;
type Challenge = F;
type Transcript = IOPTranscript<F>;
type SumCheckSubClaim = SumCheckSubClaim<F>;
/// Initialize the verifier's state.
fn verifier_init(index_info: &Self::VPAuxInfo) -> Self {
let start = start_timer!(|| "sum check verifier init");
let res = Self {
round: 1,
num_vars: index_info.num_variables,
max_degree: index_info.max_degree,
finished: false,
polynomials_received: Vec::with_capacity(index_info.num_variables),
challenges: Vec::with_capacity(index_info.num_variables),
};
end_timer!(start);
res
}
/// Run verifier for the current round, given a prover message.
///
/// Note that `verify_round_and_update_state` only samples and stores
/// challenges; and update the verifier's state accordingly. The actual
/// verifications are deferred (in batch) to `check_and_generate_subclaim`
/// at the last step.
fn verify_round_and_update_state(
&mut self,
prover_msg: &Self::ProverMessage,
transcript: &mut Self::Transcript,
) -> Result<Self::Challenge, PolyIOPErrors> {
let start =
start_timer!(|| format!("sum check verify {}-th round and update state", self.round));
if self.finished {
return Err(PolyIOPErrors::InvalidVerifier(
"Incorrect verifier state: Verifier is already finished.".to_string(),
));
}
// In an interactive protocol, the verifier should
//
// 1. check if the received 'P(0) + P(1) = expected`.
// 2. set `expected` to P(r)`
//
// When we turn the protocol to a non-interactive one, it is sufficient to defer
// such checks to `check_and_generate_subclaim` after the last round.
let challenge = transcript.get_and_append_challenge(b"Internal round")?;
self.challenges.push(challenge);
self.polynomials_received
.push(prover_msg.evaluations.to_vec());
if self.round == self.num_vars {
// accept and close
self.finished = true;
} else {
// proceed to the next round
self.round += 1;
}
end_timer!(start);
Ok(challenge)
}
/// This function verifies the deferred checks in the interactive version of
/// the protocol; and generate the subclaim. Returns an error if the
/// proof failed to verify.
///
/// If the asserted sum is correct, then the multilinear polynomial
/// evaluated at `subclaim.point` will be `subclaim.expected_evaluation`.
/// Otherwise, it is highly unlikely that those two will be equal.
/// Larger field size guarantees smaller soundness error.
fn check_and_generate_subclaim(
&self,
asserted_sum: &F,
) -> Result<Self::SumCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "sum check check and generate subclaim");
if !self.finished {
return Err(PolyIOPErrors::InvalidVerifier(
"Incorrect verifier state: Verifier has not finished.".to_string(),
));
}
if self.polynomials_received.len() != self.num_vars {
return Err(PolyIOPErrors::InvalidVerifier(
"insufficient rounds".to_string(),
));
}
// the deferred check during the interactive phase:
// 2. set `expected` to P(r)`
#[cfg(feature = "parallel")]
let mut expected_vec = self
.polynomials_received
.clone()
.into_par_iter()
.zip(self.challenges.clone().into_par_iter())
.map(|(evaluations, challenge)| {
if evaluations.len() != self.max_degree + 1 {
return Err(PolyIOPErrors::InvalidVerifier(format!(
"incorrect number of evaluations: {} vs {}",
evaluations.len(),
self.max_degree + 1
)));
}
interpolate_uni_poly::<F>(&evaluations, challenge)
})
.collect::<Result<Vec<_>, PolyIOPErrors>>()?;
#[cfg(not(feature = "parallel"))]
let mut expected_vec = self
.polynomials_received
.clone()
.into_iter()
.zip(self.challenges.clone().into_iter())
.map(|(evaluations, challenge)| {
if evaluations.len() != self.max_degree + 1 {
return Err(PolyIOPErrors::InvalidVerifier(format!(
"incorrect number of evaluations: {} vs {}",
evaluations.len(),
self.max_degree + 1
)));
}
interpolate_uni_poly::<F>(&evaluations, challenge)
})
.collect::<Result<Vec<_>, PolyIOPErrors>>()?;
// insert the asserted_sum to the first position of the expected vector
expected_vec.insert(0, *asserted_sum);
for (evaluations, &expected) in self
.polynomials_received
.iter()
.zip(expected_vec.iter())
.take(self.num_vars)
{
// the deferred check during the interactive phase:
// 1. check if the received 'P(0) + P(1) = expected`.
if evaluations[0] + evaluations[1] != expected {
return Err(PolyIOPErrors::InvalidProof(
"Prover message is not consistent with the claim.".to_string(),
));
}
}
end_timer!(start);
Ok(SumCheckSubClaim {
point: self.challenges.clone(),
// the last expected value (not checked within this function) will be included in the
// subclaim
expected_evaluation: expected_vec[self.num_vars],
})
}
}
/// Interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this
/// polynomial at `eval_at`:
///
/// \sum_{i=0}^len p_i * (\prod_{j!=i} (eval_at - j)/(i-j) )
///
/// This implementation is linear in number of inputs in terms of field
/// operations. It also has a quadratic term in primitive operations which is
/// negligible compared to field operations.
fn interpolate_uni_poly<F: PrimeField>(p_i: &[F], eval_at: F) -> Result<F, PolyIOPErrors> {
let start = start_timer!(|| "sum check interpolate uni poly opt");
let len = p_i.len();
let mut evals = vec![];
let mut prod = eval_at;
evals.push(eval_at);
// `prod = \prod_{j} (eval_at - j)`
for e in 1..len {
let tmp = eval_at - F::from(e as u64);
evals.push(tmp);
prod *= tmp;
}
let mut res = F::zero();
// we want to compute \prod (j!=i) (i-j) for a given i
//
// we start from the last step, which is
// denom[len-1] = (len-1) * (len-2) *... * 2 * 1
// the step before that is
// denom[len-2] = (len-2) * (len-3) * ... * 2 * 1 * -1
// and the step before that is
// denom[len-3] = (len-3) * (len-4) * ... * 2 * 1 * -1 * -2
//
// i.e., for any i, the one before this will be derived from
// denom[i-1] = denom[i] * (len-i) / i
//
// that is, we only need to store
// - the last denom for i = len-1, and
// - the ratio between current step and fhe last step, which is the product of
// (len-i) / i from all previous steps and we store this product as a fraction
// number to reduce field divisions.
// We know
// - 2^61 < factorial(20) < 2^62
// - 2^122 < factorial(33) < 2^123
// so we will be able to compute the ratio
// - for len <= 20 with i64
// - for len <= 33 with i128
// - for len > 33 with BigInt
if p_i.len() <= 20 {
let last_denominator = F::from(u64_factorial(len - 1));
let mut ratio_numerator = 1i64;
let mut ratio_denominator = 1u64;
for i in (0..len).rev() {
let ratio_numerator_f = if ratio_numerator < 0 {
-F::from((-ratio_numerator) as u64)
} else {
F::from(ratio_numerator as u64)
};
res += p_i[i] * prod * F::from(ratio_denominator)
/ (last_denominator * ratio_numerator_f * evals[i]);
// compute denom for the next step is current_denom * (len-i)/i
if i != 0 {
ratio_numerator *= -(len as i64 - i as i64);
ratio_denominator *= i as u64;
}
}
} else if p_i.len() <= 33 {
let last_denominator = F::from(u128_factorial(len - 1));
let mut ratio_numerator = 1i128;
let mut ratio_denominator = 1u128;
for i in (0..len).rev() {
let ratio_numerator_f = if ratio_numerator < 0 {
-F::from((-ratio_numerator) as u128)
} else {
F::from(ratio_numerator as u128)
};
res += p_i[i] * prod * F::from(ratio_denominator)
/ (last_denominator * ratio_numerator_f * evals[i]);
// compute denom for the next step is current_denom * (len-i)/i
if i != 0 {
ratio_numerator *= -(len as i128 - i as i128);
ratio_denominator *= i as u128;
}
}
} else {
let mut denom_up = field_factorial::<F>(len - 1);
let mut denom_down = F::one();
for i in (0..len).rev() {
res += p_i[i] * prod * denom_down / (denom_up * evals[i]);
// compute denom for the next step is current_denom * (len-i)/i
if i != 0 {
denom_up *= -F::from((len - i) as u64);
denom_down *= F::from(i as u64);
}
}
}
end_timer!(start);
Ok(res)
}
/// compute the factorial(a) = 1 * 2 * ... * a
#[inline]
fn field_factorial<F: PrimeField>(a: usize) -> F {
let mut res = F::one();
for i in 2..=a {
res *= F::from(i as u64);
}
res
}
/// compute the factorial(a) = 1 * 2 * ... * a
#[inline]
fn u128_factorial(a: usize) -> u128 {
let mut res = 1u128;
for i in 2..=a {
res *= i as u128;
}
res
}
/// compute the factorial(a) = 1 * 2 * ... * a
#[inline]
fn u64_factorial(a: usize) -> u64 {
let mut res = 1u64;
for i in 2..=a {
res *= i as u64;
}
res
}
#[cfg(test)]
mod test {
use super::interpolate_uni_poly;
use crate::poly_iop::errors::PolyIOPErrors;
use ark_bls12_381::Fr;
use ark_poly::{univariate::DensePolynomial, Polynomial, UVPolynomial};
use ark_std::{vec::Vec, UniformRand};
#[test]
fn test_interpolation() -> Result<(), PolyIOPErrors> {
let mut prng = ark_std::test_rng();
// test a polynomial with 20 known points, i.e., with degree 19
let poly = DensePolynomial::<Fr>::rand(20 - 1, &mut prng);
let evals = (0..20)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
let query = Fr::rand(&mut prng);
assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?);
// test a polynomial with 33 known points, i.e., with degree 32
let poly = DensePolynomial::<Fr>::rand(33 - 1, &mut prng);
let evals = (0..33)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
let query = Fr::rand(&mut prng);
assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?);
// test a polynomial with 64 known points, i.e., with degree 63
let poly = DensePolynomial::<Fr>::rand(64 - 1, &mut prng);
let evals = (0..64)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
let query = Fr::rand(&mut prng);
assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?);
Ok(())
}
}

View File

@@ -0,0 +1,28 @@
//! useful macros.
/// Takes as input a struct, and converts them to a series of bytes. All traits
/// that implement `CanonicalSerialize` can be automatically converted to bytes
/// in this manner.
#[macro_export]
macro_rules! to_bytes {
($x:expr) => {{
let mut buf = ark_std::vec![];
ark_serialize::CanonicalSerialize::serialize($x, &mut buf).map(|_| buf)
}};
}
#[cfg(test)]
mod test {
use ark_bls12_381::Fr;
use ark_serialize::CanonicalSerialize;
use ark_std::One;
#[test]
fn test_to_bytes() {
let f1 = Fr::one();
let mut bytes = ark_std::vec![];
f1.serialize(&mut bytes).unwrap();
assert_eq!(bytes, to_bytes!(&f1).unwrap());
}
}

View File

@@ -0,0 +1,205 @@
//! Main module for the ZeroCheck protocol.
use std::fmt::Debug;
use crate::poly_iop::{errors::PolyIOPErrors, sum_check::SumCheck, PolyIOP};
use arithmetic::build_eq_x_r;
use ark_ff::PrimeField;
use ark_poly::MultilinearExtension;
use ark_std::{end_timer, start_timer};
use transcript::IOPTranscript;
/// A zero check IOP subclaim for `f(x)` consists of the following:
/// - the initial challenge vector r which is used to build eq(x, r) in
/// SumCheck
/// - the random vector `v` to be evaluated
/// - the claimed evaluation of `f(v)`
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ZeroCheckSubClaim<F: PrimeField> {
// the evaluation point
pub point: Vec<F>,
/// the expected evaluation
pub expected_evaluation: F,
// the initial challenge r which is used to build eq(x, r)
pub init_challenge: Vec<F>,
}
/// A ZeroCheck for `f(x)` proves that `f(x) = 0` for all `x \in {0,1}^num_vars`
/// It is derived from SumCheck.
pub trait ZeroCheck<F: PrimeField>: SumCheck<F> {
type ZeroCheckSubClaim: Clone + Debug + Default + PartialEq;
type ZeroCheckProof: Clone + Debug + Default + PartialEq;
/// Initialize the system with a transcript
///
/// This function is optional -- in the case where a ZeroCheck is
/// an building block for a more complex protocol, the transcript
/// may be initialized by this complex protocol, and passed to the
/// ZeroCheck prover/verifier.
fn init_transcript() -> Self::Transcript;
/// initialize the prover to argue for the sum of polynomial over
/// {0,1}^`num_vars` is zero.
fn prove(
poly: &Self::VirtualPolynomial,
transcript: &mut Self::Transcript,
) -> Result<Self::ZeroCheckProof, PolyIOPErrors>;
/// verify the claimed sum using the proof
fn verify(
proof: &Self::ZeroCheckProof,
aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::ZeroCheckSubClaim, PolyIOPErrors>;
}
impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> {
type ZeroCheckSubClaim = ZeroCheckSubClaim<F>;
type ZeroCheckProof = Self::SumCheckProof;
fn init_transcript() -> Self::Transcript {
IOPTranscript::<F>::new(b"Initializing ZeroCheck transcript")
}
fn prove(
poly: &Self::VirtualPolynomial,
transcript: &mut Self::Transcript,
) -> Result<Self::ZeroCheckProof, PolyIOPErrors> {
let start = start_timer!(|| "zero check prove");
let length = poly.aux_info.num_variables;
let r = transcript.get_and_append_challenge_vectors(b"0check r", length)?;
let f_hat = poly.build_f_hat(r.as_ref())?;
let res = <Self as SumCheck<F>>::prove(&f_hat, transcript);
end_timer!(start);
res
}
fn verify(
proof: &Self::ZeroCheckProof,
fx_aux_info: &Self::VPAuxInfo,
transcript: &mut Self::Transcript,
) -> Result<Self::ZeroCheckSubClaim, PolyIOPErrors> {
let start = start_timer!(|| "zero check verify");
// check that the sum is zero
if proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1] != F::zero() {
return Err(PolyIOPErrors::InvalidProof(format!(
"zero check: sum {} is not zero",
proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1]
)));
}
// 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"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();
hat_fx_aux_info.max_degree += 1;
let sum_subclaim =
<Self as SumCheck<F>>::verify(F::zero(), proof, &hat_fx_aux_info, transcript)?;
// expected_eval = sumcheck.expect_eval/eq(v, r)
// where v = sum_check_sub_claim.point
let eq_x_r = build_eq_x_r(&r)?;
let expected_evaluation = sum_subclaim.expected_evaluation
/ eq_x_r.evaluate(&sum_subclaim.point).ok_or_else(|| {
PolyIOPErrors::InvalidParameters("evaluation dimension does not match".to_string())
})?;
end_timer!(start);
Ok(ZeroCheckSubClaim {
point: sum_subclaim.point,
expected_evaluation,
init_challenge: r,
})
}
}
#[cfg(test)]
mod test {
use super::ZeroCheck;
use crate::poly_iop::{errors::PolyIOPErrors, PolyIOP};
use arithmetic::VirtualPolynomial;
use ark_bls12_381::Fr;
use ark_std::test_rng;
fn test_zerocheck(
nv: usize,
num_multiplicands_range: (usize, usize),
num_products: usize,
) -> Result<(), PolyIOPErrors> {
let mut rng = test_rng();
{
// good path: zero virtual poly
let poly =
VirtualPolynomial::rand_zero(nv, num_multiplicands_range, num_products, &mut rng)?;
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?;
let poly_info = poly.aux_info.clone();
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let zero_subclaim =
<PolyIOP<Fr> as ZeroCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)?;
assert!(
poly.evaluate(&zero_subclaim.point)? == zero_subclaim.expected_evaluation,
"wrong subclaim"
);
}
{
// bad path: random virtual poly whose sum is not zero
let (poly, _sum) =
VirtualPolynomial::<Fr>::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?;
let poly_info = poly.aux_info.clone();
let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
transcript.append_message(b"testing", b"initializing transcript for testing")?;
assert!(
<PolyIOP<Fr> as ZeroCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)
.is_err()
);
}
Ok(())
}
#[test]
fn test_trivial_polynomial() -> Result<(), PolyIOPErrors> {
let nv = 1;
let num_multiplicands_range = (4, 5);
let num_products = 1;
test_zerocheck(nv, num_multiplicands_range, num_products)
}
#[test]
fn test_normal_polynomial() -> Result<(), PolyIOPErrors> {
let nv = 5;
let num_multiplicands_range = (4, 9);
let num_products = 5;
test_zerocheck(nv, num_multiplicands_range, num_products)
}
#[test]
fn zero_polynomial_should_error() -> Result<(), PolyIOPErrors> {
let nv = 0;
let num_multiplicands_range = (4, 13);
let num_products = 5;
assert!(test_zerocheck(nv, num_multiplicands_range, num_products).is_err());
Ok(())
}
}