| //! 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(()) | |
|     } | |
| }
 |