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