// Copyright (c) 2023 Espresso Systems (espressosys.com)
// This file is part of the HyperPlonk library.
// You should have received a copy of the MIT License
// along with the HyperPlonk library. If not, see .
//! This module implements useful functions for the product check protocol.
use crate::poly_iop::{errors::PolyIOPErrors, structs::IOPProof, zero_check::ZeroCheck, PolyIOP};
use arithmetic::{get_index, VirtualPolynomial};
use ark_ff::{batch_inversion, PrimeField};
use ark_poly::DenseMultilinearExtension;
use ark_std::{end_timer, start_timer};
use std::sync::Arc;
use transcript::IOPTranscript;
/// Compute multilinear fractional polynomial s.t. frac(x) = f1(x) * ... * fk(x)
/// / (g1(x) * ... * gk(x)) for all x \in {0,1}^n
///
/// The caller needs to sanity-check that the number of polynomials and
/// variables match in fxs and gxs; and gi(x) has no zero entries.
pub(super) fn compute_frac_poly(
fxs: &[Arc>],
gxs: &[Arc>],
) -> Result>, PolyIOPErrors> {
let start = start_timer!(|| "compute frac(x)");
let mut f_evals = vec![F::one(); 1 << fxs[0].num_vars];
for fx in fxs.iter() {
for (f_eval, fi) in f_evals.iter_mut().zip(fx.iter()) {
*f_eval *= fi;
}
}
let mut g_evals = vec![F::one(); 1 << gxs[0].num_vars];
for gx in gxs.iter() {
for (g_eval, gi) in g_evals.iter_mut().zip(gx.iter()) {
*g_eval *= gi;
}
}
batch_inversion(&mut g_evals[..]);
for (f_eval, g_eval) in f_evals.iter_mut().zip(g_evals.iter()) {
if *g_eval == F::zero() {
return Err(PolyIOPErrors::InvalidParameters(
"gxs has zero entries in the boolean hypercube".to_string(),
));
}
*f_eval *= g_eval;
}
end_timer!(start);
Ok(Arc::new(DenseMultilinearExtension::from_evaluations_vec(
fxs[0].num_vars,
f_evals,
)))
}
/// Compute the product polynomial `prod(x)` such that
/// `prod(x) = [(1-x1)*frac(x2, ..., xn, 0) + x1*prod(x2, ..., xn, 0)] *
/// [(1-x1)*frac(x2, ..., xn, 1) + x1*prod(x2, ..., xn, 1)]` on the boolean
/// hypercube {0,1}^n
///
/// The caller needs to check num_vars matches in f and g
/// Cost: linear in N.
pub(super) fn compute_product_poly(
frac_poly: &Arc>,
) -> Result>, PolyIOPErrors> {
let start = start_timer!(|| "compute evaluations of prod polynomial");
let num_vars = frac_poly.num_vars;
let frac_evals = &frac_poly.evaluations;
// ===================================
// prod(x)
// ===================================
//
// `prod(x)` can be computed via recursing the following formula for 2^n-1
// times
//
// `prod(x_1, ..., x_n) :=
// [(1-x1)*frac(x2, ..., xn, 0) + x1*prod(x2, ..., xn, 0)] *
// [(1-x1)*frac(x2, ..., xn, 1) + x1*prod(x2, ..., xn, 1)]`
//
// At any given step, the right hand side of the equation
// is available via either frac_x or the current view of prod_x
let mut prod_x_evals = vec![];
for x in 0..(1 << num_vars) - 1 {
// sign will decide if the evaluation should be looked up from frac_x or
// prod_x; x_zero_index is the index for the evaluation (x_2, ..., x_n,
// 0); x_one_index is the index for the evaluation (x_2, ..., x_n, 1);
let (x_zero_index, x_one_index, sign) = get_index(x, num_vars);
if !sign {
prod_x_evals.push(frac_evals[x_zero_index] * frac_evals[x_one_index]);
} else {
// sanity check: if we are trying to look up from the prod_x_evals table,
// then the target index must already exist
if x_zero_index >= prod_x_evals.len() || x_one_index >= prod_x_evals.len() {
return Err(PolyIOPErrors::ShouldNotArrive);
}
prod_x_evals.push(prod_x_evals[x_zero_index] * prod_x_evals[x_one_index]);
}
}
// prod(1, 1, ..., 1) := 0
prod_x_evals.push(F::zero());
end_timer!(start);
Ok(Arc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars,
prod_x_evals,
)))
}
/// generate the zerocheck proof for the virtual polynomial
/// prod(x) - p1(x) * p2(x) + alpha * [frac(x) * g1(x) * ... * gk(x) - f1(x)
/// * ... * fk(x)] where p1(x) = (1-x1) * frac(x2, ..., xn, 0) + x1 * prod(x2,
/// ..., xn, 0), p2(x) = (1-x1) * frac(x2, ..., xn, 1) + x1 * prod(x2, ...,
/// xn, 1)
/// Returns proof.
///
/// Cost: O(N)
pub(super) fn prove_zero_check(
fxs: &[Arc>],
gxs: &[Arc>],
frac_poly: &Arc>,
prod_x: &Arc>,
alpha: &F,
transcript: &mut IOPTranscript,
) -> Result<(IOPProof, VirtualPolynomial), PolyIOPErrors> {
let start = start_timer!(|| "zerocheck in product check");
let num_vars = frac_poly.num_vars;
// compute p1(x) = (1-x1) * frac(x2, ..., xn, 0) + x1 * prod(x2, ..., xn, 0)
// compute p2(x) = (1-x1) * frac(x2, ..., xn, 1) + x1 * prod(x2, ..., xn, 1)
let mut p1_evals = vec![F::zero(); 1 << num_vars];
let mut p2_evals = vec![F::zero(); 1 << num_vars];
for x in 0..1 << num_vars {
let (x0, x1, sign) = get_index(x, num_vars);
if !sign {
p1_evals[x] = frac_poly.evaluations[x0];
p2_evals[x] = frac_poly.evaluations[x1];
} else {
p1_evals[x] = prod_x.evaluations[x0];
p2_evals[x] = prod_x.evaluations[x1];
}
}
let p1 = Arc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars, p1_evals,
));
let p2 = Arc::new(DenseMultilinearExtension::from_evaluations_vec(
num_vars, p2_evals,
));
// compute Q(x)
// prod(x)
let mut q_x = VirtualPolynomial::new_from_mle(prod_x, F::one());
// prod(x)
// - p1(x) * p2(x)
q_x.add_mle_list([p1, p2], -F::one())?;
// prod(x)
// - p1(x) * p2(x)
// + alpha * frac(x) * g1(x) * ... * gk(x)
let mut mle_list = gxs.to_vec();
mle_list.push(frac_poly.clone());
q_x.add_mle_list(mle_list, *alpha)?;
// prod(x)
// - p1(x) * p2(x)
// + alpha * frac(x) * g1(x) * ... * gk(x)
// - alpha * f1(x) * ... * fk(x)]
q_x.add_mle_list(fxs.to_vec(), -*alpha)?;
let iop_proof = as ZeroCheck>::prove(&q_x, transcript)?;
end_timer!(start);
Ok((iop_proof, q_x))
}