mirror of
https://github.com/arnaucube/hyperplonk.git
synced 2026-01-12 00:51:27 +01:00
Batch all (#89)
- use sumcheck to batch open PCS - split Prod and witness into two batches - benchmark code
This commit is contained in:
56
subroutines/src/poly_iop/errors.rs
Normal file
56
subroutines/src/poly_iop/errors.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
30
subroutines/src/poly_iop/mod.rs
Normal file
30
subroutines/src/poly_iop/mod.rs
Normal 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>,
|
||||
}
|
||||
276
subroutines/src/poly_iop/perm_check/mod.rs
Normal file
276
subroutines/src/poly_iop/perm_check/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
61
subroutines/src/poly_iop/perm_check/util.rs
Normal file
61
subroutines/src/poly_iop/perm_check/util.rs
Normal 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))
|
||||
}
|
||||
4
subroutines/src/poly_iop/prelude.rs
Normal file
4
subroutines/src/poly_iop/prelude.rs
Normal 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,
|
||||
};
|
||||
304
subroutines/src/poly_iop/prod_check/mod.rs
Normal file
304
subroutines/src/poly_iop/prod_check/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
202
subroutines/src/poly_iop/prod_check/util.rs
Normal file
202
subroutines/src/poly_iop/prod_check/util.rs
Normal 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)
|
||||
}
|
||||
9
subroutines/src/poly_iop/readme.md
Normal file
9
subroutines/src/poly_iop/readme.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Poly IOP
|
||||
-----
|
||||
|
||||
Implements the following protocols
|
||||
|
||||
- sum checks
|
||||
- zero checks
|
||||
- product checks
|
||||
- permutation checks
|
||||
45
subroutines/src/poly_iop/structs.rs
Normal file
45
subroutines/src/poly_iop/structs.rs
Normal 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>,
|
||||
}
|
||||
387
subroutines/src/poly_iop/sum_check/mod.rs
Normal file
387
subroutines/src/poly_iop/sum_check/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
200
subroutines/src/poly_iop/sum_check/prover.rs
Normal file
200
subroutines/src/poly_iop/sum_check/prover.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
352
subroutines/src/poly_iop/sum_check/verifier.rs
Normal file
352
subroutines/src/poly_iop/sum_check/verifier.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
28
subroutines/src/poly_iop/utils.rs
Normal file
28
subroutines/src/poly_iop/utils.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
205
subroutines/src/poly_iop/zero_check/mod.rs
Normal file
205
subroutines/src/poly_iop/zero_check/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user