arnaucube
arnaucube/hyperplonkPublic Self-hosted repository
hyperplonk/subroutines/src/poly_iop/prod_check/mod.rs
subroutines/src/poly_iop/prod_check/mod.rsRaw
  1//! Main module for the Product Check protocol
  2
  3use crate::{
  4    pcs::PolynomialCommitmentScheme,
  5    poly_iop::{
  6        errors::PolyIOPErrors,
  7        prod_check::util::{compute_product_poly, prove_zero_check},
  8        zero_check::ZeroCheck,
  9        PolyIOP,
 10    },
 11};
 12use arithmetic::VPAuxInfo;
 13use ark_ec::PairingEngine;
 14use ark_ff::{One, PrimeField, Zero};
 15use ark_poly::DenseMultilinearExtension;
 16use ark_std::{end_timer, start_timer};
 17use std::rc::Rc;
 18use transcript::IOPTranscript;
 19
 20mod util;
 21
 22/// A product-check proves that two n-variate multilinear polynomials `f(x),
 23/// g(x)` satisfy:
 24/// \prod_{x \in {0,1}^n} f(x) = \prod_{x \in {0,1}^n} g(x)
 25///
 26/// A ProductCheck is derived from ZeroCheck.
 27///
 28/// Prover steps:
 29/// 1. build `prod(x0, ..., x_n)` from f and g,
 30///    such that `prod(0, x1, ..., xn)` equals `f/g` over domain {0,1}^n
 31/// 2. push commitments of `prod(x)` to the transcript,
 32///    and `generate_challenge` from current transcript (generate alpha)
 33/// 3. generate the zerocheck proof for the virtual polynomial
 34///    prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0, x) * g(x))
 35///
 36/// Verifier steps:
 37/// 1. Extract commitments of `prod(x)` from the proof, push
 38/// them to the transcript
 39/// 2. `generate_challenge` from current transcript (generate alpha)
 40/// 3. `verify` to verify the zerocheck proof and generate the subclaim for
 41/// polynomial evaluations
 42pub trait ProductCheck<E, PCS>: ZeroCheck<E::Fr>
 43where
 44    E: PairingEngine,
 45    PCS: PolynomialCommitmentScheme<E>,
 46{
 47    type ProductCheckSubClaim;
 48    type ProductCheckProof;
 49
 50    /// Initialize the system with a transcript
 51    ///
 52    /// This function is optional -- in the case where a ProductCheck is
 53    /// an building block for a more complex protocol, the transcript
 54    /// may be initialized by this complex protocol, and passed to the
 55    /// ProductCheck prover/verifier.
 56    fn init_transcript() -> Self::Transcript;
 57
 58    /// Generate a proof for product check, showing that witness multilinear
 59    /// polynomials f(x), g(x) satisfy `\prod_{x \in {0,1}^n} f(x) =
 60    /// \prod_{x \in {0,1}^n} g(x)`
 61    ///
 62    /// Inputs:
 63    /// - fx: the numerator multilinear polynomial
 64    /// - gx: the denominator multilinear polynomial
 65    /// - transcript: the IOP transcript
 66    /// - pk: PCS committing key
 67    ///
 68    /// Outputs
 69    /// - the product check proof
 70    /// - the product polynomial (used for testing)
 71    ///
 72    /// Cost: O(N)
 73    fn prove(
 74        pcs_param: &PCS::ProverParam,
 75        fx: &Self::MultilinearExtension,
 76        gx: &Self::MultilinearExtension,
 77        transcript: &mut IOPTranscript<E::Fr>,
 78    ) -> Result<(Self::ProductCheckProof, Self::MultilinearExtension), PolyIOPErrors>;
 79
 80    /// Verify that for witness multilinear polynomials f(x), g(x)
 81    /// it holds that `\prod_{x \in {0,1}^n} f(x) = \prod_{x \in {0,1}^n} g(x)`
 82    fn verify(
 83        proof: &Self::ProductCheckProof,
 84        aux_info: &VPAuxInfo<E::Fr>,
 85        transcript: &mut Self::Transcript,
 86    ) -> Result<Self::ProductCheckSubClaim, PolyIOPErrors>;
 87}
 88
 89/// A product check subclaim consists of
 90/// - A zero check IOP subclaim for
 91/// `Q(x) = prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0,
 92/// x) * g(x)) = 0`
 93/// - The random challenge `alpha`
 94/// - A final query for `prod(1, ..., 1, 0) = 1`.
 95// Note that this final query is in fact a constant that
 96// is independent from the proof. So we should avoid
 97// (de)serialize it.
 98#[derive(Clone, Debug, Default, PartialEq)]
 99pub struct ProductCheckSubClaim<F: PrimeField, ZC: ZeroCheck<F>> {
100    // the SubClaim from the ZeroCheck
101    pub zero_check_sub_claim: ZC::ZeroCheckSubClaim,
102    // final query which consists of
103    // - the vector `(1, ..., 1, 0)` (needs to be reversed because Arkwork's MLE uses big-endian
104    //   format for points)
105    // The expected final query evaluation is 1
106    pub final_query: (Vec<F>, F),
107    pub alpha: F,
108}
109
110/// A product check proof consists of
111/// - a zerocheck proof
112/// - a product polynomial commitment
113#[derive(Clone, Debug, Default, PartialEq)]
114pub struct ProductCheckProof<
115    E: PairingEngine,
116    PCS: PolynomialCommitmentScheme<E>,
117    ZC: ZeroCheck<E::Fr>,
118> {
119    pub zero_check_proof: ZC::ZeroCheckProof,
120    pub prod_x_comm: PCS::Commitment,
121}
122
123impl<E, PCS> ProductCheck<E, PCS> for PolyIOP<E::Fr>
124where
125    E: PairingEngine,
126    PCS: PolynomialCommitmentScheme<E, Polynomial = Rc<DenseMultilinearExtension<E::Fr>>>,
127{
128    type ProductCheckSubClaim = ProductCheckSubClaim<E::Fr, Self>;
129    type ProductCheckProof = ProductCheckProof<E, PCS, Self>;
130
131    fn init_transcript() -> Self::Transcript {
132        IOPTranscript::<E::Fr>::new(b"Initializing ProductCheck transcript")
133    }
134
135    fn prove(
136        pcs_param: &PCS::ProverParam,
137        fx: &Self::MultilinearExtension,
138        gx: &Self::MultilinearExtension,
139        transcript: &mut IOPTranscript<E::Fr>,
140    ) -> Result<(Self::ProductCheckProof, Self::MultilinearExtension), PolyIOPErrors> {
141        let start = start_timer!(|| "prod_check prove");
142
143        if fx.num_vars != gx.num_vars {
144            return Err(PolyIOPErrors::InvalidParameters(
145                "fx and gx have different number of variables".to_string(),
146            ));
147        }
148
149        // compute the product polynomial
150        let prod_x = compute_product_poly(fx, gx)?;
151
152        // generate challenge
153        let prod_x_comm = PCS::commit(pcs_param, &prod_x)?;
154        transcript.append_serializable_element(b"prod(x)", &prod_x_comm)?;
155        let alpha = transcript.get_and_append_challenge(b"alpha")?;
156
157        // build the zero-check proof
158        let (zero_check_proof, _) = prove_zero_check(fx, gx, &prod_x, &alpha, transcript)?;
159
160        end_timer!(start);
161
162        Ok((
163            ProductCheckProof {
164                zero_check_proof,
165                prod_x_comm,
166            },
167            prod_x,
168        ))
169    }
170
171    fn verify(
172        proof: &Self::ProductCheckProof,
173        aux_info: &VPAuxInfo<E::Fr>,
174        transcript: &mut Self::Transcript,
175    ) -> Result<Self::ProductCheckSubClaim, PolyIOPErrors> {
176        let start = start_timer!(|| "prod_check verify");
177
178        // update transcript and generate challenge
179        transcript.append_serializable_element(b"prod(x)", &proof.prod_x_comm)?;
180        let alpha = transcript.get_and_append_challenge(b"alpha")?;
181
182        // invoke the zero check on the iop_proof
183        // the virtual poly info for Q(x)
184        let zero_check_sub_claim =
185            <Self as ZeroCheck<E::Fr>>::verify(&proof.zero_check_proof, aux_info, transcript)?;
186
187        // the final query is on prod_x, hence has length `num_vars` + 1
188        let mut final_query = vec![E::Fr::one(); aux_info.num_variables + 1];
189        // the point has to be reversed because Arkworks uses big-endian.
190        final_query[0] = E::Fr::zero();
191        let final_eval = E::Fr::one();
192
193        end_timer!(start);
194
195        Ok(ProductCheckSubClaim {
196            zero_check_sub_claim,
197            final_query: (final_query, final_eval),
198            alpha,
199        })
200    }
201}
202
203#[cfg(test)]
204mod test {
205    use super::ProductCheck;
206    use crate::{
207        pcs::{prelude::MultilinearKzgPCS, PolynomialCommitmentScheme},
208        poly_iop::{errors::PolyIOPErrors, PolyIOP},
209    };
210    use arithmetic::VPAuxInfo;
211    use ark_bls12_381::{Bls12_381, Fr};
212    use ark_ec::PairingEngine;
213    use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
214    use ark_std::test_rng;
215    use std::{marker::PhantomData, rc::Rc};
216
217    // f and g are guaranteed to have the same product
218    fn test_product_check_helper<E, PCS>(
219        f: &DenseMultilinearExtension<E::Fr>,
220        g: &DenseMultilinearExtension<E::Fr>,
221        pcs_param: &PCS::ProverParam,
222    ) -> Result<(), PolyIOPErrors>
223    where
224        E: PairingEngine,
225        PCS: PolynomialCommitmentScheme<E, Polynomial = Rc<DenseMultilinearExtension<E::Fr>>>,
226    {
227        let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
228        transcript.append_message(b"testing", b"initializing transcript for testing")?;
229
230        let (proof, prod_x) = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::prove(
231            pcs_param,
232            &Rc::new(f.clone()),
233            &Rc::new(g.clone()),
234            &mut transcript,
235        )?;
236
237        let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
238        transcript.append_message(b"testing", b"initializing transcript for testing")?;
239
240        let aux_info = VPAuxInfo {
241            max_degree: 2,
242            num_variables: f.num_vars,
243            phantom: PhantomData::default(),
244        };
245        let prod_subclaim =
246            <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::verify(&proof, &aux_info, &mut transcript)?;
247        assert_eq!(
248            prod_x.evaluate(&prod_subclaim.final_query.0).unwrap(),
249            prod_subclaim.final_query.1,
250            "different product"
251        );
252
253        // bad path
254        let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
255        transcript.append_message(b"testing", b"initializing transcript for testing")?;
256
257        let h = f + g;
258        let (bad_proof, prod_x_bad) = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::prove(
259            pcs_param,
260            &Rc::new(f.clone()),
261            &Rc::new(h),
262            &mut transcript,
263        )?;
264
265        let mut transcript = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::init_transcript();
266        transcript.append_message(b"testing", b"initializing transcript for testing")?;
267        let bad_subclaim = <PolyIOP<E::Fr> as ProductCheck<E, PCS>>::verify(
268            &bad_proof,
269            &aux_info,
270            &mut transcript,
271        )?;
272        assert_ne!(
273            prod_x_bad.evaluate(&bad_subclaim.final_query.0).unwrap(),
274            bad_subclaim.final_query.1,
275            "can't detect wrong proof"
276        );
277
278        Ok(())
279    }
280
281    fn test_product_check(nv: usize) -> Result<(), PolyIOPErrors> {
282        let mut rng = test_rng();
283
284        let f: DenseMultilinearExtension<Fr> = DenseMultilinearExtension::rand(nv, &mut rng);
285        let mut g = f.clone();
286        g.evaluations.reverse();
287
288        let srs = MultilinearKzgPCS::<Bls12_381>::gen_srs_for_testing(&mut rng, nv + 1)?;
289        let (pcs_param, _) = MultilinearKzgPCS::<Bls12_381>::trim(&srs, None, Some(nv + 1))?;
290
291        test_product_check_helper::<Bls12_381, MultilinearKzgPCS<Bls12_381>>(&f, &g, &pcs_param)?;
292
293        Ok(())
294    }
295
296    #[test]
297    fn test_trivial_polynomial() -> Result<(), PolyIOPErrors> {
298        test_product_check(1)
299    }
300    #[test]
301    fn test_normal_polynomial() -> Result<(), PolyIOPErrors> {
302        test_product_check(10)
303    }
304}