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