You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

432 lines
14 KiB

// Copyright (c) 2022 Espresso Systems (espressosys.com)
// This file is part of the Jellyfish library.
// You should have received a copy of the MIT License
// along with the Jellyfish library. If not, see <https://mit-license.org/>.
//! Useful utilities for KZG PCS
use crate::prelude::PCSError;
use arithmetic::evaluate_no_par;
use ark_ff::PrimeField;
use ark_poly::{
univariate::DensePolynomial, DenseMultilinearExtension, EvaluationDomain, Evaluations,
MultilinearExtension, Polynomial, Radix2EvaluationDomain,
};
use ark_std::{end_timer, format, log2, start_timer, string::ToString, vec::Vec};
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
/// For an MLE w with `mle_num_vars` variables, and `point_len` number of
/// points, compute the degree of the univariate polynomial `q(x):= w(l(x))`
/// where l(x) is a list of polynomials that go through all points.
// uni_degree is computed as `mle_num_vars * point_len`:
// - each l(x) is of degree `point_len`
// - mle has degree one
// - worst case is `\prod_{i=0}^{mle_num_vars-1} l_i(x) < point_len * mle_num_vars`
#[inline]
pub fn compute_qx_degree(mle_num_vars: usize, point_len: usize) -> usize {
mle_num_vars * ((1 << log2(point_len)) - 1) + 1
}
/// Compute W \circ l.
///
/// Given an MLE W, and a list of univariate polynomials l, generate the
/// univariate polynomial that composes W with l.
///
/// Returns an error if l's length does not matches number of variables in W.
pub(crate) fn compute_w_circ_l<F: PrimeField>(
w: &DenseMultilinearExtension<F>,
l: &[DensePolynomial<F>],
num_points: usize,
with_suffix: bool,
) -> Result<DensePolynomial<F>, PCSError> {
let timer = start_timer!(|| "compute W \\circ l");
if w.num_vars != l.len() {
return Err(PCSError::InvalidParameters(format!(
"l's length ({}) does not match num_variables ({})",
l.len(),
w.num_vars(),
)));
}
let uni_degree = if with_suffix {
compute_qx_degree(w.num_vars() + log2(num_points) as usize, num_points)
} else {
compute_qx_degree(w.num_vars(), num_points)
};
let domain = match Radix2EvaluationDomain::<F>::new(uni_degree) {
Some(p) => p,
None => {
return Err(PCSError::InvalidParameters(
"failed to build radix 2 domain".to_string(),
))
},
};
let step = start_timer!(|| format!("compute eval {}-dim domain", domain.size()));
let res_eval = (0..domain.size())
.into_par_iter()
.map(|i| {
let l_eval: Vec<F> = l.iter().map(|x| x.evaluate(&domain.element(i))).collect();
evaluate_no_par(w, &l_eval)
})
.collect();
end_timer!(step);
let evaluation = Evaluations::from_vec_and_domain(res_eval, domain);
let res = evaluation.interpolate();
end_timer!(timer);
Ok(res)
}
/// Input a list of multilinear polynomials and a list of points,
/// generate a list of evaluations.
// Note that this function is only used for testing verifications.
// In practice verifier does not see polynomials, and the `mle_values`
// are included in the `batch_proof`.
#[cfg(test)]
pub(crate) fn generate_evaluations_multi_poly<F: PrimeField>(
polynomials: &[std::rc::Rc<DenseMultilinearExtension<F>>],
points: &[Vec<F>],
) -> Result<Vec<F>, PCSError> {
use arithmetic::{build_l, get_uni_domain, merge_polynomials};
if polynomials.len() != points.len() {
return Err(PCSError::InvalidParameters(
"polynomial length does not match point length".to_string(),
));
}
let uni_poly_degree = points.len();
let merge_poly = merge_polynomials(polynomials)?;
let domain = get_uni_domain::<F>(uni_poly_degree)?;
let uni_polys = build_l(points, &domain, true)?;
let mut mle_values = vec![];
for i in 0..uni_poly_degree {
let point: Vec<F> = uni_polys
.iter()
.map(|poly| poly.evaluate(&domain.element(i)))
.collect();
let mle_value = merge_poly.evaluate(&point).unwrap();
mle_values.push(mle_value)
}
Ok(mle_values)
}
/// Input a list of multilinear polynomials and a list of points,
/// generate a list of evaluations.
// Note that this function is only used for testing verifications.
// In practice verifier does not see polynomials, and the `mle_values`
// are included in the `batch_proof`.
#[cfg(test)]
pub(crate) fn generate_evaluations_single_poly<F: PrimeField>(
polynomial: &std::rc::Rc<DenseMultilinearExtension<F>>,
points: &[Vec<F>],
) -> Result<Vec<F>, PCSError> {
use arithmetic::{build_l, get_uni_domain};
let uni_poly_degree = points.len();
let domain = get_uni_domain::<F>(uni_poly_degree)?;
let uni_polys = build_l(points, &domain, false)?;
let mut mle_values = vec![];
for i in 0..uni_poly_degree {
let point: Vec<F> = uni_polys
.iter()
.map(|poly| poly.evaluate(&domain.element(i)))
.collect();
let mle_value = polynomial.evaluate(&point).unwrap();
mle_values.push(mle_value)
}
Ok(mle_values)
}
#[cfg(test)]
mod test {
use super::*;
use arithmetic::{build_l, get_uni_domain, merge_polynomials};
use ark_bls12_381::Fr;
use ark_poly::UVPolynomial;
use ark_std::{One, Zero};
use std::rc::Rc;
#[test]
fn test_w_circ_l() -> Result<(), PCSError> {
test_w_circ_l_helper::<Fr>()
}
fn test_w_circ_l_helper<F: PrimeField>() -> Result<(), PCSError> {
{
// Example from page 53:
// W = 3x1x2 + 2x2 whose evaluations are
// 0, 0 |-> 0
// 1, 0 |-> 0
// 0, 1 |-> 2
// 1, 1 |-> 5
let w_eval = vec![F::zero(), F::zero(), F::from(2u64), F::from(5u64)];
let w = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
// l0 = t + 2
// l1 = -2t + 4
let l0 = DensePolynomial::from_coefficients_vec(vec![F::from(2u64), F::one()]);
let l1 = DensePolynomial::from_coefficients_vec(vec![F::from(4u64), -F::from(2u64)]);
// res = -6t^2 - 4t + 32
let res = compute_w_circ_l(&w, [l0, l1].as_ref(), 4, false)?;
let res_rec = DensePolynomial::from_coefficients_vec(vec![
F::from(32u64),
-F::from(4u64),
-F::from(6u64),
]);
assert_eq!(res, res_rec);
}
{
// A random example
// W = x1x2x3 - 2x1x2 + 3x2x3 - 4x1x3 + 5x1 - 6x2 + 7x3
// 0, 0, 0 |-> 0
// 1, 0, 0 |-> 5
// 0, 1, 0 |-> -6
// 1, 1, 0 |-> -3
// 0, 0, 1 |-> 7
// 1, 0, 1 |-> 8
// 0, 1, 1 |-> 4
// 1, 1, 1 |-> 4
let w_eval = vec![
F::zero(),
F::from(5u64),
-F::from(6u64),
-F::from(3u64),
F::from(7u64),
F::from(8u64),
F::from(4u64),
F::from(4u64),
];
let w = DenseMultilinearExtension::from_evaluations_vec(3, w_eval);
// l0 = t + 2
// l1 = 3t - 4
// l2 = -5t + 6
let l0 = DensePolynomial::from_coefficients_vec(vec![F::from(2u64), F::one()]);
let l1 = DensePolynomial::from_coefficients_vec(vec![-F::from(4u64), F::from(3u64)]);
let l2 = DensePolynomial::from_coefficients_vec(vec![F::from(6u64), -F::from(5u64)]);
let res = compute_w_circ_l(&w, [l0, l1, l2].as_ref(), 8, false)?;
// res = -15t^3 - 23t^2 + 130t - 76
let res_rec = DensePolynomial::from_coefficients_vec(vec![
-F::from(76u64),
F::from(130u64),
-F::from(23u64),
-F::from(15u64),
]);
assert_eq!(res, res_rec);
}
Ok(())
}
#[test]
fn test_w_circ_l_with_prefix() -> Result<(), PCSError> {
test_w_circ_l_with_prefix_helper::<Fr>()
}
fn test_w_circ_l_with_prefix_helper<F: PrimeField>() -> Result<(), PCSError> {
{
// Example from page 53:
// W = 3x1x2 + 2x2 whose evaluations are
// 0, 0 |-> 0
// 1, 0 |-> 0
// 0, 1 |-> 2
// 1, 1 |-> 5
let w_eval = vec![F::zero(), F::zero(), F::from(2u64), F::from(5u64)];
let w = DenseMultilinearExtension::from_evaluations_vec(2, w_eval);
// l0 = t + 2
// l1 = -2t + 4
let l0 = DensePolynomial::from_coefficients_vec(vec![F::from(2u64), F::one()]);
let l1 = DensePolynomial::from_coefficients_vec(vec![F::from(4u64), -F::from(2u64)]);
// res = -6t^2 - 4t + 32
let res = compute_w_circ_l(&w, [l0, l1].as_ref(), 4, true)?;
let res_rec = DensePolynomial::from_coefficients_vec(vec![
F::from(32u64),
-F::from(4u64),
-F::from(6u64),
]);
assert_eq!(res, res_rec);
}
{
// A random example
// W = x1x2x3 - 2x1x2 + 3x2x3 - 4x1x3 + 5x1 - 6x2 + 7x3
// 0, 0, 0 |-> 0
// 1, 0, 0 |-> 5
// 0, 1, 0 |-> -6
// 1, 1, 0 |-> -3
// 0, 0, 1 |-> 7
// 1, 0, 1 |-> 8
// 0, 1, 1 |-> 4
// 1, 1, 1 |-> 4
let w_eval = vec![
F::zero(),
F::from(5u64),
-F::from(6u64),
-F::from(3u64),
F::from(7u64),
F::from(8u64),
F::from(4u64),
F::from(4u64),
];
let w = DenseMultilinearExtension::from_evaluations_vec(3, w_eval);
// l0 = t + 2
// l1 = 3t - 4
// l2 = -5t + 6
let l0 = DensePolynomial::from_coefficients_vec(vec![F::from(2u64), F::one()]);
let l1 = DensePolynomial::from_coefficients_vec(vec![-F::from(4u64), F::from(3u64)]);
let l2 = DensePolynomial::from_coefficients_vec(vec![F::from(6u64), -F::from(5u64)]);
let res = compute_w_circ_l(&w, [l0, l1, l2].as_ref(), 8, true)?;
// res = -15t^3 - 23t^2 + 130t - 76
let res_rec = DensePolynomial::from_coefficients_vec(vec![
-F::from(76u64),
F::from(130u64),
-F::from(23u64),
-F::from(15u64),
]);
assert_eq!(res, res_rec);
}
Ok(())
}
#[test]
fn test_qx() -> Result<(), PCSError> {
// Example from page 53:
// W1 = 3x1x2 + 2x2
let w_eval = vec![Fr::zero(), Fr::from(2u64), Fr::zero(), Fr::from(5u64)];
let w = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
let r = Fr::from(42u64);
// point 1 is [1, 2]
let point1 = vec![Fr::from(1u64), Fr::from(2u64)];
// point 2 is [3, 4]
let point2 = vec![Fr::from(3u64), Fr::from(4u64)];
// point 3 is [5, 6]
let point3 = vec![Fr::from(5u64), Fr::from(6u64)];
{
let domain = get_uni_domain::<Fr>(2)?;
let l = build_l(&[point1.clone(), point2.clone()], &domain, false)?;
let q_x = compute_w_circ_l(&w, &l, 2, false)?;
let point: Vec<Fr> = l.iter().map(|poly| poly.evaluate(&r)).collect();
assert_eq!(
q_x.evaluate(&r),
w.evaluate(&point).unwrap(),
"q(r) != w(l(r))"
);
}
{
let domain = get_uni_domain::<Fr>(3)?;
let l = build_l(&[point1, point2, point3], &domain, false)?;
let q_x = compute_w_circ_l(&w, &l, 3, false)?;
let point: Vec<Fr> = vec![l[0].evaluate(&r), l[1].evaluate(&r)];
assert_eq!(
q_x.evaluate(&r),
w.evaluate(&point).unwrap(),
"q(r) != w(l(r))"
);
}
Ok(())
}
#[test]
fn test_qx_with_prefix() -> Result<(), PCSError> {
// Example from page 53:
// W1 = 3x1x2 + 2x2
let w_eval = vec![Fr::zero(), Fr::from(2u64), Fr::zero(), Fr::from(5u64)];
let w1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W2 = x1x2 + x1
let w_eval = vec![Fr::zero(), Fr::zero(), Fr::from(1u64), Fr::from(2u64)];
let w2 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
// W3 = x1 + x2
let w_eval = vec![Fr::zero(), Fr::one(), Fr::from(1u64), Fr::from(2u64)];
let w3 = Rc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval));
let r = Fr::from(42u64);
// point 1 is [1, 2]
let point1 = vec![Fr::from(1u64), Fr::from(2u64)];
// point 2 is [3, 4]
let point2 = vec![Fr::from(3u64), Fr::from(4u64)];
// point 3 is [5, 6]
let point3 = vec![Fr::from(5u64), Fr::from(6u64)];
{
let domain = get_uni_domain::<Fr>(2)?;
// w = (3x1x2 + 2x2)(1-x0) + (x1x2 + x1)x0
// with evaluations: [0,2,0,5,0,0,1,2]
let w = merge_polynomials(&[w1.clone(), w2.clone()])?;
let l = build_l(&[point1.clone(), point2.clone()], &domain, true)?;
// sage: P.<x> = PolynomialRing(ZZ)
// sage: l0 = -1/2 * x + 1/2
// sage: l1 = -x + 2
// sage: l2 = -x + 3
// sage: w = (3 * l1 * l2 + 2 * l2) * (1-l0) + (l1 * l2 + l1) * l0
// sage: w
// x^3 - 7/2*x^2 - 7/2*x + 16
//
// q(x) = x^3 - 7/2*x^2 - 7/2*x + 16
let q_x = compute_w_circ_l(&w, &l, 2, true)?;
let point: Vec<Fr> = l.iter().map(|poly| poly.evaluate(&r)).collect();
assert_eq!(
q_x.evaluate(&r),
w.evaluate(&point).unwrap(),
"q(r) != w(l(r))"
);
}
{
let domain = get_uni_domain::<Fr>(3)?;
let w = merge_polynomials(&[w1, w2, w3])?;
let l = build_l(&[point1, point2, point3], &domain, true)?;
let q_x = compute_w_circ_l(&w, &l, 3, true)?;
let point: Vec<Fr> = vec![
l[0].evaluate(&r),
l[1].evaluate(&r),
l[2].evaluate(&r),
l[3].evaluate(&r),
];
assert_eq!(
q_x.evaluate(&r),
w.evaluate(&point).unwrap(),
"q(r) != w(l(r))"
);
}
Ok(())
}
}