arnaucube
arnaucube/hyperplonkPublic Self-hosted repository
hyperplonk/poly-iop/src/zero_check/mod.rs
poly-iop/src/zero_check/mod.rsRaw
  1//! Main module for the ZeroCheck protocol.
  2
  3use std::fmt::Debug;
  4
  5use crate::{errors::PolyIOPErrors, sum_check::SumCheck, PolyIOP};
  6use arithmetic::build_eq_x_r;
  7use ark_ff::PrimeField;
  8use ark_poly::MultilinearExtension;
  9use ark_std::{end_timer, start_timer};
 10use transcript::IOPTranscript;
 11
 12/// A zero check IOP subclaim for \hat f(x) is 0, consists of the following:
 13///   - the SubClaim from the SumCheck
 14///   - the initial challenge r which is used to build eq(x, r) in ZeroCheck
 15#[derive(Clone, Debug, Default, PartialEq)]
 16pub struct ZeroCheckSubClaim<F: PrimeField, SC: SumCheck<F>> {
 17    // the SubClaim from the SumCheck
 18    pub sum_check_sub_claim: SC::SumCheckSubClaim,
 19    /// the expected evaluation
 20    pub expected_evaluation: F,
 21    // the initial challenge r which is used to build eq(x, r)
 22    pub init_challenge: Vec<F>,
 23}
 24
 25/// A ZeroCheck is derived from SumCheck.
 26pub trait ZeroCheck<F: PrimeField>: SumCheck<F> {
 27    type ZeroCheckSubClaim: Clone + Debug + Default + PartialEq;
 28    type ZeroCheckProof: Clone + Debug + Default + PartialEq;
 29
 30    /// Initialize the system with a transcript
 31    ///
 32    /// This function is optional -- in the case where a ZeroCheck is
 33    /// an building block for a more complex protocol, the transcript
 34    /// may be initialized by this complex protocol, and passed to the
 35    /// ZeroCheck prover/verifier.
 36    fn init_transcript() -> Self::Transcript;
 37
 38    /// initialize the prover to argue for the sum of polynomial over
 39    /// {0,1}^`num_vars` is zero.
 40    fn prove(
 41        poly: &Self::VirtualPolynomial,
 42        transcript: &mut Self::Transcript,
 43    ) -> Result<Self::ZeroCheckProof, PolyIOPErrors>;
 44
 45    /// verify the claimed sum using the proof
 46    fn verify(
 47        proof: &Self::ZeroCheckProof,
 48        aux_info: &Self::VPAuxInfo,
 49        transcript: &mut Self::Transcript,
 50    ) -> Result<Self::ZeroCheckSubClaim, PolyIOPErrors>;
 51}
 52
 53impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> {
 54    /// A ZeroCheck SubClaim consists of
 55    /// - the SubClaim from the SumCheck
 56    /// - the initial challenge r which is used to build eq(x, r)
 57    type ZeroCheckSubClaim = ZeroCheckSubClaim<F, Self>;
 58    /// A ZeroCheckProof is a SumCheckProof
 59    type ZeroCheckProof = Self::SumCheckProof;
 60
 61    /// Initialize the system with a transcript
 62    ///
 63    /// This function is optional -- in the case where a ZeroCheck is
 64    /// an building block for a more complex protocol, the transcript
 65    /// may be initialized by this complex protocol, and passed to the
 66    /// ZeroCheck prover/verifier.
 67    fn init_transcript() -> Self::Transcript {
 68        IOPTranscript::<F>::new(b"Initializing ZeroCheck transcript")
 69    }
 70
 71    /// Initialize the prover to argue for the sum of polynomial f(x) over
 72    /// {0,1}^`num_vars` is zero.
 73    ///
 74    /// f(x) is zero if \hat f(x) := f(x) * eq(x,r) is also a zero polynomial
 75    /// for a random r sampled from transcript.
 76    ///
 77    /// This function will build the \hat f(x) and then invoke the sumcheck
 78    /// protocol to generate a proof for which the sum of \hat f(x) is zero
 79    fn prove(
 80        poly: &Self::VirtualPolynomial,
 81        transcript: &mut Self::Transcript,
 82    ) -> Result<Self::ZeroCheckProof, PolyIOPErrors> {
 83        let start = start_timer!(|| "zero check prove");
 84
 85        let length = poly.aux_info.num_variables;
 86        let r = transcript.get_and_append_challenge_vectors(b"0check r", length)?;
 87        let f_hat = poly.build_f_hat(r.as_ref())?;
 88        let res = <Self as SumCheck<F>>::prove(&f_hat, transcript);
 89
 90        end_timer!(start);
 91        res
 92    }
 93
 94    /// Verify that the polynomial's sum is zero using the proof.
 95    /// Return a Self::Subclaim that consists of the
 96    ///
 97    /// - a Subclaim that the sum is zero
 98    /// - the initial challenge `r` that is used to build `eq(x, r)`
 99    ///
100    /// This function will check that \hat f(x)'s sum is zero. It does not check
101    /// `\hat f(x)` is build correctly. The caller needs to makes sure that
102    /// `\hat f(x) = f(x) * eq(x, r)`
103    fn verify(
104        proof: &Self::ZeroCheckProof,
105        fx_aux_info: &Self::VPAuxInfo,
106        transcript: &mut Self::Transcript,
107    ) -> Result<Self::ZeroCheckSubClaim, PolyIOPErrors> {
108        let start = start_timer!(|| "zero check verify");
109
110        // check that the sum is zero
111        if proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1] != F::zero() {
112            return Err(PolyIOPErrors::InvalidProof(format!(
113                "zero check: sum {} is not zero",
114                proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1]
115            )));
116        }
117
118        // generate `r` and pass it to the caller for correctness check
119        let length = fx_aux_info.num_variables;
120        let r = transcript.get_and_append_challenge_vectors(b"0check r", length)?;
121
122        // hat_fx's max degree is increased by eq(x, r).degree() which is 1
123        let mut hat_fx_aux_info = fx_aux_info.clone();
124        hat_fx_aux_info.max_degree += 1;
125        let subclaim =
126            <Self as SumCheck<F>>::verify(F::zero(), proof, &hat_fx_aux_info, transcript)?;
127
128        // expected_eval = sumcheck.expect_eval/eq(x, r)
129        // where x = sum_check_sub_claim.point
130        let eq_x_r = build_eq_x_r(&r)?;
131        let expected_evaluation = subclaim.expected_evaluation
132            / eq_x_r.evaluate(&subclaim.point).ok_or_else(|| {
133                PolyIOPErrors::InvalidParameters("evaluation dimension does not match".to_string())
134            })?;
135
136        end_timer!(start);
137        Ok(ZeroCheckSubClaim {
138            sum_check_sub_claim: subclaim,
139            expected_evaluation,
140            init_challenge: r,
141        })
142    }
143}
144
145#[cfg(test)]
146mod test {
147
148    use super::ZeroCheck;
149    use crate::{errors::PolyIOPErrors, PolyIOP};
150    use arithmetic::VirtualPolynomial;
151    use ark_bls12_381::Fr;
152    use ark_std::test_rng;
153
154    fn test_zerocheck(
155        nv: usize,
156        num_multiplicands_range: (usize, usize),
157        num_products: usize,
158    ) -> Result<(), PolyIOPErrors> {
159        let mut rng = test_rng();
160
161        {
162            // good path: zero virtual poly
163            let poly =
164                VirtualPolynomial::rand_zero(nv, num_multiplicands_range, num_products, &mut rng)?;
165
166            let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
167            transcript.append_message(b"testing", b"initializing transcript for testing")?;
168            let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?;
169
170            let poly_info = poly.aux_info.clone();
171            let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
172            transcript.append_message(b"testing", b"initializing transcript for testing")?;
173            let subclaim =
174                <PolyIOP<Fr> as ZeroCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)?
175                    .sum_check_sub_claim;
176            assert!(
177                poly.evaluate(&subclaim.point)? == subclaim.expected_evaluation,
178                "wrong subclaim"
179            );
180        }
181
182        {
183            // bad path: random virtual poly whose sum is not zero
184            let (poly, _sum) =
185                VirtualPolynomial::<Fr>::rand(nv, num_multiplicands_range, num_products, &mut rng)?;
186
187            let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
188            transcript.append_message(b"testing", b"initializing transcript for testing")?;
189            let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?;
190
191            let poly_info = poly.aux_info.clone();
192            let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
193            transcript.append_message(b"testing", b"initializing transcript for testing")?;
194
195            assert!(
196                <PolyIOP<Fr> as ZeroCheck<Fr>>::verify(&proof, &poly_info, &mut transcript)
197                    .is_err()
198            );
199        }
200
201        Ok(())
202    }
203
204    #[test]
205    fn test_trivial_polynomial() -> Result<(), PolyIOPErrors> {
206        let nv = 1;
207        let num_multiplicands_range = (4, 5);
208        let num_products = 1;
209
210        test_zerocheck(nv, num_multiplicands_range, num_products)
211    }
212    #[test]
213    fn test_normal_polynomial() -> Result<(), PolyIOPErrors> {
214        let nv = 5;
215        let num_multiplicands_range = (4, 9);
216        let num_products = 5;
217
218        test_zerocheck(nv, num_multiplicands_range, num_products)
219    }
220
221    #[test]
222    fn zero_polynomial_should_error() -> Result<(), PolyIOPErrors> {
223        let nv = 0;
224        let num_multiplicands_range = (4, 13);
225        let num_products = 5;
226
227        assert!(test_zerocheck(nv, num_multiplicands_range, num_products).is_err());
228        Ok(())
229    }
230}