From a522875481ecf44e36a8b13ec6fadc16b05aad2f Mon Sep 17 00:00:00 2001 From: PatStiles Date: Thu, 11 Jan 2024 23:30:16 -0600 Subject: [PATCH] proving working --- Cargo.toml | 1 + src/poly/unipoly.rs | 390 +++++++----------------- src/subprotocols/zeromorph/zeromorph.rs | 278 ++++++++--------- 3 files changed, 240 insertions(+), 429 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e051f6cc6..dd647527c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ ark-std = { version = "0.4.0", default-features = false } ark-serialize = { version = "0.4.2", default-features = false, features = [ "derive", ] } +ark-poly-commit = { version = "0.4.0", default-features = false } # ark-bls12-381 = { version = "^0.4.0", default-features = false, features = [ "curve" ] } criterion = { version = "0.3.1", features = ["html_reports"] } diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index ff89a2f7c..e841b42a0 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -1,12 +1,13 @@ #![allow(dead_code)] -use std::ops::{Index, IndexMut}; +use std::cmp::Ordering; +use std::ops::{Index, IndexMut, MulAssign, AddAssign, Mul}; use super::commitments::{Commitments, MultiCommitGens}; use crate::utils::gaussian_elimination::gaussian_elimination; use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; use ark_ec::CurveGroup; -use ark_ff::{PrimeField, batch_inversion}; +use ark_ff::PrimeField; use ark_serialize::*; // ax^2 + bx + c stored as vec![c,b,a] @@ -35,6 +36,10 @@ impl UniPoly { } } + fn zero() -> Self { + Self::from_coeff(Vec::new()) + } + fn vandermonde_interpolation(evals: &[F]) -> Vec { let n = evals.len(); let xs: Vec = (0..n).map(|x| F::from(x as u64)).collect(); @@ -55,6 +60,37 @@ impl UniPoly { gaussian_elimination(&mut vandermonde) } + /// Divide self by another polynomial, and returns the + /// quotient and remainder. + pub fn divide_with_q_and_r(&self, divisor: &Self) -> Option<(Self, Self)> { + if self.is_zero() { + Some((Self::zero(), Self::zero())) + } else if divisor.is_zero() { + None + } else if self.degree() < divisor.degree() { + Some((Self::zero(), self.clone())) + } else { + // Now we know that self.degree() >= divisor.degree(); + let mut quotient = vec![F::ZERO; self.degree() - divisor.degree() + 1]; + let mut remainder: Self = self.clone(); + // Can unwrap here because we know self is not zero. + let divisor_leading_inv = divisor.leading_coefficient().unwrap().inverse().unwrap(); + while !remainder.is_zero() && remainder.degree() >= divisor.degree() { + let cur_q_coeff = *remainder.leading_coefficient().unwrap() * divisor_leading_inv; + let cur_q_degree = remainder.degree() - divisor.degree(); + quotient[cur_q_degree] = cur_q_coeff; + + for (i, div_coeff) in divisor.coeffs.iter().enumerate() { + remainder.coeffs[cur_q_degree + i] -= &(cur_q_coeff * div_coeff); + } + while let Some(true) = remainder.coeffs.last().map(|c| c == &F::ZERO) { + remainder.coeffs.pop(); + } + } + Some((Self::from_coeff(quotient), remainder)) + } + } + pub fn degree(&self) -> usize { self.coeffs.len() - 1 } @@ -97,94 +133,93 @@ impl UniPoly { Commitments::batch_commit(&self.coeffs, blind, gens) } - fn factor_root(&mut self, root: &F) -> UniPoly { - let mut coeffs = self.coeffs.clone(); - if root.is_zero() { - coeffs.rotate_left(1); - //YUCK!!! - coeffs = coeffs[..coeffs.len() - 1].to_vec(); - } else { - //TODO: handle this unwrap somehow - let root_inverse = -root.inverse().unwrap(); - let mut temp = F::zero(); - for coeff in &mut coeffs { - temp = *coeff - temp; - temp *= root_inverse; - *coeff = temp; - } - } - coeffs[self.coeffs.len() - 1] = F::zero(); - UniPoly { coeffs } + fn is_zero(&self) -> bool { + self.coeffs.is_empty() || self.coeffs.iter().all(|c| c == &F::zero()) } - pub fn factor_roots(&mut self, roots: &[F]) -> UniPoly { - assert!(self.len() != 0); - if roots.len() == 1 { - return self.factor_root(&roots[0]) - } - - let num_roots = roots.len(); - assert!(num_roots < self.len()); - - let new_size = self.len() - num_roots; - let mut minus_root_inverses = vec![F::zero(); num_roots]; - - let mut num_zero_roots = 0; - for root in roots { - if root.is_zero() { - num_zero_roots += 1; - } else { - minus_root_inverses.push(root.neg()); - } + fn truncate_leading_zeros(&mut self) { + while self.coeffs.last().map_or(false, |c| c == &F::zero()) { + self.coeffs.pop(); } + } - // If there are M zero roots, then the first M coefficients of poly must be zero - for i in 0..num_zero_roots { - assert!(self.coeffs[i].is_zero()) - } + fn leading_coefficient(&self) -> Option<&F> { + self.coeffs.last() + } +} - let zero_factored = self.coeffs[num_zero_roots..].to_vec(); - let num_non_zero_roots = minus_root_inverses.len(); +impl AddAssign<&F> for UniPoly { + fn add_assign(&mut self, rhs: &F) { + //TODO: feature gate parallel + self.coeffs.iter_mut().for_each(|c| *c += rhs); + } +} - if num_non_zero_roots > 0 { - batch_inversion(&mut minus_root_inverses); - let mut division_cache = vec![F::zero(); num_non_zero_roots]; +impl MulAssign<&F> for UniPoly { + fn mul_assign(&mut self, rhs: &F) { + //TODO: feature gate parallel + self.coeffs.iter_mut().for_each(|c| *c *= rhs); + } +} - let mut temp = zero_factored[0]; - for root in minus_root_inverses.clone() { - temp *= root; - division_cache.push(temp); - } +impl Mul for UniPoly { + type Output = Self; - //Note: we know this can't be 0 - self[0] = *division_cache.last().unwrap(); + fn mul(self, rhs: F) -> Self { + //TODO: feature gate parallel + Self::from_coeff(self.coeffs.into_iter().map(|c| c * rhs).collect::>()) + } - // Compute resulting coeffs one by one - for i in 1..(zero_factored.len() - num_non_zero_roots) { - temp = zero_factored[i]; +} - // Compute the intermediate values for the coefficient and save in cache - for j in 0..num_non_zero_roots { - temp -= division_cache[j]; - temp *= minus_root_inverses[j]; - division_cache[j] = temp; - } - // Save the resulting coefficient - self[i] = temp; - } +impl Mul<&F> for UniPoly { + type Output = Self; - } else if num_zero_roots > 0 { - self.coeffs.rotate_left(1); - //YUCK!!! - self.coeffs = self.coeffs[..self.coeffs.len() - 1].to_vec(); - } + fn mul(self, rhs: &F) -> Self { + //TODO: feature gate parallel + Self::from_coeff(self.coeffs.into_iter().map(|c| c * rhs).collect::>()) + } - // Clear last coefficient - for i in new_size..self.coeffs.len() { - self[i] = F::zero(); +} + +impl AddAssign<&Self> for UniPoly { + fn add_assign(&mut self, rhs: &Self) { + let ordering = self.coeffs.len().cmp(&rhs.coeffs.len()); + #[allow(clippy::disallowed_methods)] + for (lhs, rhs) in self.coeffs.iter_mut().zip(&rhs.coeffs) { + *lhs += rhs; + } + if matches!(ordering, Ordering::Less) { + self + .coeffs + .extend(rhs.coeffs[self.coeffs.len()..].iter().cloned()); } + if matches!(ordering, Ordering::Equal) { + //TODO: truncate leading zeros + self; + } + } +} + +impl AsRef> for UniPoly { + fn as_ref(&self) -> &Vec { + &self.coeffs + } +} + +impl Index for UniPoly { + type Output = F; + + #[inline(always)] + fn index(&self, _index: usize) -> &F { + &(self.coeffs[_index]) + } +} - todo!() +impl IndexMut for UniPoly { + #[inline(always)] + fn index_mut(&mut self, index: usize) -> &mut F { + &mut (self.coeffs[index]) } } @@ -205,22 +240,6 @@ impl CompressedUniPoly { } } -impl Index for UniPoly { - type Output = F; - - #[inline(always)] - fn index(&self, _index: usize) -> &F { - &(self.coeffs[_index]) - } -} - -impl IndexMut for UniPoly { - #[inline(always)] - fn index_mut(&mut self, index: usize) -> &mut F { - &mut (self.coeffs[index]) - } -} - impl AppendToTranscript for UniPoly { fn append_to_transcript>(&self, label: &'static [u8], transcript: &mut T) { transcript.append_message(label, b"UniPoly_begin"); @@ -236,193 +255,6 @@ mod tests { use super::*; use ark_curve25519::Fr; - use ark_ff::{batch_inversion, BigInt, Field}; - use ark_std::{ops::Neg, test_rng, One, UniformRand, Zero}; - - fn interpolate(points: &[Fr], evals: &[Fr]) -> UniPoly { - let n = points.len(); - - let numerator_polynomial = compute_linear_polynomial_product(&evals, points.len()); - - let mut roots_and_denominators: Vec = vec![Fr::zero(); 2 * n]; - let temp_src: Vec = points.to_vec(); - - for i in 0..n { - roots_and_denominators[i] = -evals[i]; - - // compute constant denominator - roots_and_denominators[n + i] = Fr::one(); - for j in 0..n { - if j == i { - continue; - } - roots_and_denominators[n + i] *= evals[i] - evals[j]; - } - } - - batch_inversion(&mut roots_and_denominators); - - let mut coeffs = vec![Fr::zero(); n]; - let mut temp = vec![Fr::zero(); n]; - let mut z; - let mut mult; - for i in 0..n { - z = roots_and_denominators[i]; - mult = temp_src[i] * roots_and_denominators[n + i]; - temp[0] = mult * numerator_polynomial[0]; - temp[0] *= z; - coeffs[0] += temp[0]; - - for j in 1..n { - temp[j] = mult * numerator_polynomial[j] - temp[j - 1]; - temp[j] *= z; - coeffs[j] += temp[j]; - } - } - - UniPoly::from_coeff(coeffs) - } - - // This function computes the polynomial (x - a)(x - b)(x - c)... given n distinct roots (a, b, c, ...). - fn compute_linear_polynomial_product(roots: &[Fr], n: usize) -> Vec { - let mut res = vec![Fr::zero(); n + 1]; - - let mut scratch = roots.to_vec(); - res[n] = Fr::one(); - res[n - 1] = roots.into_iter().sum::().neg(); - - let mut temp; - let mut constant = Fr::one(); - for i in 0..(n - 1) { - temp = Fr::zero(); - for j in 0..(n - 1 - i) { - scratch[j] = roots[j] * scratch[(j + 1)..].into_iter().take(n - 1 - i - j).sum::(); - temp += scratch[j]; - } - res[n - 2 - i] = temp * constant; - constant *= Fr::one().neg(); - } - - res - } - - fn compute_linear_polynomial_product_evaluation(roots: &[Fr], z: Fr, n: usize) -> Fr { - let mut expected = Fr::one(); - for i in 0..n { - expected *= z - roots[i]; - } - expected - } - - #[test] - fn linear_poly_product() { - let n = 64; - let mut roots = vec![Fr::zero(); n]; - let mut rng = test_rng(); - - let z = Fr::rand(&mut rng); - let mut expected = Fr::one(); - for i in 0..n { - roots[i] = Fr::rand(&mut rng); - expected *= z - roots[i]; - } - - let res = UniPoly::from_coeff(compute_linear_polynomial_product(&roots, n)).evaluate(&z); - assert_eq!(res, expected); - } - - #[test] - fn interpolate_poly() { - let n = 250; - let mut rng = test_rng(); - - let poly = - UniPoly::from_coeff((0..n).map(|_| Fr::rand(&mut rng)).collect::>()); - - let mut src = Vec::with_capacity(n); - let mut x = Vec::with_capacity(n); - - for _ in 0..n { - let val = Fr::rand(&mut rng); - x.push(val); - src.push(poly.evaluate(&val)); - } - let res = interpolate(&src, &x); - - for i in 0..poly.len() { - assert_eq!(res[i], poly[i]); - } - } - - #[test] - fn factor_roots() { - let n = 32; - let mut rng = test_rng(); - - let mut test_case = |num_zero_roots: usize, num_non_zero_roots: usize| { - let num_roots = num_non_zero_roots + num_zero_roots; - let mut poly = UniPoly::from_coeff((0..n).map(|_| Fr::rand(&mut rng)).collect::>()); - - let mut non_zero_roots: Vec = Vec::with_capacity(num_non_zero_roots); - let mut non_zero_evaluations: Vec = Vec::with_capacity(num_non_zero_roots); - - for _ in 0..num_non_zero_roots { - let root = Fr::rand(&mut rng); - non_zero_roots.push(root); - let root_pow = root.pow(BigInt::<1>::from(num_zero_roots as u64)); - non_zero_evaluations.push(poly.evaluate(&root) / root_pow); - } - let mut roots = (0..n).map(|_| Fr::zero()).collect::>(); - - for i in 0..num_non_zero_roots { - roots[num_zero_roots + i] = non_zero_roots[i]; - } - - if num_non_zero_roots > 0 { - //create poly that interpolates given evaluations - let interpolated = interpolate(&non_zero_roots, &non_zero_evaluations); - assert_eq!(interpolated.len(), num_non_zero_roots); - for (k, coeff) in interpolated.coeffs.iter().enumerate() { - poly.coeffs[num_non_zero_roots + k] -= coeff; - } - } - - // Sanity check that all roots are actually roots - for i in 0..num_roots { - assert_eq!(poly.evaluate(&roots[i]), Fr::zero()); - } - - let quotient = poly.factor_roots(&roots); - - // check that (t-r)q(t) == p(t) - let t = Fr::rand(&mut rng); - let roots_eval = compute_linear_polynomial_product_evaluation(&roots, t, num_roots); - let q_t = quotient.evaluate(&t); - let p_t = poly.evaluate(&t); - assert_eq!(roots_eval * q_t, p_t); - - for i in (n - num_roots)..n { - assert_eq!(quotient[i], Fr::zero()); - } - - if num_roots == 0 { - assert_eq!(poly, quotient); - } - - if num_roots == 1 { - let quotient_single = poly.factor_roots(&[roots[0]]); - assert_eq!(quotient_single, quotient); - } - }; - - test_case(1,0); - test_case(0,1); - test_case(1,0); - test_case(1,1); - test_case(2,0); - test_case(0,2); - test_case(3, 6); - } #[test] fn test_from_evals_quad() { diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index 168cb8c7b..359653307 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -1,15 +1,17 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] -use std::{borrow::Borrow, marker::PhantomData, ops::Neg}; +use std::{borrow::Borrow, marker::PhantomData, ops::Neg, iter}; use crate::poly::dense_mlpoly::DensePolynomial; use crate::poly::unipoly::UniPoly; use crate::subprotocols::traits::CommitmentScheme; use crate::utils::transcript::ProofTranscript; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; -use ark_ff::{BigInt, Field}; +use ark_ff::{BigInt, Field, batch_inversion}; use ark_std::{iterable::Iterable, One, Zero}; +use ark_poly_commit::{kzg10::{Proof, KZG10}, PolynomialCommitment}; +use itertools::Itertools; use merlin::Transcript; use thiserror::Error; @@ -27,6 +29,7 @@ use super::data_structures::{ }; // Just return vec of P::Scalar +// u_challenge = Point fn compute_multilinear_quotients( poly: &DensePolynomial, u_challenge: &[P::ScalarField], @@ -34,7 +37,7 @@ fn compute_multilinear_quotients( assert_eq!(poly.get_num_vars(), u_challenge.len()); let mut g = poly.Z.to_vec(); - let mut quotients = u_challenge + let mut quotients: Vec<_> = u_challenge .iter() .enumerate() .map(|(i, x_i)| { @@ -43,13 +46,12 @@ fn compute_multilinear_quotients( quotient .par_iter_mut() - .zip(&*g_lo) - .zip(&*g_hi) - .for_each(|((mut q, g_lo), g_hi)| { + .zip_eq(&*g_lo) + .zip_eq(&*g_hi) + .for_each(|((q, g_lo), g_hi)| { *q = *g_hi - *g_lo; }); - g_lo.par_iter_mut().zip(g_hi).for_each(|(g_lo, g_hi)| { - // WHAT IS THIS BLACK MAGIC &_ + g_lo.par_iter_mut().zip_eq(g_hi).for_each(|(g_lo, g_hi)| { *g_lo += (*g_hi - g_lo as &_) * x_i; }); @@ -57,7 +59,7 @@ fn compute_multilinear_quotients( UniPoly::from_coeff(quotient) }) - .collect::>>(); + .collect(); quotients.reverse(); (quotients, g[0]) } @@ -69,6 +71,8 @@ fn compute_batched_lifted_degree_quotient( // Batched Lifted Degreee Quotient Polynomials let mut res: Vec = vec![P::ScalarField::zero(); N]; + //TODO: separate and scan for y_powers + // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k let mut scalar = P::ScalarField::one(); // y^k for (k, quotient) in quotients.iter().enumerate() { @@ -85,6 +89,50 @@ fn compute_batched_lifted_degree_quotient( UniPoly::from_coeff(res) } +fn eval_and_quotient_scalars(y_challenge: P::ScalarField, x_challenge: P::ScalarField, z_challenge: P::ScalarField, challenges: &[P::ScalarField]) -> (P::ScalarField, (Vec, Vec)) { + let num_vars = challenges.len(); + + // squares of x = [x, x^2, .. x^{2^k}, .. x^{2^num_vars}] + let squares_of_x: Vec<_> = iter::successors(Some(x_challenge), |&x| Some(x.square())).take(num_vars + 1).collect(); + + // offsets of x = + let offsets_of_x = { + let mut offsets_of_x = squares_of_x.iter().rev().skip(1).scan(P::ScalarField::one(), |acc, pow_x| { + *acc *= pow_x; + Some(*acc) + }).collect::>(); + offsets_of_x.reverse(); + offsets_of_x + }; + + let vs = { + let v_numer = squares_of_x[num_vars] - P::ScalarField::one(); + // TODO: Batch Invert + // TODO: Switch to Result and Handle Error from Inversion + let mut v_denoms = squares_of_x.iter().map(|squares_of_x| *squares_of_x - P::ScalarField::one()).collect::>(); + batch_inversion(&mut v_denoms); + v_denoms.iter().map(|v_denom| v_numer * v_denom).collect::>() + }; + + // Note this assumes challenges come in big-endian form -> This is shared between the implementations x1, x2, x3, ... + let q_scalars = iter::successors(Some(P::ScalarField::one()), |acc| Some(*acc * y_challenge)) + .take(num_vars) + .zip_eq(offsets_of_x) + .zip(squares_of_x) + .zip(&vs) + .zip_eq(&vs[1..]) + .zip_eq(challenges.iter().rev()) + .map( + |(((((power_of_y, offset_of_x), square_of_x), v_i), v_j), u_i)| { + (-(power_of_y * offset_of_x), -(z_challenge * (square_of_x * v_j - *u_i * v_i))) + } + ) + .unzip(); + + // -vs[0] * z = -z * (x^(2^num_vars) - 1) / (x - 1) = -z ฮฆ_n(x) + (-vs[0] * z_challenge, q_scalars) +} + fn compute_partially_evaluated_degree_check_polynomial( batched_quotient: &UniPoly, quotients: &Vec>, @@ -171,6 +219,7 @@ fn compute_partially_evaluated_zeromorph_identity_polynomial( zeta_x: UniPoly, z_x: UniPoly, @@ -199,6 +248,7 @@ fn compute_batched_evaluation_and_degree_check_quotient( q_hat_com: &P::G1, @@ -277,10 +327,12 @@ fn compute_C_Z_x( ::msm(&commitments, &scalars).unwrap() } + + #[derive(Error, Debug)] pub enum ZeromorphError { #[error("oh no {0}")] - ShitIsFucked(String), + Invalid(String), } pub struct Zeromorph { @@ -349,7 +401,8 @@ impl CommitmentScheme for Zeromorph { batched_evaluation += rhos[i] * evals[i]; } - let f_polynomial = UniPoly::from_coeff(f_batched.clone()); + // confirm if these need to be interpolated or not + let mut pi_poly = UniPoly::from_coeff(f_batched.clone()); // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) let (quotients, _) = @@ -384,32 +437,33 @@ impl CommitmentScheme for Zeromorph { let z_challenge = >::challenge_scalar(transcript, b"ZM: z"); - // Compute degree check polynomials \zeta partially evaluated at x - let zeta_x = compute_partially_evaluated_degree_check_polynomial::( - &q_hat, - "ients, - &y_challenge, - &x_challenge, - ); + let (eval_scalar, (zeta_degree_check_q_scalars, z_zmpoly_q_scalars)) = eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, challenges); + // f = z * poly.Z + q_hat + (-z * ฮฆ_n(x) * e) + โˆ‘_k (q_scalars_k * q_k) + // TODO implement MulAssign and Mul + pi_poly += &z_challenge; + pi_poly += &q_hat; + pi_poly[0] += batched_evaluation * eval_scalar; + quotients.into_iter().zip_eq(zeta_degree_check_q_scalars).zip_eq(z_zmpoly_q_scalars).for_each(|((mut q, zeta_degree_check_q_scalar), z_zmpoly_q_scalar)| { + q *= &(zeta_degree_check_q_scalar + z_zmpoly_q_scalar); + pi_poly += &q; + }); - // Compute Zeromorph identity polynomial Z partially evaluated at x - let Z_x = compute_partially_evaluated_zeromorph_identity_polynomial::( - &f_polynomial, - "ients, - &batched_evaluation, - challenges, - &x_challenge, - ); + debug_assert_eq!(pi_poly.evaluate(&x_challenge), P::ScalarField::zero()); - // Compute batched degree-check and ZM-identity quotient polynomial pi - let pi_poly = compute_batched_evaluation_and_degree_check_quotient::( - zeta_x, - Z_x, - x_challenge, - z_challenge, - ); + //TODO: add msm or use arkworks + // Use arkworks polycommit + + // Compute the KZG opening proof pi_poly; -> TODO Should it really be a proof + let (pi, eval) = { + let div = UniPoly::from_coeff(vec![-x_challenge, P::ScalarField::one()]); + // TODO: make it result + let witness_poly = pi_poly.divide_with_q_and_r(&div).map(|(q, _)|q).ok_or(ZeromorphError::Invalid("()".to_string())).unwrap(); + let pi = ::msm(&pk.g1_powers[..witness_poly.len()], &witness_poly.coeffs).unwrap(); + // this is zero so whatever + let eval = pi_poly.evaluate(&x_challenge); + (pi, eval) + }; - let pi = ::msm(&pk.g1_powers, &pi_poly.coeffs).unwrap(); transcript.append_point(b"ZM: C_pi", &pi); Ok(ZeromorphProof { @@ -436,6 +490,9 @@ impl CommitmentScheme for Zeromorph { let pi = pi.into_group(); let q_k_com = q_k_com; let q_hat_com = q_hat_com.into_group(); + + //Receive q_k commitments + q_k_com.iter().for_each(|c| transcript.append_point(b"ZM: C_q_hat", &c.into_group())); // Compute powers of batching challenge rho let rho = >::challenge_scalar(transcript, b"ZM: rho"); @@ -458,6 +515,7 @@ impl CommitmentScheme for Zeromorph { >::challenge_scalar(transcript, b"ZM: y"); // Receive commitment C_{q} -> Since our transcript does not support appending and receiving data we instead store these commitments in a zeromorph proof struct + transcript.append_point(b"ZM: C_q_hat", &q_hat_com); // Challenge x, z let x_challenge = @@ -488,7 +546,8 @@ impl CommitmentScheme for Zeromorph { #[cfg(test)] mod test { - use super::*; + +use super::*; use crate::{utils::math::Math, subprotocols::zeromorph::data_structures::ZEROMORPH_SRS}; use ark_bn254::{Bn254, Fr}; use ark_ff::{BigInt, Zero}; @@ -554,14 +613,14 @@ mod test { #[test] fn quotient_construction() { // Define size params - const N: u64 = 16u64; - let log_N = (N as usize).log_2(); + let num_vars = 4; + let n: u64 = 1 << num_vars; // Construct a random multilinear polynomial f, and (u,v) such that f(u) = v let mut rng = test_rng(); let multilinear_f = - DensePolynomial::new((0..N).map(|_| Fr::rand(&mut rng)).collect::>()); - let u_challenge = (0..log_N) + DensePolynomial::new((0..n).map(|_| Fr::rand(&mut rng)).collect::>()); + let u_challenge = (0..num_vars) .into_iter() .map(|_| Fr::rand(&mut rng)) .collect::>(); @@ -579,13 +638,13 @@ mod test { //To demonstrate that q_k was properly constructd we show that the identity holds at a random multilinear challenge // i.e. ๐‘“(๐‘ง) โˆ’ ๐‘ฃ โˆ’ โˆ‘โ‚–โ‚Œโ‚€แตˆโปยน (๐‘งโ‚– โˆ’ ๐‘ขโ‚–)๐‘žโ‚–(๐‘ง) = 0 - let z_challenge = (0..log_N).map(|_| Fr::rand(&mut rng)).collect::>(); + let z_challenge = (0..num_vars).map(|_| Fr::rand(&mut rng)).collect::>(); let mut res = multilinear_f.evaluate(&z_challenge); res -= v_evaluation; for (k, q_k_uni) in quotients.iter().enumerate() { - let z_partial = &z_challenge[z_challenge.len() - k..]; + let z_partial = &z_challenge[&z_challenge.len() - k..]; //This is a weird consequence of how things are done.. the univariate polys are of the multilinear commitment in lagrange basis. Therefore we evaluate as multilinear let q_k = DensePolynomial::new(q_k_uni.coeffs.clone()); let q_k_eval = q_k.evaluate(z_partial); @@ -687,66 +746,26 @@ mod test { /// ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N #[test] fn partially_evaluated_quotient_zeta() { - const N: usize = 8; - // Define mock qโ‚– with deg(qโ‚–) = 2แตโปยน - let data_0 = vec![Fr::one()]; - let data_1 = vec![Fr::from(2u64), Fr::from(3u64)]; - let data_2 = vec![ - Fr::from(4u64), - Fr::from(5u64), - Fr::from(6u64), - Fr::from(7u64), - ]; - let q_0 = UniPoly::from_coeff(data_0); - let q_1 = UniPoly::from_coeff(data_1); - let q_2 = UniPoly::from_coeff(data_2); - let quotients = vec![q_0.clone(), q_1.clone(), q_2.clone()]; + let num_vars = 3; + let n: u64 = 1 << num_vars; let mut rng = test_rng(); - let y_challenge = Fr::rand(&mut rng); - - //Compute batched quptient ฬ‚q - let batched_quotient = - compute_batched_lifted_degree_quotient::("ients, &y_challenge); - println!("batched_quotient.len() {:?}", batched_quotient.len()); - dbg!(quotients.clone()); - let x_challenge = Fr::rand(&mut rng); + let y_challenge = Fr::rand(&mut rng); - let zeta_x = compute_partially_evaluated_degree_check_polynomial::( - &batched_quotient, - "ients, - &y_challenge, - &x_challenge, - ); + let challenges: Vec<_> = (0..num_vars).map(|_| Fr::rand(&mut rng)).collect(); + let z_challenge = Fr::rand(&mut rng); - // Construct ๐œโ‚“ explicitly - let mut zeta_x_expected = UniPoly::from_coeff(vec![Fr::zero(); N as usize]); - - //TODO: implement add and add_scalad - for i in 0..zeta_x_expected.len() { - zeta_x_expected[i] += batched_quotient[i]; - } + let (_, (zeta_x_scalars, _)) = eval_and_quotient_scalars::(y_challenge, x_challenge, z_challenge, &challenges); + // To verify we manually compute zeta using the computed powers and expected // ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N - for i in 0..q_0.len() { - zeta_x_expected[i] += q_0[i] * -x_challenge.pow(BigInt::<1>::from((N - 0 - 1) as u64)); - } + assert_eq!(zeta_x_scalars[0], -x_challenge.pow(BigInt::<1>::from((n - 1) as u64))); - for i in 0..q_1.len() { - zeta_x_expected[i] += - q_1[i] * (-y_challenge * x_challenge.pow(BigInt::<1>::from((N - 1 - 1) as u64))); - } + assert_eq!(zeta_x_scalars[1], -y_challenge * x_challenge.pow(BigInt::<1>::from((n - 1 - 1) as u64))); - for i in 0..q_2.len() { - zeta_x_expected[i] += q_2[i] - * (-y_challenge * y_challenge * x_challenge.pow(BigInt::<1>::from((N - 3 - 1) as u64))); - } - - for i in 0..zeta_x.len() { - assert_eq!(zeta_x[i], zeta_x_expected[i]); - } + assert_eq!(zeta_x_scalars[2], -y_challenge * y_challenge * x_challenge.pow(BigInt::<1>::from((n - 3 - 1) as u64))); } /// Test efficiently computing ๐›ทโ‚–(x) = โˆ‘แตขโ‚Œโ‚€แตโปยนxโฑ @@ -792,79 +811,38 @@ mod test { /// ๐‘โ‚“ = ฬ‚๐‘“ โˆ’ ๐‘ฃ โˆ‘โ‚–โ‚Œโ‚€โฟโปยน(๐‘ฅยฒ^แต๐›ทโ‚™โ‚‹โ‚–โ‚‹โ‚(๐‘ฅแตโบยน)โˆ’ ๐‘ขโ‚–๐›ทโ‚™โ‚‹โ‚–(๐‘ฅยฒ^แต)) ฬ‚qโ‚– #[test] fn partially_evaluated_quotient_z_x() { - const N: usize = 8; - let log_N = (N as usize).log_2(); + let num_vars = 3; // Construct a random multilinear polynomial f, and (u,v) such that f(u) = v. let mut rng = test_rng(); - let multilinear_f = (0..N) - .into_iter() - .map(|_| Fr::rand(&mut rng)) - .collect::>(); - let u_challenge = (0..log_N) + let challenges: Vec<_> = (0..num_vars) .into_iter() .map(|_| Fr::rand(&mut rng)) - .collect::>(); - let v_evaluation = DensePolynomial::new(multilinear_f.clone()).evaluate(&u_challenge); - - // compute batched polynomial and evaluation - let f_batched = UniPoly::from_coeff(multilinear_f); - - let v_batched = v_evaluation; + .collect(); - // Define some mock q_k with deeg(q_k) = 2^k - 1 - let q_0 = UniPoly::from_coeff( - (0..(1 << 0)) - .into_iter() - .map(|_| Fr::rand(&mut rng)) - .collect::>(), - ); - let q_1 = UniPoly::from_coeff( - (0..(1 << 1)) - .into_iter() - .map(|_| Fr::rand(&mut rng)) - .collect::>(), - ); - let q_2 = UniPoly::from_coeff( - (0..(1 << 2)) - .into_iter() - .map(|_| Fr::rand(&mut rng)) - .collect::>(), - ); - let quotients = vec![q_0.clone(), q_1.clone(), q_2.clone()]; + let u_rev = { + let mut res = challenges.clone(); + res.reverse(); + res + }; let x_challenge = Fr::rand(&mut rng); + let y_challenge = Fr::rand(&mut rng); + let z_challenge = Fr::rand(&mut rng); - // Construct Z_x using the prover method - let Z_x = compute_partially_evaluated_zeromorph_identity_polynomial::( - &f_batched, - "ients, - &v_evaluation, - &u_challenge, - &x_challenge, - ); - - // Compute Z_x directly - let mut Z_x_expected = f_batched; - - Z_x_expected[0] = - Z_x_expected[0] - v_batched * x_challenge * &phi::(&x_challenge, log_N); + // Construct Z_x scalars + let (_, (_, z_x_scalars)) = + eval_and_quotient_scalars::(y_challenge, x_challenge, z_challenge, &challenges); - for k in 0..log_N { + for k in 0..num_vars { let x_pow_2k = x_challenge.pow(BigInt::<1>::from((1 << k) as u64)); // x^{2^k} let x_pow_2kp1 = x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)); // x^{2^{k+1}} // x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k}) - let mut scalar = x_pow_2k * &phi::(&x_pow_2kp1, log_N - k - 1) - - u_challenge[k] * &phi::(&x_pow_2k, log_N - k); - scalar *= x_challenge; + let mut scalar = x_pow_2k * &phi::(&x_pow_2kp1, num_vars - k - 1) + - u_rev[k] * &phi::(&x_pow_2k, num_vars - k); + scalar *= z_challenge; scalar *= Fr::from(-1); - for i in 0..quotients[k].len() { - Z_x_expected[i] += quotients[k][i] * scalar; - } - } - - for i in 0..Z_x.len() { - assert_eq!(Z_x[i], Z_x_expected[i]); + assert_eq!(z_x_scalars[k], scalar); } }