arnaucube
arnaucube/hyperplonkPublic Self-hosted repository
hyperplonk/poly-iop/src/prod_check/util.rs
poly-iop/src/prod_check/util.rsRaw
  1//! This module implements useful functions for the product check protocol.
  2
  3use crate::{errors::PolyIOPErrors, structs::IOPProof, zero_check::ZeroCheck, PolyIOP};
  4use arithmetic::{get_index, VirtualPolynomial};
  5use ark_ff::PrimeField;
  6use ark_poly::DenseMultilinearExtension;
  7use ark_std::{end_timer, start_timer};
  8use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
  9use std::rc::Rc;
 10use transcript::IOPTranscript;
 11
 12/// Compute the product polynomial `prod(x)` where
 13///
 14///  - `prod(0,x) := prod(0, x1, …, xn)` is the MLE over the
 15/// evaluations of `f(x)/g(x)` on the boolean hypercube {0,1}^n
 16///
 17/// - `prod(1,x)` is a MLE over the evaluations of `prod(x, 0) * prod(x, 1)`
 18/// on the boolean hypercube {0,1}^n
 19///
 20/// The caller needs to check num_vars matches in f and g
 21/// Cost: linear in N.
 22pub(super) fn compute_product_poly<F: PrimeField>(
 23    fx: &Rc<DenseMultilinearExtension<F>>,
 24    gx: &Rc<DenseMultilinearExtension<F>>,
 25) -> Result<Rc<DenseMultilinearExtension<F>>, PolyIOPErrors> {
 26    let start = start_timer!(|| "compute evaluations of prod polynomial");
 27    let num_vars = fx.num_vars;
 28
 29    // ===================================
 30    // prod(0, x)
 31    // ===================================
 32    let prod_0x_eval = compute_prod_0(fx, gx)?;
 33
 34    // ===================================
 35    // prod(1, x)
 36    // ===================================
 37    //
 38    // `prod(1, x)` can be computed via recursing the following formula for 2^n-1
 39    // times
 40    //
 41    // `prod(1, x_1, ..., x_n) :=
 42    //      prod(x_1, x_2, ..., x_n, 0) * prod(x_1, x_2, ..., x_n, 1)`
 43    //
 44    // At any given step, the right hand side of the equation
 45    // is available via either eval_0x or the current view of eval_1x
 46    let mut prod_1x_eval = vec![];
 47    for x in 0..(1 << num_vars) - 1 {
 48        // sign will decide if the evaluation should be looked up from eval_0x or
 49        // eval_1x; x_zero_index is the index for the evaluation (x_2, ..., x_n,
 50        // 0); x_one_index is the index for the evaluation (x_2, ..., x_n, 1);
 51        let (x_zero_index, x_one_index, sign) = get_index(x, num_vars);
 52        if !sign {
 53            prod_1x_eval.push(prod_0x_eval[x_zero_index] * prod_0x_eval[x_one_index]);
 54        } else {
 55            // sanity check: if we are trying to look up from the eval_1x table,
 56            // then the target index must already exist
 57            if x_zero_index >= prod_1x_eval.len() || x_one_index >= prod_1x_eval.len() {
 58                return Err(PolyIOPErrors::ShouldNotArrive);
 59            }
 60            prod_1x_eval.push(prod_1x_eval[x_zero_index] * prod_1x_eval[x_one_index]);
 61        }
 62    }
 63
 64    // prod(1, 1, ..., 1) := 0
 65    prod_1x_eval.push(F::zero());
 66
 67    // ===================================
 68    // prod(x)
 69    // ===================================
 70    // prod(x)'s evaluation is indeed `e := [eval_0x[..], eval_1x[..]].concat()`
 71    let eval = [prod_0x_eval.as_slice(), prod_1x_eval.as_slice()].concat();
 72
 73    let prod_x = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
 74        num_vars + 1,
 75        eval,
 76    ));
 77
 78    end_timer!(start);
 79    Ok(prod_x)
 80}
 81
 82/// generate the zerocheck proof for the virtual polynomial
 83///    prod(1, x) - prod(x, 0) * prod(x, 1) + alpha * (f(x) - prod(0, x) * g(x))
 84///
 85/// Returns proof and Q(x) for testing purpose.
 86///
 87/// Cost: O(N)
 88pub(super) fn prove_zero_check<F: PrimeField>(
 89    fx: &Rc<DenseMultilinearExtension<F>>,
 90    gx: &Rc<DenseMultilinearExtension<F>>,
 91    prod_x: &Rc<DenseMultilinearExtension<F>>,
 92    alpha: &F,
 93    transcript: &mut IOPTranscript<F>,
 94) -> Result<(IOPProof<F>, VirtualPolynomial<F>), PolyIOPErrors> {
 95    let start = start_timer!(|| "zerocheck in product check");
 96
 97    let prod_partial_evals = build_prod_partial_eval(prod_x)?;
 98    let prod_0x = prod_partial_evals[0].clone();
 99    let prod_1x = prod_partial_evals[1].clone();
100    let prod_x0 = prod_partial_evals[2].clone();
101    let prod_x1 = prod_partial_evals[3].clone();
102
103    // compute g(x) * prod(0, x) * alpha
104    let mut q_x = VirtualPolynomial::new_from_mle(gx, F::one());
105    q_x.mul_by_mle(prod_0x, *alpha)?;
106
107    //   g(x) * prod(0, x) * alpha
108    // - f(x) * alpha
109    q_x.add_mle_list([fx.clone()], -*alpha)?;
110
111    // Q(x) := prod(1,x) - prod(x, 0) * prod(x, 1)
112    //       + alpha * (
113    //             g(x) * prod(0, x)
114    //           - f(x))
115    q_x.add_mle_list([prod_x0, prod_x1], -F::one())?;
116    q_x.add_mle_list([prod_1x], F::one())?;
117
118    let iop_proof = <PolyIOP<F> as ZeroCheck<F>>::prove(&q_x, transcript)?;
119
120    end_timer!(start);
121    Ok((iop_proof, q_x))
122}
123
124/// Helper function of the IOP.
125///
126/// Input:
127/// - prod(x)
128///
129/// Output: the following 4 polynomials
130/// - prod(0, x)
131/// - prod(1, x)
132/// - prod(x, 0)
133/// - prod(x, 1)
134fn build_prod_partial_eval<F: PrimeField>(
135    prod_x: &Rc<DenseMultilinearExtension<F>>,
136) -> Result<[Rc<DenseMultilinearExtension<F>>; 4], PolyIOPErrors> {
137    let start = start_timer!(|| "build partial prod polynomial");
138
139    let prod_x_eval = &prod_x.evaluations;
140    let num_vars = prod_x.num_vars - 1;
141
142    // prod(0, x)
143    let prod_0_x = Rc::new(DenseMultilinearExtension::from_evaluations_slice(
144        num_vars,
145        &prod_x_eval[0..1 << num_vars],
146    ));
147    // prod(1, x)
148    let prod_1_x = Rc::new(DenseMultilinearExtension::from_evaluations_slice(
149        num_vars,
150        &prod_x_eval[1 << num_vars..1 << (num_vars + 1)],
151    ));
152
153    // ===================================
154    // prod(x, 0) and prod(x, 1)
155    // ===================================
156    //
157    // now we compute eval_x0 and eval_x1
158    // eval_0x will be the odd coefficients of eval
159    // and eval_1x will be the even coefficients of eval
160    let mut eval_x0 = vec![];
161    let mut eval_x1 = vec![];
162    for (x, &prod_x) in prod_x_eval.iter().enumerate() {
163        if x & 1 == 0 {
164            eval_x0.push(prod_x);
165        } else {
166            eval_x1.push(prod_x);
167        }
168    }
169    let prod_x_0 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
170        num_vars, eval_x0,
171    ));
172    let prod_x_1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(
173        num_vars, eval_x1,
174    ));
175
176    end_timer!(start);
177
178    Ok([prod_0_x, prod_1_x, prod_x_0, prod_x_1])
179}
180
181/// Returns the evaluations of
182/// - `prod(0,x) := prod(0, x1, …, xn)` which is the MLE over the
183/// evaluations of f(x)/g(x) on the boolean hypercube {0,1}^n:
184///
185/// The caller needs to check num_vars matches in f/g
186/// Cost: linear in N.
187fn compute_prod_0<F: PrimeField>(
188    fx: &DenseMultilinearExtension<F>,
189    gx: &DenseMultilinearExtension<F>,
190) -> Result<Vec<F>, PolyIOPErrors> {
191    let start = start_timer!(|| "compute prod(0,x)");
192
193    let input = fx
194        .iter()
195        .zip(gx.iter())
196        .map(|(&fi, &gi)| (fi, gi))
197        .collect::<Vec<_>>();
198    let prod_0x_evals = input.par_iter().map(|(x, y)| *x / *y).collect::<Vec<_>>();
199
200    end_timer!(start);
201    Ok(prod_0x_evals)
202}