//! Main module for the ZeroCheck protocol.
|
|
|
|
use std::fmt::Debug;
|
|
|
|
use crate::{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::{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(())
|
|
}
|
|
}
|