// 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 . //! 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( w: &DenseMultilinearExtension, l: &[DensePolynomial], num_points: usize, with_suffix: bool, ) -> Result, 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::::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 = 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( polynomials: &[std::rc::Rc>], points: &[Vec], ) -> Result, 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::(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 = 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( polynomial: &std::rc::Rc>, points: &[Vec], ) -> Result, PCSError> { use arithmetic::{build_l, get_uni_domain}; let uni_poly_degree = points.len(); let domain = get_uni_domain::(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 = 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::() } fn test_w_circ_l_helper() -> 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::() } fn test_w_circ_l_with_prefix_helper() -> 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::(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 = 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::(3)?; let l = build_l(&[point1, point2, point3], &domain, false)?; let q_x = compute_w_circ_l(&w, &l, 3, false)?; let point: Vec = 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::(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. = 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 = 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::(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 = 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(()) } }