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