mirror of
https://github.com/arnaucube/hyperplonk.git
synced 2026-01-11 00:21:28 +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:
5
subroutines/src/lib.rs
Normal file
5
subroutines/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod pcs;
|
||||
pub mod poly_iop;
|
||||
|
||||
pub use pcs::prelude::*;
|
||||
pub use poly_iop::prelude::*;
|
||||
50
subroutines/src/pcs/errors.rs
Normal file
50
subroutines/src/pcs/errors.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2022 Espresso Systems (espressosys.com)
|
||||
// This file is part of the Jellyfish library.
|
||||
|
||||
// You should have received a copy of the MIT License
|
||||
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
|
||||
|
||||
//! Error module.
|
||||
|
||||
use arithmetic::ArithErrors;
|
||||
use ark_serialize::SerializationError;
|
||||
use ark_std::string::String;
|
||||
use displaydoc::Display;
|
||||
use transcript::TranscriptError;
|
||||
|
||||
/// A `enum` specifying the possible failure modes of the PCS.
|
||||
#[derive(Display, Debug)]
|
||||
pub enum PCSError {
|
||||
/// Invalid Prover: {0}
|
||||
InvalidProver(String),
|
||||
/// Invalid Verifier: {0}
|
||||
InvalidVerifier(String),
|
||||
/// Invalid Proof: {0}
|
||||
InvalidProof(String),
|
||||
/// Invalid parameters: {0}
|
||||
InvalidParameters(String),
|
||||
/// An error during (de)serialization: {0}
|
||||
SerializationError(SerializationError),
|
||||
/// Transcript error {0}
|
||||
TranscriptError(TranscriptError),
|
||||
/// ArithErrors error {0}
|
||||
ArithErrors(ArithErrors),
|
||||
}
|
||||
|
||||
impl From<SerializationError> for PCSError {
|
||||
fn from(e: ark_serialize::SerializationError) -> Self {
|
||||
Self::SerializationError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TranscriptError> for PCSError {
|
||||
fn from(e: TranscriptError) -> Self {
|
||||
Self::TranscriptError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArithErrors> for PCSError {
|
||||
fn from(e: ArithErrors) -> Self {
|
||||
Self::ArithErrors(e)
|
||||
}
|
||||
}
|
||||
172
subroutines/src/pcs/mod.rs
Normal file
172
subroutines/src/pcs/mod.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
mod errors;
|
||||
mod multilinear_kzg;
|
||||
mod structs;
|
||||
mod univariate_kzg;
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_ff::Field;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use ark_std::rand::{CryptoRng, RngCore};
|
||||
use errors::PCSError;
|
||||
use std::{borrow::Borrow, fmt::Debug, hash::Hash};
|
||||
use transcript::IOPTranscript;
|
||||
|
||||
/// This trait defines APIs for polynomial commitment schemes.
|
||||
/// Note that for our usage of PCS, we do not require the hiding property.
|
||||
pub trait PolynomialCommitmentScheme<E: PairingEngine> {
|
||||
/// Prover parameters
|
||||
type ProverParam: Clone;
|
||||
/// Verifier parameters
|
||||
type VerifierParam: Clone + CanonicalSerialize + CanonicalDeserialize;
|
||||
/// Structured reference string
|
||||
type SRS: Clone + Debug;
|
||||
/// Polynomial and its associated types
|
||||
type Polynomial: Clone
|
||||
+ Debug
|
||||
+ Hash
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ CanonicalSerialize
|
||||
+ CanonicalDeserialize;
|
||||
/// Polynomial input domain
|
||||
type Point: Clone + Ord + Debug + Sync + Hash + PartialEq + Eq;
|
||||
/// Polynomial Evaluation
|
||||
type Evaluation: Field;
|
||||
/// Commitments
|
||||
type Commitment: Clone + CanonicalSerialize + CanonicalDeserialize + Debug + PartialEq + Eq;
|
||||
/// Proofs
|
||||
type Proof: Clone + CanonicalSerialize + CanonicalDeserialize + Debug + PartialEq + Eq;
|
||||
/// Batch proofs
|
||||
type BatchProof;
|
||||
|
||||
/// Build SRS for testing.
|
||||
///
|
||||
/// - For univariate polynomials, `supported_size` is the maximum degree.
|
||||
/// - For multilinear polynomials, `supported_size` is the number of
|
||||
/// variables.
|
||||
///
|
||||
/// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY.
|
||||
/// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION.
|
||||
fn gen_srs_for_testing<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
supported_size: usize,
|
||||
) -> Result<Self::SRS, PCSError>;
|
||||
|
||||
/// Trim the universal parameters to specialize the public parameters.
|
||||
/// Input both `supported_degree` for univariate and
|
||||
/// `supported_num_vars` for multilinear.
|
||||
/// ## Note on function signature
|
||||
/// Usually, data structure like SRS and ProverParam are huge and users
|
||||
/// might wish to keep them in heap using different kinds of smart pointers
|
||||
/// (instead of only in stack) therefore our `impl Borrow<_>` interface
|
||||
/// allows for passing in any pointer type, e.g.: `trim(srs: &Self::SRS,
|
||||
/// ..)` or `trim(srs: Box<Self::SRS>, ..)` or `trim(srs: Arc<Self::SRS>,
|
||||
/// ..)` etc.
|
||||
fn trim(
|
||||
srs: impl Borrow<Self::SRS>,
|
||||
supported_degree: Option<usize>,
|
||||
supported_num_vars: Option<usize>,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError>;
|
||||
|
||||
/// Generate a commitment for a polynomial
|
||||
/// ## Note on function signature
|
||||
/// Usually, data structure like SRS and ProverParam are huge and users
|
||||
/// might wish to keep them in heap using different kinds of smart pointers
|
||||
/// (instead of only in stack) therefore our `impl Borrow<_>` interface
|
||||
/// allows for passing in any pointer type, e.g.: `commit(prover_param:
|
||||
/// &Self::ProverParam, ..)` or `commit(prover_param:
|
||||
/// Box<Self::ProverParam>, ..)` or `commit(prover_param:
|
||||
/// Arc<Self::ProverParam>, ..)` etc.
|
||||
fn commit(
|
||||
prover_param: impl Borrow<Self::ProverParam>,
|
||||
poly: &Self::Polynomial,
|
||||
) -> Result<Self::Commitment, PCSError>;
|
||||
|
||||
/// On input a polynomial `p` and a point `point`, outputs a proof for the
|
||||
/// same.
|
||||
fn open(
|
||||
prover_param: impl Borrow<Self::ProverParam>,
|
||||
polynomial: &Self::Polynomial,
|
||||
point: &Self::Point,
|
||||
) -> Result<(Self::Proof, Self::Evaluation), PCSError>;
|
||||
|
||||
/// Input a list of multilinear extensions, and a same number of points, and
|
||||
/// a transcript, compute a multi-opening for all the polynomials.
|
||||
fn multi_open(
|
||||
_prover_param: impl Borrow<Self::ProverParam>,
|
||||
_polynomials: &[Self::Polynomial],
|
||||
_points: &[Self::Point],
|
||||
_evals: &[Self::Evaluation],
|
||||
_transcript: &mut IOPTranscript<E::Fr>,
|
||||
) -> Result<Self::BatchProof, PCSError> {
|
||||
// the reason we use unimplemented!() is to enable developers to implement the
|
||||
// trait without always implementing the batching APIs.
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Verifies that `value` is the evaluation at `x` of the polynomial
|
||||
/// committed inside `comm`.
|
||||
fn verify(
|
||||
verifier_param: &Self::VerifierParam,
|
||||
commitment: &Self::Commitment,
|
||||
point: &Self::Point,
|
||||
value: &E::Fr,
|
||||
proof: &Self::Proof,
|
||||
) -> Result<bool, PCSError>;
|
||||
|
||||
/// Verifies that `value_i` is the evaluation at `x_i` of the polynomial
|
||||
/// `poly_i` committed inside `comm`.
|
||||
fn batch_verify(
|
||||
_verifier_param: &Self::VerifierParam,
|
||||
_commitments: &[Self::Commitment],
|
||||
_points: &[Self::Point],
|
||||
_batch_proof: &Self::BatchProof,
|
||||
_transcript: &mut IOPTranscript<E::Fr>,
|
||||
) -> Result<bool, PCSError> {
|
||||
// the reason we use unimplemented!() is to enable developers to implement the
|
||||
// trait without always implementing the batching APIs.
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
/// API definitions for structured reference string
|
||||
pub trait StructuredReferenceString<E: PairingEngine>: Sized {
|
||||
/// Prover parameters
|
||||
type ProverParam;
|
||||
/// Verifier parameters
|
||||
type VerifierParam;
|
||||
|
||||
/// Extract the prover parameters from the public parameters.
|
||||
fn extract_prover_param(&self, supported_size: usize) -> Self::ProverParam;
|
||||
/// Extract the verifier parameters from the public parameters.
|
||||
fn extract_verifier_param(&self, supported_size: usize) -> Self::VerifierParam;
|
||||
|
||||
/// Trim the universal parameters to specialize the public parameters
|
||||
/// for polynomials to the given `supported_size`, and
|
||||
/// returns committer key and verifier key.
|
||||
///
|
||||
/// - For univariate polynomials, `supported_size` is the maximum degree.
|
||||
/// - For multilinear polynomials, `supported_size` is 2 to the number of
|
||||
/// variables.
|
||||
///
|
||||
/// `supported_log_size` should be in range `1..=params.log_size`
|
||||
fn trim(
|
||||
&self,
|
||||
supported_size: usize,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError>;
|
||||
|
||||
/// Build SRS for testing.
|
||||
///
|
||||
/// - For univariate polynomials, `supported_size` is the maximum degree.
|
||||
/// - For multilinear polynomials, `supported_size` is the number of
|
||||
/// variables.
|
||||
///
|
||||
/// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY.
|
||||
/// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION.
|
||||
fn gen_srs_for_testing<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
supported_size: usize,
|
||||
) -> Result<Self, PCSError>;
|
||||
}
|
||||
329
subroutines/src/pcs/multilinear_kzg/batching.rs
Normal file
329
subroutines/src/pcs/multilinear_kzg/batching.rs
Normal file
@@ -0,0 +1,329 @@
|
||||
//! Sumcheck based batch opening and verify commitment.
|
||||
// TODO: refactoring this code to somewhere else
|
||||
// currently IOP depends on PCS because perm check requires commitment.
|
||||
// The sumcheck based batch opening therefore cannot stay in the PCS repo --
|
||||
// which creates a cyclic dependency.
|
||||
|
||||
use crate::{
|
||||
pcs::{
|
||||
multilinear_kzg::util::eq_eval,
|
||||
prelude::{Commitment, PCSError},
|
||||
PolynomialCommitmentScheme,
|
||||
},
|
||||
poly_iop::{prelude::SumCheck, PolyIOP},
|
||||
IOPProof,
|
||||
};
|
||||
use arithmetic::{
|
||||
build_eq_x_r_vec, fix_last_variables, DenseMultilinearExtension, VPAuxInfo, VirtualPolynomial,
|
||||
};
|
||||
use ark_ec::{AffineCurve, PairingEngine, ProjectiveCurve};
|
||||
use ark_std::{end_timer, log2, start_timer, One, Zero};
|
||||
use std::{marker::PhantomData, rc::Rc};
|
||||
use transcript::IOPTranscript;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct BatchProof<E, PCS>
|
||||
where
|
||||
E: PairingEngine,
|
||||
PCS: PolynomialCommitmentScheme<E>,
|
||||
{
|
||||
/// A sum check proof proving tilde g's sum
|
||||
pub(crate) sum_check_proof: IOPProof<E::Fr>,
|
||||
/// f_i(point_i)
|
||||
pub f_i_eval_at_point_i: Vec<E::Fr>,
|
||||
/// proof for g'(a_2)
|
||||
pub(crate) g_prime_proof: PCS::Proof,
|
||||
}
|
||||
|
||||
/// Steps:
|
||||
/// 1. get challenge point t from transcript
|
||||
/// 2. build eq(t,i) for i in [0..k]
|
||||
/// 3. build \tilde g(i, b) = eq(t, i) * f_i(b)
|
||||
/// 4. compute \tilde eq
|
||||
/// 5. run sumcheck on \tilde eq * \tilde g(i, b)
|
||||
/// 6. build g'(a2) where (a1, a2) is the sumcheck's point
|
||||
pub(crate) fn multi_open_internal<E, PCS>(
|
||||
prover_param: &PCS::ProverParam,
|
||||
polynomials: &[PCS::Polynomial],
|
||||
points: &[PCS::Point],
|
||||
evals: &[PCS::Evaluation],
|
||||
transcript: &mut IOPTranscript<E::Fr>,
|
||||
) -> Result<BatchProof<E, PCS>, PCSError>
|
||||
where
|
||||
E: PairingEngine,
|
||||
PCS: PolynomialCommitmentScheme<
|
||||
E,
|
||||
Polynomial = Rc<DenseMultilinearExtension<E::Fr>>,
|
||||
Point = Vec<E::Fr>,
|
||||
Evaluation = E::Fr,
|
||||
>,
|
||||
{
|
||||
let open_timer = start_timer!(|| format!("multi open {} points", points.len()));
|
||||
|
||||
// TODO: sanity checks
|
||||
let num_var = polynomials[0].num_vars;
|
||||
let k = polynomials.len();
|
||||
let ell = log2(k) as usize;
|
||||
let merged_num_var = num_var + ell;
|
||||
|
||||
// challenge point t
|
||||
let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?;
|
||||
|
||||
// eq(t, i) for i in [0..k]
|
||||
let eq_t_i_list = build_eq_x_r_vec(t.as_ref())?;
|
||||
|
||||
// \tilde g(i, b) = eq(t, i) * f_i(b)
|
||||
let timer = start_timer!(|| format!("compute tilde g for {} points", points.len()));
|
||||
let mut tilde_g_eval = vec![E::Fr::zero(); 1 << (ell + num_var)];
|
||||
let block_size = 1 << num_var;
|
||||
for (index, f_i) in polynomials.iter().enumerate() {
|
||||
for (j, &f_i_eval) in f_i.iter().enumerate() {
|
||||
tilde_g_eval[index * block_size + j] = f_i_eval * eq_t_i_list[index];
|
||||
}
|
||||
}
|
||||
let tilde_g = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
|
||||
merged_num_var,
|
||||
tilde_g_eval,
|
||||
));
|
||||
end_timer!(timer);
|
||||
|
||||
let timer = start_timer!(|| format!("compute tilde eq for {} points", points.len()));
|
||||
let mut tilde_eq_eval = vec![E::Fr::zero(); 1 << (ell + num_var)];
|
||||
for (index, point) in points.iter().enumerate() {
|
||||
let eq_b_zi = build_eq_x_r_vec(point)?;
|
||||
let start = index * block_size;
|
||||
tilde_eq_eval[start..start + block_size].copy_from_slice(eq_b_zi.as_slice());
|
||||
}
|
||||
let tilde_eq = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
|
||||
merged_num_var,
|
||||
tilde_eq_eval,
|
||||
));
|
||||
end_timer!(timer);
|
||||
|
||||
// built the virtual polynomial for SumCheck
|
||||
let timer = start_timer!(|| format!("sum check prove of {} variables", num_var + ell));
|
||||
|
||||
let step = start_timer!(|| "add mle");
|
||||
let mut sum_check_vp = VirtualPolynomial::new(num_var + ell);
|
||||
sum_check_vp.add_mle_list([tilde_g.clone(), tilde_eq], E::Fr::one())?;
|
||||
end_timer!(step);
|
||||
|
||||
let proof = match <PolyIOP<E::Fr> as SumCheck<E::Fr>>::prove(&sum_check_vp, transcript) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
// cannot wrap IOPError with PCSError due to cyclic dependency
|
||||
return Err(PCSError::InvalidProver(
|
||||
"Sumcheck in batch proving Failed".to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
|
||||
end_timer!(timer);
|
||||
|
||||
// (a1, a2) := sumcheck's point
|
||||
let step = start_timer!(|| "open at a2");
|
||||
let a1 = &proof.point[num_var..];
|
||||
let a2 = &proof.point[..num_var];
|
||||
end_timer!(step);
|
||||
|
||||
// build g'(a2)
|
||||
let step = start_timer!(|| "evaluate at a2");
|
||||
let g_prime = Rc::new(fix_last_variables(&tilde_g, a1));
|
||||
end_timer!(step);
|
||||
|
||||
let step = start_timer!(|| "pcs open");
|
||||
let (g_prime_proof, _g_prime_eval) = PCS::open(prover_param, &g_prime, a2.to_vec().as_ref())?;
|
||||
// assert_eq!(g_prime_eval, tilde_g_eval);
|
||||
end_timer!(step);
|
||||
|
||||
let step = start_timer!(|| "evaluate fi(pi)");
|
||||
end_timer!(step);
|
||||
end_timer!(open_timer);
|
||||
|
||||
Ok(BatchProof {
|
||||
sum_check_proof: proof,
|
||||
f_i_eval_at_point_i: evals.to_vec(),
|
||||
g_prime_proof,
|
||||
})
|
||||
}
|
||||
|
||||
/// Steps:
|
||||
/// 1. get challenge point t from transcript
|
||||
/// 2. build g' commitment
|
||||
/// 3. ensure \sum_i eq(t, <i>) * f_i_evals matches the sum via SumCheck
|
||||
/// verification 4. verify commitment
|
||||
pub(crate) fn batch_verify_internal<E, PCS>(
|
||||
verifier_param: &PCS::VerifierParam,
|
||||
f_i_commitments: &[Commitment<E>],
|
||||
points: &[PCS::Point],
|
||||
proof: &BatchProof<E, PCS>,
|
||||
transcript: &mut IOPTranscript<E::Fr>,
|
||||
) -> Result<bool, PCSError>
|
||||
where
|
||||
E: PairingEngine,
|
||||
PCS: PolynomialCommitmentScheme<
|
||||
E,
|
||||
Polynomial = Rc<DenseMultilinearExtension<E::Fr>>,
|
||||
Point = Vec<E::Fr>,
|
||||
Evaluation = E::Fr,
|
||||
Commitment = Commitment<E>,
|
||||
>,
|
||||
{
|
||||
let open_timer = start_timer!(|| "batch verification");
|
||||
|
||||
// TODO: sanity checks
|
||||
|
||||
let k = f_i_commitments.len();
|
||||
let ell = log2(k) as usize;
|
||||
let num_var = proof.sum_check_proof.point.len() - ell;
|
||||
|
||||
// challenge point t
|
||||
let t = transcript.get_and_append_challenge_vectors("t".as_ref(), ell)?;
|
||||
|
||||
// sum check point (a1, a2)
|
||||
let a1 = &proof.sum_check_proof.point[num_var..];
|
||||
let a2 = &proof.sum_check_proof.point[..num_var];
|
||||
|
||||
// build g' commitment
|
||||
let eq_a1_list = build_eq_x_r_vec(a1)?;
|
||||
let eq_t_list = build_eq_x_r_vec(t.as_ref())?;
|
||||
|
||||
let mut g_prime_commit = E::G1Affine::zero().into_projective();
|
||||
for i in 0..k {
|
||||
let tmp = eq_a1_list[i] * eq_t_list[i];
|
||||
g_prime_commit += &f_i_commitments[i].0.mul(tmp);
|
||||
}
|
||||
|
||||
// ensure \sum_i eq(t, <i>) * f_i_evals matches the sum via SumCheck
|
||||
// verification
|
||||
let mut sum = E::Fr::zero();
|
||||
for (i, &e) in eq_t_list.iter().enumerate().take(k) {
|
||||
sum += e * proof.f_i_eval_at_point_i[i];
|
||||
}
|
||||
let aux_info = VPAuxInfo {
|
||||
max_degree: 2,
|
||||
num_variables: num_var + ell,
|
||||
phantom: PhantomData,
|
||||
};
|
||||
let subclaim = match <PolyIOP<E::Fr> as SumCheck<E::Fr>>::verify(
|
||||
sum,
|
||||
&proof.sum_check_proof,
|
||||
&aux_info,
|
||||
transcript,
|
||||
) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
// cannot wrap IOPError with PCSError due to cyclic dependency
|
||||
return Err(PCSError::InvalidProver(
|
||||
"Sumcheck in batch verification failed".to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let mut eq_tilde_eval = E::Fr::zero();
|
||||
for (point, &coef) in points.iter().zip(eq_a1_list.iter()) {
|
||||
eq_tilde_eval += coef * eq_eval(a2, point)?;
|
||||
}
|
||||
let tilde_g_eval = subclaim.expected_evaluation / eq_tilde_eval;
|
||||
|
||||
// verify commitment
|
||||
let res = PCS::verify(
|
||||
verifier_param,
|
||||
&Commitment(g_prime_commit.into_affine()),
|
||||
a2.to_vec().as_ref(),
|
||||
&tilde_g_eval,
|
||||
&proof.g_prime_proof,
|
||||
)?;
|
||||
|
||||
end_timer!(open_timer);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::pcs::{
|
||||
prelude::{MultilinearKzgPCS, MultilinearUniversalParams},
|
||||
StructuredReferenceString,
|
||||
};
|
||||
use arithmetic::get_batched_nv;
|
||||
use ark_bls12_381::Bls12_381 as E;
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||
use ark_std::{
|
||||
rand::{CryptoRng, RngCore},
|
||||
test_rng,
|
||||
vec::Vec,
|
||||
UniformRand,
|
||||
};
|
||||
|
||||
type Fr = <E as PairingEngine>::Fr;
|
||||
|
||||
fn test_multi_open_helper<R: RngCore + CryptoRng>(
|
||||
ml_params: &MultilinearUniversalParams<E>,
|
||||
polys: &[Rc<DenseMultilinearExtension<Fr>>],
|
||||
rng: &mut R,
|
||||
) -> Result<(), PCSError> {
|
||||
let merged_nv = get_batched_nv(polys[0].num_vars(), polys.len());
|
||||
let (ml_ck, ml_vk) = ml_params.trim(merged_nv)?;
|
||||
|
||||
let mut points = Vec::new();
|
||||
for poly in polys.iter() {
|
||||
let point = (0..poly.num_vars())
|
||||
.map(|_| Fr::rand(rng))
|
||||
.collect::<Vec<Fr>>();
|
||||
points.push(point);
|
||||
}
|
||||
|
||||
let evals = polys
|
||||
.iter()
|
||||
.zip(points.iter())
|
||||
.map(|(f, p)| f.evaluate(p).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let commitments = polys
|
||||
.iter()
|
||||
.map(|poly| MultilinearKzgPCS::commit(&ml_ck.clone(), poly).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut transcript = IOPTranscript::new("test transcript".as_ref());
|
||||
transcript.append_field_element("init".as_ref(), &Fr::zero())?;
|
||||
|
||||
let batch_proof = multi_open_internal::<E, MultilinearKzgPCS<E>>(
|
||||
&ml_ck,
|
||||
polys,
|
||||
&points,
|
||||
&evals,
|
||||
&mut transcript,
|
||||
)?;
|
||||
|
||||
// good path
|
||||
let mut transcript = IOPTranscript::new("test transcript".as_ref());
|
||||
transcript.append_field_element("init".as_ref(), &Fr::zero())?;
|
||||
assert!(batch_verify_internal::<E, MultilinearKzgPCS<E>>(
|
||||
&ml_vk,
|
||||
&commitments,
|
||||
&points,
|
||||
&batch_proof,
|
||||
&mut transcript
|
||||
)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_open_internal() -> Result<(), PCSError> {
|
||||
let mut rng = test_rng();
|
||||
|
||||
let ml_params = MultilinearUniversalParams::<E>::gen_srs_for_testing(&mut rng, 20)?;
|
||||
for num_poly in 5..6 {
|
||||
for nv in 15..16 {
|
||||
let polys1: Vec<_> = (0..num_poly)
|
||||
.map(|_| Rc::new(DenseMultilinearExtension::rand(nv, &mut rng)))
|
||||
.collect();
|
||||
test_multi_open_helper(&ml_params, &polys1, &mut rng)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
405
subroutines/src/pcs/multilinear_kzg/mod.rs
Normal file
405
subroutines/src/pcs/multilinear_kzg/mod.rs
Normal file
@@ -0,0 +1,405 @@
|
||||
// Copyright (c) 2022 Espresso Systems (espressosys.com)
|
||||
// This file is part of the Jellyfish library.
|
||||
|
||||
// You should have received a copy of the MIT License
|
||||
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
|
||||
|
||||
//! Main module for multilinear KZG commitment scheme
|
||||
|
||||
pub(crate) mod batching;
|
||||
pub(crate) mod srs;
|
||||
pub(crate) mod util;
|
||||
|
||||
use crate::{
|
||||
pcs::{prelude::Commitment, PCSError, PolynomialCommitmentScheme, StructuredReferenceString},
|
||||
BatchProof,
|
||||
};
|
||||
use arithmetic::evaluate_opt;
|
||||
use ark_ec::{
|
||||
msm::{FixedBaseMSM, VariableBaseMSM},
|
||||
AffineCurve, PairingEngine, ProjectiveCurve,
|
||||
};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
|
||||
use ark_std::{
|
||||
borrow::Borrow,
|
||||
end_timer, format,
|
||||
marker::PhantomData,
|
||||
rand::{CryptoRng, RngCore},
|
||||
rc::Rc,
|
||||
start_timer,
|
||||
string::ToString,
|
||||
vec,
|
||||
vec::Vec,
|
||||
One, Zero,
|
||||
};
|
||||
// use batching::{batch_verify_internal, multi_open_internal};
|
||||
use srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam};
|
||||
use transcript::IOPTranscript;
|
||||
|
||||
use self::batching::{batch_verify_internal, multi_open_internal};
|
||||
|
||||
/// KZG Polynomial Commitment Scheme on multilinear polynomials.
|
||||
pub struct MultilinearKzgPCS<E: PairingEngine> {
|
||||
#[doc(hidden)]
|
||||
phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
|
||||
/// proof of opening
|
||||
pub struct MultilinearKzgProof<E: PairingEngine> {
|
||||
/// Evaluation of quotients
|
||||
pub proofs: Vec<E::G1Affine>,
|
||||
}
|
||||
|
||||
impl<E: PairingEngine> PolynomialCommitmentScheme<E> for MultilinearKzgPCS<E> {
|
||||
// Parameters
|
||||
type ProverParam = MultilinearProverParam<E>;
|
||||
type VerifierParam = MultilinearVerifierParam<E>;
|
||||
type SRS = MultilinearUniversalParams<E>;
|
||||
// Polynomial and its associated types
|
||||
type Polynomial = Rc<DenseMultilinearExtension<E::Fr>>;
|
||||
type Point = Vec<E::Fr>;
|
||||
type Evaluation = E::Fr;
|
||||
// Commitments and proofs
|
||||
type Commitment = Commitment<E>;
|
||||
type Proof = MultilinearKzgProof<E>;
|
||||
type BatchProof = BatchProof<E, Self>;
|
||||
|
||||
/// Build SRS for testing.
|
||||
///
|
||||
/// - For univariate polynomials, `log_size` is the log of maximum degree.
|
||||
/// - For multilinear polynomials, `log_size` is the number of variables.
|
||||
///
|
||||
/// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY.
|
||||
/// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION.
|
||||
fn gen_srs_for_testing<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
log_size: usize,
|
||||
) -> Result<Self::SRS, PCSError> {
|
||||
MultilinearUniversalParams::<E>::gen_srs_for_testing(rng, log_size)
|
||||
}
|
||||
|
||||
/// Trim the universal parameters to specialize the public parameters.
|
||||
/// Input both `supported_log_degree` for univariate and
|
||||
/// `supported_num_vars` for multilinear.
|
||||
fn trim(
|
||||
srs: impl Borrow<Self::SRS>,
|
||||
supported_degree: Option<usize>,
|
||||
supported_num_vars: Option<usize>,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError> {
|
||||
assert!(supported_degree.is_none());
|
||||
|
||||
let supported_num_vars = match supported_num_vars {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
return Err(PCSError::InvalidParameters(
|
||||
"multilinear should receive a num_var param".to_string(),
|
||||
))
|
||||
},
|
||||
};
|
||||
let (ml_ck, ml_vk) = srs.borrow().trim(supported_num_vars)?;
|
||||
|
||||
Ok((ml_ck, ml_vk))
|
||||
}
|
||||
|
||||
/// Generate a commitment for a polynomial.
|
||||
///
|
||||
/// This function takes `2^num_vars` number of scalar multiplications over
|
||||
/// G1.
|
||||
fn commit(
|
||||
prover_param: impl Borrow<Self::ProverParam>,
|
||||
poly: &Self::Polynomial,
|
||||
) -> Result<Self::Commitment, PCSError> {
|
||||
let prover_param = prover_param.borrow();
|
||||
let commit_timer = start_timer!(|| "commit");
|
||||
if prover_param.num_vars < poly.num_vars {
|
||||
return Err(PCSError::InvalidParameters(format!(
|
||||
"MlE length ({}) exceeds param limit ({})",
|
||||
poly.num_vars, prover_param.num_vars
|
||||
)));
|
||||
}
|
||||
let ignored = prover_param.num_vars - poly.num_vars;
|
||||
let scalars: Vec<_> = poly
|
||||
.to_evaluations()
|
||||
.into_iter()
|
||||
.map(|x| x.into_repr())
|
||||
.collect();
|
||||
let commitment = VariableBaseMSM::multi_scalar_mul(
|
||||
&prover_param.powers_of_g[ignored].evals,
|
||||
scalars.as_slice(),
|
||||
)
|
||||
.into_affine();
|
||||
|
||||
end_timer!(commit_timer);
|
||||
Ok(Commitment(commitment))
|
||||
}
|
||||
|
||||
/// On input a polynomial `p` and a point `point`, outputs a proof for the
|
||||
/// same. This function does not need to take the evaluation value as an
|
||||
/// input.
|
||||
///
|
||||
/// This function takes 2^{num_var +1} number of scalar multiplications over
|
||||
/// G1:
|
||||
/// - it prodceeds with `num_var` number of rounds,
|
||||
/// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2
|
||||
/// elements.
|
||||
fn open(
|
||||
prover_param: impl Borrow<Self::ProverParam>,
|
||||
polynomial: &Self::Polynomial,
|
||||
point: &Self::Point,
|
||||
) -> Result<(Self::Proof, Self::Evaluation), PCSError> {
|
||||
open_internal(prover_param.borrow(), polynomial, point)
|
||||
}
|
||||
|
||||
/// Input a list of multilinear extensions, and a same number of points, and
|
||||
/// a transcript, compute a multi-opening for all the polynomials.
|
||||
fn multi_open(
|
||||
prover_param: impl Borrow<Self::ProverParam>,
|
||||
polynomials: &[Self::Polynomial],
|
||||
points: &[Self::Point],
|
||||
evals: &[Self::Evaluation],
|
||||
transcript: &mut IOPTranscript<E::Fr>,
|
||||
) -> Result<BatchProof<E, Self>, PCSError> {
|
||||
multi_open_internal(
|
||||
prover_param.borrow(),
|
||||
polynomials,
|
||||
points,
|
||||
evals,
|
||||
transcript,
|
||||
)
|
||||
}
|
||||
|
||||
/// Verifies that `value` is the evaluation at `x` of the polynomial
|
||||
/// committed inside `comm`.
|
||||
///
|
||||
/// This function takes
|
||||
/// - num_var number of pairing product.
|
||||
/// - num_var number of MSM
|
||||
fn verify(
|
||||
verifier_param: &Self::VerifierParam,
|
||||
commitment: &Self::Commitment,
|
||||
point: &Self::Point,
|
||||
value: &E::Fr,
|
||||
proof: &Self::Proof,
|
||||
) -> Result<bool, PCSError> {
|
||||
verify_internal(verifier_param, commitment, point, value, proof)
|
||||
}
|
||||
|
||||
/// Verifies that `value_i` is the evaluation at `x_i` of the polynomial
|
||||
/// `poly_i` committed inside `comm`.
|
||||
fn batch_verify(
|
||||
verifier_param: &Self::VerifierParam,
|
||||
commitments: &[Self::Commitment],
|
||||
points: &[Self::Point],
|
||||
batch_proof: &Self::BatchProof,
|
||||
transcript: &mut IOPTranscript<E::Fr>,
|
||||
) -> Result<bool, PCSError> {
|
||||
batch_verify_internal(verifier_param, commitments, points, batch_proof, transcript)
|
||||
}
|
||||
}
|
||||
|
||||
/// On input a polynomial `p` and a point `point`, outputs a proof for the
|
||||
/// same. This function does not need to take the evaluation value as an
|
||||
/// input.
|
||||
///
|
||||
/// This function takes 2^{num_var +1} number of scalar multiplications over
|
||||
/// G1:
|
||||
/// - it proceeds with `num_var` number of rounds,
|
||||
/// - at round i, we compute an MSM for `2^{num_var - i + 1}` number of G2
|
||||
/// elements.
|
||||
fn open_internal<E: PairingEngine>(
|
||||
prover_param: &MultilinearProverParam<E>,
|
||||
polynomial: &DenseMultilinearExtension<E::Fr>,
|
||||
point: &[E::Fr],
|
||||
) -> Result<(MultilinearKzgProof<E>, E::Fr), PCSError> {
|
||||
let open_timer = start_timer!(|| format!("open mle with {} variable", polynomial.num_vars));
|
||||
|
||||
if polynomial.num_vars() > prover_param.num_vars {
|
||||
return Err(PCSError::InvalidParameters(format!(
|
||||
"Polynomial num_vars {} exceed the limit {}",
|
||||
polynomial.num_vars, prover_param.num_vars
|
||||
)));
|
||||
}
|
||||
|
||||
if polynomial.num_vars() != point.len() {
|
||||
return Err(PCSError::InvalidParameters(format!(
|
||||
"Polynomial num_vars {} does not match point len {}",
|
||||
polynomial.num_vars,
|
||||
point.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let nv = polynomial.num_vars();
|
||||
let ignored = prover_param.num_vars - nv;
|
||||
let mut r: Vec<Vec<E::Fr>> = (0..nv + 1).map(|_| Vec::new()).collect();
|
||||
let mut q: Vec<Vec<E::Fr>> = (0..nv + 1).map(|_| Vec::new()).collect();
|
||||
|
||||
r[nv] = polynomial.to_evaluations();
|
||||
|
||||
let mut proofs = Vec::new();
|
||||
|
||||
for (i, (&point_at_k, gi)) in point
|
||||
.iter()
|
||||
.zip(prover_param.powers_of_g[ignored..].iter())
|
||||
.take(nv)
|
||||
.enumerate()
|
||||
{
|
||||
let ith_round = start_timer!(|| format!("{}-th round", i));
|
||||
|
||||
let k = nv - i;
|
||||
let cur_dim = 1 << (k - 1);
|
||||
let mut cur_q = vec![E::Fr::zero(); cur_dim];
|
||||
let mut cur_r = vec![E::Fr::zero(); cur_dim];
|
||||
let one_minus_point_at_k = E::Fr::one() - point_at_k;
|
||||
|
||||
let ith_round_eval = start_timer!(|| format!("{}-th round eval", i));
|
||||
for b in 0..(1 << (k - 1)) {
|
||||
// q_b = pre_r [2^b + 1] - pre_r [2^b]
|
||||
cur_q[b] = r[k][(b << 1) + 1] - r[k][b << 1];
|
||||
|
||||
// r_b = pre_r [2^b]*(1-p) + pre_r [2^b + 1] * p
|
||||
cur_r[b] = r[k][b << 1] * one_minus_point_at_k + (r[k][(b << 1) + 1] * point_at_k);
|
||||
}
|
||||
end_timer!(ith_round_eval);
|
||||
let scalars: Vec<_> = (0..(1 << k)).map(|x| cur_q[x >> 1].into_repr()).collect();
|
||||
|
||||
q[k] = cur_q;
|
||||
r[k - 1] = cur_r;
|
||||
|
||||
// this is a MSM over G1 and is likely to be the bottleneck
|
||||
proofs.push(VariableBaseMSM::multi_scalar_mul(&gi.evals, &scalars).into_affine());
|
||||
end_timer!(ith_round);
|
||||
}
|
||||
let eval = evaluate_opt(polynomial, point);
|
||||
end_timer!(open_timer);
|
||||
Ok((MultilinearKzgProof { proofs }, eval))
|
||||
}
|
||||
|
||||
/// Verifies that `value` is the evaluation at `x` of the polynomial
|
||||
/// committed inside `comm`.
|
||||
///
|
||||
/// This function takes
|
||||
/// - num_var number of pairing product.
|
||||
/// - num_var number of MSM
|
||||
fn verify_internal<E: PairingEngine>(
|
||||
verifier_param: &MultilinearVerifierParam<E>,
|
||||
commitment: &Commitment<E>,
|
||||
point: &[E::Fr],
|
||||
value: &E::Fr,
|
||||
proof: &MultilinearKzgProof<E>,
|
||||
) -> Result<bool, PCSError> {
|
||||
let verify_timer = start_timer!(|| "verify");
|
||||
let num_var = point.len();
|
||||
|
||||
if num_var > verifier_param.num_vars {
|
||||
return Err(PCSError::InvalidParameters(format!(
|
||||
"point length ({}) exceeds param limit ({})",
|
||||
num_var, verifier_param.num_vars
|
||||
)));
|
||||
}
|
||||
|
||||
let ignored = verifier_param.num_vars - num_var;
|
||||
let prepare_inputs_timer = start_timer!(|| "prepare pairing inputs");
|
||||
|
||||
let scalar_size = E::Fr::size_in_bits();
|
||||
let window_size = FixedBaseMSM::get_mul_window_size(num_var);
|
||||
|
||||
let h_table = FixedBaseMSM::get_window_table(
|
||||
scalar_size,
|
||||
window_size,
|
||||
verifier_param.h.into_projective(),
|
||||
);
|
||||
let h_mul: Vec<E::G2Projective> =
|
||||
FixedBaseMSM::multi_scalar_mul(scalar_size, window_size, &h_table, point);
|
||||
|
||||
let h_vec: Vec<_> = (0..num_var)
|
||||
.map(|i| verifier_param.h_mask[ignored + i].into_projective() - h_mul[i])
|
||||
.collect();
|
||||
let h_vec: Vec<E::G2Affine> = E::G2Projective::batch_normalization_into_affine(&h_vec);
|
||||
end_timer!(prepare_inputs_timer);
|
||||
|
||||
let pairing_product_timer = start_timer!(|| "pairing product");
|
||||
|
||||
let mut pairings: Vec<_> = proof
|
||||
.proofs
|
||||
.iter()
|
||||
.map(|&x| E::G1Prepared::from(x))
|
||||
.zip(h_vec.into_iter().take(num_var).map(E::G2Prepared::from))
|
||||
.collect();
|
||||
|
||||
pairings.push((
|
||||
E::G1Prepared::from(
|
||||
(verifier_param.g.mul(*value) - commitment.0.into_projective()).into_affine(),
|
||||
),
|
||||
E::G2Prepared::from(verifier_param.h),
|
||||
));
|
||||
|
||||
let res = E::product_of_pairings(pairings.iter()) == E::Fqk::one();
|
||||
|
||||
end_timer!(pairing_product_timer);
|
||||
end_timer!(verify_timer);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_bls12_381::Bls12_381;
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
|
||||
use ark_std::{rand::RngCore, test_rng, vec::Vec, UniformRand};
|
||||
|
||||
type E = Bls12_381;
|
||||
type Fr = <E as PairingEngine>::Fr;
|
||||
|
||||
fn test_single_helper<R: RngCore + CryptoRng>(
|
||||
params: &MultilinearUniversalParams<E>,
|
||||
poly: &Rc<DenseMultilinearExtension<Fr>>,
|
||||
rng: &mut R,
|
||||
) -> Result<(), PCSError> {
|
||||
let nv = poly.num_vars();
|
||||
assert_ne!(nv, 0);
|
||||
let (ck, vk) = MultilinearKzgPCS::trim(params, None, Some(nv + 1))?;
|
||||
let point: Vec<_> = (0..nv).map(|_| Fr::rand(rng)).collect();
|
||||
let com = MultilinearKzgPCS::commit(&ck, poly)?;
|
||||
let (proof, value) = MultilinearKzgPCS::open(&ck, poly, &point)?;
|
||||
|
||||
assert!(MultilinearKzgPCS::verify(
|
||||
&vk, &com, &point, &value, &proof
|
||||
)?);
|
||||
|
||||
let value = Fr::rand(rng);
|
||||
assert!(!MultilinearKzgPCS::verify(
|
||||
&vk, &com, &point, &value, &proof
|
||||
)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_commit() -> Result<(), PCSError> {
|
||||
let mut rng = test_rng();
|
||||
|
||||
let params = MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 10)?;
|
||||
|
||||
// normal polynomials
|
||||
let poly1 = Rc::new(DenseMultilinearExtension::rand(8, &mut rng));
|
||||
test_single_helper(¶ms, &poly1, &mut rng)?;
|
||||
|
||||
// single-variate polynomials
|
||||
let poly2 = Rc::new(DenseMultilinearExtension::rand(1, &mut rng));
|
||||
test_single_helper(¶ms, &poly2, &mut rng)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setup_commit_verify_constant_polynomial() {
|
||||
let mut rng = test_rng();
|
||||
|
||||
// normal polynomials
|
||||
assert!(MultilinearKzgPCS::<E>::gen_srs_for_testing(&mut rng, 0).is_err());
|
||||
}
|
||||
}
|
||||
253
subroutines/src/pcs/multilinear_kzg/srs.rs
Normal file
253
subroutines/src/pcs/multilinear_kzg/srs.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
// Copyright (c) 2022 Espresso Systems (espressosys.com)
|
||||
// This file is part of the Jellyfish library.
|
||||
|
||||
// You should have received a copy of the MIT License
|
||||
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
|
||||
|
||||
//! Implementing Structured Reference Strings for multilinear polynomial KZG
|
||||
use crate::pcs::{
|
||||
multilinear_kzg::util::eq_extension, prelude::PCSError, StructuredReferenceString,
|
||||
};
|
||||
use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve};
|
||||
use ark_ff::{Field, PrimeField};
|
||||
use ark_poly::DenseMultilinearExtension;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
|
||||
use ark_std::{
|
||||
collections::LinkedList,
|
||||
end_timer, format,
|
||||
rand::{CryptoRng, RngCore},
|
||||
start_timer,
|
||||
string::ToString,
|
||||
vec::Vec,
|
||||
UniformRand,
|
||||
};
|
||||
use core::iter::FromIterator;
|
||||
|
||||
/// Evaluations over {0,1}^n for G1 or G2
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
|
||||
pub struct Evaluations<C: AffineCurve> {
|
||||
/// The evaluations.
|
||||
pub evals: Vec<C>,
|
||||
}
|
||||
|
||||
/// Universal Parameter
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
|
||||
pub struct MultilinearUniversalParams<E: PairingEngine> {
|
||||
/// prover parameters
|
||||
pub prover_param: MultilinearProverParam<E>,
|
||||
/// h^randomness: h^t1, h^t2, ..., **h^{t_nv}**
|
||||
pub h_mask: Vec<E::G2Affine>,
|
||||
}
|
||||
|
||||
/// Prover Parameters
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
|
||||
pub struct MultilinearProverParam<E: PairingEngine> {
|
||||
/// number of variables
|
||||
pub num_vars: usize,
|
||||
/// `pp_{num_vars}`, `pp_{num_vars - 1}`, `pp_{num_vars - 2}`, ..., defined
|
||||
/// by XZZPD19
|
||||
pub powers_of_g: Vec<Evaluations<E::G1Affine>>,
|
||||
/// generator for G1
|
||||
pub g: E::G1Affine,
|
||||
/// generator for G2
|
||||
pub h: E::G2Affine,
|
||||
}
|
||||
|
||||
/// Verifier Parameters
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
|
||||
pub struct MultilinearVerifierParam<E: PairingEngine> {
|
||||
/// number of variables
|
||||
pub num_vars: usize,
|
||||
/// generator of G1
|
||||
pub g: E::G1Affine,
|
||||
/// generator of G2
|
||||
pub h: E::G2Affine,
|
||||
/// h^randomness: h^t1, h^t2, ..., **h^{t_nv}**
|
||||
pub h_mask: Vec<E::G2Affine>,
|
||||
}
|
||||
|
||||
impl<E: PairingEngine> StructuredReferenceString<E> for MultilinearUniversalParams<E> {
|
||||
type ProverParam = MultilinearProverParam<E>;
|
||||
type VerifierParam = MultilinearVerifierParam<E>;
|
||||
|
||||
/// Extract the prover parameters from the public parameters.
|
||||
fn extract_prover_param(&self, supported_num_vars: usize) -> Self::ProverParam {
|
||||
let to_reduce = self.prover_param.num_vars - supported_num_vars;
|
||||
|
||||
Self::ProverParam {
|
||||
powers_of_g: self.prover_param.powers_of_g[to_reduce..].to_vec(),
|
||||
g: self.prover_param.g,
|
||||
h: self.prover_param.h,
|
||||
num_vars: supported_num_vars,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the verifier parameters from the public parameters.
|
||||
fn extract_verifier_param(&self, supported_num_vars: usize) -> Self::VerifierParam {
|
||||
let to_reduce = self.prover_param.num_vars - supported_num_vars;
|
||||
Self::VerifierParam {
|
||||
num_vars: supported_num_vars,
|
||||
g: self.prover_param.g,
|
||||
h: self.prover_param.h,
|
||||
h_mask: self.h_mask[to_reduce..].to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Trim the universal parameters to specialize the public parameters
|
||||
/// for multilinear polynomials to the given `supported_num_vars`, and
|
||||
/// returns committer key and verifier key. `supported_num_vars` should
|
||||
/// be in range `1..=params.num_vars`
|
||||
fn trim(
|
||||
&self,
|
||||
supported_num_vars: usize,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError> {
|
||||
if supported_num_vars > self.prover_param.num_vars {
|
||||
return Err(PCSError::InvalidParameters(format!(
|
||||
"SRS does not support target number of vars {}",
|
||||
supported_num_vars
|
||||
)));
|
||||
}
|
||||
|
||||
let to_reduce = self.prover_param.num_vars - supported_num_vars;
|
||||
let ck = Self::ProverParam {
|
||||
powers_of_g: self.prover_param.powers_of_g[to_reduce..].to_vec(),
|
||||
g: self.prover_param.g,
|
||||
h: self.prover_param.h,
|
||||
num_vars: supported_num_vars,
|
||||
};
|
||||
let vk = Self::VerifierParam {
|
||||
num_vars: supported_num_vars,
|
||||
g: self.prover_param.g,
|
||||
h: self.prover_param.h,
|
||||
h_mask: self.h_mask[to_reduce..].to_vec(),
|
||||
};
|
||||
Ok((ck, vk))
|
||||
}
|
||||
|
||||
/// Build SRS for testing.
|
||||
/// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY.
|
||||
/// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION.
|
||||
fn gen_srs_for_testing<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
num_vars: usize,
|
||||
) -> Result<Self, PCSError> {
|
||||
if num_vars == 0 {
|
||||
return Err(PCSError::InvalidParameters(
|
||||
"constant polynomial not supported".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let total_timer = start_timer!(|| "SRS generation");
|
||||
|
||||
let pp_generation_timer = start_timer!(|| "Prover Param generation");
|
||||
|
||||
let g = E::G1Projective::rand(rng);
|
||||
let h = E::G2Projective::rand(rng);
|
||||
|
||||
let mut powers_of_g = Vec::new();
|
||||
|
||||
let t: Vec<_> = (0..num_vars).map(|_| E::Fr::rand(rng)).collect();
|
||||
let scalar_bits = E::Fr::size_in_bits();
|
||||
|
||||
let mut eq: LinkedList<DenseMultilinearExtension<E::Fr>> =
|
||||
LinkedList::from_iter(eq_extension(&t).into_iter());
|
||||
let mut eq_arr = LinkedList::new();
|
||||
let mut base = eq.pop_back().unwrap().evaluations;
|
||||
|
||||
for i in (0..num_vars).rev() {
|
||||
eq_arr.push_front(remove_dummy_variable(&base, i)?);
|
||||
if i != 0 {
|
||||
let mul = eq.pop_back().unwrap().evaluations;
|
||||
base = base
|
||||
.into_iter()
|
||||
.zip(mul.into_iter())
|
||||
.map(|(a, b)| a * b)
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
let mut pp_powers = Vec::new();
|
||||
let mut total_scalars = 0;
|
||||
for i in 0..num_vars {
|
||||
let eq = eq_arr.pop_front().unwrap();
|
||||
let pp_k_powers = (0..(1 << (num_vars - i))).map(|x| eq[x]);
|
||||
pp_powers.extend(pp_k_powers);
|
||||
total_scalars += 1 << (num_vars - i);
|
||||
}
|
||||
let window_size = FixedBaseMSM::get_mul_window_size(total_scalars);
|
||||
let g_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, g);
|
||||
|
||||
let pp_g = E::G1Projective::batch_normalization_into_affine(
|
||||
&FixedBaseMSM::multi_scalar_mul(scalar_bits, window_size, &g_table, &pp_powers),
|
||||
);
|
||||
|
||||
let mut start = 0;
|
||||
for i in 0..num_vars {
|
||||
let size = 1 << (num_vars - i);
|
||||
let pp_k_g = Evaluations {
|
||||
evals: pp_g[start..(start + size)].to_vec(),
|
||||
};
|
||||
powers_of_g.push(pp_k_g);
|
||||
start += size;
|
||||
}
|
||||
|
||||
let pp = Self::ProverParam {
|
||||
num_vars,
|
||||
g: g.into_affine(),
|
||||
h: h.into_affine(),
|
||||
powers_of_g,
|
||||
};
|
||||
|
||||
end_timer!(pp_generation_timer);
|
||||
|
||||
let vp_generation_timer = start_timer!(|| "VP generation");
|
||||
let h_mask = {
|
||||
let window_size = FixedBaseMSM::get_mul_window_size(num_vars);
|
||||
let h_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, h);
|
||||
E::G2Projective::batch_normalization_into_affine(&FixedBaseMSM::multi_scalar_mul(
|
||||
scalar_bits,
|
||||
window_size,
|
||||
&h_table,
|
||||
&t,
|
||||
))
|
||||
};
|
||||
end_timer!(vp_generation_timer);
|
||||
end_timer!(total_timer);
|
||||
Ok(Self {
|
||||
prover_param: pp,
|
||||
h_mask,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// fix first `pad` variables of `poly` represented in evaluation form to zero
|
||||
fn remove_dummy_variable<F: Field>(poly: &[F], pad: usize) -> Result<Vec<F>, PCSError> {
|
||||
if pad == 0 {
|
||||
return Ok(poly.to_vec());
|
||||
}
|
||||
if !poly.len().is_power_of_two() {
|
||||
return Err(PCSError::InvalidParameters(
|
||||
"Size of polynomial should be power of two.".to_string(),
|
||||
));
|
||||
}
|
||||
let nv = ark_std::log2(poly.len()) as usize - pad;
|
||||
Ok((0..(1 << nv)).map(|x| poly[x << pad]).collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_bls12_381::Bls12_381;
|
||||
use ark_std::test_rng;
|
||||
type E = Bls12_381;
|
||||
|
||||
#[test]
|
||||
fn test_srs_gen() -> Result<(), PCSError> {
|
||||
let mut rng = test_rng();
|
||||
for nv in 4..10 {
|
||||
let _ = MultilinearUniversalParams::<E>::gen_srs_for_testing(&mut rng, nv)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
51
subroutines/src/pcs/multilinear_kzg/util.rs
Normal file
51
subroutines/src/pcs/multilinear_kzg/util.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2022 Espresso Systems (espressosys.com)
|
||||
// This file is part of the Jellyfish library.
|
||||
|
||||
// You should have received a copy of the MIT License
|
||||
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
|
||||
|
||||
//! Useful utilities for KZG PCS
|
||||
use ark_ff::PrimeField;
|
||||
use ark_poly::DenseMultilinearExtension;
|
||||
use ark_std::{end_timer, start_timer, vec::Vec};
|
||||
|
||||
use crate::PCSError;
|
||||
|
||||
/// Generate eq(t,x), a product of multilinear polynomials with fixed t.
|
||||
/// eq(a,b) is takes extensions of a,b in {0,1}^num_vars such that if a and b in
|
||||
/// {0,1}^num_vars are equal then this polynomial evaluates to 1.
|
||||
pub(crate) fn eq_extension<F: PrimeField>(t: &[F]) -> Vec<DenseMultilinearExtension<F>> {
|
||||
let start = start_timer!(|| "eq extension");
|
||||
|
||||
let dim = t.len();
|
||||
let mut result = Vec::new();
|
||||
for (i, &ti) in t.iter().enumerate().take(dim) {
|
||||
let mut poly = Vec::with_capacity(1 << dim);
|
||||
for x in 0..(1 << dim) {
|
||||
let xi = if x >> i & 1 == 1 { F::one() } else { F::zero() };
|
||||
let ti_xi = ti * xi;
|
||||
poly.push(ti_xi + ti_xi - xi - ti + F::one());
|
||||
}
|
||||
result.push(DenseMultilinearExtension::from_evaluations_vec(dim, poly));
|
||||
}
|
||||
|
||||
end_timer!(start);
|
||||
result
|
||||
}
|
||||
|
||||
/// Evaluate eq polynomial. use the public one later
|
||||
pub(crate) fn eq_eval<F: PrimeField>(x: &[F], y: &[F]) -> Result<F, PCSError> {
|
||||
if x.len() != y.len() {
|
||||
return Err(PCSError::InvalidParameters(
|
||||
"x and y have different length".to_string(),
|
||||
));
|
||||
}
|
||||
let start = start_timer!(|| "eq_eval");
|
||||
let mut res = F::one();
|
||||
for (&xi, &yi) in x.iter().zip(y.iter()) {
|
||||
let xi_yi = xi * yi;
|
||||
res *= xi_yi + xi_yi - xi - yi + F::one();
|
||||
}
|
||||
end_timer!(start);
|
||||
Ok(res)
|
||||
}
|
||||
21
subroutines/src/pcs/prelude.rs
Normal file
21
subroutines/src/pcs/prelude.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2022 Espresso Systems (espressosys.com)
|
||||
// This file is part of the Jellyfish library.
|
||||
|
||||
// You should have received a copy of the MIT License
|
||||
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
|
||||
|
||||
//! Prelude
|
||||
pub use crate::pcs::{
|
||||
errors::PCSError,
|
||||
multilinear_kzg::{
|
||||
batching::BatchProof,
|
||||
srs::{MultilinearProverParam, MultilinearUniversalParams, MultilinearVerifierParam},
|
||||
MultilinearKzgPCS, MultilinearKzgProof,
|
||||
},
|
||||
structs::Commitment,
|
||||
univariate_kzg::{
|
||||
srs::{UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam},
|
||||
UnivariateKzgBatchProof, UnivariateKzgPCS, UnivariateKzgProof,
|
||||
},
|
||||
PolynomialCommitmentScheme, StructuredReferenceString,
|
||||
};
|
||||
7
subroutines/src/pcs/readme.md
Normal file
7
subroutines/src/pcs/readme.md
Normal file
@@ -0,0 +1,7 @@
|
||||
KZG based multilinear polynomial commitment
|
||||
-----
|
||||
|
||||
# Compiling features:
|
||||
- `parallel`: use multi-threading when possible.
|
||||
- `print-trace`: print out user friendly information about the running time for each micro component.
|
||||
- `extensive_sanity_checks`: runs additional sanity checks that is not essential and will slow down the scheme.
|
||||
25
subroutines/src/pcs/structs.rs
Normal file
25
subroutines/src/pcs/structs.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2022 Espresso Systems (espressosys.com)
|
||||
// This file is part of the Jellyfish library.
|
||||
|
||||
// You should have received a copy of the MIT License
|
||||
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
|
||||
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
|
||||
use derivative::Derivative;
|
||||
|
||||
#[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)]
|
||||
#[derivative(
|
||||
Default(bound = ""),
|
||||
Hash(bound = ""),
|
||||
Clone(bound = ""),
|
||||
Copy(bound = ""),
|
||||
Debug(bound = ""),
|
||||
PartialEq(bound = ""),
|
||||
Eq(bound = "")
|
||||
)]
|
||||
/// A commitment is an Affine point.
|
||||
pub struct Commitment<E: PairingEngine>(
|
||||
/// the actual commitment is an affine point.
|
||||
pub E::G1Affine,
|
||||
);
|
||||
269
subroutines/src/pcs/univariate_kzg/mod.rs
Normal file
269
subroutines/src/pcs/univariate_kzg/mod.rs
Normal file
@@ -0,0 +1,269 @@
|
||||
// Copyright (c) 2022 Espresso Systems (espressosys.com)
|
||||
// This file is part of the Jellyfish library.
|
||||
|
||||
// You should have received a copy of the MIT License
|
||||
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
|
||||
|
||||
//! Main module for univariate KZG commitment scheme
|
||||
|
||||
use crate::pcs::{
|
||||
prelude::Commitment, PCSError, PolynomialCommitmentScheme, StructuredReferenceString,
|
||||
};
|
||||
use ark_ec::{msm::VariableBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_poly::{univariate::DensePolynomial, Polynomial, UVPolynomial};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
|
||||
use ark_std::{
|
||||
borrow::Borrow,
|
||||
end_timer, format,
|
||||
marker::PhantomData,
|
||||
rand::{CryptoRng, RngCore},
|
||||
start_timer,
|
||||
string::ToString,
|
||||
vec,
|
||||
vec::Vec,
|
||||
One,
|
||||
};
|
||||
use srs::{UnivariateProverParam, UnivariateUniversalParams, UnivariateVerifierParam};
|
||||
|
||||
pub(crate) mod srs;
|
||||
|
||||
/// KZG Polynomial Commitment Scheme on univariate polynomial.
|
||||
pub struct UnivariateKzgPCS<E: PairingEngine> {
|
||||
#[doc(hidden)]
|
||||
phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq, Eq)]
|
||||
/// proof of opening
|
||||
pub struct UnivariateKzgProof<E: PairingEngine> {
|
||||
/// Evaluation of quotients
|
||||
pub proof: E::G1Affine,
|
||||
}
|
||||
/// batch proof
|
||||
pub type UnivariateKzgBatchProof<E> = Vec<UnivariateKzgProof<E>>;
|
||||
|
||||
impl<E: PairingEngine> PolynomialCommitmentScheme<E> for UnivariateKzgPCS<E> {
|
||||
// Parameters
|
||||
type ProverParam = UnivariateProverParam<E::G1Affine>;
|
||||
type VerifierParam = UnivariateVerifierParam<E>;
|
||||
type SRS = UnivariateUniversalParams<E>;
|
||||
// Polynomial and its associated types
|
||||
type Polynomial = DensePolynomial<E::Fr>;
|
||||
type Point = E::Fr;
|
||||
type Evaluation = E::Fr;
|
||||
// Polynomial and its associated types
|
||||
type Commitment = Commitment<E>;
|
||||
type Proof = UnivariateKzgProof<E>;
|
||||
|
||||
// We do not implement batch univariate KZG at the current version.
|
||||
type BatchProof = ();
|
||||
|
||||
/// Build SRS for testing.
|
||||
///
|
||||
/// - For univariate polynomials, `supported_size` is the maximum degree.
|
||||
///
|
||||
/// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY.
|
||||
/// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION.
|
||||
fn gen_srs_for_testing<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
supported_size: usize,
|
||||
) -> Result<Self::SRS, PCSError> {
|
||||
Self::SRS::gen_srs_for_testing(rng, supported_size)
|
||||
}
|
||||
|
||||
/// Trim the universal parameters to specialize the public parameters.
|
||||
/// Input `max_degree` for univariate.
|
||||
/// `supported_num_vars` must be None or an error is returned.
|
||||
fn trim(
|
||||
srs: impl Borrow<Self::SRS>,
|
||||
supported_degree: Option<usize>,
|
||||
supported_num_vars: Option<usize>,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError> {
|
||||
assert!(supported_num_vars.is_none());
|
||||
if supported_num_vars.is_some() {
|
||||
return Err(PCSError::InvalidParameters(
|
||||
"univariate should not receive a num_var param".to_string(),
|
||||
));
|
||||
}
|
||||
srs.borrow().trim(supported_degree.unwrap())
|
||||
}
|
||||
|
||||
/// Generate a commitment for a polynomial
|
||||
/// Note that the scheme is not hidding
|
||||
fn commit(
|
||||
prover_param: impl Borrow<Self::ProverParam>,
|
||||
poly: &Self::Polynomial,
|
||||
) -> Result<Self::Commitment, PCSError> {
|
||||
let prover_param = prover_param.borrow();
|
||||
let commit_time =
|
||||
start_timer!(|| format!("Committing to polynomial of degree {} ", poly.degree()));
|
||||
|
||||
if poly.degree() >= prover_param.powers_of_g.len() {
|
||||
return Err(PCSError::InvalidParameters(format!(
|
||||
"uni poly degree {} is larger than allowed {}",
|
||||
poly.degree(),
|
||||
prover_param.powers_of_g.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let (num_leading_zeros, plain_coeffs) = skip_leading_zeros_and_convert_to_bigints(poly);
|
||||
|
||||
let msm_time = start_timer!(|| "MSM to compute commitment to plaintext poly");
|
||||
let commitment = VariableBaseMSM::multi_scalar_mul(
|
||||
&prover_param.powers_of_g[num_leading_zeros..],
|
||||
&plain_coeffs,
|
||||
)
|
||||
.into_affine();
|
||||
end_timer!(msm_time);
|
||||
|
||||
end_timer!(commit_time);
|
||||
Ok(Commitment(commitment))
|
||||
}
|
||||
|
||||
/// On input a polynomial `p` and a point `point`, outputs a proof for the
|
||||
/// same.
|
||||
fn open(
|
||||
prover_param: impl Borrow<Self::ProverParam>,
|
||||
polynomial: &Self::Polynomial,
|
||||
point: &Self::Point,
|
||||
) -> Result<(Self::Proof, Self::Evaluation), PCSError> {
|
||||
let open_time =
|
||||
start_timer!(|| format!("Opening polynomial of degree {}", polynomial.degree()));
|
||||
let divisor = Self::Polynomial::from_coefficients_vec(vec![-*point, E::Fr::one()]);
|
||||
|
||||
let witness_time = start_timer!(|| "Computing witness polynomial");
|
||||
let witness_polynomial = polynomial / &divisor;
|
||||
end_timer!(witness_time);
|
||||
|
||||
let (num_leading_zeros, witness_coeffs) =
|
||||
skip_leading_zeros_and_convert_to_bigints(&witness_polynomial);
|
||||
|
||||
let proof = VariableBaseMSM::multi_scalar_mul(
|
||||
&prover_param.borrow().powers_of_g[num_leading_zeros..],
|
||||
&witness_coeffs,
|
||||
)
|
||||
.into_affine();
|
||||
|
||||
let eval = polynomial.evaluate(point);
|
||||
|
||||
end_timer!(open_time);
|
||||
Ok((Self::Proof { proof }, eval))
|
||||
}
|
||||
|
||||
/// Verifies that `value` is the evaluation at `x` of the polynomial
|
||||
/// committed inside `comm`.
|
||||
fn verify(
|
||||
verifier_param: &Self::VerifierParam,
|
||||
commitment: &Self::Commitment,
|
||||
point: &Self::Point,
|
||||
value: &E::Fr,
|
||||
proof: &Self::Proof,
|
||||
) -> Result<bool, PCSError> {
|
||||
let check_time = start_timer!(|| "Checking evaluation");
|
||||
let pairing_inputs: Vec<(E::G1Prepared, E::G2Prepared)> = vec![
|
||||
(
|
||||
(verifier_param.g.mul(value.into_repr())
|
||||
- proof.proof.mul(point.into_repr())
|
||||
- commitment.0.into_projective())
|
||||
.into_affine()
|
||||
.into(),
|
||||
verifier_param.h.into(),
|
||||
),
|
||||
(proof.proof.into(), verifier_param.beta_h.into()),
|
||||
];
|
||||
|
||||
let res = E::product_of_pairings(pairing_inputs.iter()).is_one();
|
||||
|
||||
end_timer!(check_time, || format!("Result: {}", res));
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_leading_zeros_and_convert_to_bigints<F: PrimeField, P: UVPolynomial<F>>(
|
||||
p: &P,
|
||||
) -> (usize, Vec<F::BigInt>) {
|
||||
let mut num_leading_zeros = 0;
|
||||
while num_leading_zeros < p.coeffs().len() && p.coeffs()[num_leading_zeros].is_zero() {
|
||||
num_leading_zeros += 1;
|
||||
}
|
||||
let coeffs = convert_to_bigints(&p.coeffs()[num_leading_zeros..]);
|
||||
(num_leading_zeros, coeffs)
|
||||
}
|
||||
|
||||
fn convert_to_bigints<F: PrimeField>(p: &[F]) -> Vec<F::BigInt> {
|
||||
let to_bigint_time = start_timer!(|| "Converting polynomial coeffs to bigints");
|
||||
let coeffs = p.iter().map(|s| s.into_repr()).collect::<Vec<_>>();
|
||||
end_timer!(to_bigint_time);
|
||||
coeffs
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::StructuredReferenceString;
|
||||
use ark_bls12_381::Bls12_381;
|
||||
use ark_ec::PairingEngine;
|
||||
use ark_poly::univariate::DensePolynomial;
|
||||
use ark_std::{test_rng, UniformRand};
|
||||
|
||||
fn end_to_end_test_template<E>() -> Result<(), PCSError>
|
||||
where
|
||||
E: PairingEngine,
|
||||
{
|
||||
let rng = &mut test_rng();
|
||||
for _ in 0..100 {
|
||||
let mut degree = 0;
|
||||
while degree <= 1 {
|
||||
degree = usize::rand(rng) % 20;
|
||||
}
|
||||
let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(rng, degree)?;
|
||||
let (ck, vk) = pp.trim(degree)?;
|
||||
let p = <DensePolynomial<E::Fr> as UVPolynomial<E::Fr>>::rand(degree, rng);
|
||||
let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
|
||||
let point = E::Fr::rand(rng);
|
||||
let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
|
||||
assert!(
|
||||
UnivariateKzgPCS::<E>::verify(&vk, &comm, &point, &value, &proof)?,
|
||||
"proof was incorrect for max_degree = {}, polynomial_degree = {}",
|
||||
degree,
|
||||
p.degree(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn linear_polynomial_test_template<E>() -> Result<(), PCSError>
|
||||
where
|
||||
E: PairingEngine,
|
||||
{
|
||||
let rng = &mut test_rng();
|
||||
for _ in 0..100 {
|
||||
let degree = 50;
|
||||
|
||||
let pp = UnivariateKzgPCS::<E>::gen_srs_for_testing(rng, degree)?;
|
||||
let (ck, vk) = pp.trim(degree)?;
|
||||
let p = <DensePolynomial<E::Fr> as UVPolynomial<E::Fr>>::rand(degree, rng);
|
||||
let comm = UnivariateKzgPCS::<E>::commit(&ck, &p)?;
|
||||
let point = E::Fr::rand(rng);
|
||||
let (proof, value) = UnivariateKzgPCS::<E>::open(&ck, &p, &point)?;
|
||||
assert!(
|
||||
UnivariateKzgPCS::<E>::verify(&vk, &comm, &point, &value, &proof)?,
|
||||
"proof was incorrect for max_degree = {}, polynomial_degree = {}",
|
||||
degree,
|
||||
p.degree(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_to_end_test() {
|
||||
end_to_end_test_template::<Bls12_381>().expect("test failed for bls12-381");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linear_polynomial_test() {
|
||||
linear_polynomial_test_template::<Bls12_381>().expect("test failed for bls12-381");
|
||||
}
|
||||
}
|
||||
156
subroutines/src/pcs/univariate_kzg/srs.rs
Normal file
156
subroutines/src/pcs/univariate_kzg/srs.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2022 Espresso Systems (espressosys.com)
|
||||
// This file is part of the Jellyfish library.
|
||||
|
||||
// You should have received a copy of the MIT License
|
||||
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
|
||||
|
||||
//! Implementing Structured Reference Strings for univariate polynomial KZG
|
||||
|
||||
use crate::pcs::{PCSError, StructuredReferenceString};
|
||||
use ark_ec::{msm::FixedBaseMSM, AffineCurve, PairingEngine, ProjectiveCurve};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
|
||||
use ark_std::{
|
||||
end_timer,
|
||||
rand::{CryptoRng, RngCore},
|
||||
start_timer, vec,
|
||||
vec::Vec,
|
||||
One, UniformRand,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
|
||||
/// `UniversalParams` are the universal parameters for the KZG10 scheme.
|
||||
// Adapted from
|
||||
// https://github.com/arkworks-rs/poly-commit/blob/master/src/kzg10/data_structures.rs#L20
|
||||
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize, Default)]
|
||||
pub struct UnivariateUniversalParams<E: PairingEngine> {
|
||||
/// Group elements of the form `{ \beta^i G }`, where `i` ranges from 0 to
|
||||
/// `degree`.
|
||||
pub powers_of_g: Vec<E::G1Affine>,
|
||||
/// The generator of G2.
|
||||
pub h: E::G2Affine,
|
||||
/// \beta times the above generator of G2.
|
||||
pub beta_h: E::G2Affine,
|
||||
}
|
||||
|
||||
impl<E: PairingEngine> UnivariateUniversalParams<E> {
|
||||
/// Returns the maximum supported degree
|
||||
pub fn max_degree(&self) -> usize {
|
||||
self.powers_of_g.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// `UnivariateProverParam` is used to generate a proof
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub struct UnivariateProverParam<C: AffineCurve> {
|
||||
/// Parameters
|
||||
pub powers_of_g: Vec<C>,
|
||||
}
|
||||
|
||||
/// `UnivariateVerifierParam` is used to check evaluation proofs for a given
|
||||
/// commitment.
|
||||
#[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)]
|
||||
#[derivative(
|
||||
Default(bound = ""),
|
||||
Clone(bound = ""),
|
||||
Copy(bound = ""),
|
||||
Debug(bound = ""),
|
||||
PartialEq(bound = ""),
|
||||
Eq(bound = "")
|
||||
)]
|
||||
pub struct UnivariateVerifierParam<E: PairingEngine> {
|
||||
/// The generator of G1.
|
||||
pub g: E::G1Affine,
|
||||
/// The generator of G2.
|
||||
pub h: E::G2Affine,
|
||||
/// \beta times the above generator of G2.
|
||||
pub beta_h: E::G2Affine,
|
||||
}
|
||||
|
||||
impl<E: PairingEngine> StructuredReferenceString<E> for UnivariateUniversalParams<E> {
|
||||
type ProverParam = UnivariateProverParam<E::G1Affine>;
|
||||
type VerifierParam = UnivariateVerifierParam<E>;
|
||||
|
||||
/// Extract the prover parameters from the public parameters.
|
||||
fn extract_prover_param(&self, supported_size: usize) -> Self::ProverParam {
|
||||
let powers_of_g = self.powers_of_g[..=supported_size].to_vec();
|
||||
|
||||
Self::ProverParam { powers_of_g }
|
||||
}
|
||||
|
||||
/// Extract the verifier parameters from the public parameters.
|
||||
fn extract_verifier_param(&self, _supported_size: usize) -> Self::VerifierParam {
|
||||
Self::VerifierParam {
|
||||
g: self.powers_of_g[0],
|
||||
h: self.h,
|
||||
beta_h: self.beta_h,
|
||||
}
|
||||
}
|
||||
|
||||
/// Trim the universal parameters to specialize the public parameters
|
||||
/// for univariate polynomials to the given `supported_size`, and
|
||||
/// returns committer key and verifier key. `supported_size` should
|
||||
/// be in range `1..params.len()`
|
||||
fn trim(
|
||||
&self,
|
||||
supported_size: usize,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), PCSError> {
|
||||
let powers_of_g = self.powers_of_g[..=supported_size].to_vec();
|
||||
|
||||
let pk = Self::ProverParam { powers_of_g };
|
||||
let vk = Self::VerifierParam {
|
||||
g: self.powers_of_g[0],
|
||||
h: self.h,
|
||||
beta_h: self.beta_h,
|
||||
};
|
||||
Ok((pk, vk))
|
||||
}
|
||||
|
||||
/// Build SRS for testing.
|
||||
/// WARNING: THIS FUNCTION IS FOR TESTING PURPOSE ONLY.
|
||||
/// THE OUTPUT SRS SHOULD NOT BE USED IN PRODUCTION.
|
||||
fn gen_srs_for_testing<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
max_degree: usize,
|
||||
) -> Result<Self, PCSError> {
|
||||
let setup_time = start_timer!(|| format!("KZG10::Setup with degree {}", max_degree));
|
||||
let beta = E::Fr::rand(rng);
|
||||
let g = E::G1Projective::rand(rng);
|
||||
let h = E::G2Projective::rand(rng);
|
||||
|
||||
let mut powers_of_beta = vec![E::Fr::one()];
|
||||
|
||||
let mut cur = beta;
|
||||
for _ in 0..max_degree {
|
||||
powers_of_beta.push(cur);
|
||||
cur *= β
|
||||
}
|
||||
|
||||
let window_size = FixedBaseMSM::get_mul_window_size(max_degree + 1);
|
||||
|
||||
let scalar_bits = E::Fr::size_in_bits();
|
||||
let g_time = start_timer!(|| "Generating powers of G");
|
||||
// TODO: parallelization
|
||||
let g_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, g);
|
||||
let powers_of_g = FixedBaseMSM::multi_scalar_mul::<E::G1Projective>(
|
||||
scalar_bits,
|
||||
window_size,
|
||||
&g_table,
|
||||
&powers_of_beta,
|
||||
);
|
||||
end_timer!(g_time);
|
||||
|
||||
let powers_of_g = E::G1Projective::batch_normalization_into_affine(&powers_of_g);
|
||||
|
||||
let h = h.into_affine();
|
||||
let beta_h = h.mul(beta).into_affine();
|
||||
|
||||
let pp = Self {
|
||||
powers_of_g,
|
||||
h,
|
||||
beta_h,
|
||||
};
|
||||
end_timer!(setup_time);
|
||||
Ok(pp)
|
||||
}
|
||||
}
|
||||
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