From b8c331794461e281ffe2e5efe36061c9cc289a37 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Mon, 30 Oct 2023 17:28:12 -0500 Subject: [PATCH 01/21] layout and start zeromorph --- src/poly/mod.rs | 2 +- src/subprotocols/kzg.rs | 0 src/subprotocols/mod.rs | 2 + src/subprotocols/zeromorph.rs | 202 ++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 src/subprotocols/kzg.rs create mode 100644 src/subprotocols/zeromorph.rs diff --git a/src/poly/mod.rs b/src/poly/mod.rs index aa120d5e6..e1ca67a91 100644 --- a/src/poly/mod.rs +++ b/src/poly/mod.rs @@ -2,4 +2,4 @@ pub mod commitments; pub mod dense_mlpoly; pub mod eq_poly; pub mod identity_poly; -pub mod unipoly; +pub mod unipoly; \ No newline at end of file diff --git a/src/subprotocols/kzg.rs b/src/subprotocols/kzg.rs new file mode 100644 index 000000000..e69de29bb diff --git a/src/subprotocols/mod.rs b/src/subprotocols/mod.rs index 55fd040a6..9a1ef7d8e 100644 --- a/src/subprotocols/mod.rs +++ b/src/subprotocols/mod.rs @@ -6,3 +6,5 @@ mod zk; pub mod dot_product; pub mod grand_product; pub mod sumcheck; +pub mod zeromorph; +pub mod kzg; \ No newline at end of file diff --git a/src/subprotocols/zeromorph.rs b/src/subprotocols/zeromorph.rs new file mode 100644 index 000000000..2a0866226 --- /dev/null +++ b/src/subprotocols/zeromorph.rs @@ -0,0 +1,202 @@ +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] + +use std::marker::PhantomData; +use std::ops::Index; + +use crate::poly::commitments::MultiCommitGens; +use crate::poly::dense_mlpoly::DensePolynomial; +use crate::poly::unipoly::{CompressedUniPoly, UniPoly}; +use crate::subprotocols::dot_product::DotProductProof; +use crate::utils::errors::ProofVerifyError; +use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; +use ark_ec::short_weierstrass::{SWCurveConfig, Affine}; +use ark_ec::{CurveConfig, Group, CurveGroup}; +use ark_ff::{PrimeField, Field, BigInteger}; +use ark_serialize::*; +use ark_std::One; +use merlin::Transcript; + +#[cfg(feature = "ark-msm")] +use ark_ec::VariableBaseMSM; + +#[cfg(not(feature = "ark-msm"))] +use crate::msm::VariableBaseMSM; + +#[cfg(feature = "multicore")] +use rayon::prelude::*; + +pub struct Zeromorph { + _phantom: PhantomData, +} + +/// Compute the powers of a challenge +/// +impl Zeromorph { + fn powers_of_challenge(challenge: C::ScalarField, num_powers: usize) -> Vec { + (2..num_powers).fold(vec![C::ScalarField::one(), challenge], |mut acc, i| { + acc.push(acc[i - 1] * challenge); + acc + }) + } + + + fn compute_multilinear_quotients(polynimial: DensePolynomial, u_challenge: &[C::ScalarField] ) -> Vec> { + let log_N = polynimial.get_num_vars(); + + // The size of the multilinear challenge must equal the log of the polynomial size + assert!(log_N == u_challenge.len()); + + // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 + let mut quotients = Vec::with_capacity(log_N); + + // Compute the coefficients of q_{n - 1} + let mut size_q = (1 << (log_N - 1)) as usize; + let q = DensePolynomial::new( + (0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + }) + ); + + //Probs can't avoid this clone + quotients[log_N - 1] = q.clone(); + + let mut f_k = Vec::with_capacity(size_q); + + //We can probably clean this up some but for now we're being explicit + let mut g = polynimial.clone(); + + for k in 1..log_N { + // Compute f_k + for l in 0..size_q { + f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; + } + + size_q = size_q / 2; + let q = DensePolynomial::new( + (0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + }) + ); + + quotients[log_N - k - 1] = q; + + //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. + g = DensePolynomial::new(f_k.clone()); + }; + + quotients + } + + fn compute_batched_lifted_degree_quotient(quotients: Vec>, y_challenge: C::ScalarField) -> DensePolynomial { + // Batched Lifted Degreee Quotient Polynomials + let mut res: Vec = Vec::with_capacity(N); + + // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k + let mut scalar = C::ScalarField::one(); + for (k, quotient) in quotients.iter().enumerate() { + // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - + // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 + let deg_k = (1 << k) as usize - 1; + let offset = N - deg_k - 1; + for i in 0..(deg_k + 1) { + res[offset + i] += scalar * quotient[i]; + } + scalar *= y_challenge; // update batching scalar y^k + } + + DensePolynomial::new(res) + } + + fn compute_partially_evaluated_degree_check_polynomial( + batched_quotient: DensePolynomial, + quotients: Vec>, + y_challenge: C::ScalarField, + x_challenge: C::ScalarField + ) -> DensePolynomial { + todo!() + } + + fn compute_partially_evaluated_zeromorph_identity_polynomial( + f_batched: DensePolynomial, + g_batched: DensePolynomial, + quotients: Vec>, + v_evaluation: C::ScalarField, + u_challenge: &[C::ScalarField], + x_evaluation: C::ScalarField, + ) -> DensePolynomial { + todo!() + } + + fn compute_batched_evaluation_and_degree_check_quotient( + zeta_x: DensePolynomial, + z_x: DensePolynomial, + x_challenge: C::ScalarField, + z_challenge: C::ScalarField, + ) -> DensePolynomial { + todo!() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::utils::math::Math; + use crate::utils::test::TestTranscript; + use ark_curve25519::{EdwardsProjective as G1Projective, Fr}; + use ark_ff::{Zero, BigInt}; + + // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula + fn phi(challenge: C::BaseField, subscript: usize) -> C::BaseField { + let len = (1 << subscript) as u64; + let res = C::BaseField::zero(); + (0..len).into_iter().fold(C::BaseField::zero(),|mut acc, i| { + //Note this is ridiculous DevX + acc += challenge.pow(BigInt::<1>::from(i)); + acc + }); + res + } + + #[test] + fn quotient_construction() { + todo!() + } + + #[test] + fn batched_lifted_degree_quotient() { + todo!() + } + + #[test] + fn partially_evaluated_quotient_zeta() { + todo!() + } + + #[test] + fn phi_evaluation() { + todo!() + } + + #[test] + fn partially_evaluated_quotient_z_x() { + todo!() + } + + #[test] + fn prove_verify_single() { + todo!() + } + + #[test] + fn prove_and_verify_batched_with_shifts() { + todo!() + } + + #[test] + fn test_commit_open_verify() { + todo!() + } +} \ No newline at end of file From f78959918c2c8b9a8f01cbe9ed20499d240e4fa4 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Mon, 30 Oct 2023 17:28:41 -0500 Subject: [PATCH 02/21] fmt --- src/lasso/memory_checking.rs | 17 +- src/lasso/surge.rs | 8 +- src/poly/mod.rs | 2 +- src/subprotocols/kzg.rs | 1 + src/subprotocols/mod.rs | 2 +- src/subprotocols/zeromorph.rs | 331 +++++++++++++++++----------------- 6 files changed, 188 insertions(+), 173 deletions(-) diff --git a/src/lasso/memory_checking.rs b/src/lasso/memory_checking.rs index 33f062344..9965d59bc 100644 --- a/src/lasso/memory_checking.rs +++ b/src/lasso/memory_checking.rs @@ -36,8 +36,12 @@ pub struct MemoryCheckingProof< proof_hash_layer: HashLayerProof, } -impl + Sync> - MemoryCheckingProof +impl< + G: CurveGroup, + const C: usize, + const M: usize, + S: SubtableStrategy + Sync, + > MemoryCheckingProof where [(); S::NUM_SUBTABLES]: Sized, [(); S::NUM_MEMORIES]: Sized, @@ -282,15 +286,18 @@ impl GrandProducts { #[cfg(not(feature = "multicore"))] let num_ops = 0..dim_i.len(); let grand_product_input_read = DensePolynomial::new( - num_ops.clone().map(|i| { + num_ops + .clone() + .map(|i| { // addr is given by dim_i, value is given by eval_table, and ts is given by read_ts hash_func(&dim_i[i], &eval_table[dim_i_usize[i]], &read_i[i]) }) - .collect::>() + .collect::>(), ); // write: s hash evaluation => log(s)-variate polynomial let grand_product_input_write = DensePolynomial::new( - num_ops.map(|i| { + num_ops + .map(|i| { // addr is given by dim_i, value is given by eval_table, and ts is given by write_ts = read_ts + 1 hash_func( &dim_i[i], diff --git a/src/lasso/surge.rs b/src/lasso/surge.rs index f0b0a4dac..fc0cfaa22 100644 --- a/src/lasso/surge.rs +++ b/src/lasso/surge.rs @@ -103,8 +103,12 @@ pub struct SparsePolynomialEvaluationProof< memory_check: MemoryCheckingProof, } -impl + Sync> - SparsePolynomialEvaluationProof +impl< + G: CurveGroup, + const C: usize, + const M: usize, + S: SubtableStrategy + Sync, + > SparsePolynomialEvaluationProof where [(); S::NUM_SUBTABLES]: Sized, [(); S::NUM_MEMORIES]: Sized, diff --git a/src/poly/mod.rs b/src/poly/mod.rs index e1ca67a91..aa120d5e6 100644 --- a/src/poly/mod.rs +++ b/src/poly/mod.rs @@ -2,4 +2,4 @@ pub mod commitments; pub mod dense_mlpoly; pub mod eq_poly; pub mod identity_poly; -pub mod unipoly; \ No newline at end of file +pub mod unipoly; diff --git a/src/subprotocols/kzg.rs b/src/subprotocols/kzg.rs index e69de29bb..8b1378917 100644 --- a/src/subprotocols/kzg.rs +++ b/src/subprotocols/kzg.rs @@ -0,0 +1 @@ + diff --git a/src/subprotocols/mod.rs b/src/subprotocols/mod.rs index 9a1ef7d8e..242488af1 100644 --- a/src/subprotocols/mod.rs +++ b/src/subprotocols/mod.rs @@ -5,6 +5,6 @@ mod zk; pub mod dot_product; pub mod grand_product; +pub mod kzg; pub mod sumcheck; pub mod zeromorph; -pub mod kzg; \ No newline at end of file diff --git a/src/subprotocols/zeromorph.rs b/src/subprotocols/zeromorph.rs index 2a0866226..8d9b3f7ca 100644 --- a/src/subprotocols/zeromorph.rs +++ b/src/subprotocols/zeromorph.rs @@ -10,9 +10,9 @@ use crate::poly::unipoly::{CompressedUniPoly, UniPoly}; use crate::subprotocols::dot_product::DotProductProof; use crate::utils::errors::ProofVerifyError; use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; -use ark_ec::short_weierstrass::{SWCurveConfig, Affine}; -use ark_ec::{CurveConfig, Group, CurveGroup}; -use ark_ff::{PrimeField, Field, BigInteger}; +use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; +use ark_ec::{CurveConfig, CurveGroup, Group}; +use ark_ff::{BigInteger, Field, PrimeField}; use ark_serialize::*; use ark_std::One; use merlin::Transcript; @@ -27,176 +27,179 @@ use crate::msm::VariableBaseMSM; use rayon::prelude::*; pub struct Zeromorph { - _phantom: PhantomData, + _phantom: PhantomData, } /// Compute the powers of a challenge -/// +/// impl Zeromorph { - fn powers_of_challenge(challenge: C::ScalarField, num_powers: usize) -> Vec { - (2..num_powers).fold(vec![C::ScalarField::one(), challenge], |mut acc, i| { - acc.push(acc[i - 1] * challenge); - acc - }) + fn powers_of_challenge(challenge: C::ScalarField, num_powers: usize) -> Vec { + (2..num_powers).fold(vec![C::ScalarField::one(), challenge], |mut acc, i| { + acc.push(acc[i - 1] * challenge); + acc + }) + } + + fn compute_multilinear_quotients( + polynimial: DensePolynomial, + u_challenge: &[C::ScalarField], + ) -> Vec> { + let log_N = polynimial.get_num_vars(); + + // The size of the multilinear challenge must equal the log of the polynomial size + assert!(log_N == u_challenge.len()); + + // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 + let mut quotients = Vec::with_capacity(log_N); + + // Compute the coefficients of q_{n - 1} + let mut size_q = (1 << (log_N - 1)) as usize; + let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + })); + + //Probs can't avoid this clone + quotients[log_N - 1] = q.clone(); + + let mut f_k = Vec::with_capacity(size_q); + + //We can probably clean this up some but for now we're being explicit + let mut g = polynimial.clone(); + + for k in 1..log_N { + // Compute f_k + for l in 0..size_q { + f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; + } + + size_q = size_q / 2; + let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + })); + + quotients[log_N - k - 1] = q; + + //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. + g = DensePolynomial::new(f_k.clone()); } - - fn compute_multilinear_quotients(polynimial: DensePolynomial, u_challenge: &[C::ScalarField] ) -> Vec> { - let log_N = polynimial.get_num_vars(); - - // The size of the multilinear challenge must equal the log of the polynomial size - assert!(log_N == u_challenge.len()); - - // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 - let mut quotients = Vec::with_capacity(log_N); - - // Compute the coefficients of q_{n - 1} - let mut size_q = (1 << (log_N - 1)) as usize; - let q = DensePolynomial::new( - (0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - }) - ); - - //Probs can't avoid this clone - quotients[log_N - 1] = q.clone(); - - let mut f_k = Vec::with_capacity(size_q); - - //We can probably clean this up some but for now we're being explicit - let mut g = polynimial.clone(); - - for k in 1..log_N { - // Compute f_k - for l in 0..size_q { - f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; - } - - size_q = size_q / 2; - let q = DensePolynomial::new( - (0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - }) - ); - - quotients[log_N - k - 1] = q; - - //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. - g = DensePolynomial::new(f_k.clone()); - }; - - quotients - } - - fn compute_batched_lifted_degree_quotient(quotients: Vec>, y_challenge: C::ScalarField) -> DensePolynomial { - // Batched Lifted Degreee Quotient Polynomials - let mut res: Vec = Vec::with_capacity(N); - - // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k - let mut scalar = C::ScalarField::one(); - for (k, quotient) in quotients.iter().enumerate() { - // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - - // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 - let deg_k = (1 << k) as usize - 1; - let offset = N - deg_k - 1; - for i in 0..(deg_k + 1) { - res[offset + i] += scalar * quotient[i]; - } - scalar *= y_challenge; // update batching scalar y^k - } - - DensePolynomial::new(res) - } - - fn compute_partially_evaluated_degree_check_polynomial( - batched_quotient: DensePolynomial, - quotients: Vec>, - y_challenge: C::ScalarField, - x_challenge: C::ScalarField - ) -> DensePolynomial { - todo!() - } - - fn compute_partially_evaluated_zeromorph_identity_polynomial( - f_batched: DensePolynomial, - g_batched: DensePolynomial, - quotients: Vec>, - v_evaluation: C::ScalarField, - u_challenge: &[C::ScalarField], - x_evaluation: C::ScalarField, - ) -> DensePolynomial { - todo!() + quotients + } + + fn compute_batched_lifted_degree_quotient( + quotients: Vec>, + y_challenge: C::ScalarField, + ) -> DensePolynomial { + // Batched Lifted Degreee Quotient Polynomials + let mut res: Vec = Vec::with_capacity(N); + + // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k + let mut scalar = C::ScalarField::one(); + for (k, quotient) in quotients.iter().enumerate() { + // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - + // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 + let deg_k = (1 << k) as usize - 1; + let offset = N - deg_k - 1; + for i in 0..(deg_k + 1) { + res[offset + i] += scalar * quotient[i]; + } + scalar *= y_challenge; // update batching scalar y^k } - fn compute_batched_evaluation_and_degree_check_quotient( - zeta_x: DensePolynomial, - z_x: DensePolynomial, - x_challenge: C::ScalarField, - z_challenge: C::ScalarField, - ) -> DensePolynomial { - todo!() - } + DensePolynomial::new(res) + } + + fn compute_partially_evaluated_degree_check_polynomial( + batched_quotient: DensePolynomial, + quotients: Vec>, + y_challenge: C::ScalarField, + x_challenge: C::ScalarField, + ) -> DensePolynomial { + todo!() + } + + fn compute_partially_evaluated_zeromorph_identity_polynomial( + f_batched: DensePolynomial, + g_batched: DensePolynomial, + quotients: Vec>, + v_evaluation: C::ScalarField, + u_challenge: &[C::ScalarField], + x_evaluation: C::ScalarField, + ) -> DensePolynomial { + todo!() + } + + fn compute_batched_evaluation_and_degree_check_quotient( + zeta_x: DensePolynomial, + z_x: DensePolynomial, + x_challenge: C::ScalarField, + z_challenge: C::ScalarField, + ) -> DensePolynomial { + todo!() + } } #[cfg(test)] mod test { - use super::*; - use crate::utils::math::Math; - use crate::utils::test::TestTranscript; - use ark_curve25519::{EdwardsProjective as G1Projective, Fr}; - use ark_ff::{Zero, BigInt}; - - // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula - fn phi(challenge: C::BaseField, subscript: usize) -> C::BaseField { - let len = (1 << subscript) as u64; - let res = C::BaseField::zero(); - (0..len).into_iter().fold(C::BaseField::zero(),|mut acc, i| { - //Note this is ridiculous DevX - acc += challenge.pow(BigInt::<1>::from(i)); - acc - }); - res - } - - #[test] - fn quotient_construction() { - todo!() - } - - #[test] - fn batched_lifted_degree_quotient() { - todo!() - } - - #[test] - fn partially_evaluated_quotient_zeta() { - todo!() - } - - #[test] - fn phi_evaluation() { - todo!() - } - - #[test] - fn partially_evaluated_quotient_z_x() { - todo!() - } - - #[test] - fn prove_verify_single() { - todo!() - } - - #[test] - fn prove_and_verify_batched_with_shifts() { - todo!() - } - - #[test] - fn test_commit_open_verify() { - todo!() - } -} \ No newline at end of file + use super::*; + use crate::utils::math::Math; + use crate::utils::test::TestTranscript; + use ark_curve25519::{EdwardsProjective as G1Projective, Fr}; + use ark_ff::{BigInt, Zero}; + + // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula + fn phi(challenge: C::BaseField, subscript: usize) -> C::BaseField { + let len = (1 << subscript) as u64; + let res = C::BaseField::zero(); + (0..len) + .into_iter() + .fold(C::BaseField::zero(), |mut acc, i| { + //Note this is ridiculous DevX + acc += challenge.pow(BigInt::<1>::from(i)); + acc + }); + res + } + + #[test] + fn quotient_construction() { + todo!() + } + + #[test] + fn batched_lifted_degree_quotient() { + todo!() + } + + #[test] + fn partially_evaluated_quotient_zeta() { + todo!() + } + + #[test] + fn phi_evaluation() { + todo!() + } + + #[test] + fn partially_evaluated_quotient_z_x() { + todo!() + } + + #[test] + fn prove_verify_single() { + todo!() + } + + #[test] + fn prove_and_verify_batched_with_shifts() { + todo!() + } + + #[test] + fn test_commit_open_verify() { + todo!() + } +} From 3ec6bc93ffc4ae196f0b3ec00d96521e230789b5 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Wed, 8 Nov 2023 11:54:04 -0600 Subject: [PATCH 03/21] add bn_256, move to Pairing, add structs --- Cargo.toml | 1 + src/poly/dense_mlpoly.rs | 8 + src/subprotocols/zeromorph.rs | 291 +++++++++++++++++++++++----------- 3 files changed, 204 insertions(+), 96 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 70cb0e906..8cfe18b44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ criterion = { version = "0.3.1", features = ["html_reports"] } num-integer = "0.1.45" seq-macro = "0.3.3" ark-curve25519 = "0.4.0" +ark-bn254 = "0.4.0" tracing = "0.1.37" tracing-subscriber = "0.3.17" tracing-texray = "0.2.0" diff --git a/src/poly/dense_mlpoly.rs b/src/poly/dense_mlpoly.rs index b498f53b8..127b139e2 100644 --- a/src/poly/dense_mlpoly.rs +++ b/src/poly/dense_mlpoly.rs @@ -14,6 +14,7 @@ use ark_serialize::*; use ark_std::Zero; use core::ops::Index; use merlin::Transcript; +use std::ops::IndexMut; #[cfg(feature = "ark-msm")] use ark_ec::VariableBaseMSM; @@ -278,6 +279,13 @@ impl Index for DensePolynomial { } } +impl IndexMut for DensePolynomial { + #[inline(always)] + fn index_mut(&mut self, index: usize) -> &mut F { + &mut (self.Z[index]) + } +} + impl AppendToTranscript for PolyCommitment { fn append_to_transcript>(&self, label: &'static [u8], transcript: &mut T) { transcript.append_message(label, b"poly_commitment_begin"); diff --git a/src/subprotocols/zeromorph.rs b/src/subprotocols/zeromorph.rs index 8d9b3f7ca..6d6a420a3 100644 --- a/src/subprotocols/zeromorph.rs +++ b/src/subprotocols/zeromorph.rs @@ -11,8 +11,8 @@ use crate::subprotocols::dot_product::DotProductProof; use crate::utils::errors::ProofVerifyError; use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; -use ark_ec::{CurveConfig, CurveGroup, Group}; -use ark_ff::{BigInteger, Field, PrimeField}; +use ark_ec::{CurveConfig, CurveGroup, Group, pairing::Pairing}; +use ark_ff::{BigInteger, Field, PrimeField, BigInt}; use ark_serialize::*; use ark_std::One; use merlin::Transcript; @@ -26,119 +26,218 @@ use crate::msm::VariableBaseMSM; #[cfg(feature = "multicore")] use rayon::prelude::*; -pub struct Zeromorph { - _phantom: PhantomData, +pub struct Proof { + _phantom: PhantomData

+} + +pub struct CommitmentKey { + _phantom: PhantomData

+} + +pub struct Zeromorph { + _phantom: PhantomData

, } /// Compute the powers of a challenge /// -impl Zeromorph { - fn powers_of_challenge(challenge: C::ScalarField, num_powers: usize) -> Vec { - (2..num_powers).fold(vec![C::ScalarField::one(), challenge], |mut acc, i| { - acc.push(acc[i - 1] * challenge); - acc - }) - } +impl Zeromorph { + fn powers_of_challenge(challenge: P::ScalarField, num_powers: usize) -> Vec { + //TODO: switch to successors + (2..num_powers).fold(vec![P::ScalarField::one(), challenge], |mut acc, i| { + acc.push(acc[i - 1] * challenge); + acc + }) + } - fn compute_multilinear_quotients( - polynimial: DensePolynomial, - u_challenge: &[C::ScalarField], - ) -> Vec> { - let log_N = polynimial.get_num_vars(); + fn compute_multilinear_quotients( + polynimial: DensePolynomial, + u_challenge: &[P::ScalarField], + ) -> Vec> { + let log_N = polynimial.get_num_vars(); - // The size of the multilinear challenge must equal the log of the polynomial size - assert!(log_N == u_challenge.len()); + // The size of the multilinear challenge must equal the log of the polynomial size + assert!(log_N == u_challenge.len()); - // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 - let mut quotients = Vec::with_capacity(log_N); + // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 + let mut quotients = Vec::with_capacity(log_N); - // Compute the coefficients of q_{n - 1} - let mut size_q = (1 << (log_N - 1)) as usize; - let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - })); + // Compute the coefficients of q_{n - 1} + let mut size_q = (1 << (log_N - 1)) as usize; + let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + })); - //Probs can't avoid this clone - quotients[log_N - 1] = q.clone(); + //Probs can't avoid this clone + quotients[log_N - 1] = q.clone(); - let mut f_k = Vec::with_capacity(size_q); + let mut f_k: Vec = Vec::with_capacity(size_q); - //We can probably clean this up some but for now we're being explicit - let mut g = polynimial.clone(); + //We can probably clean this up some but for now we're being explicit + let mut g = Vec::new();//polynimial.clone(); - for k in 1..log_N { - // Compute f_k - for l in 0..size_q { - f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; - } + for k in 1..log_N { + // Compute f_k + for l in 0..size_q { + f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; + } - size_q = size_q / 2; - let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - })); + size_q = size_q / 2; + let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + })); + + quotients[log_N - k - 1] = q; - quotients[log_N - k - 1] = q; + //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. + g = f_k.clone(); + } - //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. - g = DensePolynomial::new(f_k.clone()); + quotients } - quotients - } + fn compute_batched_lifted_degree_quotient( + quotients: Vec>, + y_challenge: P::ScalarField, + ) -> DensePolynomial { + // Batched Lifted Degreee Quotient Polynomials + let mut res: Vec = Vec::with_capacity(N); + + // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k + let mut scalar = P::ScalarField::one(); + for (k, quotient) in quotients.iter().enumerate() { + // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - + // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 + //TODO: verify if this is needed as we are not interested in shifts + let deg_k = (1 << k) as usize - 1; + let offset = N - deg_k - 1; + for i in 0..(deg_k + 1) { + res[offset + i] += scalar * quotient[i]; + } + scalar *= y_challenge; // update batching scalar y^k + } + + DensePolynomial::new(res) + } - fn compute_batched_lifted_degree_quotient( - quotients: Vec>, - y_challenge: C::ScalarField, - ) -> DensePolynomial { - // Batched Lifted Degreee Quotient Polynomials - let mut res: Vec = Vec::with_capacity(N); - - // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k - let mut scalar = C::ScalarField::one(); - for (k, quotient) in quotients.iter().enumerate() { - // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - - // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 - let deg_k = (1 << k) as usize - 1; - let offset = N - deg_k - 1; - for i in 0..(deg_k + 1) { - res[offset + i] += scalar * quotient[i]; + fn compute_partially_evaluated_degree_check_polynomial( + batched_quotient: DensePolynomial, + quotients: Vec>, + y_challenge: P::ScalarField, + x_challenge: P::ScalarField, + ) -> DensePolynomial { + let n = batched_quotient.len(); + let log_N = quotients.len(); + + // initialize partially evaluated degree check polynomial \zeta_x to \hat{q} + let mut res = batched_quotient.clone(); + + let mut y_power = P::ScalarField::one(); + for k in 0..log_N { + // Accumulate y^k * x^{N - d_k - 1} * q_k into \hat{q} + let deg_k = (1 << k) as usize - 1; + let x_power = x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); + + // Add poly and scale -> Note this can be parallelized + // See -> https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/honk/pcs/zeromorph/zeromorph.hpp#L173 + // https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/polynomials/polynomial.cpp#L332 + // res += quotient[i] * (-y_power * x_power) + for i in 0..res.len() { + res[i] += quotients[k][i] * (-y_power * x_power); + } + + y_power *= y_challenge; // updated batching scalar y^k + } + + res + } + + fn compute_partially_evaluated_zeromorph_identity_polynomial( + f_batched: DensePolynomial, + g_batched: DensePolynomial, + quotients: Vec>, + v_evaluation: P::ScalarField, + u_challenge: &[P::ScalarField], + x_challenge: P::ScalarField, + ) -> DensePolynomial { + let n = f_batched.len(); + let log_N = quotients.len(); + + //Question for non-shifted can we exclude sum_{i=0}^{l-i} + // Initialize Z_x with x * \sum_{i=0}^{m-1} f_i + /sum_{i=0}^{l-i} * g_i + let mut res: DensePolynomial = g_batched.clone(); + + //add scaled + for i in 0..res.len() { + res[i] += f_batched[i] * x_challenge; } - scalar *= y_challenge; // update batching scalar y^k + + // Compute Z_x -= v * x * \Phi_n(x) + let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 + let phi_n_x = phi_numerator / (x_challenge - P::ScalarField::one()); + res[0] -= v_evaluation * x_challenge * phi_n_x; + + //Add contribution from q_k polynomials + for k in 0..log_N { + let x_power = x_challenge.pow(BigInt::<1>::from((1 << k) as u64)); // x^{2^k} + + // \Phi_{n-k-1}(x^{2^{k + 1}}) + let phi_term_1 = phi_numerator / (x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)) - P::ScalarField::one()); + + // \Phi_{n-k}(x^{2^k}) + let phi_term_2 = phi_numerator / (x_challenge.pow(BigInt::<1>::from((1 << k) as u64)) - P::ScalarField::one()); + + // x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k}) + let mut scalar = x_power * phi_term_1 - u_challenge[k] * phi_term_2; + + scalar *= x_challenge; + scalar *= -P::ScalarField::one(); + + for i in 0..res.len() { + res[i] += quotients[k][i] * scalar; + } + } + res } - DensePolynomial::new(res) - } + fn compute_batched_evaluation_and_degree_check_quotient( + zeta_x: DensePolynomial, + z_x: DensePolynomial, + x_challenge: P::ScalarField, + z_challenge: P::ScalarField, + ) -> DensePolynomial { + // We cannont commit to polynomials with size > N_max + let n = zeta_x.len(); + assert!(n <= N); + + // Compute q_{\zeta} and q_Z in place + let mut batched_quotient = zeta_x; + for i in 0..batched_quotient.len() { + batched_quotient[i] += z_x[i] * z_challenge; + } - fn compute_partially_evaluated_degree_check_polynomial( - batched_quotient: DensePolynomial, - quotients: Vec>, - y_challenge: C::ScalarField, - x_challenge: C::ScalarField, - ) -> DensePolynomial { - todo!() - } + //TODO: finish for batch and non-shifted quotient - fn compute_partially_evaluated_zeromorph_identity_polynomial( - f_batched: DensePolynomial, - g_batched: DensePolynomial, - quotients: Vec>, - v_evaluation: C::ScalarField, - u_challenge: &[C::ScalarField], - x_evaluation: C::ScalarField, - ) -> DensePolynomial { - todo!() - } + batched_quotient + } - fn compute_batched_evaluation_and_degree_check_quotient( - zeta_x: DensePolynomial, - z_x: DensePolynomial, - x_challenge: C::ScalarField, - z_challenge: C::ScalarField, - ) -> DensePolynomial { - todo!() - } + pub fn prove( + f_polynomials: Vec>, + evaluations: Vec, + multilinear_challenge: Vec, + commitment_key: CommitmentKey

, + transcript: Transcript, + ) -> Proof

{ + todo!() + } + + pub fn verify( + + ) { + todo!() + + } } #[cfg(test)] @@ -146,16 +245,16 @@ mod test { use super::*; use crate::utils::math::Math; use crate::utils::test::TestTranscript; - use ark_curve25519::{EdwardsProjective as G1Projective, Fr}; + use ark_bn254::Config; use ark_ff::{BigInt, Zero}; // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula - fn phi(challenge: C::BaseField, subscript: usize) -> C::BaseField { + fn phi(challenge: P::BaseField, subscript: usize) -> P::BaseField { let len = (1 << subscript) as u64; - let res = C::BaseField::zero(); + let res = P::BaseField::zero(); (0..len) .into_iter() - .fold(C::BaseField::zero(), |mut acc, i| { + .fold(P::BaseField::zero(), |mut acc, i| { //Note this is ridiculous DevX acc += challenge.pow(BigInt::<1>::from(i)); acc @@ -194,7 +293,7 @@ mod test { } #[test] - fn prove_and_verify_batched_with_shifts() { + fn prove_and_verify_batched() { todo!() } From baf70e04246caef34e31595ed93f861b37ca5633 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Wed, 8 Nov 2023 20:06:57 -0600 Subject: [PATCH 04/21] add tests --- src/poly/unipoly.rs | 22 +++ src/subprotocols/zeromorph.rs | 320 +++++++++++++++++++++++++++++----- 2 files changed, 298 insertions(+), 44 deletions(-) diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index 223d54a38..f5797578a 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -1,5 +1,7 @@ #![allow(dead_code)] +use std::ops::{Index, IndexMut}; + use super::commitments::{Commitments, MultiCommitGens}; use crate::utils::gaussian_elimination::gaussian_elimination; use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; @@ -60,6 +62,10 @@ impl UniPoly { pub fn as_vec(&self) -> Vec { self.coeffs.clone() } + + pub fn len(&self) -> usize { + self.coeffs.len() + } pub fn eval_at_zero(&self) -> F { self.coeffs[0] @@ -109,6 +115,22 @@ 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"); diff --git a/src/subprotocols/zeromorph.rs b/src/subprotocols/zeromorph.rs index 6d6a420a3..3aa34b3aa 100644 --- a/src/subprotocols/zeromorph.rs +++ b/src/subprotocols/zeromorph.rs @@ -34,13 +34,20 @@ pub struct CommitmentKey { _phantom: PhantomData

} -pub struct Zeromorph { +pub struct Zeromorph { _phantom: PhantomData

, } + /// Compute the powers of a challenge /// -impl Zeromorph { +impl Zeromorph { + pub fn new() -> Self { + Self { + _phantom: PhantomData::

, + } + } + fn powers_of_challenge(challenge: P::ScalarField, num_powers: usize) -> Vec { //TODO: switch to successors (2..num_powers).fold(vec![P::ScalarField::one(), challenge], |mut acc, i| { @@ -49,8 +56,8 @@ impl Zeromorph { }) } - fn compute_multilinear_quotients( - polynimial: DensePolynomial, + pub fn compute_multilinear_quotients( + polynimial: &DensePolynomial, u_challenge: &[P::ScalarField], ) -> Vec> { let log_N = polynimial.get_num_vars(); @@ -63,6 +70,7 @@ impl Zeromorph { // Compute the coefficients of q_{n - 1} let mut size_q = (1 << (log_N - 1)) as usize; + //TODO: check if this is correct. Based on Barretenburg's mle I think it is??? let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { acc.push(polynimial[size_q + l] - polynimial[l]); acc @@ -97,12 +105,12 @@ impl Zeromorph { quotients } - fn compute_batched_lifted_degree_quotient( - quotients: Vec>, - y_challenge: P::ScalarField, - ) -> DensePolynomial { + pub fn compute_batched_lifted_degree_quotient( + quotients: &Vec>, + y_challenge: &P::ScalarField, + ) -> UniPoly { // Batched Lifted Degreee Quotient Polynomials - let mut res: Vec = Vec::with_capacity(N); + let mut res: Vec = Vec::with_capacity(N as usize); // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k let mut scalar = P::ScalarField::one(); @@ -111,22 +119,22 @@ impl Zeromorph { // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 //TODO: verify if this is needed as we are not interested in shifts let deg_k = (1 << k) as usize - 1; - let offset = N - deg_k - 1; + let offset = N as usize - deg_k - 1; for i in 0..(deg_k + 1) { res[offset + i] += scalar * quotient[i]; } scalar *= y_challenge; // update batching scalar y^k } - DensePolynomial::new(res) + UniPoly::from_coeff(res) } - fn compute_partially_evaluated_degree_check_polynomial( - batched_quotient: DensePolynomial, - quotients: Vec>, - y_challenge: P::ScalarField, - x_challenge: P::ScalarField, - ) -> DensePolynomial { + pub fn compute_partially_evaluated_degree_check_polynomial( + batched_quotient: &UniPoly, + quotients: &Vec>, + y_challenge: &P::ScalarField, + x_challenge: &P::ScalarField, + ) -> UniPoly { let n = batched_quotient.len(); let log_N = quotients.len(); @@ -153,20 +161,20 @@ impl Zeromorph { res } - fn compute_partially_evaluated_zeromorph_identity_polynomial( - f_batched: DensePolynomial, - g_batched: DensePolynomial, - quotients: Vec>, - v_evaluation: P::ScalarField, + pub fn compute_partially_evaluated_zeromorph_identity_polynomial( + f_batched: &UniPoly, + g_batched: &UniPoly, + quotients: &Vec>, + v_evaluation: &P::ScalarField, u_challenge: &[P::ScalarField], - x_challenge: P::ScalarField, - ) -> DensePolynomial { + x_challenge: &P::ScalarField, + ) -> UniPoly { let n = f_batched.len(); let log_N = quotients.len(); //Question for non-shifted can we exclude sum_{i=0}^{l-i} // Initialize Z_x with x * \sum_{i=0}^{m-1} f_i + /sum_{i=0}^{l-i} * g_i - let mut res: DensePolynomial = g_batched.clone(); + let mut res: UniPoly = g_batched.clone(); //add scaled for i in 0..res.len() { @@ -175,8 +183,8 @@ impl Zeromorph { // Compute Z_x -= v * x * \Phi_n(x) let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 - let phi_n_x = phi_numerator / (x_challenge - P::ScalarField::one()); - res[0] -= v_evaluation * x_challenge * phi_n_x; + let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); + res[0] -= *v_evaluation * *x_challenge * phi_n_x; //Add contribution from q_k polynomials for k in 0..log_N { @@ -201,15 +209,15 @@ impl Zeromorph { res } - fn compute_batched_evaluation_and_degree_check_quotient( - zeta_x: DensePolynomial, - z_x: DensePolynomial, + pub fn compute_batched_evaluation_and_degree_check_quotient( + zeta_x: UniPoly, + z_x: UniPoly, x_challenge: P::ScalarField, z_challenge: P::ScalarField, - ) -> DensePolynomial { + ) -> UniPoly { // We cannont commit to polynomials with size > N_max let n = zeta_x.len(); - assert!(n <= N); + assert!(n <= N as usize); // Compute q_{\zeta} and q_Z in place let mut batched_quotient = zeta_x; @@ -236,7 +244,6 @@ impl Zeromorph { ) { todo!() - } } @@ -245,16 +252,17 @@ mod test { use super::*; use crate::utils::math::Math; use crate::utils::test::TestTranscript; - use ark_bn254::Config; + use ark_bn254::{Bn254, Fr, Fq, G1Affine, G1Projective}; use ark_ff::{BigInt, Zero}; + use ark_std::{test_rng,UniformRand}; // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula - fn phi(challenge: P::BaseField, subscript: usize) -> P::BaseField { + fn phi(challenge: &P::ScalarField, subscript: usize) -> P::ScalarField { let len = (1 << subscript) as u64; - let res = P::BaseField::zero(); + let res = P::ScalarField::zero(); (0..len) .into_iter() - .fold(P::BaseField::zero(), |mut acc, i| { + .fold(P::ScalarField::zero(), |mut acc, i| { //Note this is ridiculous DevX acc += challenge.pow(BigInt::<1>::from(i)); acc @@ -262,29 +270,253 @@ mod test { res } + /// Test for computing qk given multilinear f + /// Given ๐‘“(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚™โ‚‹โ‚), and `(๐‘ข, ๐‘ฃ)` such that \f(\u) = \v, compute `qโ‚–(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚–โ‚‹โ‚)` + /// such that the following identity holds: + /// + /// `๐‘“(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚™โ‚‹โ‚) โˆ’ ๐‘ฃ = โˆ‘โ‚–โ‚Œโ‚€โฟโปยน (๐‘‹โ‚– โˆ’ ๐‘ขโ‚–) qโ‚–(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚–โ‚‹โ‚)` #[test] fn quotient_construction() { - todo!() + // Define size params + const N: u64 = 16u64; + let log_N = (N as usize).log_2(); + + // 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).into_iter().map(|_| Fr::rand(&mut rng)).collect::>()); + let u_challenge = (0..log_N).into_iter().map(|_| Fr::rand(&mut rng)).collect::>(); + let v_evaluation = multilinear_f.evaluate(&u_challenge); + + // Compute multilinear quotients `qโ‚–(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚–โ‚‹โ‚)` + let quotients = Zeromorph::::compute_multilinear_quotients(&multilinear_f, &u_challenge); + + //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).into_iter().map(|_| Fr::rand(&mut rng)).collect::>(); + + let mut res = multilinear_f.evaluate(&z_challenge); + res -= v_evaluation; + for k in 0..log_N { + let q_k_eval; + if k == 0 { + // ๐‘žโ‚€ = ๐‘Žโ‚€ is a constant polynomial so it's evaluation is simply its constant coefficient + q_k_eval = quotients[k][0]; + } else { + // Construct (๐‘ขโ‚€, ..., ๐‘ขโ‚–โ‚‹โ‚) + q_k_eval = quotients[k].evaluate(&z_challenge[..k]); + } + // res = res - (๐‘งโ‚– โˆ’ ๐‘ขโ‚–) * ๐‘žโ‚–(๐‘ขโ‚€, ..., ๐‘ขโ‚–โ‚‹โ‚) + res -= (z_challenge[k] - u_challenge[k]) * q_k_eval; + } + assert_eq!(res, Fr::zero()); } + /// Test for construction of batched lifted degree quotient: + /// ฬ‚q = โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, ๐‘‘โ‚– = deg(ฬ‚q), ๐‘š = ๐‘ #[test] fn batched_lifted_degree_quotient() { - todo!() - } + const N: u64 = 8u64; + + // 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 = DensePolynomial::new(data_0); + let q_1 = DensePolynomial::new(data_1); + let q_2 = DensePolynomial::new(data_2); + let quotients = vec![q_0, q_1, q_2]; + + let mut rng = test_rng(); + let y_challenge = Fr::rand(&mut rng); + + //Compute batched quptient ฬ‚q + let batched_quotient = Zeromorph::::compute_batched_lifted_degree_quotient("ients, &y_challenge); + + //Explicitly define q_k_lifted = X^{N-2^k} * q_k and compute the expected batched result + //Note: we've hard programmed in the size of these vectors not the best practice + let data_0_lifted = vec![Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::one()]; + let data_1_lifted = vec![Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::from(2u64), Fr::from(3u64)]; + let data_2_lifted = vec![Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::from(4u64), Fr::from(5u64), Fr::from(6u64), Fr::from(7u64)]; + let q_0_lifted = DensePolynomial::new(data_0_lifted); + let q_1_lifted = DensePolynomial::new(data_1_lifted); + let q_2_lifted = DensePolynomial::new(data_2_lifted); + + //Explicitly compute ฬ‚q i.e. RLC of lifted polys + let mut batched_quotient_expected = DensePolynomial::new(vec![Fr::zero(); N as usize]); + //TODO: implement add and add_scalad + for i in 0..batched_quotient_expected.len() { + batched_quotient_expected[i] += q_0_lifted[i]; + } + + for i in 0..batched_quotient_expected.len() { + batched_quotient_expected[i] += q_1_lifted[i] * y_challenge; + } + for i in 0..batched_quotient_expected.len() { + batched_quotient_expected[i] += q_2_lifted[i] * (y_challenge * y_challenge); + } + + for i in 0..batched_quotient.len() { + assert_eq!(batched_quotient[i], batched_quotient_expected[i]); + } + // Implement PartialEq in DensePolynomial + } + + /// evaluated quotient \zeta_x + /// + /// ๐œ = ๐‘“ โˆ’ โˆ‘โ‚–โ‚Œโ‚€โฟโปยน๐‘ฆแต๐‘ฅสทหขโปสทโบยน๐‘“โ‚– = ๐‘“ โˆ’ โˆ‘_{d โˆˆ {dโ‚€, ..., dโ‚™โ‚‹โ‚}} X^{d* - d + 1} โˆ’ โˆ‘{kโˆถ dโ‚–=d} yแต fโ‚– , where d* = lifted degree + /// + /// ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N #[test] fn partially_evaluated_quotient_zeta() { - todo!() + const N: u64 = 8u64; + + // 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 = DensePolynomial::new(data_0); + let q_1 = DensePolynomial::new(data_1); + let q_2 = DensePolynomial::new(data_2); + let quotients = vec![q_0.clone(), q_1.clone(), q_2.clone()]; + + let mut rng = test_rng(); + let y_challenge = Fr::rand(&mut rng); + + //Compute batched quptient ฬ‚q + let batched_quotient = Zeromorph::::compute_batched_lifted_degree_quotient("ients, &y_challenge); + + let x_challenge = Fr::rand(&mut rng); + + let zeta_x = Zeromorph::::compute_partially_evaluated_degree_check_polynomial(&batched_quotient, "ients, &y_challenge, &x_challenge); + + // 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]; + } + + // ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N + for i in 0..zeta_x_expected.len() { + zeta_x_expected[i] += q_0[i] * -x_challenge.pow(BigInt::<1>::from((N - 0 - 1) as u64)); + } + + for i in 0..zeta_x_expected.len() { + zeta_x_expected[i] += q_1[i] * (-y_challenge * x_challenge.pow(BigInt::<1>::from((N - 1 - 1) as u64))); + } + + for i in 0..zeta_x_expected.len() { + zeta_x_expected[i] += q_1[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]); + } + + } + + /// Test efficiently computing ๐›ทโ‚–(x) = โˆ‘แตขโ‚Œโ‚€แตโปยนxโฑ + /// ๐›ทโ‚–(๐‘ฅ) = โˆ‘แตขโ‚Œโ‚€แตโปยน๐‘ฅโฑ = (๐‘ฅยฒ^แต โˆ’ 1) / (๐‘ฅ โˆ’ 1) #[test] - fn phi_evaluation() { - todo!() + fn phi_n_x_evaluation() { + const N: u64 = 8u64; + let log_N = (N as usize).log_2(); + + // ๐›ทโ‚–(๐‘ฅ) + let mut rng = test_rng(); + let x_challenge = Fr::rand(&mut rng); + + let efficient = (x_challenge.pow(BigInt::<1>::from((1 << log_N) as u64)) - Fr::one()) / (x_challenge - Fr::one()); + let expected: Fr = phi::(&x_challenge, log_N); + assert_eq!(efficient, expected); } + /// Test efficiently computing ๐›ทโ‚–(x) = โˆ‘แตขโ‚Œโ‚€แตโปยนxโฑ + /// ๐›ทโ‚™โ‚‹โ‚–โ‚‹โ‚(๐‘ฅยฒ^แตโบยน) = (๐‘ฅยฒ^โฟ โˆ’ 1) / (๐‘ฅยฒ^แตโบยน โˆ’ 1) + #[test] + fn phi_n_k_1_x_evaluation() { + const N: u64 = 8u64; + let log_N = (N as usize).log_2(); + + // ๐›ทโ‚–(๐‘ฅ) + let mut rng = test_rng(); + let x_challenge = Fr::rand(&mut rng); + let k = 2; + + //๐‘ฅยฒ^แตโบยน + let x_pow = x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)); + + //(๐‘ฅยฒ^โฟ โˆ’ 1) / (๐‘ฅยฒ^แตโบยน โˆ’ 1) + let efficient = (x_challenge.pow(BigInt::<1>::from((1 << log_N) as u64)) - Fr::one()) / (x_pow - Fr::one()); + let expected: Fr = phi::(&x_challenge, log_N - k - 1); + assert_eq!(efficient, expected); + } + + /// Test construction of ๐‘โ‚“ + /// ๐‘โ‚“ = ฬ‚๐‘“ โˆ’ ๐‘ฃ โˆ‘โ‚–โ‚Œโ‚€โฟโปยน(๐‘ฅยฒ^แต๐›ทโ‚™โ‚‹โ‚–โ‚‹โ‚(๐‘ฅแตโบยน)โˆ’ ๐‘ขโ‚–๐›ทโ‚™โ‚‹โ‚–(๐‘ฅยฒ^แต)) ฬ‚qโ‚– #[test] fn partially_evaluated_quotient_z_x() { - todo!() + const N: u64 = 8u64; + let log_N = (N as usize).log_2(); + + // 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 mut multilinear_g = (0..N).into_iter().map(|_| Fr::rand(&mut rng)).collect::>(); + multilinear_g[0] = Fr::zero(); + let u_challenge = (0..log_N).into_iter().map(|_| Fr::rand(&mut rng)).collect::>(); + let v_evaluation = DensePolynomial::new(multilinear_f.clone()).evaluate(&u_challenge); + let w_evaluation = DensePolynomial::new(multilinear_g.clone()).evaluate(&u_challenge); // This says shifted??? mayhaps exclude -> first ask Kobi + + let rho = Fr::rand(&mut rng); + + // compute batched polynomial and evaluation + let f_batched = UniPoly::from_coeff(multilinear_f); + let mut g_batched = UniPoly::from_coeff(multilinear_g); + + for i in 0..g_batched.len() { + g_batched[i] = g_batched[i] * rho; + } + let v_batched = v_evaluation + rho * w_evaluation; + + // 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 x_challenge = Fr::rand(&mut rng); + + // Construct Z_x using the prover method + let Z_x = Zeromorph::::compute_partially_evaluated_zeromorph_identity_polynomial(&f_batched, &g_batched, "ients, &v_evaluation, &u_challenge, &x_challenge); + + // Compute Z_x directly + let mut Z_x_expected = g_batched; + for i in 0..Z_x_expected.len() { + Z_x_expected[i] += f_batched[i] * x_challenge; + } + + Z_x_expected[0] = Z_x_expected[0] - v_batched * x_challenge * &phi::(&x_challenge, log_N); + + for k in 0..log_N { + 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; + scalar *= Fr::from(-1); + for i in 0..Z_x_expected.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]); + } } #[test] From 5e6723a0df21d1ca0789e29d9e88ae173bd3b668 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Thu, 9 Nov 2023 00:01:01 -0600 Subject: [PATCH 05/21] fix failing --- src/subprotocols/zeromorph.rs | 70 +++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/subprotocols/zeromorph.rs b/src/subprotocols/zeromorph.rs index 3aa34b3aa..71e8518ed 100644 --- a/src/subprotocols/zeromorph.rs +++ b/src/subprotocols/zeromorph.rs @@ -9,12 +9,13 @@ use crate::poly::dense_mlpoly::DensePolynomial; use crate::poly::unipoly::{CompressedUniPoly, UniPoly}; use crate::subprotocols::dot_product::DotProductProof; use crate::utils::errors::ProofVerifyError; +use crate::utils::math::Math; use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; use ark_ec::{CurveConfig, CurveGroup, Group, pairing::Pairing}; use ark_ff::{BigInteger, Field, PrimeField, BigInt}; use ark_serialize::*; -use ark_std::One; +use ark_std::{One, Zero}; use merlin::Transcript; #[cfg(feature = "ark-msm")] @@ -60,49 +61,54 @@ impl Zeromorph { polynimial: &DensePolynomial, u_challenge: &[P::ScalarField], ) -> Vec> { - let log_N = polynimial.get_num_vars(); + // TODO: can grab from poly + let log_N = (N as usize).log_2(); - // The size of the multilinear challenge must equal the log of the polynomial size - assert!(log_N == u_challenge.len()); + // The size of the multilinear challenge must equal the log of the polynomial size + assert!(log_N == u_challenge.len()); - // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 - let mut quotients = Vec::with_capacity(log_N); + // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 + let mut quotients = (0..log_N).into_iter().map(|_| DensePolynomial::from_usize(&[0])).collect::>(); + println!("log_N {:?} N {:?} quotients {:?}", log_N, N, quotients.len()); - // Compute the coefficients of q_{n - 1} - let mut size_q = (1 << (log_N - 1)) as usize; - //TODO: check if this is correct. Based on Barretenburg's mle I think it is??? - let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - })); + // Compute the coefficients of q_{n - 1} + let mut size_q = (1 << (log_N - 1)) as usize; + //TODO: check if this is correct. Based on Barretenburg's mle I think it is??? + let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + })); - //Probs can't avoid this clone - quotients[log_N - 1] = q.clone(); + //Probs can't avoid this clone + quotients.insert(log_N - 1,q.clone()); - let mut f_k: Vec = Vec::with_capacity(size_q); + let mut f_k: Vec = vec![P::ScalarField::zero(); size_q]; - //We can probably clean this up some but for now we're being explicit - let mut g = Vec::new();//polynimial.clone(); + //We can probably clean this up some but for now we're being explicit + let mut g = (0..size_q).fold(Vec::new(), |mut acc, i| { + acc.push(polynimial[i]); + acc + }); - for k in 1..log_N { - // Compute f_k - for l in 0..size_q { + for k in 1..log_N { + // Compute f_k + for l in 0..size_q { f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; - } + } - size_q = size_q / 2; - let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - })); + size_q = size_q / 2; + let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + })); - quotients[log_N - k - 1] = q; + quotients[log_N - k - 1] = q; - //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. - g = f_k.clone(); - } + //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. + g = f_k.clone(); + } - quotients + quotients } pub fn compute_batched_lifted_degree_quotient( From 5943adaad84aa1b046163415893cd32d0213a1ae Mon Sep 17 00:00:00 2001 From: PatStiles Date: Thu, 9 Nov 2023 00:51:44 -0600 Subject: [PATCH 06/21] stub out verifier helper functions --- src/poly/unipoly.rs | 2 +- src/subprotocols/zeromorph.rs | 538 +++++++++++++++++++++------------- 2 files changed, 329 insertions(+), 211 deletions(-) diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index f5797578a..5c7ca3c72 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -62,7 +62,7 @@ impl UniPoly { pub fn as_vec(&self) -> Vec { self.coeffs.clone() } - + pub fn len(&self) -> usize { self.coeffs.len() } diff --git a/src/subprotocols/zeromorph.rs b/src/subprotocols/zeromorph.rs index 71e8518ed..d84681c5e 100644 --- a/src/subprotocols/zeromorph.rs +++ b/src/subprotocols/zeromorph.rs @@ -12,8 +12,8 @@ use crate::utils::errors::ProofVerifyError; use crate::utils::math::Math; use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; -use ark_ec::{CurveConfig, CurveGroup, Group, pairing::Pairing}; -use ark_ff::{BigInteger, Field, PrimeField, BigInt}; +use ark_ec::{pairing::Pairing, CurveConfig, CurveGroup, Group}; +use ark_ff::{BigInt, BigInteger, Field, PrimeField}; use ark_serialize::*; use ark_std::{One, Zero}; use merlin::Transcript; @@ -28,229 +28,257 @@ use crate::msm::VariableBaseMSM; use rayon::prelude::*; pub struct Proof { - _phantom: PhantomData

+ _phantom: PhantomData

, } pub struct CommitmentKey { - _phantom: PhantomData

+ _phantom: PhantomData

, } pub struct Zeromorph { _phantom: PhantomData

, } - /// Compute the powers of a challenge /// impl Zeromorph { - pub fn new() -> Self { - Self { - _phantom: PhantomData::

, - } + pub fn new() -> Self { + Self { + _phantom: PhantomData::

, } + } - fn powers_of_challenge(challenge: P::ScalarField, num_powers: usize) -> Vec { - //TODO: switch to successors - (2..num_powers).fold(vec![P::ScalarField::one(), challenge], |mut acc, i| { - acc.push(acc[i - 1] * challenge); - acc - }) - } + fn powers_of_challenge(challenge: P::ScalarField, num_powers: usize) -> Vec { + //TODO: switch to successors + (2..num_powers).fold(vec![P::ScalarField::one(), challenge], |mut acc, i| { + acc.push(acc[i - 1] * challenge); + acc + }) + } - pub fn compute_multilinear_quotients( + pub fn compute_multilinear_quotients( polynimial: &DensePolynomial, u_challenge: &[P::ScalarField], - ) -> Vec> { - // TODO: can grab from poly - let log_N = (N as usize).log_2(); + ) -> Vec> { + // TODO: can grab from poly + let log_N = (N as usize).log_2(); - // The size of the multilinear challenge must equal the log of the polynomial size - assert!(log_N == u_challenge.len()); + // The size of the multilinear challenge must equal the log of the polynomial size + assert!(log_N == u_challenge.len()); - // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 - let mut quotients = (0..log_N).into_iter().map(|_| DensePolynomial::from_usize(&[0])).collect::>(); - println!("log_N {:?} N {:?} quotients {:?}", log_N, N, quotients.len()); + // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 + let mut quotients = (0..log_N) + .into_iter() + .map(|_| DensePolynomial::from_usize(&[0])) + .collect::>(); + println!( + "log_N {:?} N {:?} quotients {:?}", + log_N, + N, + quotients.len() + ); + + // Compute the coefficients of q_{n - 1} + let mut size_q = (1 << (log_N - 1)) as usize; + //TODO: check if this is correct. Based on Barretenburg's mle I think it is??? + let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { + acc.push(polynimial[size_q + l] - polynimial[l]); + acc + })); + + //Probs can't avoid this clone + quotients.insert(log_N - 1, q.clone()); + + let mut f_k: Vec = vec![P::ScalarField::zero(); size_q]; + + //We can probably clean this up some but for now we're being explicit + let mut g = (0..size_q).fold(Vec::new(), |mut acc, i| { + acc.push(polynimial[i]); + acc + }); + + for k in 1..log_N { + // Compute f_k + for l in 0..size_q { + f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; + } - // Compute the coefficients of q_{n - 1} - let mut size_q = (1 << (log_N - 1)) as usize; - //TODO: check if this is correct. Based on Barretenburg's mle I think it is??? + size_q = size_q / 2; let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - })); - - //Probs can't avoid this clone - quotients.insert(log_N - 1,q.clone()); - - let mut f_k: Vec = vec![P::ScalarField::zero(); size_q]; - - //We can probably clean this up some but for now we're being explicit - let mut g = (0..size_q).fold(Vec::new(), |mut acc, i| { - acc.push(polynimial[i]); + acc.push(polynimial[size_q + l] - polynimial[l]); acc - }); - - for k in 1..log_N { - // Compute f_k - for l in 0..size_q { - f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; - } - - size_q = size_q / 2; - let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - })); + })); - quotients[log_N - k - 1] = q; + quotients[log_N - k - 1] = q; - //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. - g = f_k.clone(); - } - - quotients + //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. + g = f_k.clone(); } - pub fn compute_batched_lifted_degree_quotient( + quotients + } + + pub fn compute_batched_lifted_degree_quotient( quotients: &Vec>, y_challenge: &P::ScalarField, - ) -> UniPoly { - // Batched Lifted Degreee Quotient Polynomials - let mut res: Vec = Vec::with_capacity(N as usize); - - // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k - let mut scalar = P::ScalarField::one(); - for (k, quotient) in quotients.iter().enumerate() { - // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - - // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 - //TODO: verify if this is needed as we are not interested in shifts - let deg_k = (1 << k) as usize - 1; - let offset = N as usize - deg_k - 1; - for i in 0..(deg_k + 1) { - res[offset + i] += scalar * quotient[i]; - } - scalar *= y_challenge; // update batching scalar y^k - } - - UniPoly::from_coeff(res) + ) -> UniPoly { + // Batched Lifted Degreee Quotient Polynomials + let mut res: Vec = Vec::with_capacity(N as usize); + + // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k + let mut scalar = P::ScalarField::one(); + for (k, quotient) in quotients.iter().enumerate() { + // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - + // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 + //TODO: verify if this is needed as we are not interested in shifts + let deg_k = (1 << k) as usize - 1; + let offset = N as usize - deg_k - 1; + for i in 0..(deg_k + 1) { + res[offset + i] += scalar * quotient[i]; + } + scalar *= y_challenge; // update batching scalar y^k } - pub fn compute_partially_evaluated_degree_check_polynomial( + UniPoly::from_coeff(res) + } + + pub fn compute_partially_evaluated_degree_check_polynomial( batched_quotient: &UniPoly, quotients: &Vec>, y_challenge: &P::ScalarField, x_challenge: &P::ScalarField, - ) -> UniPoly { - let n = batched_quotient.len(); - let log_N = quotients.len(); - - // initialize partially evaluated degree check polynomial \zeta_x to \hat{q} - let mut res = batched_quotient.clone(); - - let mut y_power = P::ScalarField::one(); - for k in 0..log_N { - // Accumulate y^k * x^{N - d_k - 1} * q_k into \hat{q} - let deg_k = (1 << k) as usize - 1; - let x_power = x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); - - // Add poly and scale -> Note this can be parallelized - // See -> https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/honk/pcs/zeromorph/zeromorph.hpp#L173 - // https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/polynomials/polynomial.cpp#L332 - // res += quotient[i] * (-y_power * x_power) - for i in 0..res.len() { - res[i] += quotients[k][i] * (-y_power * x_power); - } - - y_power *= y_challenge; // updated batching scalar y^k - } - - res + ) -> UniPoly { + let n = batched_quotient.len(); + let log_N = quotients.len(); + + // initialize partially evaluated degree check polynomial \zeta_x to \hat{q} + let mut res = batched_quotient.clone(); + + let mut y_power = P::ScalarField::one(); + for k in 0..log_N { + // Accumulate y^k * x^{N - d_k - 1} * q_k into \hat{q} + let deg_k = (1 << k) as usize - 1; + let x_power = x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); + + // Add poly and scale -> Note this can be parallelized + // See -> https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/honk/pcs/zeromorph/zeromorph.hpp#L173 + // https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/polynomials/polynomial.cpp#L332 + // res += quotient[i] * (-y_power * x_power) + for i in 0..res.len() { + res[i] += quotients[k][i] * (-y_power * x_power); + } + + y_power *= y_challenge; // updated batching scalar y^k } - pub fn compute_partially_evaluated_zeromorph_identity_polynomial( + res + } + + pub fn compute_partially_evaluated_zeromorph_identity_polynomial( f_batched: &UniPoly, g_batched: &UniPoly, quotients: &Vec>, v_evaluation: &P::ScalarField, u_challenge: &[P::ScalarField], x_challenge: &P::ScalarField, - ) -> UniPoly { - let n = f_batched.len(); - let log_N = quotients.len(); + ) -> UniPoly { + let n = f_batched.len(); + let log_N = quotients.len(); - //Question for non-shifted can we exclude sum_{i=0}^{l-i} - // Initialize Z_x with x * \sum_{i=0}^{m-1} f_i + /sum_{i=0}^{l-i} * g_i - let mut res: UniPoly = g_batched.clone(); + //Question for non-shifted can we exclude sum_{i=0}^{l-i} + // Initialize Z_x with x * \sum_{i=0}^{m-1} f_i + /sum_{i=0}^{l-i} * g_i + let mut res: UniPoly = g_batched.clone(); - //add scaled - for i in 0..res.len() { - res[i] += f_batched[i] * x_challenge; - } + //add scaled + for i in 0..res.len() { + res[i] += f_batched[i] * x_challenge; + } - // Compute Z_x -= v * x * \Phi_n(x) - let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 - let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); - res[0] -= *v_evaluation * *x_challenge * phi_n_x; + // Compute Z_x -= v * x * \Phi_n(x) + let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 + let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); + res[0] -= *v_evaluation * *x_challenge * phi_n_x; - //Add contribution from q_k polynomials - for k in 0..log_N { - let x_power = x_challenge.pow(BigInt::<1>::from((1 << k) as u64)); // x^{2^k} + //Add contribution from q_k polynomials + for k in 0..log_N { + let x_power = x_challenge.pow(BigInt::<1>::from((1 << k) as u64)); // x^{2^k} - // \Phi_{n-k-1}(x^{2^{k + 1}}) - let phi_term_1 = phi_numerator / (x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)) - P::ScalarField::one()); + // \Phi_{n-k-1}(x^{2^{k + 1}}) + let phi_term_1 = phi_numerator + / (x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)) - P::ScalarField::one()); - // \Phi_{n-k}(x^{2^k}) - let phi_term_2 = phi_numerator / (x_challenge.pow(BigInt::<1>::from((1 << k) as u64)) - P::ScalarField::one()); + // \Phi_{n-k}(x^{2^k}) + let phi_term_2 = phi_numerator + / (x_challenge.pow(BigInt::<1>::from((1 << k) as u64)) - P::ScalarField::one()); - // x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k}) - let mut scalar = x_power * phi_term_1 - u_challenge[k] * phi_term_2; + // x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k}) + let mut scalar = x_power * phi_term_1 - u_challenge[k] * phi_term_2; - scalar *= x_challenge; - scalar *= -P::ScalarField::one(); + scalar *= x_challenge; + scalar *= -P::ScalarField::one(); - for i in 0..res.len() { - res[i] += quotients[k][i] * scalar; - } + for i in 0..res.len() { + res[i] += quotients[k][i] * scalar; } - res } + res + } - pub fn compute_batched_evaluation_and_degree_check_quotient( + pub fn compute_batched_evaluation_and_degree_check_quotient( zeta_x: UniPoly, z_x: UniPoly, x_challenge: P::ScalarField, z_challenge: P::ScalarField, - ) -> UniPoly { - // We cannont commit to polynomials with size > N_max - let n = zeta_x.len(); - assert!(n <= N as usize); - - // Compute q_{\zeta} and q_Z in place - let mut batched_quotient = zeta_x; - for i in 0..batched_quotient.len() { - batched_quotient[i] += z_x[i] * z_challenge; - } - - //TODO: finish for batch and non-shifted quotient + ) -> UniPoly { + // We cannont commit to polynomials with size > N_max + let n = zeta_x.len(); + assert!(n <= N as usize); - batched_quotient + // Compute q_{\zeta} and q_Z in place + let mut batched_quotient = zeta_x; + for i in 0..batched_quotient.len() { + batched_quotient[i] += z_x[i] * z_challenge; } - pub fn prove( - f_polynomials: Vec>, - evaluations: Vec, - multilinear_challenge: Vec, - commitment_key: CommitmentKey

, - transcript: Transcript, - ) -> Proof

{ - todo!() - } + //TODO: finish once srs gen is completed - pub fn verify( + batched_quotient + } - ) { - todo!() - } + pub fn prove( + f_polynomials: Vec>, + evaluations: Vec, + multilinear_challenge: Vec, + commitment_key: CommitmentKey

, + transcript: Transcript, + ) -> P::G1 { + todo!(); + } + + pub fn compute_C_zeta_x( + C_q: P::G1, + C_q_k: Vec, + y_challenge: &P::ScalarField, + x_challenge: &P::ScalarField, + ) -> P::G1 { + todo!() + } + + pub fn compute_C_z_X( + f_commitments: Vec, + g_commitments: Vec, + C_q_k: &Vec, + rho: &P::ScalarField, + batched_evaluation: &P::ScalarField, + x_challenge: &P::ScalarField, + u_challenge: &[P::ScalarField], + ) -> P::G1 { + todo!(); + } + + pub fn verify() { + todo!() + } } #[cfg(test)] @@ -258,9 +286,9 @@ mod test { use super::*; use crate::utils::math::Math; use crate::utils::test::TestTranscript; - use ark_bn254::{Bn254, Fr, Fq, G1Affine, G1Projective}; + use ark_bn254::{Bn254, Fq, Fr, G1Affine, G1Projective}; use ark_ff::{BigInt, Zero}; - use ark_std::{test_rng,UniformRand}; + use ark_std::{test_rng, UniformRand}; // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula fn phi(challenge: &P::ScalarField, subscript: usize) -> P::ScalarField { @@ -288,17 +316,29 @@ mod test { let log_N = (N as usize).log_2(); // 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).into_iter().map(|_| Fr::rand(&mut rng)).collect::>()); - let u_challenge = (0..log_N).into_iter().map(|_| Fr::rand(&mut rng)).collect::>(); + let mut rng = test_rng(); + let multilinear_f = DensePolynomial::new( + (0..N) + .into_iter() + .map(|_| Fr::rand(&mut rng)) + .collect::>(), + ); + let u_challenge = (0..log_N) + .into_iter() + .map(|_| Fr::rand(&mut rng)) + .collect::>(); let v_evaluation = multilinear_f.evaluate(&u_challenge); - + // Compute multilinear quotients `qโ‚–(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚–โ‚‹โ‚)` - let quotients = Zeromorph::::compute_multilinear_quotients(&multilinear_f, &u_challenge); - + let quotients = + Zeromorph::::compute_multilinear_quotients(&multilinear_f, &u_challenge); + //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).into_iter().map(|_| Fr::rand(&mut rng)).collect::>(); + let z_challenge = (0..log_N) + .into_iter() + .map(|_| Fr::rand(&mut rng)) + .collect::>(); let mut res = multilinear_f.evaluate(&z_challenge); res -= v_evaluation; @@ -324,25 +364,58 @@ mod test { const N: u64 = 8u64; // 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 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 = DensePolynomial::new(data_0); let q_1 = DensePolynomial::new(data_1); let q_2 = DensePolynomial::new(data_2); let quotients = vec![q_0, q_1, q_2]; - let mut rng = test_rng(); + let mut rng = test_rng(); let y_challenge = Fr::rand(&mut rng); //Compute batched quptient ฬ‚q - let batched_quotient = Zeromorph::::compute_batched_lifted_degree_quotient("ients, &y_challenge); + let batched_quotient = + Zeromorph::::compute_batched_lifted_degree_quotient("ients, &y_challenge); //Explicitly define q_k_lifted = X^{N-2^k} * q_k and compute the expected batched result //Note: we've hard programmed in the size of these vectors not the best practice - let data_0_lifted = vec![Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::one()]; - let data_1_lifted = vec![Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::from(2u64), Fr::from(3u64)]; - let data_2_lifted = vec![Fr::zero(), Fr::zero(), Fr::zero(), Fr::zero(), Fr::from(4u64), Fr::from(5u64), Fr::from(6u64), Fr::from(7u64)]; + let data_0_lifted = vec![ + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::one(), + ]; + let data_1_lifted = vec![ + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::from(2u64), + Fr::from(3u64), + ]; + let data_2_lifted = vec![ + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::zero(), + Fr::from(4u64), + Fr::from(5u64), + Fr::from(6u64), + Fr::from(7u64), + ]; let q_0_lifted = DensePolynomial::new(data_0_lifted); let q_1_lifted = DensePolynomial::new(data_1_lifted); let q_2_lifted = DensePolynomial::new(data_2_lifted); @@ -353,7 +426,7 @@ mod test { for i in 0..batched_quotient_expected.len() { batched_quotient_expected[i] += q_0_lifted[i]; } - + for i in 0..batched_quotient_expected.len() { batched_quotient_expected[i] += q_1_lifted[i] * y_challenge; } @@ -367,34 +440,45 @@ mod test { } // Implement PartialEq in DensePolynomial } - + /// evaluated quotient \zeta_x - /// + /// /// ๐œ = ๐‘“ โˆ’ โˆ‘โ‚–โ‚Œโ‚€โฟโปยน๐‘ฆแต๐‘ฅสทหขโปสทโบยน๐‘“โ‚– = ๐‘“ โˆ’ โˆ‘_{d โˆˆ {dโ‚€, ..., dโ‚™โ‚‹โ‚}} X^{d* - d + 1} โˆ’ โˆ‘{kโˆถ dโ‚–=d} yแต fโ‚– , where d* = lifted degree - /// + /// /// ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N #[test] fn partially_evaluated_quotient_zeta() { const N: u64 = 8u64; // 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 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 = DensePolynomial::new(data_0); let q_1 = DensePolynomial::new(data_1); let q_2 = DensePolynomial::new(data_2); let quotients = vec![q_0.clone(), q_1.clone(), q_2.clone()]; - let mut rng = test_rng(); + let mut rng = test_rng(); let y_challenge = Fr::rand(&mut rng); //Compute batched quptient ฬ‚q - let batched_quotient = Zeromorph::::compute_batched_lifted_degree_quotient("ients, &y_challenge); + let batched_quotient = + Zeromorph::::compute_batched_lifted_degree_quotient("ients, &y_challenge); let x_challenge = Fr::rand(&mut rng); - let zeta_x = Zeromorph::::compute_partially_evaluated_degree_check_polynomial(&batched_quotient, "ients, &y_challenge, &x_challenge); + let zeta_x = Zeromorph::::compute_partially_evaluated_degree_check_polynomial( + &batched_quotient, + "ients, + &y_challenge, + &x_challenge, + ); // Construct ๐œโ‚“ explicitly let mut zeta_x_expected = UniPoly::from_coeff(vec![Fr::zero(); N as usize]); @@ -403,28 +487,27 @@ mod test { for i in 0..zeta_x_expected.len() { zeta_x_expected[i] += batched_quotient[i]; } - + // ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N for i in 0..zeta_x_expected.len() { zeta_x_expected[i] += q_0[i] * -x_challenge.pow(BigInt::<1>::from((N - 0 - 1) as u64)); } for i in 0..zeta_x_expected.len() { - zeta_x_expected[i] += q_1[i] * (-y_challenge * x_challenge.pow(BigInt::<1>::from((N - 1 - 1) as u64))); + zeta_x_expected[i] += + q_1[i] * (-y_challenge * x_challenge.pow(BigInt::<1>::from((N - 1 - 1) as u64))); } for i in 0..zeta_x_expected.len() { - zeta_x_expected[i] += q_1[i] * (-y_challenge * y_challenge * x_challenge.pow(BigInt::<1>::from((N - 3 - 1) as u64))); + zeta_x_expected[i] += q_1[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]); } - - } - /// Test efficiently computing ๐›ทโ‚–(x) = โˆ‘แตขโ‚Œโ‚€แตโปยนxโฑ /// ๐›ทโ‚–(๐‘ฅ) = โˆ‘แตขโ‚Œโ‚€แตโปยน๐‘ฅโฑ = (๐‘ฅยฒ^แต โˆ’ 1) / (๐‘ฅ โˆ’ 1) #[test] @@ -436,7 +519,8 @@ mod test { let mut rng = test_rng(); let x_challenge = Fr::rand(&mut rng); - let efficient = (x_challenge.pow(BigInt::<1>::from((1 << log_N) as u64)) - Fr::one()) / (x_challenge - Fr::one()); + let efficient = (x_challenge.pow(BigInt::<1>::from((1 << log_N) as u64)) - Fr::one()) + / (x_challenge - Fr::one()); let expected: Fr = phi::(&x_challenge, log_N); assert_eq!(efficient, expected); } @@ -457,7 +541,8 @@ mod test { let x_pow = x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)); //(๐‘ฅยฒ^โฟ โˆ’ 1) / (๐‘ฅยฒ^แตโบยน โˆ’ 1) - let efficient = (x_challenge.pow(BigInt::<1>::from((1 << log_N) as u64)) - Fr::one()) / (x_pow - Fr::one()); + let efficient = + (x_challenge.pow(BigInt::<1>::from((1 << log_N) as u64)) - Fr::one()) / (x_pow - Fr::one()); let expected: Fr = phi::(&x_challenge, log_N - k - 1); assert_eq!(efficient, expected); } @@ -470,16 +555,25 @@ mod test { let log_N = (N as usize).log_2(); // 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 mut multilinear_g = (0..N).into_iter().map(|_| Fr::rand(&mut rng)).collect::>(); + let mut rng = test_rng(); + let multilinear_f = (0..N) + .into_iter() + .map(|_| Fr::rand(&mut rng)) + .collect::>(); + let mut multilinear_g = (0..N) + .into_iter() + .map(|_| Fr::rand(&mut rng)) + .collect::>(); multilinear_g[0] = Fr::zero(); - let u_challenge = (0..log_N).into_iter().map(|_| Fr::rand(&mut rng)).collect::>(); + let u_challenge = (0..log_N) + .into_iter() + .map(|_| Fr::rand(&mut rng)) + .collect::>(); let v_evaluation = DensePolynomial::new(multilinear_f.clone()).evaluate(&u_challenge); let w_evaluation = DensePolynomial::new(multilinear_g.clone()).evaluate(&u_challenge); // This says shifted??? mayhaps exclude -> first ask Kobi let rho = Fr::rand(&mut rng); - + // compute batched polynomial and evaluation let f_batched = UniPoly::from_coeff(multilinear_f); let mut g_batched = UniPoly::from_coeff(multilinear_g); @@ -490,15 +584,37 @@ mod test { let v_batched = v_evaluation + rho * w_evaluation; // 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 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 x_challenge = Fr::rand(&mut rng); // Construct Z_x using the prover method - let Z_x = Zeromorph::::compute_partially_evaluated_zeromorph_identity_polynomial(&f_batched, &g_batched, "ients, &v_evaluation, &u_challenge, &x_challenge); + let Z_x = Zeromorph::::compute_partially_evaluated_zeromorph_identity_polynomial( + &f_batched, + &g_batched, + "ients, + &v_evaluation, + &u_challenge, + &x_challenge, + ); // Compute Z_x directly let mut Z_x_expected = g_batched; @@ -506,13 +622,15 @@ mod test { Z_x_expected[i] += f_batched[i] * x_challenge; } - Z_x_expected[0] = Z_x_expected[0] - v_batched * x_challenge * &phi::(&x_challenge, log_N); + Z_x_expected[0] = + Z_x_expected[0] - v_batched * x_challenge * &phi::(&x_challenge, log_N); for k in 0..log_N { 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); + 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; scalar *= Fr::from(-1); for i in 0..Z_x_expected.len() { From 733ff3d722f8739750c7e148e074a3220ad36010 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Thu, 9 Nov 2023 00:54:53 -0600 Subject: [PATCH 07/21] remove proof struct --- src/subprotocols/zeromorph.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/subprotocols/zeromorph.rs b/src/subprotocols/zeromorph.rs index d84681c5e..69bc49907 100644 --- a/src/subprotocols/zeromorph.rs +++ b/src/subprotocols/zeromorph.rs @@ -27,10 +27,6 @@ use crate::msm::VariableBaseMSM; #[cfg(feature = "multicore")] use rayon::prelude::*; -pub struct Proof { - _phantom: PhantomData

, -} - pub struct CommitmentKey { _phantom: PhantomData

, } From ac6d2adc785e66651a146baafd90b586fbeec212 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Mon, 27 Nov 2023 12:34:16 -0600 Subject: [PATCH 08/21] everything but srs --- Cargo.toml | 1 + src/poly/dense_mlpoly.rs | 3 +- src/poly/unipoly.rs | 161 +++++- src/subprotocols/kzg.rs | 1 - src/subprotocols/kzg10.rs | 14 + src/subprotocols/mod.rs | 2 +- src/subprotocols/zeromorph/data_structures.rs | 106 ++++ src/subprotocols/zeromorph/mod.rs | 2 + src/subprotocols/{ => zeromorph}/zeromorph.rs | 538 ++++++++++++------ 9 files changed, 643 insertions(+), 185 deletions(-) delete mode 100644 src/subprotocols/kzg.rs create mode 100644 src/subprotocols/kzg10.rs create mode 100644 src/subprotocols/zeromorph/data_structures.rs create mode 100644 src/subprotocols/zeromorph/mod.rs rename src/subprotocols/{ => zeromorph}/zeromorph.rs (53%) diff --git a/Cargo.toml b/Cargo.toml index 8cfe18b44..e051f6cc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ tracing-subscriber = "0.3.17" tracing-texray = "0.2.0" clap = { version = "4.3.10", features = ["derive"] } hashbrown = "0.14.0" +lazy_static = "1.4.0" [dev-dependencies] criterion = "0.3.1" diff --git a/src/poly/dense_mlpoly.rs b/src/poly/dense_mlpoly.rs index 127b139e2..0c0bfa690 100644 --- a/src/poly/dense_mlpoly.rs +++ b/src/poly/dense_mlpoly.rs @@ -29,7 +29,8 @@ use rayon::prelude::*; pub struct DensePolynomial { num_vars: usize, // the number of variables in the multilinear polynomial len: usize, - Z: Vec, // evaluations of the polynomial in all the 2^num_vars Boolean inputs + //TODO make getter + pub Z: Vec, // evaluations of the polynomial in all the 2^num_vars Boolean inputs } pub struct PolyCommitmentGens { diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index 5c7ca3c72..68c58b0e5 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -13,7 +13,7 @@ use ark_serialize::*; // ax^3 + bx^2 + cx + d stored as vec![d,c,b,a] #[derive(Debug, Clone)] pub struct UniPoly { - coeffs: Vec, + pub coeffs: Vec, } // ax^2 + bx + c stored as vec![c,a] @@ -96,6 +96,26 @@ impl UniPoly { pub fn commit>(&self, gens: &MultiCommitGens, blind: &F) -> G { Commitments::batch_commit(&self.coeffs, blind, gens) } + + pub fn factor_roots(&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 } + } } impl CompressedUniPoly { @@ -146,6 +166,145 @@ 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 * points.len()]; + + 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 == 1 { + 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 = 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]; + + res[n] = Fr::one(); + res[n - 1] = -roots.into_iter().sum::(); + + let mut temp; + let mut constant = Fr::one(); + for i in 0..(n - 1) { + temp = Fr::zero(); + for j in 0..(n - 1 - i) { + res[n - 2 - i] = + res[n - 2 - i] + roots[j] * roots[j + 1..].into_iter().take(n - 1 - i - j).sum::(); + temp = temp + res[n - 2 - i]; + } + res[n - 2 - i] = temp * constant; + constant = constant.neg(); + } + + res + } + + #[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 test_case = |num_zero_roots: usize, num_non_zero_roots: usize| { + let num_roots = num_non_zero_roots + num_zero_roots; + let 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 = UniPoly::from_coeff((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); + } + + //TODO: + }; + } #[test] fn test_from_evals_quad() { diff --git a/src/subprotocols/kzg.rs b/src/subprotocols/kzg.rs deleted file mode 100644 index 8b1378917..000000000 --- a/src/subprotocols/kzg.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/subprotocols/kzg10.rs b/src/subprotocols/kzg10.rs new file mode 100644 index 000000000..822c81393 --- /dev/null +++ b/src/subprotocols/kzg10.rs @@ -0,0 +1,14 @@ +use ark_ec::{pairing::Pairing, CurveGroup}; + +use crate::msm::VariableBaseMSM; + +/// MINIMAL KZG IMPLEMENTATION BASED ON ARKWORKS. PROVIDES METHOD OF CONVERTING FROM LAGRANGE TO MONOMIAL BASIS AND CREATES A lazy_static reference used directly in the setup ceremony. +// Questions: +// Should we encapsulate this the proof and commitment into there own structs? +// +// +pub fn commit(powers: &[P::G1Affine], scalars: &[P::ScalarField]) -> P::G1Affine { + ::msm(powers, &scalars) + .unwrap() + .into_affine() +} diff --git a/src/subprotocols/mod.rs b/src/subprotocols/mod.rs index 242488af1..450b463bd 100644 --- a/src/subprotocols/mod.rs +++ b/src/subprotocols/mod.rs @@ -5,6 +5,6 @@ mod zk; pub mod dot_product; pub mod grand_product; -pub mod kzg; +pub mod kzg10; pub mod sumcheck; pub mod zeromorph; diff --git a/src/subprotocols/zeromorph/data_structures.rs b/src/subprotocols/zeromorph/data_structures.rs new file mode 100644 index 000000000..33a17e976 --- /dev/null +++ b/src/subprotocols/zeromorph/data_structures.rs @@ -0,0 +1,106 @@ +use ark_bn254::Bn254; +use ark_ec::{pairing::Pairing, CurveGroup}; +use ark_std::rand::rngs::StdRng; +use ark_ff::UniformRand; +use lazy_static::lazy_static; +use rand_chacha::rand_core::SeedableRng; +use std::sync::{Arc, Mutex}; + +//TODO: The SRS is set with a default value of ____ if this is to be changed (extended) use the cli arg and change it manually. +//TODO: add input specifiying monomial or lagrange basis +lazy_static! { + pub static ref ZEROMORPHSRS: Arc>> = + Arc::new(Mutex::new(ZeromorphSRS::setup(None))); +} + +#[derive(Debug, Clone, Default)] +pub struct ZeromorphSRS { + g1: P::G1Affine, + g2: P::G2Affine, + g1_powers: Vec, + g2_powers: Vec, +} + +impl ZeromorphSRS

{ + + fn compute_g_powers(tau: G::ScalarField, n: usize) -> Vec { + let mut g_srs = vec![G::zero(); n - 1]; + + #[cfg(not(feature = "parallel"))] + let g_srs: Vec = std::iter::once(G::generator()) + .chain(g_srs.iter().scan(G::generator(), |state, _| { + *state *= τ + Some(*state) + })) + .collect(); + + #[cfg(feature = "parallel")] + { + use ark_ff::Field; + use ark_ff::Zero; + g_srs.push(G::zero()); + parallelize(&mut g_srs, |g, start| { + let mut current_g: G = G::generator(); + current_g = current_g.mul(tau.pow(&[start as u64])); + for g in g.iter_mut() { + *g = current_g; + current_g *= tau; + } + }); + } + + G::normalize_batch(&g_srs) + } + + pub fn setup(tau: Option<&[u8]>) -> ZeromorphSRS

{ + let N_MAX = 250; + /* + if tau.is_none() { + return ZeromorphSRS::default(); + todo!() + } + */ + /* + if ENV_VAR_NOT_PASSED_IN + */ + let mut bytes = [0u8; 32]; + let len = tau.unwrap().len().min(32); + bytes[..len].copy_from_slice(&tau.unwrap()[..len]); + let rng = &mut StdRng::from_seed(bytes); + + let tau = P::ScalarField::rand(rng); + let g1_powers = Self::compute_g_powers::(tau, N_MAX); + let g2_powers = Self::compute_g_powers::(tau, N_MAX); + ZeromorphSRS { g1: g1_powers[0], g2: g2_powers[0], g1_powers, g2_powers } + } + + pub fn get_verifier_key() -> ProverKey

{ + todo!() + } + + pub fn get_prover_key() -> VerifierKey

{ + todo!() + } +} + +pub struct ProverKey { + // generator + pub g1: P::G1Affine, + pub tau_1: P::G1Affine, + // random power of tau + g1 used for commitments + pub g1_tau_powers: Vec, +} + +pub struct VerifierKey { + pub g1: P::G1Affine, + pub g2: P::G2Affine, + pub tau_2: P::G2Affine, + pub tau_N_max_sub_2_N: P::G2Affine, +} + +//TODO: can we upgrade the transcript to give not just absorb +pub struct ZeromorphProof { + pub pi: P::G1Affine, + pub q_hat_com: P::G1Affine, + pub q_k_com: Vec, +} diff --git a/src/subprotocols/zeromorph/mod.rs b/src/subprotocols/zeromorph/mod.rs new file mode 100644 index 000000000..d888c4da1 --- /dev/null +++ b/src/subprotocols/zeromorph/mod.rs @@ -0,0 +1,2 @@ +pub mod data_structures; +pub mod zeromorph; diff --git a/src/subprotocols/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs similarity index 53% rename from src/subprotocols/zeromorph.rs rename to src/subprotocols/zeromorph/zeromorph.rs index 69bc49907..eca85a145 100644 --- a/src/subprotocols/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -1,20 +1,14 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] -use std::marker::PhantomData; -use std::ops::Index; - -use crate::poly::commitments::MultiCommitGens; -use crate::poly::dense_mlpoly::DensePolynomial; -use crate::poly::unipoly::{CompressedUniPoly, UniPoly}; -use crate::subprotocols::dot_product::DotProductProof; -use crate::utils::errors::ProofVerifyError; -use crate::utils::math::Math; -use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; -use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; -use ark_ec::{pairing::Pairing, CurveConfig, CurveGroup, Group}; -use ark_ff::{BigInt, BigInteger, Field, PrimeField}; -use ark_serialize::*; +use std::{borrow::Borrow, marker::PhantomData, ops::Neg}; + +use crate::poly::unipoly::UniPoly; +use crate::poly::{commitments, dense_mlpoly::DensePolynomial}; +use crate::utils::transcript::ProofTranscript; +use ark_ec::pairing; +use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; +use ark_ff::{BigInt, Field}; use ark_std::{One, Zero}; use merlin::Transcript; @@ -27,9 +21,7 @@ use crate::msm::VariableBaseMSM; #[cfg(feature = "multicore")] use rayon::prelude::*; -pub struct CommitmentKey { - _phantom: PhantomData

, -} +use super::data_structures::{ProverKey, VerifierKey, ZeromorphProof}; pub struct Zeromorph { _phantom: PhantomData

, @@ -44,89 +36,66 @@ impl Zeromorph { } } - fn powers_of_challenge(challenge: P::ScalarField, num_powers: usize) -> Vec { - //TODO: switch to successors - (2..num_powers).fold(vec![P::ScalarField::one(), challenge], |mut acc, i| { - acc.push(acc[i - 1] * challenge); - acc - }) - } - + // Just return vec of P::Scalar pub fn compute_multilinear_quotients( - polynimial: &DensePolynomial, + poly: &DensePolynomial, u_challenge: &[P::ScalarField], - ) -> Vec> { - // TODO: can grab from poly - let log_N = (N as usize).log_2(); - - // The size of the multilinear challenge must equal the log of the polynomial size - assert!(log_N == u_challenge.len()); - - // Define vector of quotient polynomials q_k, k = 0, ..., log_N - 1 - let mut quotients = (0..log_N) - .into_iter() - .map(|_| DensePolynomial::from_usize(&[0])) - .collect::>(); - println!( - "log_N {:?} N {:?} quotients {:?}", - log_N, - N, - quotients.len() - ); - - // Compute the coefficients of q_{n - 1} - let mut size_q = (1 << (log_N - 1)) as usize; - //TODO: check if this is correct. Based on Barretenburg's mle I think it is??? - let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - })); - - //Probs can't avoid this clone - quotients.insert(log_N - 1, q.clone()); - - let mut f_k: Vec = vec![P::ScalarField::zero(); size_q]; - - //We can probably clean this up some but for now we're being explicit - let mut g = (0..size_q).fold(Vec::new(), |mut acc, i| { - acc.push(polynimial[i]); - acc - }); - - for k in 1..log_N { - // Compute f_k - for l in 0..size_q { - f_k[l] = g[l] + u_challenge[log_N - k] * q[l]; - } - - size_q = size_q / 2; - let q = DensePolynomial::new((0..size_q).fold(Vec::new(), |mut acc, l| { - acc.push(polynimial[size_q + l] - polynimial[l]); - acc - })); - - quotients[log_N - k - 1] = q; - - //Would be great to remove this new instantiation probably best way is to just have vectors of coeffs. - g = f_k.clone(); - } - - quotients + ) -> (Vec>, P::ScalarField) { + assert_eq!(poly.get_num_vars(), u_challenge.len()); + + let mut g = poly.Z.to_vec(); + let mut quotients = u_challenge + .iter() + .enumerate() + .map(|(i, x_i)| { + let (g_lo, g_hi) = g.split_at_mut(1 << (poly.get_num_vars() - 1 - i)); + let mut quotient = vec![P::ScalarField::zero(); g_lo.len()]; + + quotient + .par_iter_mut() + .zip(&*g_lo) + .zip(&*g_hi) + .for_each(|((mut 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 += (*g_hi - g_lo as &_) * x_i; + }); + + g.truncate(1 << (poly.get_num_vars() - 1 - i)); + + UniPoly::from_coeff(quotient) + }) + .collect::>>(); + quotients.reverse(); + (quotients, g[0]) } + /** + * @brief Construct batched, lifted-degree univariate quotient \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k + * @details The purpose of the batched lifted-degree quotient is to reduce the individual degree checks + * deg(q_k) <= 2^k - 1 to a single degree check on \hat{q}. This is done by first shifting each of the q_k to the + * right (i.e. multiplying by an appropriate power of X) so that each is degree N-1, then batching them all together + * using powers of the provided challenge. Note: In practice, we do not actually compute the shifted q_k, we simply + * accumulate them into \hat{q} at the appropriate offset. + * + * @param quotients Polynomials q_k, interpreted as univariates; deg(q_k) = 2^k - 1 + * @param N circuit size + * @return Polynomial + */ pub fn compute_batched_lifted_degree_quotient( - quotients: &Vec>, + quotients: &Vec>, y_challenge: &P::ScalarField, ) -> UniPoly { // Batched Lifted Degreee Quotient Polynomials - let mut res: Vec = Vec::with_capacity(N as usize); + let mut res: Vec = vec![P::ScalarField::zero(); N as usize]; // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k - let mut scalar = P::ScalarField::one(); + let mut scalar = P::ScalarField::one(); // y^k for (k, quotient) in quotients.iter().enumerate() { // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 - //TODO: verify if this is needed as we are not interested in shifts let deg_k = (1 << k) as usize - 1; let offset = N as usize - deg_k - 1; for i in 0..(deg_k + 1) { @@ -140,7 +109,7 @@ impl Zeromorph { pub fn compute_partially_evaluated_degree_check_polynomial( batched_quotient: &UniPoly, - quotients: &Vec>, + quotients: &Vec>, y_challenge: &P::ScalarField, x_challenge: &P::ScalarField, ) -> UniPoly { @@ -160,7 +129,7 @@ impl Zeromorph { // See -> https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/honk/pcs/zeromorph/zeromorph.hpp#L173 // https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/polynomials/polynomial.cpp#L332 // res += quotient[i] * (-y_power * x_power) - for i in 0..res.len() { + for i in 0..quotients[k].len() { res[i] += quotients[k][i] * (-y_power * x_power); } @@ -172,7 +141,7 @@ impl Zeromorph { pub fn compute_partially_evaluated_zeromorph_identity_polynomial( f_batched: &UniPoly, - g_batched: &UniPoly, + //g_batched: &UniPoly, quotients: &Vec>, v_evaluation: &P::ScalarField, u_challenge: &[P::ScalarField], @@ -183,12 +152,14 @@ impl Zeromorph { //Question for non-shifted can we exclude sum_{i=0}^{l-i} // Initialize Z_x with x * \sum_{i=0}^{m-1} f_i + /sum_{i=0}^{l-i} * g_i - let mut res: UniPoly = g_batched.clone(); + //let mut res: UniPoly = g_batched.clone(); //add scaled - for i in 0..res.len() { - res[i] += f_batched[i] * x_challenge; - } + //for i in 0..res.len() { + // res[i] += f_batched[i] * x_challenge; + //} + + let mut res = f_batched.clone(); // Compute Z_x -= v * x * \Phi_n(x) let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 @@ -213,24 +184,35 @@ impl Zeromorph { scalar *= x_challenge; scalar *= -P::ScalarField::one(); - for i in 0..res.len() { + for i in 0..quotients[k].len() { res[i] += quotients[k][i] * scalar; } } res } + //TODO: Need SRS pub fn compute_batched_evaluation_and_degree_check_quotient( zeta_x: UniPoly, z_x: UniPoly, x_challenge: P::ScalarField, z_challenge: P::ScalarField, ) -> UniPoly { - // We cannont commit to polynomials with size > N_max + // We cannot commit to polynomials with size > N_max let n = zeta_x.len(); assert!(n <= N as usize); - // Compute q_{\zeta} and q_Z in place + //Compute quotient polynomials q_{\zeta} and q_Z + + //q_{\zeta} = \zeta_x / (X-x) + //q_z = Z_x / (X - x) + //TODO: remove these clones + let mut q_zeta_x = zeta_x.clone(); + let mut q_z_x = z_x.clone(); + q_zeta_x.factor_roots(&x_challenge); + q_z_x.factor_roots(&x_challenge); + + // Compute batched quotient q_{\zeta} + z*q_Z in place let mut batched_quotient = zeta_x; for i in 0..batched_quotient.len() { batched_quotient[i] += z_x[i] * z_challenge; @@ -241,39 +223,258 @@ impl Zeromorph { batched_quotient } - pub fn prove( - f_polynomials: Vec>, - evaluations: Vec, - multilinear_challenge: Vec, - commitment_key: CommitmentKey

, - transcript: Transcript, - ) -> P::G1 { - todo!(); - } - pub fn compute_C_zeta_x( - C_q: P::G1, - C_q_k: Vec, + q_hat_com: &P::G1, + q_k_com: &Vec, y_challenge: &P::ScalarField, x_challenge: &P::ScalarField, ) -> P::G1 { - todo!() + let n = 1 << q_k_com.len(); + + let one = P::ScalarField::one(); + let mut scalars = vec![one]; + let mut commitments = vec![q_hat_com.into_affine()]; + + for (i, com) in q_k_com.iter().enumerate() { + let deg_k = (1 << i) - 1; + // Compute scalar y^k * x^{N - deg_k - 1} + let mut scalar = y_challenge.pow(BigInt::<1>::from(i as u64)); + scalar *= x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); + scalar *= P::ScalarField::one().neg(); + scalars.push(scalar); + commitments.push(*com); + } + + ::msm(&commitments, &scalars).unwrap() } - pub fn compute_C_z_X( - f_commitments: Vec, - g_commitments: Vec, - C_q_k: &Vec, + pub fn compute_C_Z_x( + f_commitments: &Vec, + q_k_com: &Vec, rho: &P::ScalarField, batched_evaluation: &P::ScalarField, x_challenge: &P::ScalarField, u_challenge: &[P::ScalarField], + g1: &P::G1Affine, ) -> P::G1 { - todo!(); + let n = 1 < q_k_com.len(); + + // Phi_n(x) = (x^N - 1) / (x - 1) + let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 + let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); + + // Add: -v * x * \Phi_n(x) * [1]_1 + let mut scalars = + vec![*batched_evaluation * x_challenge * phi_n_x * P::ScalarField::one().neg()]; + let mut commitments = vec![*g1]; + + // Add x * \sum_{i=0}^{m-1} \rho^i*[f_i] + let mut rho_pow = P::ScalarField::one(); + for com in f_commitments { + scalars.push(*x_challenge * rho_pow); + commitments.push(com.into_affine()); + rho_pow *= rho; + } + + // Add: scalar * [q_k], k = 0, ..., log_N, where + // scalar = -x * (x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k})) + let mut x_pow_2k = *x_challenge; // x^{2^k} + let mut x_pow_2kp1 = *x_challenge * x_challenge; // x^{2^{k + 1}} + + for k in 0..q_k_com.len() { + let phi_term_1 = phi_numerator / (x_pow_2kp1 - P::ScalarField::one()); // \Phi_{n-k-1}(x^{2^{k + 1}}) + let phi_term_2 = phi_numerator / (x_pow_2k - P::ScalarField::one()); // \Phi_{n-k-1}(x^{2^k}) + + let mut scalar = x_pow_2k * phi_term_1; + scalar -= u_challenge[k] * phi_term_2; + scalar *= x_challenge; + scalar *= P::ScalarField::one().neg(); + + scalars.push(scalar); + commitments.push(q_k_com[k]); + + // update powers of challenge x + x_pow_2k = x_pow_2kp1; + x_pow_2kp1 *= x_pow_2kp1; + } + + ::msm(&commitments, &scalars).unwrap() } - pub fn verify() { - todo!() + pub fn prove( + f_polynomials: Vec>, + evaluations: Vec, + multilinear_challenge: Vec, + pk: impl Borrow>, + transcript: &mut Transcript, + ) -> ZeromorphProof

{ + // ASSERT evaluations, challenges, and polynomials are the same size + assert_eq!(evaluations.len(), multilinear_challenge.len()); + assert_eq!(evaluations.len(), f_polynomials.len()); + + // Generate batching challenge \rho and powers 1,...,\rho^{m-1} + let rho = >::challenge_scalar(transcript, b"ZM: rho"); + let rhos = (0..evaluations.len()) + .scan(P::ScalarField::one(), |acc, _| { + let val = *acc; + *acc *= rho; + Some(val) + }) + .collect::>(); + + let log_N = multilinear_challenge.len(); + let n = 1 << log_N; + let pk = pk.borrow(); + + // Compute batching of unshifted polynomials f_i: + // f_batched = sum_{i=0}^{m-1}\rho^i*f_i + let mut batched_evaluation = P::ScalarField::zero(); + let mut f_batched = vec![P::ScalarField::zero(); n]; + for (i, f_poly) in f_polynomials.iter().enumerate() { + // add_scaled + for j in 0..f_batched.len() { + f_batched[j] = f_poly[j] * rhos[i]; + } + + batched_evaluation += rhos[i] * evaluations[i]; + } + let f_polynomial = UniPoly::from_coeff(f_batched.clone()); + + // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) + let (quotients, _) = Self::compute_multilinear_quotients( + &DensePolynomial::new(f_batched.clone()), + &multilinear_challenge, + ); + + // Compute and send commitments C_{q_k} = [q_k], k = 0, ..., d-1 + let label = b"q_k_commitments"; + transcript.append_message(label, b"begin_append_vector"); + let q_k_commitments = (0..log_N).into_iter().fold(Vec::new(), |mut acc, i| { + let q_k_commitment = + ::msm(&pk.g1_tau_powers, "ients[i].coeffs).unwrap(); + transcript.append_point(label, &q_k_commitment); + acc.push(q_k_commitment.into_affine()); + acc + }); + transcript.append_message(label, b"end_append_vector"); + + // Get challenge y + let y_challenge = + >::challenge_scalar(transcript, b"ZM: y"); + + // Compute the batched, lifted-degree quotient \hat{q} + let q_hat = Self::compute_batched_lifted_degree_quotient("ients, &y_challenge); + + // Compute and send the commitment C_q = [\hat{q}] + let C_q_hat = ::msm(&pk.g1_tau_powers, &q_hat.coeffs).unwrap(); + transcript.append_point(b"ZM: C_q_hat", &C_q_hat); + + // Get challenges x and z + let x_challenge = + >::challenge_scalar(transcript, b"ZM: x"); + let z_challenge = + >::challenge_scalar(transcript, b"ZM: z"); + + // Compute degree check polynomials \zeta partially evaluated at x + let zeta_x = Self::compute_partially_evaluated_degree_check_polynomial( + &q_hat, + "ients, + &y_challenge, + &x_challenge, + ); + + // Compute Zeromorph identity polynomial Z partially evaluated at x + let Z_x = Self::compute_partially_evaluated_zeromorph_identity_polynomial( + &f_polynomial, + "ients, + &batched_evaluation, + &multilinear_challenge, + &x_challenge, + ); + + // Compute batched degree-check and ZM-identity quotient polynomial pi + let pi_poly = Self::compute_batched_evaluation_and_degree_check_quotient( + zeta_x, + Z_x, + x_challenge, + z_challenge, + ); + + let pi = ::msm(&pk.g1_tau_powers, &pi_poly.coeffs).unwrap(); + transcript.append_point(b"ZM: C_pi", &pi); + + ZeromorphProof { + pi: pi.into_affine(), + q_hat_com: C_q_hat.into_affine(), + q_k_com: q_k_commitments, + } + } + + pub fn verify( + commitments: &Vec, + claimed_evaluations: &Vec, + multilinear_challenge: &Vec, + vk: impl Borrow>, + transcript: &mut Transcript, + proof: ZeromorphProof

, + ) -> bool { + let log_N = multilinear_challenge.len(); + let vk = vk.borrow(); + let ZeromorphProof { + pi, + q_k_com, + q_hat_com, + } = proof; + let pi = pi.into_group(); + let q_k_com = q_k_com; + let q_hat_com = q_hat_com.into_group(); + + // Compute powers of batching challenge rho + let rho = >::challenge_scalar(transcript, b"ZM: rho"); + let rhos = (0..claimed_evaluations.len()) + .scan(P::ScalarField::one(), |acc, _| { + let val = *acc; + *acc *= rho; + Some(val) + }) + .collect::>(); + + // Construct batched evaluations v = sum_{i=0}^{m-1}\rho^i*f_i(u) + let mut batched_evaluation = P::ScalarField::zero(); + for (i, val) in claimed_evaluations.iter().enumerate() { + batched_evaluation += *val * rhos[i]; + } + + // Challenge y + let y_challenge = + >::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 + + // Challenge x, z + let x_challenge = + >::challenge_scalar(transcript, b"ZM: x"); + let z_challenge = + >::challenge_scalar(transcript, b"ZM: z"); + + let C_zeta_x = Self::compute_C_zeta_x(&q_hat_com, &q_k_com, &y_challenge, &x_challenge); + let C_Z_x = Self::compute_C_Z_x( + commitments, + &q_k_com, + &rho, + &batched_evaluation, + &x_challenge, + &multilinear_challenge, + &vk.g1, + ); + + // Compute commitment C_{\zeta,Z} + let C_zeta_Z = C_zeta_x + C_Z_x * z_challenge; + + // e(pi, [tau]_2 - x * [1]_2) == e(C_{\zeta,Z}, [X^(N_max - 2^n - 1)]_2) <==> e(C_{\zeta,Z} - x * pi, [X^{N_max - 2^n - 1}]_2) * e(-pi, [tau_2]) == 1 + let lhs = P::pairing(pi, vk.tau_2.into_group() - (vk.g2 * x_challenge)); + let rhs = P::pairing(C_zeta_Z, vk.tau_N_max_sub_2_N); + lhs == rhs } } @@ -281,7 +482,6 @@ impl Zeromorph { mod test { use super::*; use crate::utils::math::Math; - use crate::utils::test::TestTranscript; use ark_bn254::{Bn254, Fq, Fr, G1Affine, G1Projective}; use ark_ff::{BigInt, Zero}; use ark_std::{test_rng, UniformRand}; @@ -289,15 +489,13 @@ mod test { // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula fn phi(challenge: &P::ScalarField, subscript: usize) -> P::ScalarField { let len = (1 << subscript) as u64; - let res = P::ScalarField::zero(); (0..len) .into_iter() .fold(P::ScalarField::zero(), |mut acc, i| { //Note this is ridiculous DevX acc += challenge.pow(BigInt::<1>::from(i)); acc - }); - res + }) } /// Test for computing qk given multilinear f @@ -313,12 +511,8 @@ mod test { // 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) - .into_iter() - .map(|_| Fr::rand(&mut rng)) - .collect::>(), - ); + let multilinear_f = + DensePolynomial::new((0..N).map(|_| Fr::rand(&mut rng)).collect::>()); let u_challenge = (0..log_N) .into_iter() .map(|_| Fr::rand(&mut rng)) @@ -326,31 +520,32 @@ mod test { let v_evaluation = multilinear_f.evaluate(&u_challenge); // Compute multilinear quotients `qโ‚–(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚–โ‚‹โ‚)` - let quotients = + let (quotients, constant_term) = Zeromorph::::compute_multilinear_quotients(&multilinear_f, &u_challenge); + // Assert the constant term is equal to v_evaluation + assert_eq!( + constant_term, v_evaluation, + "The constant term equal to the evaluation of the polynomial at challenge point." + ); + //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) - .into_iter() - .map(|_| Fr::rand(&mut rng)) - .collect::>(); + let z_challenge = (0..log_N).map(|_| Fr::rand(&mut rng)).collect::>(); let mut res = multilinear_f.evaluate(&z_challenge); res -= v_evaluation; - for k in 0..log_N { - let q_k_eval; - if k == 0 { - // ๐‘žโ‚€ = ๐‘Žโ‚€ is a constant polynomial so it's evaluation is simply its constant coefficient - q_k_eval = quotients[k][0]; - } else { - // Construct (๐‘ขโ‚€, ..., ๐‘ขโ‚–โ‚‹โ‚) - q_k_eval = quotients[k].evaluate(&z_challenge[..k]); - } - // res = res - (๐‘งโ‚– โˆ’ ๐‘ขโ‚–) * ๐‘žโ‚–(๐‘ขโ‚€, ..., ๐‘ขโ‚–โ‚‹โ‚) - res -= (z_challenge[k] - u_challenge[k]) * q_k_eval; + + for (k, q_k_uni) in quotients.iter().enumerate() { + 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); + + res -= (z_challenge[z_challenge.len() - k - 1] - u_challenge[z_challenge.len() - k - 1]) + * q_k_eval; } - assert_eq!(res, Fr::zero()); + assert!(res.is_zero()); } /// Test for construction of batched lifted degree quotient: @@ -368,9 +563,9 @@ mod test { Fr::from(6u64), Fr::from(7u64), ]; - let q_0 = DensePolynomial::new(data_0); - let q_1 = DensePolynomial::new(data_1); - let q_2 = DensePolynomial::new(data_2); + 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, q_1, q_2]; let mut rng = test_rng(); @@ -412,9 +607,9 @@ mod test { Fr::from(6u64), Fr::from(7u64), ]; - let q_0_lifted = DensePolynomial::new(data_0_lifted); - let q_1_lifted = DensePolynomial::new(data_1_lifted); - let q_2_lifted = DensePolynomial::new(data_2_lifted); + let q_0_lifted = UniPoly::from_coeff(data_0_lifted); + let q_1_lifted = UniPoly::from_coeff(data_1_lifted); + let q_2_lifted = UniPoly::from_coeff(data_2_lifted); //Explicitly compute ฬ‚q i.e. RLC of lifted polys let mut batched_quotient_expected = DensePolynomial::new(vec![Fr::zero(); N as usize]); @@ -455,9 +650,9 @@ mod test { Fr::from(6u64), Fr::from(7u64), ]; - let q_0 = DensePolynomial::new(data_0); - let q_1 = DensePolynomial::new(data_1); - let q_2 = DensePolynomial::new(data_2); + 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 mut rng = test_rng(); @@ -466,6 +661,8 @@ mod test { //Compute batched quptient ฬ‚q let batched_quotient = Zeromorph::::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); @@ -485,17 +682,17 @@ mod test { } // ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N - for i in 0..zeta_x_expected.len() { + 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)); } - for i in 0..zeta_x_expected.len() { + 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))); } - for i in 0..zeta_x_expected.len() { - zeta_x_expected[i] += q_1[i] + 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))); } @@ -556,28 +753,16 @@ mod test { .into_iter() .map(|_| Fr::rand(&mut rng)) .collect::>(); - let mut multilinear_g = (0..N) - .into_iter() - .map(|_| Fr::rand(&mut rng)) - .collect::>(); - multilinear_g[0] = Fr::zero(); let u_challenge = (0..log_N) .into_iter() .map(|_| Fr::rand(&mut rng)) .collect::>(); let v_evaluation = DensePolynomial::new(multilinear_f.clone()).evaluate(&u_challenge); - let w_evaluation = DensePolynomial::new(multilinear_g.clone()).evaluate(&u_challenge); // This says shifted??? mayhaps exclude -> first ask Kobi - - let rho = Fr::rand(&mut rng); // compute batched polynomial and evaluation let f_batched = UniPoly::from_coeff(multilinear_f); - let mut g_batched = UniPoly::from_coeff(multilinear_g); - for i in 0..g_batched.len() { - g_batched[i] = g_batched[i] * rho; - } - let v_batched = v_evaluation + rho * w_evaluation; + let v_batched = v_evaluation; // Define some mock q_k with deeg(q_k) = 2^k - 1 let q_0 = UniPoly::from_coeff( @@ -605,7 +790,6 @@ mod test { // Construct Z_x using the prover method let Z_x = Zeromorph::::compute_partially_evaluated_zeromorph_identity_polynomial( &f_batched, - &g_batched, "ients, &v_evaluation, &u_challenge, @@ -613,10 +797,7 @@ mod test { ); // Compute Z_x directly - let mut Z_x_expected = g_batched; - for i in 0..Z_x_expected.len() { - Z_x_expected[i] += f_batched[i] * x_challenge; - } + let mut Z_x_expected = f_batched; Z_x_expected[0] = Z_x_expected[0] - v_batched * x_challenge * &phi::(&x_challenge, log_N); @@ -629,7 +810,7 @@ mod test { - u_challenge[k] * &phi::(&x_pow_2k, log_N - k); scalar *= x_challenge; scalar *= Fr::from(-1); - for i in 0..Z_x_expected.len() { + for i in 0..quotients[k].len() { Z_x_expected[i] += quotients[k][i] * scalar; } } @@ -648,9 +829,4 @@ mod test { fn prove_and_verify_batched() { todo!() } - - #[test] - fn test_commit_open_verify() { - todo!() - } } From 0306142965172c7e5894c3ba1eb2e82e9508221a Mon Sep 17 00:00:00 2001 From: PatStiles Date: Mon, 27 Nov 2023 13:45:21 -0600 Subject: [PATCH 09/21] derive pk and vk from srs --- src/subprotocols/zeromorph/data_structures.rs | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/subprotocols/zeromorph/data_structures.rs b/src/subprotocols/zeromorph/data_structures.rs index 33a17e976..37a4dc661 100644 --- a/src/subprotocols/zeromorph/data_structures.rs +++ b/src/subprotocols/zeromorph/data_structures.rs @@ -1,11 +1,13 @@ use ark_bn254::Bn254; -use ark_ec::{pairing::Pairing, CurveGroup}; +use ark_ec::{pairing::Pairing, CurveGroup, Group}; use ark_std::rand::rngs::StdRng; use ark_ff::UniformRand; use lazy_static::lazy_static; use rand_chacha::rand_core::SeedableRng; use std::sync::{Arc, Mutex}; +use crate::utils::math::Math; + //TODO: The SRS is set with a default value of ____ if this is to be changed (extended) use the cli arg and change it manually. //TODO: add input specifiying monomial or lagrange basis lazy_static! { @@ -17,6 +19,8 @@ lazy_static! { pub struct ZeromorphSRS { g1: P::G1Affine, g2: P::G2Affine, + tau_g1: P::G1Affine, + tau_g2: P::G2Affine, g1_powers: Vec, g2_powers: Vec, } @@ -26,7 +30,6 @@ impl ZeromorphSRS

{ fn compute_g_powers(tau: G::ScalarField, n: usize) -> Vec { let mut g_srs = vec![G::zero(); n - 1]; - #[cfg(not(feature = "parallel"))] let g_srs: Vec = std::iter::once(G::generator()) .chain(g_srs.iter().scan(G::generator(), |state, _| { *state *= τ @@ -34,21 +37,6 @@ impl ZeromorphSRS

{ })) .collect(); - #[cfg(feature = "parallel")] - { - use ark_ff::Field; - use ark_ff::Zero; - g_srs.push(G::zero()); - parallelize(&mut g_srs, |g, start| { - let mut current_g: G = G::generator(); - current_g = current_g.mul(tau.pow(&[start as u64])); - for g in g.iter_mut() { - *g = current_g; - current_g *= tau; - } - }); - } - G::normalize_batch(&g_srs) } @@ -71,24 +59,25 @@ impl ZeromorphSRS

{ let tau = P::ScalarField::rand(rng); let g1_powers = Self::compute_g_powers::(tau, N_MAX); let g2_powers = Self::compute_g_powers::(tau, N_MAX); - ZeromorphSRS { g1: g1_powers[0], g2: g2_powers[0], g1_powers, g2_powers } + ZeromorphSRS { g1: P::G1::generator().into_affine(), g2: P::G2::generator().into_affine(), tau_g1: g1_powers[0], tau_g2: g2_powers[0], g1_powers, g2_powers } } - pub fn get_verifier_key() -> ProverKey

{ - todo!() + pub fn get_prover_key(&self) -> ProverKey

{ + ProverKey { g1: self.g1, tau_1: self.tau_g1, g1_powers: self.g1_powers } } - pub fn get_prover_key() -> VerifierKey

{ - todo!() + pub fn get_verifier_key(&self, n_max: usize) -> VerifierKey

{ + let idx = n_max - (2_usize.pow(n_max.log_2() as u32) - 1); + VerifierKey { g1: self.g1, g2: self.g2, tau_2: self.tau_g2, tau_N_max_sub_2_N: self.g2_powers[idx] } } + } pub struct ProverKey { // generator pub g1: P::G1Affine, pub tau_1: P::G1Affine, - // random power of tau + g1 used for commitments - pub g1_tau_powers: Vec, + pub g1_powers: Vec, } pub struct VerifierKey { From e649154a47943678bdbb8c2078c20f273d75fc24 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Mon, 27 Nov 2023 13:57:05 -0600 Subject: [PATCH 10/21] rm unneeded kzg module add sanity test --- src/subprotocols/kzg10.rs | 14 ------- src/subprotocols/zeromorph/data_structures.rs | 41 ++++++++++++++++++- src/subprotocols/zeromorph/zeromorph.rs | 9 ++-- 3 files changed, 43 insertions(+), 21 deletions(-) delete mode 100644 src/subprotocols/kzg10.rs diff --git a/src/subprotocols/kzg10.rs b/src/subprotocols/kzg10.rs deleted file mode 100644 index 822c81393..000000000 --- a/src/subprotocols/kzg10.rs +++ /dev/null @@ -1,14 +0,0 @@ -use ark_ec::{pairing::Pairing, CurveGroup}; - -use crate::msm::VariableBaseMSM; - -/// MINIMAL KZG IMPLEMENTATION BASED ON ARKWORKS. PROVIDES METHOD OF CONVERTING FROM LAGRANGE TO MONOMIAL BASIS AND CREATES A lazy_static reference used directly in the setup ceremony. -// Questions: -// Should we encapsulate this the proof and commitment into there own structs? -// -// -pub fn commit(powers: &[P::G1Affine], scalars: &[P::ScalarField]) -> P::G1Affine { - ::msm(powers, &scalars) - .unwrap() - .into_affine() -} diff --git a/src/subprotocols/zeromorph/data_structures.rs b/src/subprotocols/zeromorph/data_structures.rs index 37a4dc661..e24533cda 100644 --- a/src/subprotocols/zeromorph/data_structures.rs +++ b/src/subprotocols/zeromorph/data_structures.rs @@ -28,7 +28,7 @@ pub struct ZeromorphSRS { impl ZeromorphSRS

{ fn compute_g_powers(tau: G::ScalarField, n: usize) -> Vec { - let mut g_srs = vec![G::zero(); n - 1]; + let g_srs = vec![G::zero(); n - 1]; let g_srs: Vec = std::iter::once(G::generator()) .chain(g_srs.iter().scan(G::generator(), |state, _| { @@ -63,7 +63,7 @@ impl ZeromorphSRS

{ } pub fn get_prover_key(&self) -> ProverKey

{ - ProverKey { g1: self.g1, tau_1: self.tau_g1, g1_powers: self.g1_powers } + ProverKey { g1: self.g1, tau_1: self.tau_g1, g1_powers: self.g1_powers.clone() } } pub fn get_verifier_key(&self, n_max: usize) -> VerifierKey

{ @@ -93,3 +93,40 @@ pub struct ZeromorphProof { pub q_hat_com: P::G1Affine, pub q_k_com: Vec, } + +#[cfg(test)] +mod test { + use ark_bn254::{Bn254, Fr, G1Projective}; + use ark_ec::{pairing::Pairing, AffineRepr}; + use ark_ff::One; + use std::ops::Mul; + use super::*; + + fn expected_srs(n: usize, tau: E::ScalarField) -> Vec { + let powers_of_tau: Vec = + std::iter::successors(Some(E::ScalarField::one()), |p| Some(*p * tau)) + .take(n) + .collect(); + + let g1_gen = E::G1Affine::generator(); + + let srs_g1: Vec = powers_of_tau + .iter() + .map(|tp| g1_gen.mul(tp).into()) + .collect(); + + srs_g1 + } + + #[test] + fn test_srs() { + let k = 1; + let n = 1 << k; + let tau = Fr::from(100 as u64); + + let srs_expected = expected_srs::(n, tau); + + let g1_srs = ZeromorphSRS::::setup(None).g1_powers; + assert_eq!(srs_expected, g1_srs); + } +} \ No newline at end of file diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index eca85a145..68b4d5762 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -4,9 +4,8 @@ use std::{borrow::Borrow, marker::PhantomData, ops::Neg}; use crate::poly::unipoly::UniPoly; -use crate::poly::{commitments, dense_mlpoly::DensePolynomial}; +use crate::poly::dense_mlpoly::DensePolynomial; use crate::utils::transcript::ProofTranscript; -use ark_ec::pairing; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; use ark_ff::{BigInt, Field}; use ark_std::{One, Zero}; @@ -351,7 +350,7 @@ impl Zeromorph { transcript.append_message(label, b"begin_append_vector"); let q_k_commitments = (0..log_N).into_iter().fold(Vec::new(), |mut acc, i| { let q_k_commitment = - ::msm(&pk.g1_tau_powers, "ients[i].coeffs).unwrap(); + ::msm(&pk.g1_powers, "ients[i].coeffs).unwrap(); transcript.append_point(label, &q_k_commitment); acc.push(q_k_commitment.into_affine()); acc @@ -366,7 +365,7 @@ impl Zeromorph { let q_hat = Self::compute_batched_lifted_degree_quotient("ients, &y_challenge); // Compute and send the commitment C_q = [\hat{q}] - let C_q_hat = ::msm(&pk.g1_tau_powers, &q_hat.coeffs).unwrap(); + let C_q_hat = ::msm(&pk.g1_powers, &q_hat.coeffs).unwrap(); transcript.append_point(b"ZM: C_q_hat", &C_q_hat); // Get challenges x and z @@ -400,7 +399,7 @@ impl Zeromorph { z_challenge, ); - let pi = ::msm(&pk.g1_tau_powers, &pi_poly.coeffs).unwrap(); + let pi = ::msm(&pk.g1_powers, &pi_poly.coeffs).unwrap(); transcript.append_point(b"ZM: C_pi", &pi); ZeromorphProof { From 8ce6f57a80c13ed2be622917faa9f25ed81cdaae Mon Sep 17 00:00:00 2001 From: PatStiles Date: Tue, 28 Nov 2023 17:27:37 -0600 Subject: [PATCH 11/21] transition to trait interface --- src/subprotocols/mod.rs | 2 +- src/subprotocols/traits.rs | 41 ++++ src/subprotocols/zeromorph/data_structures.rs | 92 ++++---- src/subprotocols/zeromorph/zeromorph.rs | 207 ++++++++++-------- 4 files changed, 212 insertions(+), 130 deletions(-) create mode 100644 src/subprotocols/traits.rs diff --git a/src/subprotocols/mod.rs b/src/subprotocols/mod.rs index 450b463bd..241712af5 100644 --- a/src/subprotocols/mod.rs +++ b/src/subprotocols/mod.rs @@ -5,6 +5,6 @@ mod zk; pub mod dot_product; pub mod grand_product; -pub mod kzg10; pub mod sumcheck; pub mod zeromorph; +pub mod traits; \ No newline at end of file diff --git a/src/subprotocols/traits.rs b/src/subprotocols/traits.rs new file mode 100644 index 000000000..2cfef9657 --- /dev/null +++ b/src/subprotocols/traits.rs @@ -0,0 +1,41 @@ +use std::borrow::Borrow; + +use ark_std::iterable::Iterable; +use merlin::Transcript; + +use crate::utils::transcript; + +pub trait CommitmentScheme { + type Commitment; + type Evaluation; + type Polynomial; + type Challenge; + type Proof; + type Error; + + type ProverKey; + type VerifierKey; + + //TODO: convert to impl IntoIterator + fn commit( + polys: &[Self::Polynomial], + ck: &Self::ProverKey + ) -> Result, Self::Error>; + + fn prove( + polys: &[Self::Polynomial], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + pk: impl Borrow, + transcript: &mut Transcript + ) -> Result; + + fn verify( + commitments: &[Self::Commitment], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + vk: impl Borrow, + transcript: &mut Transcript, + proof: Self::Proof, + ) -> Result; +} \ No newline at end of file diff --git a/src/subprotocols/zeromorph/data_structures.rs b/src/subprotocols/zeromorph/data_structures.rs index e24533cda..22da35478 100644 --- a/src/subprotocols/zeromorph/data_structures.rs +++ b/src/subprotocols/zeromorph/data_structures.rs @@ -1,5 +1,5 @@ use ark_bn254::Bn254; -use ark_ec::{pairing::Pairing, CurveGroup, Group}; +use ark_ec::{pairing::Pairing, CurveGroup}; use ark_std::rand::rngs::StdRng; use ark_ff::UniformRand; use lazy_static::lazy_static; @@ -11,24 +11,20 @@ use crate::utils::math::Math; //TODO: The SRS is set with a default value of ____ if this is to be changed (extended) use the cli arg and change it manually. //TODO: add input specifiying monomial or lagrange basis lazy_static! { - pub static ref ZEROMORPHSRS: Arc>> = + pub static ref ZEROMORPH_SRS: Arc>> = Arc::new(Mutex::new(ZeromorphSRS::setup(None))); } #[derive(Debug, Clone, Default)] -pub struct ZeromorphSRS { - g1: P::G1Affine, - g2: P::G2Affine, - tau_g1: P::G1Affine, - tau_g2: P::G2Affine, +pub struct ZeromorphSRS { g1_powers: Vec, g2_powers: Vec, } -impl ZeromorphSRS

{ +impl ZeromorphSRS { - fn compute_g_powers(tau: G::ScalarField, n: usize) -> Vec { - let g_srs = vec![G::zero(); n - 1]; + fn compute_g_powers(tau: G::ScalarField) -> Vec { + let g_srs = vec![G::zero(); N_MAX - 1]; let g_srs: Vec = std::iter::once(G::generator()) .chain(g_srs.iter().scan(G::generator(), |state, _| { @@ -40,47 +36,48 @@ impl ZeromorphSRS

{ G::normalize_batch(&g_srs) } - pub fn setup(tau: Option<&[u8]>) -> ZeromorphSRS

{ - let N_MAX = 250; - /* - if tau.is_none() { - return ZeromorphSRS::default(); - todo!() + pub fn setup(toxic_waste: Option<&[u8]>) -> ZeromorphSRS { + let tau: &[u8]; + if toxic_waste.is_none() { + tau = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + } else { + tau = toxic_waste.unwrap() } - */ /* - if ENV_VAR_NOT_PASSED_IN - */ + if ENV_VAR_NOT_PASSED_IN + */ let mut bytes = [0u8; 32]; - let len = tau.unwrap().len().min(32); - bytes[..len].copy_from_slice(&tau.unwrap()[..len]); + let len = tau.len(); + bytes[..len].copy_from_slice(&tau[..len]); let rng = &mut StdRng::from_seed(bytes); let tau = P::ScalarField::rand(rng); - let g1_powers = Self::compute_g_powers::(tau, N_MAX); - let g2_powers = Self::compute_g_powers::(tau, N_MAX); - ZeromorphSRS { g1: P::G1::generator().into_affine(), g2: P::G2::generator().into_affine(), tau_g1: g1_powers[0], tau_g2: g2_powers[0], g1_powers, g2_powers } + let g1_powers = Self::compute_g_powers::(tau); + let g2_powers = Self::compute_g_powers::(tau); + ZeromorphSRS { g1_powers, g2_powers } } - pub fn get_prover_key(&self) -> ProverKey

{ - ProverKey { g1: self.g1, tau_1: self.tau_g1, g1_powers: self.g1_powers.clone() } + pub fn get_prover_key(&self) -> ZeromorphProverKey

{ + ZeromorphProverKey { g1: self.g1_powers[0], tau_1: self.g1_powers[1], g1_powers: self.g1_powers.clone() } } - pub fn get_verifier_key(&self, n_max: usize) -> VerifierKey

{ - let idx = n_max - (2_usize.pow(n_max.log_2() as u32) - 1); - VerifierKey { g1: self.g1, g2: self.g2, tau_2: self.tau_g2, tau_N_max_sub_2_N: self.g2_powers[idx] } + pub fn get_verifier_key(&self) -> ZeromorphVerifierKey

{ + let idx = N_MAX - (2_usize.pow(N_MAX.log_2() as u32) - 1); + ZeromorphVerifierKey { g1: self.g1_powers[0], g2: self.g2_powers[0], tau_2: self.g2_powers[1], tau_N_max_sub_2_N: self.g2_powers[idx] } } } -pub struct ProverKey { +#[derive(Clone, Debug)] +pub struct ZeromorphProverKey { // generator pub g1: P::G1Affine, pub tau_1: P::G1Affine, pub g1_powers: Vec, } -pub struct VerifierKey { +#[derive(Copy, Clone, Debug)] +pub struct ZeromorphVerifierKey { pub g1: P::G1Affine, pub g2: P::G2Affine, pub tau_2: P::G2Affine, @@ -88,6 +85,7 @@ pub struct VerifierKey { } //TODO: can we upgrade the transcript to give not just absorb +#[derive(Clone, Debug)] pub struct ZeromorphProof { pub pi: P::G1Affine, pub q_hat_com: P::G1Affine, @@ -96,37 +94,51 @@ pub struct ZeromorphProof { #[cfg(test)] mod test { - use ark_bn254::{Bn254, Fr, G1Projective}; + use ark_bn254::Bn254; use ark_ec::{pairing::Pairing, AffineRepr}; use ark_ff::One; use std::ops::Mul; use super::*; - fn expected_srs(n: usize, tau: E::ScalarField) -> Vec { + fn expected_srs(n: usize, seed: &[u8]) -> (Vec, Vec) { + + let mut bytes = [0u8; 32]; + let len = seed.len(); + bytes[..len].copy_from_slice(&seed[..len]); + let rng = &mut StdRng::from_seed(bytes); + + let tau = E::ScalarField::rand(rng); + let powers_of_tau: Vec = std::iter::successors(Some(E::ScalarField::one()), |p| Some(*p * tau)) .take(n) .collect(); let g1_gen = E::G1Affine::generator(); + let g2_gen = E::G2Affine::generator(); let srs_g1: Vec = powers_of_tau .iter() .map(|tp| g1_gen.mul(tp).into()) .collect(); + let srs_g2: Vec = powers_of_tau + .iter() + .map(|tp| g2_gen.mul(tp).into()) + .collect(); - srs_g1 + (srs_g1, srs_g2) } #[test] fn test_srs() { - let k = 1; - let n = 1 << k; - let tau = Fr::from(100 as u64); + const K: i32 = 1; + const N: usize = 1 << K; + let seed = b"111111111111111111111111111"; - let srs_expected = expected_srs::(n, tau); + let (g1_srs_expected, g2_srs_expected) = expected_srs::(N, seed); - let g1_srs = ZeromorphSRS::::setup(None).g1_powers; - assert_eq!(srs_expected, g1_srs); + let srs = ZeromorphSRS::::setup(Some(seed)); + assert_eq!(g1_srs_expected, srs.g1_powers); + assert_eq!(g2_srs_expected, srs.g2_powers); } } \ No newline at end of file diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index 68b4d5762..f308d54bc 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -8,8 +8,10 @@ use crate::poly::dense_mlpoly::DensePolynomial; use crate::utils::transcript::ProofTranscript; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; use ark_ff::{BigInt, Field}; -use ark_std::{One, Zero}; +use ark_std::{One, Zero, iterable::Iterable}; use merlin::Transcript; +use crate::subprotocols::traits::CommitmentScheme; +use thiserror::Error; #[cfg(feature = "ark-msm")] use ark_ec::VariableBaseMSM; @@ -20,23 +22,10 @@ use crate::msm::VariableBaseMSM; #[cfg(feature = "multicore")] use rayon::prelude::*; -use super::data_structures::{ProverKey, VerifierKey, ZeromorphProof}; - -pub struct Zeromorph { - _phantom: PhantomData

, -} - -/// Compute the powers of a challenge -/// -impl Zeromorph { - pub fn new() -> Self { - Self { - _phantom: PhantomData::

, - } - } +use super::data_structures::{ZeromorphProverKey, ZeromorphVerifierKey, ZeromorphProof, ZEROMORPH_SRS}; // Just return vec of P::Scalar - pub fn compute_multilinear_quotients( + fn compute_multilinear_quotients( poly: &DensePolynomial, u_challenge: &[P::ScalarField], ) -> (Vec>, P::ScalarField) { @@ -71,19 +60,7 @@ impl Zeromorph { (quotients, g[0]) } - /** - * @brief Construct batched, lifted-degree univariate quotient \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k - * @details The purpose of the batched lifted-degree quotient is to reduce the individual degree checks - * deg(q_k) <= 2^k - 1 to a single degree check on \hat{q}. This is done by first shifting each of the q_k to the - * right (i.e. multiplying by an appropriate power of X) so that each is degree N-1, then batching them all together - * using powers of the provided challenge. Note: In practice, we do not actually compute the shifted q_k, we simply - * accumulate them into \hat{q} at the appropriate offset. - * - * @param quotients Polynomials q_k, interpreted as univariates; deg(q_k) = 2^k - 1 - * @param N circuit size - * @return Polynomial - */ - pub fn compute_batched_lifted_degree_quotient( + fn compute_batched_lifted_degree_quotient( quotients: &Vec>, y_challenge: &P::ScalarField, ) -> UniPoly { @@ -106,7 +83,7 @@ impl Zeromorph { UniPoly::from_coeff(res) } - pub fn compute_partially_evaluated_degree_check_polynomial( + fn compute_partially_evaluated_degree_check_polynomial( batched_quotient: &UniPoly, quotients: &Vec>, y_challenge: &P::ScalarField, @@ -138,7 +115,7 @@ impl Zeromorph { res } - pub fn compute_partially_evaluated_zeromorph_identity_polynomial( + fn compute_partially_evaluated_zeromorph_identity_polynomial( f_batched: &UniPoly, //g_batched: &UniPoly, quotients: &Vec>, @@ -191,7 +168,7 @@ impl Zeromorph { } //TODO: Need SRS - pub fn compute_batched_evaluation_and_degree_check_quotient( + fn compute_batched_evaluation_and_degree_check_quotient( zeta_x: UniPoly, z_x: UniPoly, x_challenge: P::ScalarField, @@ -217,12 +194,10 @@ impl Zeromorph { batched_quotient[i] += z_x[i] * z_challenge; } - //TODO: finish once srs gen is completed - batched_quotient } - pub fn compute_C_zeta_x( + fn compute_C_zeta_x( q_hat_com: &P::G1, q_k_com: &Vec, y_challenge: &P::ScalarField, @@ -247,9 +222,9 @@ impl Zeromorph { ::msm(&commitments, &scalars).unwrap() } - pub fn compute_C_Z_x( - f_commitments: &Vec, - q_k_com: &Vec, + fn compute_C_Z_x( + f_commitments: &[P::G1], + q_k_com: &[P::G1Affine], rho: &P::ScalarField, batched_evaluation: &P::ScalarField, x_challenge: &P::ScalarField, @@ -300,20 +275,54 @@ impl Zeromorph { ::msm(&commitments, &scalars).unwrap() } - pub fn prove( - f_polynomials: Vec>, - evaluations: Vec, - multilinear_challenge: Vec, - pk: impl Borrow>, - transcript: &mut Transcript, - ) -> ZeromorphProof

{ +#[derive(Error, Debug)] +pub enum ZeromorphError { + #[error("oh no {0}")] + ShitIsFucked(String), +} + +pub struct Zeromorph { + _phantom: PhantomData

, +} + +/// Compute the powers of a challenge +/// +impl CommitmentScheme for Zeromorph { + type Commitment = P::G1; + type Evaluation = P::ScalarField; + type Polynomial = DensePolynomial; + type Challenge = P::ScalarField; + type Proof = ZeromorphProof

; + type Error = ZeromorphError; + + type ProverKey = ZeromorphProverKey

; + type VerifierKey = ZeromorphVerifierKey

; + + fn commit( + polys: &[Self::Polynomial], + pk: &Self::ProverKey + ) -> Result, Self::Error> { + Ok(polys.into_iter().map(|poly| ::msm(&pk.g1_powers, &poly.Z).unwrap()).collect::>()) + } + + fn prove( + polys: &[Self::Polynomial], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + pk: impl Borrow, + transcript: &mut Transcript + ) -> Result { // ASSERT evaluations, challenges, and polynomials are the same size - assert_eq!(evaluations.len(), multilinear_challenge.len()); - assert_eq!(evaluations.len(), f_polynomials.len()); + assert_eq!(evals.len(), challenges.len()); + assert_eq!(evals.len(), polys.len()); + + let log_N = challenges.len(); + let n = 1 << log_N; + let pk = pk.borrow(); // Generate batching challenge \rho and powers 1,...,\rho^{m-1} let rho = >::challenge_scalar(transcript, b"ZM: rho"); - let rhos = (0..evaluations.len()) + let rhos = (0..evals.len()) .scan(P::ScalarField::one(), |acc, _| { let val = *acc; *acc *= rho; @@ -321,28 +330,24 @@ impl Zeromorph { }) .collect::>(); - let log_N = multilinear_challenge.len(); - let n = 1 << log_N; - let pk = pk.borrow(); - // Compute batching of unshifted polynomials f_i: // f_batched = sum_{i=0}^{m-1}\rho^i*f_i let mut batched_evaluation = P::ScalarField::zero(); let mut f_batched = vec![P::ScalarField::zero(); n]; - for (i, f_poly) in f_polynomials.iter().enumerate() { + for (i, f_poly) in polys.iter().enumerate() { // add_scaled for j in 0..f_batched.len() { f_batched[j] = f_poly[j] * rhos[i]; } - batched_evaluation += rhos[i] * evaluations[i]; + batched_evaluation += rhos[i] * evals[i]; } let f_polynomial = UniPoly::from_coeff(f_batched.clone()); // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) - let (quotients, _) = Self::compute_multilinear_quotients( + let (quotients, _) = compute_multilinear_quotients::

( &DensePolynomial::new(f_batched.clone()), - &multilinear_challenge, + &challenges, ); // Compute and send commitments C_{q_k} = [q_k], k = 0, ..., d-1 @@ -362,7 +367,7 @@ impl Zeromorph { >::challenge_scalar(transcript, b"ZM: y"); // Compute the batched, lifted-degree quotient \hat{q} - let q_hat = Self::compute_batched_lifted_degree_quotient("ients, &y_challenge); + let q_hat = compute_batched_lifted_degree_quotient::("ients, &y_challenge); // Compute and send the commitment C_q = [\hat{q}] let C_q_hat = ::msm(&pk.g1_powers, &q_hat.coeffs).unwrap(); @@ -375,7 +380,7 @@ impl Zeromorph { >::challenge_scalar(transcript, b"ZM: z"); // Compute degree check polynomials \zeta partially evaluated at x - let zeta_x = Self::compute_partially_evaluated_degree_check_polynomial( + let zeta_x = compute_partially_evaluated_degree_check_polynomial::( &q_hat, "ients, &y_challenge, @@ -383,16 +388,16 @@ impl Zeromorph { ); // Compute Zeromorph identity polynomial Z partially evaluated at x - let Z_x = Self::compute_partially_evaluated_zeromorph_identity_polynomial( + let Z_x = compute_partially_evaluated_zeromorph_identity_polynomial::( &f_polynomial, "ients, &batched_evaluation, - &multilinear_challenge, + &challenges, &x_challenge, ); // Compute batched degree-check and ZM-identity quotient polynomial pi - let pi_poly = Self::compute_batched_evaluation_and_degree_check_quotient( + let pi_poly = compute_batched_evaluation_and_degree_check_quotient::( zeta_x, Z_x, x_challenge, @@ -402,22 +407,21 @@ impl Zeromorph { let pi = ::msm(&pk.g1_powers, &pi_poly.coeffs).unwrap(); transcript.append_point(b"ZM: C_pi", &pi); - ZeromorphProof { + Ok(ZeromorphProof { pi: pi.into_affine(), q_hat_com: C_q_hat.into_affine(), q_k_com: q_k_commitments, - } + }) } - pub fn verify( - commitments: &Vec, - claimed_evaluations: &Vec, - multilinear_challenge: &Vec, - vk: impl Borrow>, + fn verify( + commitments: &[Self::Commitment], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + vk: impl Borrow, transcript: &mut Transcript, - proof: ZeromorphProof

, - ) -> bool { - let log_N = multilinear_challenge.len(); + proof: Self::Proof, + ) -> Result { let vk = vk.borrow(); let ZeromorphProof { pi, @@ -430,7 +434,7 @@ impl Zeromorph { // Compute powers of batching challenge rho let rho = >::challenge_scalar(transcript, b"ZM: rho"); - let rhos = (0..claimed_evaluations.len()) + let rhos = (0..evals.len()) .scan(P::ScalarField::one(), |acc, _| { let val = *acc; *acc *= rho; @@ -440,7 +444,7 @@ impl Zeromorph { // Construct batched evaluations v = sum_{i=0}^{m-1}\rho^i*f_i(u) let mut batched_evaluation = P::ScalarField::zero(); - for (i, val) in claimed_evaluations.iter().enumerate() { + for (i, val) in evals.iter().enumerate() { batched_evaluation += *val * rhos[i]; } @@ -456,14 +460,14 @@ impl Zeromorph { let z_challenge = >::challenge_scalar(transcript, b"ZM: z"); - let C_zeta_x = Self::compute_C_zeta_x(&q_hat_com, &q_k_com, &y_challenge, &x_challenge); - let C_Z_x = Self::compute_C_Z_x( + let C_zeta_x = compute_C_zeta_x::(&q_hat_com, &q_k_com, &y_challenge, &x_challenge); + let C_Z_x = compute_C_Z_x::( commitments, &q_k_com, &rho, &batched_evaluation, &x_challenge, - &multilinear_challenge, + &challenges, &vk.g1, ); @@ -473,15 +477,15 @@ impl Zeromorph { // e(pi, [tau]_2 - x * [1]_2) == e(C_{\zeta,Z}, [X^(N_max - 2^n - 1)]_2) <==> e(C_{\zeta,Z} - x * pi, [X^{N_max - 2^n - 1}]_2) * e(-pi, [tau_2]) == 1 let lhs = P::pairing(pi, vk.tau_2.into_group() - (vk.g2 * x_challenge)); let rhs = P::pairing(C_zeta_Z, vk.tau_N_max_sub_2_N); - lhs == rhs + Ok(lhs == rhs) } } #[cfg(test)] mod test { use super::*; - use crate::utils::math::Math; - use ark_bn254::{Bn254, Fq, Fr, G1Affine, G1Projective}; + use crate::utils::{math::Math, transcript}; + use ark_bn254::{Bn254, Fr}; use ark_ff::{BigInt, Zero}; use ark_std::{test_rng, UniformRand}; @@ -497,6 +501,31 @@ mod test { }) } + fn execute_zeromorph(num_polys: usize) -> bool { + const N: usize = 64; + let log_N = N.log_2(); + + let mut rng = test_rng(); + let polys: Vec> = (0..num_polys).map(|_| { + DensePolynomial::new((0..N).map(|_| Fr::rand(&mut rng)).collect::>()) + }).collect::>(); + let challenges = (0..log_N) + .into_iter() + .map(|_| Fr::rand(&mut rng)) + .collect::>(); + let evals = polys.clone().into_iter().map(|poly| poly.evaluate(&challenges)).collect::>(); + + let srs = ZEROMORPH_SRS.lock().unwrap(); + let pk = srs.get_prover_key(); + let vk = srs.get_verifier_key(); + let mut prover_transcript = Transcript::new(b"example"); + let mut verifier_transcript = Transcript::new(b"example"); + let commitments = Zeromorph::::commit(&polys.clone(), &pk).unwrap(); + + let proof = Zeromorph::::prove(&polys, &evals, &challenges, &pk, &mut prover_transcript).unwrap(); + Zeromorph::::verify(&commitments, &evals, &challenges, &vk, &mut verifier_transcript, proof).unwrap() + } + /// Test for computing qk given multilinear f /// Given ๐‘“(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚™โ‚‹โ‚), and `(๐‘ข, ๐‘ฃ)` such that \f(\u) = \v, compute `qโ‚–(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚–โ‚‹โ‚)` /// such that the following identity holds: @@ -520,7 +549,7 @@ mod test { // Compute multilinear quotients `qโ‚–(๐‘‹โ‚€, โ€ฆ, ๐‘‹โ‚–โ‚‹โ‚)` let (quotients, constant_term) = - Zeromorph::::compute_multilinear_quotients(&multilinear_f, &u_challenge); + compute_multilinear_quotients::(&multilinear_f, &u_challenge); // Assert the constant term is equal to v_evaluation assert_eq!( @@ -551,7 +580,7 @@ mod test { /// ฬ‚q = โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, ๐‘‘โ‚– = deg(ฬ‚q), ๐‘š = ๐‘ #[test] fn batched_lifted_degree_quotient() { - const N: u64 = 8u64; + const N: usize = 8; // Define mock qโ‚– with deg(qโ‚–) = 2แตโปยน let data_0 = vec![Fr::one()]; @@ -572,7 +601,7 @@ mod test { //Compute batched quptient ฬ‚q let batched_quotient = - Zeromorph::::compute_batched_lifted_degree_quotient("ients, &y_challenge); + compute_batched_lifted_degree_quotient::("ients, &y_challenge); //Explicitly define q_k_lifted = X^{N-2^k} * q_k and compute the expected batched result //Note: we've hard programmed in the size of these vectors not the best practice @@ -638,7 +667,7 @@ mod test { /// ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N #[test] fn partially_evaluated_quotient_zeta() { - const N: u64 = 8u64; + const N: usize = 8; // Define mock qโ‚– with deg(qโ‚–) = 2แตโปยน let data_0 = vec![Fr::one()]; @@ -659,13 +688,13 @@ mod test { //Compute batched quptient ฬ‚q let batched_quotient = - Zeromorph::::compute_batched_lifted_degree_quotient("ients, &y_challenge); + 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 zeta_x = Zeromorph::::compute_partially_evaluated_degree_check_polynomial( + let zeta_x = compute_partially_evaluated_degree_check_polynomial::( &batched_quotient, "ients, &y_challenge, @@ -743,7 +772,7 @@ mod test { /// ๐‘โ‚“ = ฬ‚๐‘“ โˆ’ ๐‘ฃ โˆ‘โ‚–โ‚Œโ‚€โฟโปยน(๐‘ฅยฒ^แต๐›ทโ‚™โ‚‹โ‚–โ‚‹โ‚(๐‘ฅแตโบยน)โˆ’ ๐‘ขโ‚–๐›ทโ‚™โ‚‹โ‚–(๐‘ฅยฒ^แต)) ฬ‚qโ‚– #[test] fn partially_evaluated_quotient_z_x() { - const N: u64 = 8u64; + const N: usize = 8; let log_N = (N as usize).log_2(); // Construct a random multilinear polynomial f, and (u,v) such that f(u) = v. @@ -787,7 +816,7 @@ mod test { let x_challenge = Fr::rand(&mut rng); // Construct Z_x using the prover method - let Z_x = Zeromorph::::compute_partially_evaluated_zeromorph_identity_polynomial( + let Z_x = compute_partially_evaluated_zeromorph_identity_polynomial::( &f_batched, "ients, &v_evaluation, @@ -821,11 +850,11 @@ mod test { #[test] fn prove_verify_single() { - todo!() + assert!(execute_zeromorph(1)); } #[test] fn prove_and_verify_batched() { - todo!() + assert!(execute_zeromorph(10)); } } From 8444a6e121d5915287b98398e0d010a65d52a48d Mon Sep 17 00:00:00 2001 From: PatStiles Date: Tue, 28 Nov 2023 17:41:02 -0600 Subject: [PATCH 12/21] fmt --- src/poly/unipoly.rs | 5 +- src/subprotocols/mod.rs | 2 +- src/subprotocols/traits.rs | 58 +- src/subprotocols/zeromorph/data_structures.rs | 191 ++++--- src/subprotocols/zeromorph/zeromorph.rs | 532 +++++++++--------- 5 files changed, 407 insertions(+), 381 deletions(-) diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index 68c58b0e5..bbba6d228 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -256,8 +256,7 @@ mod tests { 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 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); @@ -267,7 +266,7 @@ mod tests { src.push(poly.evaluate(&val)); } let res = interpolate(&src, &x); - + for i in 0..poly.len() { assert_eq!(res[i], poly[i]); } diff --git a/src/subprotocols/mod.rs b/src/subprotocols/mod.rs index 241712af5..47ba4c0dc 100644 --- a/src/subprotocols/mod.rs +++ b/src/subprotocols/mod.rs @@ -6,5 +6,5 @@ mod zk; pub mod dot_product; pub mod grand_product; pub mod sumcheck; +pub mod traits; pub mod zeromorph; -pub mod traits; \ No newline at end of file diff --git a/src/subprotocols/traits.rs b/src/subprotocols/traits.rs index 2cfef9657..59050dbb1 100644 --- a/src/subprotocols/traits.rs +++ b/src/subprotocols/traits.rs @@ -6,36 +6,36 @@ use merlin::Transcript; use crate::utils::transcript; pub trait CommitmentScheme { - type Commitment; - type Evaluation; - type Polynomial; - type Challenge; - type Proof; - type Error; + type Commitment; + type Evaluation; + type Polynomial; + type Challenge; + type Proof; + type Error; - type ProverKey; - type VerifierKey; + type ProverKey; + type VerifierKey; - //TODO: convert to impl IntoIterator - fn commit( - polys: &[Self::Polynomial], - ck: &Self::ProverKey - ) -> Result, Self::Error>; + //TODO: convert to impl IntoIterator + fn commit( + polys: &[Self::Polynomial], + ck: &Self::ProverKey, + ) -> Result, Self::Error>; - fn prove( - polys: &[Self::Polynomial], - evals: &[Self::Evaluation], - challenges: &[Self::Challenge], - pk: impl Borrow, - transcript: &mut Transcript - ) -> Result; + fn prove( + polys: &[Self::Polynomial], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + pk: impl Borrow, + transcript: &mut Transcript, + ) -> Result; - fn verify( - commitments: &[Self::Commitment], - evals: &[Self::Evaluation], - challenges: &[Self::Challenge], - vk: impl Borrow, - transcript: &mut Transcript, - proof: Self::Proof, - ) -> Result; -} \ No newline at end of file + fn verify( + commitments: &[Self::Commitment], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + vk: impl Borrow, + transcript: &mut Transcript, + proof: Self::Proof, + ) -> Result; +} diff --git a/src/subprotocols/zeromorph/data_structures.rs b/src/subprotocols/zeromorph/data_structures.rs index 22da35478..2449cbbd2 100644 --- a/src/subprotocols/zeromorph/data_structures.rs +++ b/src/subprotocols/zeromorph/data_structures.rs @@ -1,7 +1,7 @@ use ark_bn254::Bn254; use ark_ec::{pairing::Pairing, CurveGroup}; -use ark_std::rand::rngs::StdRng; use ark_ff::UniformRand; +use ark_std::rand::rngs::StdRng; use lazy_static::lazy_static; use rand_chacha::rand_core::SeedableRng; use std::sync::{Arc, Mutex}; @@ -11,61 +11,71 @@ use crate::utils::math::Math; //TODO: The SRS is set with a default value of ____ if this is to be changed (extended) use the cli arg and change it manually. //TODO: add input specifiying monomial or lagrange basis lazy_static! { - pub static ref ZEROMORPH_SRS: Arc>> = + pub static ref ZEROMORPH_SRS: Arc>> = Arc::new(Mutex::new(ZeromorphSRS::setup(None))); } #[derive(Debug, Clone, Default)] pub struct ZeromorphSRS { - g1_powers: Vec, - g2_powers: Vec, + g1_powers: Vec, + g2_powers: Vec, } impl ZeromorphSRS { - - fn compute_g_powers(tau: G::ScalarField) -> Vec { - let g_srs = vec![G::zero(); N_MAX - 1]; - - let g_srs: Vec = std::iter::once(G::generator()) - .chain(g_srs.iter().scan(G::generator(), |state, _| { - *state *= τ - Some(*state) - })) - .collect(); - - G::normalize_batch(&g_srs) + fn compute_g_powers(tau: G::ScalarField) -> Vec { + let g_srs = vec![G::zero(); N_MAX - 1]; + + let g_srs: Vec = std::iter::once(G::generator()) + .chain(g_srs.iter().scan(G::generator(), |state, _| { + *state *= τ + Some(*state) + })) + .collect(); + + G::normalize_batch(&g_srs) + } + + pub fn setup(toxic_waste: Option<&[u8]>) -> ZeromorphSRS { + let tau: &[u8]; + if toxic_waste.is_none() { + tau = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + } else { + tau = toxic_waste.unwrap() } - - pub fn setup(toxic_waste: Option<&[u8]>) -> ZeromorphSRS { - let tau: &[u8]; - if toxic_waste.is_none() { - tau = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - } else { - tau = toxic_waste.unwrap() - } - /* - if ENV_VAR_NOT_PASSED_IN - */ - let mut bytes = [0u8; 32]; - let len = tau.len(); - bytes[..len].copy_from_slice(&tau[..len]); - let rng = &mut StdRng::from_seed(bytes); - - let tau = P::ScalarField::rand(rng); - let g1_powers = Self::compute_g_powers::(tau); - let g2_powers = Self::compute_g_powers::(tau); - ZeromorphSRS { g1_powers, g2_powers } + /* + if ENV_VAR_NOT_PASSED_IN + */ + let mut bytes = [0u8; 32]; + let len = tau.len(); + bytes[..len].copy_from_slice(&tau[..len]); + let rng = &mut StdRng::from_seed(bytes); + + let tau = P::ScalarField::rand(rng); + let g1_powers = Self::compute_g_powers::(tau); + let g2_powers = Self::compute_g_powers::(tau); + ZeromorphSRS { + g1_powers, + g2_powers, } + } - pub fn get_prover_key(&self) -> ZeromorphProverKey

{ - ZeromorphProverKey { g1: self.g1_powers[0], tau_1: self.g1_powers[1], g1_powers: self.g1_powers.clone() } + pub fn get_prover_key(&self) -> ZeromorphProverKey

{ + ZeromorphProverKey { + g1: self.g1_powers[0], + tau_1: self.g1_powers[1], + g1_powers: self.g1_powers.clone(), } - - pub fn get_verifier_key(&self) -> ZeromorphVerifierKey

{ - let idx = N_MAX - (2_usize.pow(N_MAX.log_2() as u32) - 1); - ZeromorphVerifierKey { g1: self.g1_powers[0], g2: self.g2_powers[0], tau_2: self.g2_powers[1], tau_N_max_sub_2_N: self.g2_powers[idx] } + } + + pub fn get_verifier_key(&self) -> ZeromorphVerifierKey

{ + let idx = N_MAX - (2_usize.pow(N_MAX.log_2() as u32) - 1); + ZeromorphVerifierKey { + g1: self.g1_powers[0], + g2: self.g2_powers[0], + tau_2: self.g2_powers[1], + tau_N_max_sub_2_N: self.g2_powers[idx], } - + } } #[derive(Clone, Debug)] @@ -94,51 +104,50 @@ pub struct ZeromorphProof { #[cfg(test)] mod test { - use ark_bn254::Bn254; - use ark_ec::{pairing::Pairing, AffineRepr}; - use ark_ff::One; - use std::ops::Mul; - use super::*; - - fn expected_srs(n: usize, seed: &[u8]) -> (Vec, Vec) { - - let mut bytes = [0u8; 32]; - let len = seed.len(); - bytes[..len].copy_from_slice(&seed[..len]); - let rng = &mut StdRng::from_seed(bytes); - - let tau = E::ScalarField::rand(rng); - - let powers_of_tau: Vec = - std::iter::successors(Some(E::ScalarField::one()), |p| Some(*p * tau)) - .take(n) - .collect(); - - let g1_gen = E::G1Affine::generator(); - let g2_gen = E::G2Affine::generator(); - - let srs_g1: Vec = powers_of_tau - .iter() - .map(|tp| g1_gen.mul(tp).into()) - .collect(); - let srs_g2: Vec = powers_of_tau - .iter() - .map(|tp| g2_gen.mul(tp).into()) - .collect(); - - (srs_g1, srs_g2) - } - - #[test] - fn test_srs() { - const K: i32 = 1; - const N: usize = 1 << K; - let seed = b"111111111111111111111111111"; - - let (g1_srs_expected, g2_srs_expected) = expected_srs::(N, seed); - - let srs = ZeromorphSRS::::setup(Some(seed)); - assert_eq!(g1_srs_expected, srs.g1_powers); - assert_eq!(g2_srs_expected, srs.g2_powers); - } -} \ No newline at end of file + use super::*; + use ark_bn254::Bn254; + use ark_ec::{pairing::Pairing, AffineRepr}; + use ark_ff::One; + use std::ops::Mul; + + fn expected_srs(n: usize, seed: &[u8]) -> (Vec, Vec) { + let mut bytes = [0u8; 32]; + let len = seed.len(); + bytes[..len].copy_from_slice(&seed[..len]); + let rng = &mut StdRng::from_seed(bytes); + + let tau = E::ScalarField::rand(rng); + + let powers_of_tau: Vec = + std::iter::successors(Some(E::ScalarField::one()), |p| Some(*p * tau)) + .take(n) + .collect(); + + let g1_gen = E::G1Affine::generator(); + let g2_gen = E::G2Affine::generator(); + + let srs_g1: Vec = powers_of_tau + .iter() + .map(|tp| g1_gen.mul(tp).into()) + .collect(); + let srs_g2: Vec = powers_of_tau + .iter() + .map(|tp| g2_gen.mul(tp).into()) + .collect(); + + (srs_g1, srs_g2) + } + + #[test] + fn test_srs() { + const K: i32 = 1; + const N: usize = 1 << K; + let seed = b"111111111111111111111111111"; + + let (g1_srs_expected, g2_srs_expected) = expected_srs::(N, seed); + + let srs = ZeromorphSRS::::setup(Some(seed)); + assert_eq!(g1_srs_expected, srs.g1_powers); + assert_eq!(g2_srs_expected, srs.g2_powers); + } +} diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index f308d54bc..22e4c90b9 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -3,14 +3,14 @@ use std::{borrow::Borrow, marker::PhantomData, ops::Neg}; -use crate::poly::unipoly::UniPoly; 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_std::{One, Zero, iterable::Iterable}; +use ark_std::{iterable::Iterable, One, Zero}; use merlin::Transcript; -use crate::subprotocols::traits::CommitmentScheme; use thiserror::Error; #[cfg(feature = "ark-msm")] @@ -22,259 +22,260 @@ use crate::msm::VariableBaseMSM; #[cfg(feature = "multicore")] use rayon::prelude::*; -use super::data_structures::{ZeromorphProverKey, ZeromorphVerifierKey, ZeromorphProof, ZEROMORPH_SRS}; - - // Just return vec of P::Scalar - fn compute_multilinear_quotients( - poly: &DensePolynomial, - u_challenge: &[P::ScalarField], - ) -> (Vec>, P::ScalarField) { - assert_eq!(poly.get_num_vars(), u_challenge.len()); - - let mut g = poly.Z.to_vec(); - let mut quotients = u_challenge - .iter() - .enumerate() - .map(|(i, x_i)| { - let (g_lo, g_hi) = g.split_at_mut(1 << (poly.get_num_vars() - 1 - i)); - let mut quotient = vec![P::ScalarField::zero(); g_lo.len()]; - - quotient - .par_iter_mut() - .zip(&*g_lo) - .zip(&*g_hi) - .for_each(|((mut 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 += (*g_hi - g_lo as &_) * x_i; +use super::data_structures::{ + ZeromorphProof, ZeromorphProverKey, ZeromorphVerifierKey, ZEROMORPH_SRS, +}; + +// Just return vec of P::Scalar +fn compute_multilinear_quotients( + poly: &DensePolynomial, + u_challenge: &[P::ScalarField], +) -> (Vec>, P::ScalarField) { + assert_eq!(poly.get_num_vars(), u_challenge.len()); + + let mut g = poly.Z.to_vec(); + let mut quotients = u_challenge + .iter() + .enumerate() + .map(|(i, x_i)| { + let (g_lo, g_hi) = g.split_at_mut(1 << (poly.get_num_vars() - 1 - i)); + let mut quotient = vec![P::ScalarField::zero(); g_lo.len()]; + + quotient + .par_iter_mut() + .zip(&*g_lo) + .zip(&*g_hi) + .for_each(|((mut 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 += (*g_hi - g_lo as &_) * x_i; + }); - g.truncate(1 << (poly.get_num_vars() - 1 - i)); + g.truncate(1 << (poly.get_num_vars() - 1 - i)); - UniPoly::from_coeff(quotient) - }) - .collect::>>(); - quotients.reverse(); - (quotients, g[0]) - } + UniPoly::from_coeff(quotient) + }) + .collect::>>(); + quotients.reverse(); + (quotients, g[0]) +} - fn compute_batched_lifted_degree_quotient( - quotients: &Vec>, - y_challenge: &P::ScalarField, - ) -> UniPoly { - // Batched Lifted Degreee Quotient Polynomials - let mut res: Vec = vec![P::ScalarField::zero(); N as usize]; - - // 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() { - // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - - // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 - let deg_k = (1 << k) as usize - 1; - let offset = N as usize - deg_k - 1; - for i in 0..(deg_k + 1) { - res[offset + i] += scalar * quotient[i]; - } - scalar *= y_challenge; // update batching scalar y^k +fn compute_batched_lifted_degree_quotient( + quotients: &Vec>, + y_challenge: &P::ScalarField, +) -> UniPoly { + // Batched Lifted Degreee Quotient Polynomials + let mut res: Vec = vec![P::ScalarField::zero(); N as usize]; + + // 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() { + // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - + // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 + let deg_k = (1 << k) as usize - 1; + let offset = N as usize - deg_k - 1; + for i in 0..(deg_k + 1) { + res[offset + i] += scalar * quotient[i]; } - - UniPoly::from_coeff(res) + scalar *= y_challenge; // update batching scalar y^k } - fn compute_partially_evaluated_degree_check_polynomial( - batched_quotient: &UniPoly, - quotients: &Vec>, - y_challenge: &P::ScalarField, - x_challenge: &P::ScalarField, - ) -> UniPoly { - let n = batched_quotient.len(); - let log_N = quotients.len(); - - // initialize partially evaluated degree check polynomial \zeta_x to \hat{q} - let mut res = batched_quotient.clone(); - - let mut y_power = P::ScalarField::one(); - for k in 0..log_N { - // Accumulate y^k * x^{N - d_k - 1} * q_k into \hat{q} - let deg_k = (1 << k) as usize - 1; - let x_power = x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); - - // Add poly and scale -> Note this can be parallelized - // See -> https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/honk/pcs/zeromorph/zeromorph.hpp#L173 - // https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/polynomials/polynomial.cpp#L332 - // res += quotient[i] * (-y_power * x_power) - for i in 0..quotients[k].len() { - res[i] += quotients[k][i] * (-y_power * x_power); - } + UniPoly::from_coeff(res) +} - y_power *= y_challenge; // updated batching scalar y^k +fn compute_partially_evaluated_degree_check_polynomial( + batched_quotient: &UniPoly, + quotients: &Vec>, + y_challenge: &P::ScalarField, + x_challenge: &P::ScalarField, +) -> UniPoly { + let n = batched_quotient.len(); + let log_N = quotients.len(); + + // initialize partially evaluated degree check polynomial \zeta_x to \hat{q} + let mut res = batched_quotient.clone(); + + let mut y_power = P::ScalarField::one(); + for k in 0..log_N { + // Accumulate y^k * x^{N - d_k - 1} * q_k into \hat{q} + let deg_k = (1 << k) as usize - 1; + let x_power = x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); + + // Add poly and scale -> Note this can be parallelized + // See -> https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/honk/pcs/zeromorph/zeromorph.hpp#L173 + // https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/polynomials/polynomial.cpp#L332 + // res += quotient[i] * (-y_power * x_power) + for i in 0..quotients[k].len() { + res[i] += quotients[k][i] * (-y_power * x_power); } - res + y_power *= y_challenge; // updated batching scalar y^k } - fn compute_partially_evaluated_zeromorph_identity_polynomial( - f_batched: &UniPoly, - //g_batched: &UniPoly, - quotients: &Vec>, - v_evaluation: &P::ScalarField, - u_challenge: &[P::ScalarField], - x_challenge: &P::ScalarField, - ) -> UniPoly { - let n = f_batched.len(); - let log_N = quotients.len(); - - //Question for non-shifted can we exclude sum_{i=0}^{l-i} - // Initialize Z_x with x * \sum_{i=0}^{m-1} f_i + /sum_{i=0}^{l-i} * g_i - //let mut res: UniPoly = g_batched.clone(); - - //add scaled - //for i in 0..res.len() { - // res[i] += f_batched[i] * x_challenge; - //} - - let mut res = f_batched.clone(); - - // Compute Z_x -= v * x * \Phi_n(x) - let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 - let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); - res[0] -= *v_evaluation * *x_challenge * phi_n_x; - - //Add contribution from q_k polynomials - for k in 0..log_N { - let x_power = x_challenge.pow(BigInt::<1>::from((1 << k) as u64)); // x^{2^k} - - // \Phi_{n-k-1}(x^{2^{k + 1}}) - let phi_term_1 = phi_numerator - / (x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)) - P::ScalarField::one()); - - // \Phi_{n-k}(x^{2^k}) - let phi_term_2 = phi_numerator - / (x_challenge.pow(BigInt::<1>::from((1 << k) as u64)) - P::ScalarField::one()); - - // x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k}) - let mut scalar = x_power * phi_term_1 - u_challenge[k] * phi_term_2; - - scalar *= x_challenge; - scalar *= -P::ScalarField::one(); + res +} - for i in 0..quotients[k].len() { - res[i] += quotients[k][i] * scalar; - } +fn compute_partially_evaluated_zeromorph_identity_polynomial( + f_batched: &UniPoly, + //g_batched: &UniPoly, + quotients: &Vec>, + v_evaluation: &P::ScalarField, + u_challenge: &[P::ScalarField], + x_challenge: &P::ScalarField, +) -> UniPoly { + let n = f_batched.len(); + let log_N = quotients.len(); + + //Question for non-shifted can we exclude sum_{i=0}^{l-i} + // Initialize Z_x with x * \sum_{i=0}^{m-1} f_i + /sum_{i=0}^{l-i} * g_i + //let mut res: UniPoly = g_batched.clone(); + + //add scaled + //for i in 0..res.len() { + // res[i] += f_batched[i] * x_challenge; + //} + + let mut res = f_batched.clone(); + + // Compute Z_x -= v * x * \Phi_n(x) + let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 + let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); + res[0] -= *v_evaluation * *x_challenge * phi_n_x; + + //Add contribution from q_k polynomials + for k in 0..log_N { + let x_power = x_challenge.pow(BigInt::<1>::from((1 << k) as u64)); // x^{2^k} + + // \Phi_{n-k-1}(x^{2^{k + 1}}) + let phi_term_1 = phi_numerator + / (x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)) - P::ScalarField::one()); + + // \Phi_{n-k}(x^{2^k}) + let phi_term_2 = + phi_numerator / (x_challenge.pow(BigInt::<1>::from((1 << k) as u64)) - P::ScalarField::one()); + + // x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k}) + let mut scalar = x_power * phi_term_1 - u_challenge[k] * phi_term_2; + + scalar *= x_challenge; + scalar *= -P::ScalarField::one(); + + for i in 0..quotients[k].len() { + res[i] += quotients[k][i] * scalar; } - res } + res +} - //TODO: Need SRS - fn compute_batched_evaluation_and_degree_check_quotient( - zeta_x: UniPoly, - z_x: UniPoly, - x_challenge: P::ScalarField, - z_challenge: P::ScalarField, - ) -> UniPoly { - // We cannot commit to polynomials with size > N_max - let n = zeta_x.len(); - assert!(n <= N as usize); - - //Compute quotient polynomials q_{\zeta} and q_Z - - //q_{\zeta} = \zeta_x / (X-x) - //q_z = Z_x / (X - x) - //TODO: remove these clones - let mut q_zeta_x = zeta_x.clone(); - let mut q_z_x = z_x.clone(); - q_zeta_x.factor_roots(&x_challenge); - q_z_x.factor_roots(&x_challenge); - - // Compute batched quotient q_{\zeta} + z*q_Z in place - let mut batched_quotient = zeta_x; - for i in 0..batched_quotient.len() { - batched_quotient[i] += z_x[i] * z_challenge; - } - - batched_quotient +//TODO: Need SRS +fn compute_batched_evaluation_and_degree_check_quotient( + zeta_x: UniPoly, + z_x: UniPoly, + x_challenge: P::ScalarField, + z_challenge: P::ScalarField, +) -> UniPoly { + // We cannot commit to polynomials with size > N_max + let n = zeta_x.len(); + assert!(n <= N as usize); + + //Compute quotient polynomials q_{\zeta} and q_Z + + //q_{\zeta} = \zeta_x / (X-x) + //q_z = Z_x / (X - x) + //TODO: remove these clones + let mut q_zeta_x = zeta_x.clone(); + let mut q_z_x = z_x.clone(); + q_zeta_x.factor_roots(&x_challenge); + q_z_x.factor_roots(&x_challenge); + + // Compute batched quotient q_{\zeta} + z*q_Z in place + let mut batched_quotient = zeta_x; + for i in 0..batched_quotient.len() { + batched_quotient[i] += z_x[i] * z_challenge; } - fn compute_C_zeta_x( - q_hat_com: &P::G1, - q_k_com: &Vec, - y_challenge: &P::ScalarField, - x_challenge: &P::ScalarField, - ) -> P::G1 { - let n = 1 << q_k_com.len(); - - let one = P::ScalarField::one(); - let mut scalars = vec![one]; - let mut commitments = vec![q_hat_com.into_affine()]; - - for (i, com) in q_k_com.iter().enumerate() { - let deg_k = (1 << i) - 1; - // Compute scalar y^k * x^{N - deg_k - 1} - let mut scalar = y_challenge.pow(BigInt::<1>::from(i as u64)); - scalar *= x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); - scalar *= P::ScalarField::one().neg(); - scalars.push(scalar); - commitments.push(*com); - } + batched_quotient +} - ::msm(&commitments, &scalars).unwrap() +fn compute_C_zeta_x( + q_hat_com: &P::G1, + q_k_com: &Vec, + y_challenge: &P::ScalarField, + x_challenge: &P::ScalarField, +) -> P::G1 { + let n = 1 << q_k_com.len(); + + let one = P::ScalarField::one(); + let mut scalars = vec![one]; + let mut commitments = vec![q_hat_com.into_affine()]; + + for (i, com) in q_k_com.iter().enumerate() { + let deg_k = (1 << i) - 1; + // Compute scalar y^k * x^{N - deg_k - 1} + let mut scalar = y_challenge.pow(BigInt::<1>::from(i as u64)); + scalar *= x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); + scalar *= P::ScalarField::one().neg(); + scalars.push(scalar); + commitments.push(*com); } - fn compute_C_Z_x( - f_commitments: &[P::G1], - q_k_com: &[P::G1Affine], - rho: &P::ScalarField, - batched_evaluation: &P::ScalarField, - x_challenge: &P::ScalarField, - u_challenge: &[P::ScalarField], - g1: &P::G1Affine, - ) -> P::G1 { - let n = 1 < q_k_com.len(); - - // Phi_n(x) = (x^N - 1) / (x - 1) - let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 - let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); - - // Add: -v * x * \Phi_n(x) * [1]_1 - let mut scalars = - vec![*batched_evaluation * x_challenge * phi_n_x * P::ScalarField::one().neg()]; - let mut commitments = vec![*g1]; - - // Add x * \sum_{i=0}^{m-1} \rho^i*[f_i] - let mut rho_pow = P::ScalarField::one(); - for com in f_commitments { - scalars.push(*x_challenge * rho_pow); - commitments.push(com.into_affine()); - rho_pow *= rho; - } + ::msm(&commitments, &scalars).unwrap() +} - // Add: scalar * [q_k], k = 0, ..., log_N, where - // scalar = -x * (x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k})) - let mut x_pow_2k = *x_challenge; // x^{2^k} - let mut x_pow_2kp1 = *x_challenge * x_challenge; // x^{2^{k + 1}} +fn compute_C_Z_x( + f_commitments: &[P::G1], + q_k_com: &[P::G1Affine], + rho: &P::ScalarField, + batched_evaluation: &P::ScalarField, + x_challenge: &P::ScalarField, + u_challenge: &[P::ScalarField], + g1: &P::G1Affine, +) -> P::G1 { + let n = 1 < q_k_com.len(); + + // Phi_n(x) = (x^N - 1) / (x - 1) + let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 + let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); + + // Add: -v * x * \Phi_n(x) * [1]_1 + let mut scalars = vec![*batched_evaluation * x_challenge * phi_n_x * P::ScalarField::one().neg()]; + let mut commitments = vec![*g1]; + + // Add x * \sum_{i=0}^{m-1} \rho^i*[f_i] + let mut rho_pow = P::ScalarField::one(); + for com in f_commitments { + scalars.push(*x_challenge * rho_pow); + commitments.push(com.into_affine()); + rho_pow *= rho; + } - for k in 0..q_k_com.len() { - let phi_term_1 = phi_numerator / (x_pow_2kp1 - P::ScalarField::one()); // \Phi_{n-k-1}(x^{2^{k + 1}}) - let phi_term_2 = phi_numerator / (x_pow_2k - P::ScalarField::one()); // \Phi_{n-k-1}(x^{2^k}) + // Add: scalar * [q_k], k = 0, ..., log_N, where + // scalar = -x * (x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k})) + let mut x_pow_2k = *x_challenge; // x^{2^k} + let mut x_pow_2kp1 = *x_challenge * x_challenge; // x^{2^{k + 1}} - let mut scalar = x_pow_2k * phi_term_1; - scalar -= u_challenge[k] * phi_term_2; - scalar *= x_challenge; - scalar *= P::ScalarField::one().neg(); + for k in 0..q_k_com.len() { + let phi_term_1 = phi_numerator / (x_pow_2kp1 - P::ScalarField::one()); // \Phi_{n-k-1}(x^{2^{k + 1}}) + let phi_term_2 = phi_numerator / (x_pow_2k - P::ScalarField::one()); // \Phi_{n-k-1}(x^{2^k}) - scalars.push(scalar); - commitments.push(q_k_com[k]); + let mut scalar = x_pow_2k * phi_term_1; + scalar -= u_challenge[k] * phi_term_2; + scalar *= x_challenge; + scalar *= P::ScalarField::one().neg(); - // update powers of challenge x - x_pow_2k = x_pow_2kp1; - x_pow_2kp1 *= x_pow_2kp1; - } + scalars.push(scalar); + commitments.push(q_k_com[k]); - ::msm(&commitments, &scalars).unwrap() + // update powers of challenge x + x_pow_2k = x_pow_2kp1; + x_pow_2kp1 *= x_pow_2kp1; } + ::msm(&commitments, &scalars).unwrap() +} + #[derive(Error, Debug)] pub enum ZeromorphError { #[error("oh no {0}")] @@ -288,29 +289,34 @@ pub struct Zeromorph { /// Compute the powers of a challenge /// impl CommitmentScheme for Zeromorph { - type Commitment = P::G1; - type Evaluation = P::ScalarField; - type Polynomial = DensePolynomial; - type Challenge = P::ScalarField; - type Proof = ZeromorphProof

; - type Error = ZeromorphError; + type Commitment = P::G1; + type Evaluation = P::ScalarField; + type Polynomial = DensePolynomial; + type Challenge = P::ScalarField; + type Proof = ZeromorphProof

; + type Error = ZeromorphError; - type ProverKey = ZeromorphProverKey

; - type VerifierKey = ZeromorphVerifierKey

; + type ProverKey = ZeromorphProverKey

; + type VerifierKey = ZeromorphVerifierKey

; fn commit( polys: &[Self::Polynomial], - pk: &Self::ProverKey + pk: &Self::ProverKey, ) -> Result, Self::Error> { - Ok(polys.into_iter().map(|poly| ::msm(&pk.g1_powers, &poly.Z).unwrap()).collect::>()) + Ok( + polys + .into_iter() + .map(|poly| ::msm(&pk.g1_powers, &poly.Z).unwrap()) + .collect::>(), + ) } fn prove( - polys: &[Self::Polynomial], - evals: &[Self::Evaluation], - challenges: &[Self::Challenge], - pk: impl Borrow, - transcript: &mut Transcript + polys: &[Self::Polynomial], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + pk: impl Borrow, + transcript: &mut Transcript, ) -> Result { // ASSERT evaluations, challenges, and polynomials are the same size assert_eq!(evals.len(), challenges.len()); @@ -345,10 +351,8 @@ impl CommitmentScheme for Zeromorph { let f_polynomial = UniPoly::from_coeff(f_batched.clone()); // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) - let (quotients, _) = compute_multilinear_quotients::

( - &DensePolynomial::new(f_batched.clone()), - &challenges, - ); + let (quotients, _) = + compute_multilinear_quotients::

(&DensePolynomial::new(f_batched.clone()), &challenges); // Compute and send commitments C_{q_k} = [q_k], k = 0, ..., d-1 let label = b"q_k_commitments"; @@ -380,7 +384,7 @@ impl CommitmentScheme for Zeromorph { >::challenge_scalar(transcript, b"ZM: z"); // Compute degree check polynomials \zeta partially evaluated at x - let zeta_x = compute_partially_evaluated_degree_check_polynomial::( + let zeta_x = compute_partially_evaluated_degree_check_polynomial::( &q_hat, "ients, &y_challenge, @@ -388,7 +392,7 @@ impl CommitmentScheme for Zeromorph { ); // Compute Zeromorph identity polynomial Z partially evaluated at x - let Z_x = compute_partially_evaluated_zeromorph_identity_polynomial::( + let Z_x = compute_partially_evaluated_zeromorph_identity_polynomial::( &f_polynomial, "ients, &batched_evaluation, @@ -397,7 +401,7 @@ impl CommitmentScheme for Zeromorph { ); // Compute batched degree-check and ZM-identity quotient polynomial pi - let pi_poly = compute_batched_evaluation_and_degree_check_quotient::( + let pi_poly = compute_batched_evaluation_and_degree_check_quotient::( zeta_x, Z_x, x_challenge, @@ -460,8 +464,8 @@ impl CommitmentScheme for Zeromorph { let z_challenge = >::challenge_scalar(transcript, b"ZM: z"); - let C_zeta_x = compute_C_zeta_x::(&q_hat_com, &q_k_com, &y_challenge, &x_challenge); - let C_Z_x = compute_C_Z_x::( + let C_zeta_x = compute_C_zeta_x::(&q_hat_com, &q_k_com, &y_challenge, &x_challenge); + let C_Z_x = compute_C_Z_x::( commitments, &q_k_com, &rho, @@ -506,14 +510,18 @@ mod test { let log_N = N.log_2(); let mut rng = test_rng(); - let polys: Vec> = (0..num_polys).map(|_| { - DensePolynomial::new((0..N).map(|_| Fr::rand(&mut rng)).collect::>()) - }).collect::>(); + let polys: Vec> = (0..num_polys) + .map(|_| DensePolynomial::new((0..N).map(|_| Fr::rand(&mut rng)).collect::>())) + .collect::>(); let challenges = (0..log_N) .into_iter() .map(|_| Fr::rand(&mut rng)) .collect::>(); - let evals = polys.clone().into_iter().map(|poly| poly.evaluate(&challenges)).collect::>(); + let evals = polys + .clone() + .into_iter() + .map(|poly| poly.evaluate(&challenges)) + .collect::>(); let srs = ZEROMORPH_SRS.lock().unwrap(); let pk = srs.get_prover_key(); @@ -522,8 +530,18 @@ mod test { let mut verifier_transcript = Transcript::new(b"example"); let commitments = Zeromorph::::commit(&polys.clone(), &pk).unwrap(); - let proof = Zeromorph::::prove(&polys, &evals, &challenges, &pk, &mut prover_transcript).unwrap(); - Zeromorph::::verify(&commitments, &evals, &challenges, &vk, &mut verifier_transcript, proof).unwrap() + let proof = + Zeromorph::::prove(&polys, &evals, &challenges, &pk, &mut prover_transcript) + .unwrap(); + Zeromorph::::verify( + &commitments, + &evals, + &challenges, + &vk, + &mut verifier_transcript, + proof, + ) + .unwrap() } /// Test for computing qk given multilinear f From 2b256e85df1ad3a78a7a93e8778208a99efc18e1 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Tue, 28 Nov 2023 17:49:02 -0600 Subject: [PATCH 13/21] clippy --- src/subprotocols/traits.rs | 3 --- src/subprotocols/zeromorph/data_structures.rs | 9 ++++----- src/subprotocols/zeromorph/zeromorph.rs | 20 ++++++++++--------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/subprotocols/traits.rs b/src/subprotocols/traits.rs index 59050dbb1..7fd0217ca 100644 --- a/src/subprotocols/traits.rs +++ b/src/subprotocols/traits.rs @@ -1,10 +1,7 @@ use std::borrow::Borrow; -use ark_std::iterable::Iterable; use merlin::Transcript; -use crate::utils::transcript; - pub trait CommitmentScheme { type Commitment; type Evaluation; diff --git a/src/subprotocols/zeromorph/data_structures.rs b/src/subprotocols/zeromorph/data_structures.rs index 2449cbbd2..052ed2450 100644 --- a/src/subprotocols/zeromorph/data_structures.rs +++ b/src/subprotocols/zeromorph/data_structures.rs @@ -36,12 +36,11 @@ impl ZeromorphSRS { } pub fn setup(toxic_waste: Option<&[u8]>) -> ZeromorphSRS { - let tau: &[u8]; - if toxic_waste.is_none() { - tau = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + let tau: &[u8] = if toxic_waste.is_none() { + b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } else { - tau = toxic_waste.unwrap() - } + toxic_waste.unwrap() + }; /* if ENV_VAR_NOT_PASSED_IN */ diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index 22e4c90b9..f986c65fb 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -23,7 +23,7 @@ use crate::msm::VariableBaseMSM; use rayon::prelude::*; use super::data_structures::{ - ZeromorphProof, ZeromorphProverKey, ZeromorphVerifierKey, ZEROMORPH_SRS, + ZeromorphProof, ZeromorphProverKey, ZeromorphVerifierKey }; // Just return vec of P::Scalar @@ -67,7 +67,7 @@ fn compute_batched_lifted_degree_quotient( y_challenge: &P::ScalarField, ) -> UniPoly { // Batched Lifted Degreee Quotient Polynomials - let mut res: Vec = vec![P::ScalarField::zero(); N as usize]; + let mut res: Vec = vec![P::ScalarField::zero(); N]; // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k let mut scalar = P::ScalarField::one(); // y^k @@ -75,7 +75,7 @@ fn compute_batched_lifted_degree_quotient( // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 let deg_k = (1 << k) as usize - 1; - let offset = N as usize - deg_k - 1; + let offset = N - deg_k - 1; for i in 0..(deg_k + 1) { res[offset + i] += scalar * quotient[i]; } @@ -98,6 +98,7 @@ fn compute_partially_evaluated_degree_check_polynomial UniPoly { // We cannot commit to polynomials with size > N_max let n = zeta_x.len(); - assert!(n <= N as usize); + assert!(n <= N); //Compute quotient polynomials q_{\zeta} and q_Z @@ -305,7 +306,7 @@ impl CommitmentScheme for Zeromorph { ) -> Result, Self::Error> { Ok( polys - .into_iter() + .iter() .map(|poly| ::msm(&pk.g1_powers, &poly.Z).unwrap()) .collect::>(), ) @@ -352,12 +353,12 @@ impl CommitmentScheme for Zeromorph { // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) let (quotients, _) = - compute_multilinear_quotients::

(&DensePolynomial::new(f_batched.clone()), &challenges); + compute_multilinear_quotients::

(&DensePolynomial::new(f_batched.clone()), challenges); // Compute and send commitments C_{q_k} = [q_k], k = 0, ..., d-1 let label = b"q_k_commitments"; transcript.append_message(label, b"begin_append_vector"); - let q_k_commitments = (0..log_N).into_iter().fold(Vec::new(), |mut acc, i| { + let q_k_commitments = (0..log_N).fold(Vec::new(), |mut acc, i| { let q_k_commitment = ::msm(&pk.g1_powers, "ients[i].coeffs).unwrap(); transcript.append_point(label, &q_k_commitment); @@ -396,7 +397,7 @@ impl CommitmentScheme for Zeromorph { &f_polynomial, "ients, &batched_evaluation, - &challenges, + challenges, &x_challenge, ); @@ -488,10 +489,11 @@ impl CommitmentScheme for Zeromorph { #[cfg(test)] mod test { use super::*; - use crate::utils::{math::Math, transcript}; + use crate::{utils::math::Math, subprotocols::zeromorph::data_structures::ZEROMORPH_SRS}; use ark_bn254::{Bn254, Fr}; use ark_ff::{BigInt, Zero}; use ark_std::{test_rng, UniformRand}; + // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula fn phi(challenge: &P::ScalarField, subscript: usize) -> P::ScalarField { From cb1f33b87765e72d9238676e949017d4b357c036 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Tue, 28 Nov 2023 20:14:33 -0600 Subject: [PATCH 14/21] add factor_roots test --- src/poly/unipoly.rs | 153 +++++++++++++++++++++--- src/subprotocols/zeromorph/zeromorph.rs | 4 +- 2 files changed, 138 insertions(+), 19 deletions(-) diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index bbba6d228..ff89a2f7c 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -6,12 +6,12 @@ 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; +use ark_ff::{PrimeField, batch_inversion}; use ark_serialize::*; // ax^2 + bx + c stored as vec![c,b,a] // ax^3 + bx^2 + cx + d stored as vec![d,c,b,a] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct UniPoly { pub coeffs: Vec, } @@ -97,7 +97,7 @@ impl UniPoly { Commitments::batch_commit(&self.coeffs, blind, gens) } - pub fn factor_roots(&mut self, root: &F) -> UniPoly { + fn factor_root(&mut self, root: &F) -> UniPoly { let mut coeffs = self.coeffs.clone(); if root.is_zero() { coeffs.rotate_left(1); @@ -116,6 +116,76 @@ impl UniPoly { coeffs[self.coeffs.len() - 1] = F::zero(); UniPoly { coeffs } } + + 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()); + } + } + + // 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()) + } + + let zero_factored = self.coeffs[num_zero_roots..].to_vec(); + let num_non_zero_roots = minus_root_inverses.len(); + + if num_non_zero_roots > 0 { + batch_inversion(&mut minus_root_inverses); + let mut division_cache = vec![F::zero(); num_non_zero_roots]; + + let mut temp = zero_factored[0]; + for root in minus_root_inverses.clone() { + temp *= root; + division_cache.push(temp); + } + + //Note: we know this can't be 0 + self[0] = *division_cache.last().unwrap(); + + // 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; + } + + } else if num_zero_roots > 0 { + self.coeffs.rotate_left(1); + //YUCK!!! + self.coeffs = self.coeffs[..self.coeffs.len() - 1].to_vec(); + } + + // Clear last coefficient + for i in new_size..self.coeffs.len() { + self[i] = F::zero(); + } + + todo!() + } } impl CompressedUniPoly { @@ -174,7 +244,8 @@ mod tests { let numerator_polynomial = compute_linear_polynomial_product(&evals, points.len()); - let mut roots_and_denominators: Vec = vec![Fr::zero(); 2 * 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]; @@ -182,7 +253,7 @@ mod tests { // compute constant denominator roots_and_denominators[n + i] = Fr::one(); for j in 0..n { - if j == 1 { + if j == i { continue; } roots_and_denominators[n + i] *= evals[i] - evals[j]; @@ -197,7 +268,7 @@ mod tests { let mut mult; for i in 0..n { z = roots_and_denominators[i]; - mult = roots_and_denominators[n + i]; + mult = temp_src[i] * roots_and_denominators[n + i]; temp[0] = mult * numerator_polynomial[0]; temp[0] *= z; coeffs[0] += temp[0]; @@ -216,25 +287,33 @@ mod tests { 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::(); + 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) { - res[n - 2 - i] = - res[n - 2 - i] + roots[j] * roots[j + 1..].into_iter().take(n - 1 - i - j).sum::(); - temp = temp + res[n - 2 - 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 = constant.neg(); + 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; @@ -256,7 +335,10 @@ mod tests { 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 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); @@ -266,7 +348,7 @@ mod tests { src.push(poly.evaluate(&val)); } let res = interpolate(&src, &x); - + for i in 0..poly.len() { assert_eq!(res[i], poly[i]); } @@ -277,9 +359,9 @@ mod tests { let n = 32; let mut rng = test_rng(); - let test_case = |num_zero_roots: usize, num_non_zero_roots: usize| { + let mut test_case = |num_zero_roots: usize, num_non_zero_roots: usize| { let num_roots = num_non_zero_roots + num_zero_roots; - let poly = UniPoly::from_coeff((0..n).map(|_| Fr::rand(&mut rng)).collect::>()); + 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); @@ -290,7 +372,7 @@ mod tests { 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 = UniPoly::from_coeff((0..n).map(|_| Fr::zero()).collect::>()); + 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]; @@ -299,10 +381,47 @@ mod tests { 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); } - //TODO: + 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] diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index f986c65fb..168cb8c7b 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -188,8 +188,8 @@ fn compute_batched_evaluation_and_degree_check_quotient Date: Thu, 11 Jan 2024 23:30:16 -0600 Subject: [PATCH 15/21] 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); } } From 11435cae041b5cdb01f46970dbf2dd48d1ba28db Mon Sep 17 00:00:00 2001 From: PatStiles Date: Sun, 14 Jan 2024 21:03:09 -0600 Subject: [PATCH 16/21] batching zeromorph working --- src/lasso/densified.rs | 1 + src/lasso/surge.rs | 3 + src/poly/unipoly.rs | 6 +- src/subprotocols/traits.rs | 13 +- src/subprotocols/zeromorph/data_structures.rs | 156 ++--- src/subprotocols/zeromorph/kzg.rs | 284 ++++++++ src/subprotocols/zeromorph/mod.rs | 1 + src/subprotocols/zeromorph/zeromorph.rs | 638 ++++++++---------- 8 files changed, 633 insertions(+), 469 deletions(-) create mode 100644 src/subprotocols/zeromorph/kzg.rs diff --git a/src/lasso/densified.rs b/src/lasso/densified.rs index 50de39f75..69555381a 100644 --- a/src/lasso/densified.rs +++ b/src/lasso/densified.rs @@ -74,6 +74,7 @@ impl DensifiedRepresentation { } } + //TODO: make this a commitment generic over #[tracing::instrument(skip_all, name = "DensifiedRepresentation.commit")] pub fn commit>( &self, diff --git a/src/lasso/surge.rs b/src/lasso/surge.rs index fc0cfaa22..ab70e5ac6 100644 --- a/src/lasso/surge.rs +++ b/src/lasso/surge.rs @@ -22,6 +22,7 @@ use ark_std::log2; use merlin::Transcript; use std::marker::Sync; +// Public Params pub struct SparsePolyCommitmentGens { pub gens_combined_l_variate: PolyCommitmentGens, pub gens_combined_log_m_variate: PolyCommitmentGens, @@ -89,6 +90,8 @@ struct PrimarySumcheck { proof_derefs: CombinedTableEvalProof, } +// TODO Implement trait interface for this: +// DensifiedRepresentation -> #[derive(Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct SparsePolynomialEvaluationProof< G: CurveGroup, diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index e841b42a0..4271c9216 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use std::cmp::Ordering; -use std::ops::{Index, IndexMut, MulAssign, AddAssign, Mul}; +use std::ops::{AddAssign, Index, IndexMut, Mul, MulAssign}; use super::commitments::{Commitments, MultiCommitGens}; use crate::utils::gaussian_elimination::gaussian_elimination; @@ -60,7 +60,7 @@ impl UniPoly { gaussian_elimination(&mut vandermonde) } - /// Divide self by another polynomial, and returns the + /// 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() { @@ -169,7 +169,6 @@ impl Mul for UniPoly { //TODO: feature gate parallel Self::from_coeff(self.coeffs.into_iter().map(|c| c * rhs).collect::>()) } - } impl Mul<&F> for UniPoly { @@ -179,7 +178,6 @@ impl Mul<&F> for UniPoly { //TODO: feature gate parallel Self::from_coeff(self.coeffs.into_iter().map(|c| c * rhs).collect::>()) } - } impl AddAssign<&Self> for UniPoly { diff --git a/src/subprotocols/traits.rs b/src/subprotocols/traits.rs index 7fd0217ca..22d59e125 100644 --- a/src/subprotocols/traits.rs +++ b/src/subprotocols/traits.rs @@ -2,10 +2,11 @@ use std::borrow::Borrow; use merlin::Transcript; -pub trait CommitmentScheme { +pub trait PCS { type Commitment; type Evaluation; type Polynomial; + type CommitPolynomial; type Challenge; type Proof; type Error; @@ -15,7 +16,7 @@ pub trait CommitmentScheme { //TODO: convert to impl IntoIterator fn commit( - polys: &[Self::Polynomial], + polys: &[Self::CommitPolynomial], ck: &Self::ProverKey, ) -> Result, Self::Error>; @@ -27,6 +28,14 @@ pub trait CommitmentScheme { transcript: &mut Transcript, ) -> Result; + fn open( + polys: &[Self::Polynomial], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + pk: impl Borrow, + transcript: &mut Transcript, + ) -> Result; + fn verify( commitments: &[Self::Commitment], evals: &[Self::Evaluation], diff --git a/src/subprotocols/zeromorph/data_structures.rs b/src/subprotocols/zeromorph/data_structures.rs index 052ed2450..0dbd87d19 100644 --- a/src/subprotocols/zeromorph/data_structures.rs +++ b/src/subprotocols/zeromorph/data_structures.rs @@ -1,88 +1,68 @@ +use crate::subprotocols::zeromorph::kzg::UNIVERSAL_KZG_SRS; use ark_bn254::Bn254; use ark_ec::{pairing::Pairing, CurveGroup}; use ark_ff::UniformRand; use ark_std::rand::rngs::StdRng; use lazy_static::lazy_static; -use rand_chacha::rand_core::SeedableRng; +use rand_chacha::{ + rand_core::{RngCore, SeedableRng}, + ChaCha20Rng, +}; use std::sync::{Arc, Mutex}; -use crate::utils::math::Math; +use super::kzg::KZGProverKey; //TODO: The SRS is set with a default value of ____ if this is to be changed (extended) use the cli arg and change it manually. //TODO: add input specifiying monomial or lagrange basis +const MAX_VARS: usize = 20; lazy_static! { - pub static ref ZEROMORPH_SRS: Arc>> = - Arc::new(Mutex::new(ZeromorphSRS::setup(None))); + pub static ref ZEROMORPH_SRS: Arc>> = + Arc::new(Mutex::new(ZeromorphSRS::setup( + None, + 1 << (MAX_VARS + 1), + &mut ChaCha20Rng::from_seed(*b"11111111111111111111111111111111") + ))); } #[derive(Debug, Clone, Default)] -pub struct ZeromorphSRS { - g1_powers: Vec, - g2_powers: Vec, -} - -impl ZeromorphSRS { - fn compute_g_powers(tau: G::ScalarField) -> Vec { - let g_srs = vec![G::zero(); N_MAX - 1]; - - let g_srs: Vec = std::iter::once(G::generator()) - .chain(g_srs.iter().scan(G::generator(), |state, _| { - *state *= τ - Some(*state) - })) - .collect(); - - G::normalize_batch(&g_srs) - } - - pub fn setup(toxic_waste: Option<&[u8]>) -> ZeromorphSRS { - let tau: &[u8] = if toxic_waste.is_none() { - b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - } else { - toxic_waste.unwrap() - }; - /* - if ENV_VAR_NOT_PASSED_IN - */ - let mut bytes = [0u8; 32]; - let len = tau.len(); - bytes[..len].copy_from_slice(&tau[..len]); - let rng = &mut StdRng::from_seed(bytes); - - let tau = P::ScalarField::rand(rng); - let g1_powers = Self::compute_g_powers::(tau); - let g2_powers = Self::compute_g_powers::(tau); - ZeromorphSRS { - g1_powers, - g2_powers, - } - } - - pub fn get_prover_key(&self) -> ZeromorphProverKey

{ - ZeromorphProverKey { - g1: self.g1_powers[0], - tau_1: self.g1_powers[1], - g1_powers: self.g1_powers.clone(), - } +pub struct ZeromorphSRS(UNIVERSAL_KZG_SRS

); + +impl ZeromorphSRS

{ + pub fn setup( + toxic_waste: Option<&[u8]>, + max_degree: usize, + mut rng: &mut R, + ) -> ZeromorphSRS

{ + ZeromorphSRS(UNIVERSAL_KZG_SRS::

::setup(None, max_degree, rng)) } - pub fn get_verifier_key(&self) -> ZeromorphVerifierKey

{ - let idx = N_MAX - (2_usize.pow(N_MAX.log_2() as u32) - 1); - ZeromorphVerifierKey { - g1: self.g1_powers[0], - g2: self.g2_powers[0], - tau_2: self.g2_powers[1], - tau_N_max_sub_2_N: self.g2_powers[idx], - } + pub fn get_pk_vk(&self, max_degree: usize) -> (ZeromorphProverKey

, ZeromorphVerifierKey

) { + let offset = self.0.g1_powers.len() - max_degree; + let offset_g1_powers = self.0.g1_powers[offset..].to_vec(); + ( + ZeromorphProverKey { + g1_powers: KZGProverKey { + g1_powers: self.0.g1_powers.clone(), + }, + offset_g1_powers: KZGProverKey { + g1_powers: offset_g1_powers, + }, + }, + ZeromorphVerifierKey { + g1: self.0.g1_powers[0], + g2: self.0.g2_powers[0], + tau_2: self.0.g2_powers[1], + tau_N_max_sub_2_N: self.0.g2_powers[offset], + }, + ) } } #[derive(Clone, Debug)] pub struct ZeromorphProverKey { // generator - pub g1: P::G1Affine, - pub tau_1: P::G1Affine, - pub g1_powers: Vec, + pub g1_powers: KZGProverKey

, + pub offset_g1_powers: KZGProverKey

, } #[derive(Copy, Clone, Debug)] @@ -100,53 +80,3 @@ pub struct ZeromorphProof { pub q_hat_com: P::G1Affine, pub q_k_com: Vec, } - -#[cfg(test)] -mod test { - use super::*; - use ark_bn254::Bn254; - use ark_ec::{pairing::Pairing, AffineRepr}; - use ark_ff::One; - use std::ops::Mul; - - fn expected_srs(n: usize, seed: &[u8]) -> (Vec, Vec) { - let mut bytes = [0u8; 32]; - let len = seed.len(); - bytes[..len].copy_from_slice(&seed[..len]); - let rng = &mut StdRng::from_seed(bytes); - - let tau = E::ScalarField::rand(rng); - - let powers_of_tau: Vec = - std::iter::successors(Some(E::ScalarField::one()), |p| Some(*p * tau)) - .take(n) - .collect(); - - let g1_gen = E::G1Affine::generator(); - let g2_gen = E::G2Affine::generator(); - - let srs_g1: Vec = powers_of_tau - .iter() - .map(|tp| g1_gen.mul(tp).into()) - .collect(); - let srs_g2: Vec = powers_of_tau - .iter() - .map(|tp| g2_gen.mul(tp).into()) - .collect(); - - (srs_g1, srs_g2) - } - - #[test] - fn test_srs() { - const K: i32 = 1; - const N: usize = 1 << K; - let seed = b"111111111111111111111111111"; - - let (g1_srs_expected, g2_srs_expected) = expected_srs::(N, seed); - - let srs = ZeromorphSRS::::setup(Some(seed)); - assert_eq!(g1_srs_expected, srs.g1_powers); - assert_eq!(g2_srs_expected, srs.g2_powers); - } -} diff --git a/src/subprotocols/zeromorph/kzg.rs b/src/subprotocols/zeromorph/kzg.rs new file mode 100644 index 000000000..0bc89f557 --- /dev/null +++ b/src/subprotocols/zeromorph/kzg.rs @@ -0,0 +1,284 @@ +use ark_bn254::Bn254; +use ark_ec::scalar_mul::fixed_base::FixedBase; +use lazy_static::lazy_static; +use std::sync::{Arc, Mutex}; +use std::{borrow::Borrow, marker::PhantomData}; + +use crate::msm::VariableBaseMSM; +use crate::poly::unipoly::UniPoly; +use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; +use ark_ff::PrimeField; +use ark_std::One; +use ark_std::{ + rand::{Rng, SeedableRng}, + UniformRand, +}; +use rand_chacha::rand_core::RngCore; +use thiserror::Error; + +/// `UniversalParams` are the universal parameters for the KZG10 scheme. +#[derive(Debug, Clone, Default)] +pub struct UNIVERSAL_KZG_SRS { + /// Group elements of the form `{ ฮฒ^i G }`, where `i` ranges from 0 to + /// `degree`. + pub g1_powers: Vec, + /// Group elements of the form `{ ฮฒ^i H }`, where `i` ranges from 0 to + /// `degree`. + pub g2_powers: Vec, +} + +/// `UnivariateProverKey` is used to generate a proof +#[derive(Clone, Debug)] +pub struct KZGProverKey { + /// generators + pub g1_powers: Vec, +} + +/// `UVKZGVerifierKey` is used to check evaluation proofs for a given +/// commitment. +pub struct KZGVerifierKey { + /// The generator of G1. + pub g1: P::G1Affine, + /// The generator of G2. + pub g2: P::G2Affine, + /// tau times the above generator of G2. + pub tau_2: P::G2Affine, +} + +impl UNIVERSAL_KZG_SRS

{ + /// Generates a new srs + pub fn setup( + toxic_waste: Option<&[u8]>, + max_degree: usize, + mut rng: &mut R, + ) -> UNIVERSAL_KZG_SRS

{ + let tau = P::ScalarField::rand(rng); + let g1 = P::G1::rand(rng); + let g2 = P::G2::rand(rng); + + let tau_powers: Vec<_> = (0..=max_degree) + .scan(tau, |state, _| { + let val = *state; + *state *= τ + Some(val) + }) + .collect(); + + let window_size = FixedBase::get_mul_window_size(max_degree); + let scalar_bits = P::ScalarField::MODULUS_BIT_SIZE as usize; + + //TODO: gate with rayon + + let g1_table = FixedBase::get_window_table(scalar_bits, window_size, g1); + let g2_table = FixedBase::get_window_table(scalar_bits, window_size, g2); + let g1_powers_projective = FixedBase::msm(scalar_bits, window_size, &g1_table, &tau_powers); + let g2_powers_projective = FixedBase::msm(scalar_bits, window_size, &g2_table, &tau_powers); + let g1_powers = P::G1::normalize_batch(&g1_powers_projective); + let g2_powers = P::G2::normalize_batch(&g2_powers_projective); + + UNIVERSAL_KZG_SRS { + g1_powers, + g2_powers, + } + } + /// Returns the maximum supported degree + pub fn max_degree(&self) -> usize { + self.g1_powers.len() + } + + /// Returns the prover parameters + /// + /// # Panics + /// if `supported_size` is greater than `self.max_degree()` + pub fn extract_prover_key(&self, supported_size: usize) -> KZGProverKey

{ + let g1_powers = self.g1_powers[..=supported_size].to_vec(); + KZGProverKey { g1_powers } + } + + /// Returns the verifier parameters + /// + /// # Panics + /// If self.prover_params is empty. + pub fn extract_verifier_key(&self, supported_size: usize) -> KZGVerifierKey

{ + assert!( + self.g1_powers.len() >= supported_size, + "supported_size is greater than self.max_degree()" + ); + KZGVerifierKey { + g1: self.g1_powers[0], + g2: self.g2_powers[0], + tau_2: self.g2_powers[1], + } + } + + /// Trim the universal parameters to specialize the public parameters + /// for univariate polynomials to the given `supported_size`, and + /// returns prover key and verifier key. `supported_size` should + /// be in range `1..params.len()` + /// + /// # Panics + /// If `supported_size` is greater than `self.max_degree()`, or `self.max_degree()` is zero. + pub fn get_pk_vk(&self, supported_size: usize) -> (KZGProverKey

, KZGVerifierKey

) { + let g1_powers = self.g1_powers[..=supported_size].to_vec(); + + let pk = KZGProverKey { g1_powers }; + let vk = KZGVerifierKey { + g1: self.g1_powers[0], + g2: self.g2_powers[0], + tau_2: self.g2_powers[1], + }; + (pk, vk) + } +} + +/// Commitments + +/// Polynomial Evaluation + +#[derive(Error, Debug)] +pub enum KZGError { + #[error("length error")] + LengthError, + #[error("Failed to compute quotient polynomial due to polynomial division")] + PolyDivisionError, +} + +/// KZG Polynomial Commitment Scheme on univariate polynomial. +/// Note: this is non-hiding, which is why we will implement traits on this token struct, +/// as we expect to have several impls for the trait pegged on the same instance of a pairing::Engine. +pub struct UVKZGPCS

{ + phantom: PhantomData

, +} + +impl UVKZGPCS

{ + pub fn commit_offset( + prover_param: impl Borrow>, + poly: &UniPoly, + offset: usize, + ) -> Result { + let prover_param = prover_param.borrow(); + + if poly.degree() > prover_param.g1_powers.len() { + return Err(KZGError::LengthError); + } + + let scalars = poly.coeffs.as_slice(); + let bases = prover_param.g1_powers.as_slice(); + + // We can avoid some scalar multiplications if 'scalars' contains a lot of leading zeroes using + // offset, that points where non-zero scalars start. + let C = ::msm(&bases[offset..scalars.len()], &poly.coeffs[offset..]) + .unwrap(); + + Ok(C.into_affine()) + } + + /// Generate a commitment for a polynomial + /// Note that the scheme is not hidding + pub fn commit( + prover_param: impl Borrow>, + poly: &UniPoly, + ) -> Result { + let prover_param = prover_param.borrow(); + + if poly.degree() > prover_param.g1_powers.len() { + return Err(KZGError::LengthError); + } + let C = ::msm( + &prover_param.g1_powers.as_slice()[..poly.coeffs.len()], + poly.coeffs.as_slice(), + ) + .unwrap(); + Ok(C.into_affine()) + } + + /// On input a polynomial `p` and a point `point`, outputs a proof for the + /// same. + pub fn open( + prover_param: impl Borrow>, + polynomial: &UniPoly, + point: &P::ScalarField, + ) -> Result<(P::G1Affine, P::ScalarField), KZGError> { + let prover_param = prover_param.borrow(); + let divisor = UniPoly { + coeffs: vec![-*point, P::ScalarField::one()], + }; + let witness_polynomial = polynomial + .divide_with_q_and_r(&divisor) + .map(|(q, _r)| q) + .ok_or(KZGError::PolyDivisionError)?; + let proof = ::msm( + &prover_param.g1_powers.as_slice()[..witness_polynomial.coeffs.len()], + witness_polynomial.coeffs.as_slice(), + ) + .unwrap(); + let evaluation = polynomial.evaluate(point); + + Ok((proof.into_affine(), evaluation)) + } + + /// Verifies that `value` is the evaluation at `x` of the polynomial + /// committed inside `comm`. + fn verify( + vk: impl Borrow>, + commitment: &P::G1Affine, + point: &P::ScalarField, + proof: &P::G1Affine, + evaluation: &P::ScalarField, + ) -> Result { + let vk = vk.borrow(); + + let lhs = P::pairing( + commitment.into_group() - vk.g1.into_group() * evaluation, + vk.g2, + ); + let rhs = P::pairing(proof, vk.tau_2.into_group() - (vk.g2 * point)); + Ok(lhs == rhs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::poly::unipoly::UniPoly; + use ark_bn254::Bn254; + use ark_std::{ + rand::{Rng, SeedableRng}, + UniformRand, + }; + use rand_chacha::{rand_core::RngCore, ChaCha20Rng}; + + fn random(degree: usize, mut rng: &mut R) -> UniPoly { + let coeffs = (0..=degree) + .map(|_| P::ScalarField::rand(&mut rng)) + .collect::>(); + UniPoly::from_coeff(coeffs) + } + + fn end_to_end_test_template() -> Result<(), KZGError> { + let seed = b"11111111111111111111111111111111"; + for _ in 0..100 { + let mut rng = &mut ChaCha20Rng::from_seed(*seed); + let degree = rng.gen_range(2..20); + + let pp = UNIVERSAL_KZG_SRS::

::setup(None, degree, &mut rng); + let (ck, vk) = pp.get_pk_vk(degree); + let p = random::(degree, rng); + let comm = UVKZGPCS::

::commit(&ck, &p)?; + let point = P::ScalarField::rand(rng); + let (proof, value) = UVKZGPCS::

::open(&ck, &p, &point)?; + assert!( + UVKZGPCS::

::verify(&vk, &comm, &point, &proof, &value)?, + "proof was incorrect for max_degree = {}, polynomial_degree = {}", + degree, + p.degree(), + ); + } + Ok(()) + } + + #[test] + fn end_to_end_test() { + end_to_end_test_template::().expect("test failed for Bn256"); + } +} diff --git a/src/subprotocols/zeromorph/mod.rs b/src/subprotocols/zeromorph/mod.rs index d888c4da1..022013089 100644 --- a/src/subprotocols/zeromorph/mod.rs +++ b/src/subprotocols/zeromorph/mod.rs @@ -1,2 +1,3 @@ pub mod data_structures; +pub mod kzg; pub mod zeromorph; diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index 359653307..d6dfb29df 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -1,16 +1,15 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] -use std::{borrow::Borrow, marker::PhantomData, ops::Neg, iter}; +use std::{borrow::Borrow, iter, marker::PhantomData}; use crate::poly::dense_mlpoly::DensePolynomial; use crate::poly::unipoly::UniPoly; -use crate::subprotocols::traits::CommitmentScheme; +use crate::subprotocols::traits::PCS; use crate::utils::transcript::ProofTranscript; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; -use ark_ff::{BigInt, Field, batch_inversion}; +use ark_ff::{batch_inversion, Field}; use ark_std::{iterable::Iterable, One, Zero}; -use ark_poly_commit::{kzg10::{Proof, KZG10}, PolynomialCommitment}; use itertools::Itertools; use merlin::Transcript; use thiserror::Error; @@ -24,8 +23,9 @@ use crate::msm::VariableBaseMSM; #[cfg(feature = "multicore")] use rayon::prelude::*; -use super::data_structures::{ - ZeromorphProof, ZeromorphProverKey, ZeromorphVerifierKey +use super::{ + data_structures::{ZeromorphProof, ZeromorphProverKey, ZeromorphVerifierKey}, + kzg::UVKZGPCS, }; // Just return vec of P::Scalar @@ -64,12 +64,13 @@ fn compute_multilinear_quotients( (quotients, g[0]) } -fn compute_batched_lifted_degree_quotient( +fn compute_batched_lifted_degree_quotient( + n: usize, quotients: &Vec>, y_challenge: &P::ScalarField, ) -> UniPoly { // Batched Lifted Degreee Quotient Polynomials - let mut res: Vec = vec![P::ScalarField::zero(); N]; + let mut res: Vec = vec![P::ScalarField::zero(); n]; //TODO: separate and scan for y_powers @@ -79,7 +80,7 @@ fn compute_batched_lifted_degree_quotient( // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 let deg_k = (1 << k) as usize - 1; - let offset = N - deg_k - 1; + let offset = n - deg_k - 1; for i in 0..(deg_k + 1) { res[offset + i] += scalar * quotient[i]; } @@ -89,263 +90,88 @@ 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)) { +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(); + let squares_of_x: Vec<_> = iter::successors(Some(x_challenge), |&x| Some(x.square())) + .take(num_vars + 1) + .collect(); - // offsets of x = + // 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::>(); + 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::>(); + 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::>() + 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(); + .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>, - y_challenge: &P::ScalarField, - x_challenge: &P::ScalarField, -) -> UniPoly { - let n = batched_quotient.len(); - let log_N = quotients.len(); - - // initialize partially evaluated degree check polynomial \zeta_x to \hat{q} - let mut res = batched_quotient.clone(); - - let mut y_power = P::ScalarField::one(); - //TODO: iterate over quotients - for k in 0..log_N { - // Accumulate y^k * x^{N - d_k - 1} * q_k into \hat{q} - let deg_k = (1 << k) as usize - 1; - let x_power = x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); - - // Add poly and scale -> Note this can be parallelized - // See -> https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/honk/pcs/zeromorph/zeromorph.hpp#L173 - // https://github.com/AztecProtocol/barretenberg/blob/master/cpp/src/barretenberg/polynomials/polynomial.cpp#L332 - // res += quotient[i] * (-y_power * x_power) - for i in 0..quotients[k].len() { - res[i] += quotients[k][i] * (-y_power * x_power); - } - - y_power *= y_challenge; // updated batching scalar y^k - } - - res -} - -fn compute_partially_evaluated_zeromorph_identity_polynomial( - f_batched: &UniPoly, - //g_batched: &UniPoly, - quotients: &Vec>, - v_evaluation: &P::ScalarField, - u_challenge: &[P::ScalarField], - x_challenge: &P::ScalarField, -) -> UniPoly { - let n = f_batched.len(); - let log_N = quotients.len(); - - //Question for non-shifted can we exclude sum_{i=0}^{l-i} - // Initialize Z_x with x * \sum_{i=0}^{m-1} f_i + /sum_{i=0}^{l-i} * g_i - //let mut res: UniPoly = g_batched.clone(); - - //add scaled - //for i in 0..res.len() { - // res[i] += f_batched[i] * x_challenge; - //} - - let mut res = f_batched.clone(); - - // Compute Z_x -= v * x * \Phi_n(x) - let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 - let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); - res[0] -= *v_evaluation * *x_challenge * phi_n_x; - - //Add contribution from q_k polynomials - for k in 0..log_N { - let x_power = x_challenge.pow(BigInt::<1>::from((1 << k) as u64)); // x^{2^k} - - // \Phi_{n-k-1}(x^{2^{k + 1}}) - let phi_term_1 = phi_numerator - / (x_challenge.pow(BigInt::<1>::from((1 << (k + 1)) as u64)) - P::ScalarField::one()); - - // \Phi_{n-k}(x^{2^k}) - let phi_term_2 = - phi_numerator / (x_challenge.pow(BigInt::<1>::from((1 << k) as u64)) - P::ScalarField::one()); - - // x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k}) - let mut scalar = x_power * phi_term_1 - u_challenge[k] * phi_term_2; - - scalar *= x_challenge; - scalar *= -P::ScalarField::one(); - - for i in 0..quotients[k].len() { - res[i] += quotients[k][i] * scalar; - } - } - res -} - -//TODO: Need SRS -/* -fn compute_batched_evaluation_and_degree_check_quotient( - zeta_x: UniPoly, - z_x: UniPoly, - x_challenge: P::ScalarField, - z_challenge: P::ScalarField, -) -> UniPoly { - // We cannot commit to polynomials with size > N_max - let n = zeta_x.len(); - assert!(n <= N); - - //Compute quotient polynomials q_{\zeta} and q_Z - - //q_{\zeta} = \zeta_x / (X-x) - //q_z = Z_x / (X - x) - //TODO: remove these clones - let mut q_zeta_x = zeta_x.clone(); - let mut q_z_x = z_x.clone(); - q_zeta_x.factor_roots(&[x_challenge]); - q_z_x.factor_roots(&[x_challenge]); - - // Compute batched quotient q_{\zeta} + z*q_Z in place - let mut batched_quotient = zeta_x; - for i in 0..batched_quotient.len() { - batched_quotient[i] += z_x[i] * z_challenge; - } - - batched_quotient -} -*/ - -fn compute_C_zeta_x( - q_hat_com: &P::G1, - q_k_com: &Vec, - y_challenge: &P::ScalarField, - x_challenge: &P::ScalarField, -) -> P::G1 { - let n = 1 << q_k_com.len(); - - let one = P::ScalarField::one(); - let mut scalars = vec![one]; - let mut commitments = vec![q_hat_com.into_affine()]; - - for (i, com) in q_k_com.iter().enumerate() { - let deg_k = (1 << i) - 1; - // Compute scalar y^k * x^{N - deg_k - 1} - let mut scalar = y_challenge.pow(BigInt::<1>::from(i as u64)); - scalar *= x_challenge.pow(BigInt::<1>::from((n - deg_k - 1) as u64)); - scalar *= P::ScalarField::one().neg(); - scalars.push(scalar); - commitments.push(*com); - } - - ::msm(&commitments, &scalars).unwrap() -} - -fn compute_C_Z_x( - f_commitments: &[P::G1], - q_k_com: &[P::G1Affine], - rho: &P::ScalarField, - batched_evaluation: &P::ScalarField, - x_challenge: &P::ScalarField, - u_challenge: &[P::ScalarField], - g1: &P::G1Affine, -) -> P::G1 { - let n = 1 < q_k_com.len(); - - // Phi_n(x) = (x^N - 1) / (x - 1) - let phi_numerator = x_challenge.pow(BigInt::<1>::from(n as u64)) - P::ScalarField::one(); //x^N - 1 - let phi_n_x = phi_numerator / (*x_challenge - P::ScalarField::one()); - - // Add: -v * x * \Phi_n(x) * [1]_1 - let mut scalars = vec![*batched_evaluation * x_challenge * phi_n_x * P::ScalarField::one().neg()]; - let mut commitments = vec![*g1]; - - // Add x * \sum_{i=0}^{m-1} \rho^i*[f_i] - let mut rho_pow = P::ScalarField::one(); - for com in f_commitments { - scalars.push(*x_challenge * rho_pow); - commitments.push(com.into_affine()); - rho_pow *= rho; - } - - // Add: scalar * [q_k], k = 0, ..., log_N, where - // scalar = -x * (x^{2^k} * \Phi_{n-k-1}(x^{2^{k+1}}) - u_k * \Phi_{n-k}(x^{2^k})) - let mut x_pow_2k = *x_challenge; // x^{2^k} - let mut x_pow_2kp1 = *x_challenge * x_challenge; // x^{2^{k + 1}} - - for k in 0..q_k_com.len() { - let phi_term_1 = phi_numerator / (x_pow_2kp1 - P::ScalarField::one()); // \Phi_{n-k-1}(x^{2^{k + 1}}) - let phi_term_2 = phi_numerator / (x_pow_2k - P::ScalarField::one()); // \Phi_{n-k-1}(x^{2^k}) - - let mut scalar = x_pow_2k * phi_term_1; - scalar -= u_challenge[k] * phi_term_2; - scalar *= x_challenge; - scalar *= P::ScalarField::one().neg(); - - scalars.push(scalar); - commitments.push(q_k_com[k]); - - // update powers of challenge x - x_pow_2k = x_pow_2kp1; - x_pow_2kp1 *= x_pow_2kp1; - } - - ::msm(&commitments, &scalars).unwrap() -} - - - #[derive(Error, Debug)] pub enum ZeromorphError { #[error("oh no {0}")] Invalid(String), } -pub struct Zeromorph { +pub struct Zeromorph { _phantom: PhantomData

, } /// Compute the powers of a challenge /// -impl CommitmentScheme for Zeromorph { - type Commitment = P::G1; +impl PCS for Zeromorph

{ + type Commitment = P::G1Affine; type Evaluation = P::ScalarField; - type Polynomial = DensePolynomial; type Challenge = P::ScalarField; + type Polynomial = DensePolynomial; + type CommitPolynomial = UniPoly; type Proof = ZeromorphProof

; type Error = ZeromorphError; @@ -353,18 +179,18 @@ impl CommitmentScheme for Zeromorph { type VerifierKey = ZeromorphVerifierKey

; fn commit( - polys: &[Self::Polynomial], + polys: &[Self::CommitPolynomial], pk: &Self::ProverKey, ) -> Result, Self::Error> { Ok( polys .iter() - .map(|poly| ::msm(&pk.g1_powers, &poly.Z).unwrap()) + .map(|poly| UVKZGPCS::commit(&pk.g1_powers, poly).unwrap()) .collect::>(), ) } - fn prove( + fn open( polys: &[Self::Polynomial], evals: &[Self::Evaluation], challenges: &[Self::Challenge], @@ -372,14 +198,17 @@ impl CommitmentScheme for Zeromorph { transcript: &mut Transcript, ) -> Result { // ASSERT evaluations, challenges, and polynomials are the same size - assert_eq!(evals.len(), challenges.len()); - assert_eq!(evals.len(), polys.len()); + debug_assert_eq!(evals.len(), polys.len()); + + // TODO: assert that the commitments to the polys match the commitments of the polys + // TODO: assert that the evals match the poly evlatuated at the point. - let log_N = challenges.len(); - let n = 1 << log_N; + let num_vars = challenges.len(); + let n: usize = 1 << num_vars; let pk = pk.borrow(); // Generate batching challenge \rho and powers 1,...,\rho^{m-1} + //TODO: condense this into one scan for the entire batching mechanism let rho = >::challenge_scalar(transcript, b"ZM: rho"); let rhos = (0..evals.len()) .scan(P::ScalarField::one(), |acc, _| { @@ -391,33 +220,42 @@ impl CommitmentScheme for Zeromorph { // Compute batching of unshifted polynomials f_i: // f_batched = sum_{i=0}^{m-1}\rho^i*f_i + // TODO: add += for multilinear let mut batched_evaluation = P::ScalarField::zero(); let mut f_batched = vec![P::ScalarField::zero(); n]; for (i, f_poly) in polys.iter().enumerate() { // add_scaled for j in 0..f_batched.len() { - f_batched[j] = f_poly[j] * rhos[i]; + f_batched[j] += f_poly[j] * rhos[i]; } batched_evaluation += rhos[i] * evals[i]; } - // confirm if these need to be interpolated or not let mut pi_poly = UniPoly::from_coeff(f_batched.clone()); + let f_batched = DensePolynomial::new(f_batched); + /* + let f_batched = &polys[0]; + let batched_evaluation = evals[0]; + */ + + // confirm if these need to be interpolated or not // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) - let (quotients, _) = - compute_multilinear_quotients::

(&DensePolynomial::new(f_batched.clone()), challenges); + let (quotients, remainder) = compute_multilinear_quotients::

(&f_batched, challenges); + debug_assert_eq!(quotients.len(), f_batched.get_num_vars()); + debug_assert_eq!(remainder, batched_evaluation); // Compute and send commitments C_{q_k} = [q_k], k = 0, ..., d-1 let label = b"q_k_commitments"; transcript.append_message(label, b"begin_append_vector"); - let q_k_commitments = (0..log_N).fold(Vec::new(), |mut acc, i| { - let q_k_commitment = - ::msm(&pk.g1_powers, "ients[i].coeffs).unwrap(); - transcript.append_point(label, &q_k_commitment); - acc.push(q_k_commitment.into_affine()); - acc - }); + let q_k_commitments: Vec<_> = quotients + .iter() + .map(|q| { + let q_k_commitment = UVKZGPCS::commit(&pk.g1_powers, q).unwrap(); + transcript.append_point(label, &q_k_commitment.into_group()); + q_k_commitment + }) + .collect(); transcript.append_message(label, b"end_append_vector"); // Get challenge y @@ -425,11 +263,13 @@ impl CommitmentScheme for Zeromorph { >::challenge_scalar(transcript, b"ZM: y"); // Compute the batched, lifted-degree quotient \hat{q} - let q_hat = compute_batched_lifted_degree_quotient::("ients, &y_challenge); + let q_hat = compute_batched_lifted_degree_quotient::

(n, "ients, &y_challenge); // Compute and send the commitment C_q = [\hat{q}] - let C_q_hat = ::msm(&pk.g1_powers, &q_hat.coeffs).unwrap(); - transcript.append_point(b"ZM: C_q_hat", &C_q_hat); + // commit at offset + let offset = 1 << (quotients.len() - 1); + let q_hat_com = UVKZGPCS::commit_offset(&pk.g1_powers, &q_hat, offset).unwrap(); + transcript.append_point(b"ZM: C_q_hat", &q_hat_com.into_group()); // Get challenges x and z let x_challenge = @@ -437,42 +277,44 @@ impl CommitmentScheme for Zeromorph { let z_challenge = >::challenge_scalar(transcript, b"ZM: z"); - 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) + 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 * x * poly.Z + q_hat + (-z * x * ฮฆ_n(x) * e) + x * โˆ‘_k (q_scalars_k * q_k) // TODO implement MulAssign and Mul - pi_poly += &z_challenge; + 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; - }); + 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; + }); debug_assert_eq!(pi_poly.evaluate(&x_challenge), P::ScalarField::zero()); - //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) - }; - - transcript.append_point(b"ZM: C_pi", &pi); + // Compute the KZG opening proof pi_poly; -> TODO should abstract into separate trait + let (pi, _) = UVKZGPCS::open(&pk.offset_g1_powers, &pi_poly, &x_challenge).unwrap(); Ok(ZeromorphProof { - pi: pi.into_affine(), - q_hat_com: C_q_hat.into_affine(), + pi, + q_hat_com, q_k_com: q_k_commitments, }) } + fn prove( + polys: &[Self::Polynomial], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + pk: impl Borrow, + transcript: &mut Transcript, + ) -> Result { + Self::open(&polys, evals, challenges, pk, transcript) + } + fn verify( commitments: &[Self::Commitment], evals: &[Self::Evaluation], @@ -481,6 +323,7 @@ impl CommitmentScheme for Zeromorph { transcript: &mut Transcript, proof: Self::Proof, ) -> Result { + debug_assert_eq!(evals.len(), commitments.len()); let vk = vk.borrow(); let ZeromorphProof { pi, @@ -490,12 +333,19 @@ 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())); + let label = b"q_k_commitments"; + transcript.append_message(label, b"begin_append_vector"); + q_k_com + .iter() + .for_each(|c| transcript.append_point(b"ZM: C_q_k", &c.into_group())); + transcript.append_message(label, b"end_append_vector"); // Compute powers of batching challenge rho let rho = >::challenge_scalar(transcript, b"ZM: rho"); + + // TODO merge all of batching into one scan/fold let rhos = (0..evals.len()) .scan(P::ScalarField::one(), |acc, _| { let val = *acc; @@ -506,10 +356,17 @@ impl CommitmentScheme for Zeromorph { // Construct batched evaluations v = sum_{i=0}^{m-1}\rho^i*f_i(u) let mut batched_evaluation = P::ScalarField::zero(); - for (i, val) in evals.iter().enumerate() { - batched_evaluation += *val * rhos[i]; + let mut batched_commitment = P::G1::zero(); + for (i, (eval, commitment)) in evals.iter().zip(commitments.iter()).enumerate() { + batched_evaluation += *eval * rhos[i]; + batched_commitment += *commitment * rhos[i]; } + /* + let batched_commitment = commitments[0]; + let batched_evaluation = evals[0]; + */ + // Challenge y let y_challenge = >::challenge_scalar(transcript, b"ZM: y"); @@ -523,23 +380,36 @@ impl CommitmentScheme for Zeromorph { let z_challenge = >::challenge_scalar(transcript, b"ZM: z"); - let C_zeta_x = compute_C_zeta_x::(&q_hat_com, &q_k_com, &y_challenge, &x_challenge); - let C_Z_x = compute_C_Z_x::( - commitments, - &q_k_com, - &rho, - &batched_evaluation, - &x_challenge, - &challenges, - &vk.g1, - ); + let (eval_scalar, (mut q_scalars, zm_poly_q_scalars)) = + eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, challenges); + + q_scalars + .iter_mut() + .zip_eq(zm_poly_q_scalars) + .for_each(|(scalar, zm_poly_scalar)| { + *scalar += zm_poly_scalar; + }); - // Compute commitment C_{\zeta,Z} - let C_zeta_Z = C_zeta_x + C_Z_x * z_challenge; + let scalars = [ + vec![ + P::ScalarField::one(), + z_challenge, + batched_evaluation * eval_scalar, + ], + q_scalars, + ] + .concat(); + + let bases = [ + vec![q_hat_com.into_affine(), batched_commitment.into(), vk.g1], + q_k_com, + ] + .concat(); + let Zeta_z_com = ::msm(&bases, &scalars).unwrap(); // e(pi, [tau]_2 - x * [1]_2) == e(C_{\zeta,Z}, [X^(N_max - 2^n - 1)]_2) <==> e(C_{\zeta,Z} - x * pi, [X^{N_max - 2^n - 1}]_2) * e(-pi, [tau_2]) == 1 let lhs = P::pairing(pi, vk.tau_2.into_group() - (vk.g2 * x_challenge)); - let rhs = P::pairing(C_zeta_Z, vk.tau_N_max_sub_2_N); + let rhs = P::pairing(Zeta_z_com, vk.tau_N_max_sub_2_N); Ok(lhs == rhs) } } @@ -547,12 +417,11 @@ impl CommitmentScheme for Zeromorph { #[cfg(test)] mod test { -use super::*; - use crate::{utils::math::Math, subprotocols::zeromorph::data_structures::ZEROMORPH_SRS}; + use super::*; + use crate::{subprotocols::zeromorph::data_structures::ZEROMORPH_SRS, utils::math::Math}; use ark_bn254::{Bn254, Fr}; use ark_ff::{BigInt, Zero}; use ark_std::{test_rng, UniformRand}; - // Evaluate Phi_k(x) = \sum_{i=0}^k x^i using the direct inefficent formula fn phi(challenge: &P::ScalarField, subscript: usize) -> P::ScalarField { @@ -566,43 +435,110 @@ use super::*; }) } - fn execute_zeromorph(num_polys: usize) -> bool { - const N: usize = 64; - let log_N = N.log_2(); - + #[test] + fn prove_verify_single() { + let max_vars = 16; let mut rng = test_rng(); - let polys: Vec> = (0..num_polys) - .map(|_| DensePolynomial::new((0..N).map(|_| Fr::rand(&mut rng)).collect::>())) - .collect::>(); - let challenges = (0..log_N) - .into_iter() - .map(|_| Fr::rand(&mut rng)) - .collect::>(); - let evals = polys - .clone() - .into_iter() - .map(|poly| poly.evaluate(&challenges)) - .collect::>(); + let srs = ZEROMORPH_SRS.lock().unwrap(); + + for num_vars in 3..max_vars { + // Setup + let (pk, vk) = { + let poly_size = 1 << (num_vars + 1); + srs.get_pk_vk(poly_size - 1) + }; + let polys = DensePolynomial::new( + (0..(1 << num_vars)) + .map(|_| Fr::rand(&mut rng)) + .collect::>(), + ); + let challenges = (0..num_vars) + .map(|_| Fr::rand(&mut rng)) + .collect::>(); + let evals = polys.evaluate(&challenges); + + // Commit and open + let commitments = + Zeromorph::::commit(&[UniPoly::from_coeff(polys.Z.clone())], &pk).unwrap(); + + let mut prover_transcript = Transcript::new(b"example"); + let proof = + Zeromorph::::prove(&[polys], &[evals], &challenges, &pk, &mut prover_transcript) + .unwrap(); + + let mut verifier_transcript = Transcript::new(b"example"); + Zeromorph::::verify( + &commitments, + &[evals], + &challenges, + &vk, + &mut verifier_transcript, + proof, + ) + .unwrap(); + + //TODO: check both random oracles are synced + } + } + #[test] + fn prove_verify_batched() { + let max_vars = 16; + let mut rng = test_rng(); + let num_polys = 8; let srs = ZEROMORPH_SRS.lock().unwrap(); - let pk = srs.get_prover_key(); - let vk = srs.get_verifier_key(); - let mut prover_transcript = Transcript::new(b"example"); - let mut verifier_transcript = Transcript::new(b"example"); - let commitments = Zeromorph::::commit(&polys.clone(), &pk).unwrap(); - - let proof = - Zeromorph::::prove(&polys, &evals, &challenges, &pk, &mut prover_transcript) - .unwrap(); - Zeromorph::::verify( - &commitments, - &evals, - &challenges, - &vk, - &mut verifier_transcript, - proof, - ) - .unwrap() + + for num_vars in 3..max_vars { + // Setup + let (pk, vk) = { + let poly_size = 1 << (num_vars + 1); + srs.get_pk_vk(poly_size - 1) + }; + let polys: Vec> = (0..num_polys) + .map(|_| { + DensePolynomial::new( + (0..(1 << num_vars)) + .map(|_| Fr::rand(&mut rng)) + .collect::>(), + ) + }) + .collect::>(); + let challenges = (0..num_vars) + .into_iter() + .map(|_| Fr::rand(&mut rng)) + .collect::>(); + let evals = polys + .clone() + .into_iter() + .map(|poly| poly.evaluate(&challenges)) + .collect::>(); + + // Commit and open + // TODO: move to commit + let uni_polys: Vec<_> = polys + .iter() + .map(|poly| UniPoly::from_coeff(poly.Z.clone())) + .collect(); + let commitments = Zeromorph::::commit(&uni_polys, &pk).unwrap(); + + let mut prover_transcript = Transcript::new(b"example"); + let proof = + Zeromorph::::prove(&polys, &evals, &challenges, &pk, &mut prover_transcript) + .unwrap(); + + let mut verifier_transcript = Transcript::new(b"example"); + Zeromorph::::verify( + &commitments, + &evals, + &challenges, + &vk, + &mut verifier_transcript, + proof, + ) + .unwrap(); + + //TODO: check both random oracles are synced + } } /// Test for computing qk given multilinear f @@ -638,7 +574,9 @@ use super::*; //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..num_vars).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; @@ -659,7 +597,8 @@ use super::*; /// ฬ‚q = โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, ๐‘‘โ‚– = deg(ฬ‚q), ๐‘š = ๐‘ #[test] fn batched_lifted_degree_quotient() { - const N: usize = 8; + let num_vars = 3; + let n = 1 << num_vars; // Define mock qโ‚– with deg(qโ‚–) = 2แตโปยน let data_0 = vec![Fr::one()]; @@ -680,7 +619,7 @@ use super::*; //Compute batched quptient ฬ‚q let batched_quotient = - compute_batched_lifted_degree_quotient::("ients, &y_challenge); + compute_batched_lifted_degree_quotient::(n, "ients, &y_challenge); //Explicitly define q_k_lifted = X^{N-2^k} * q_k and compute the expected batched result //Note: we've hard programmed in the size of these vectors not the best practice @@ -719,7 +658,7 @@ use super::*; let q_2_lifted = UniPoly::from_coeff(data_2_lifted); //Explicitly compute ฬ‚q i.e. RLC of lifted polys - let mut batched_quotient_expected = DensePolynomial::new(vec![Fr::zero(); N as usize]); + let mut batched_quotient_expected = DensePolynomial::new(vec![Fr::zero(); n]); //TODO: implement add and add_scalad for i in 0..batched_quotient_expected.len() { batched_quotient_expected[i] += q_0_lifted[i]; @@ -746,7 +685,6 @@ use super::*; /// ๐œ = ฬ‚q - โˆ‘โ‚–โ‚Œโ‚€โฟโปยน yแต Xแตโปแตˆแตโปยน ฬ‚qโ‚–, m = N #[test] fn partially_evaluated_quotient_zeta() { - let num_vars = 3; let n: u64 = 1 << num_vars; @@ -757,15 +695,25 @@ use super::*; let challenges: Vec<_> = (0..num_vars).map(|_| Fr::rand(&mut rng)).collect(); let z_challenge = Fr::rand(&mut rng); - let (_, (zeta_x_scalars, _)) = eval_and_quotient_scalars::(y_challenge, x_challenge, z_challenge, &challenges); + 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 - assert_eq!(zeta_x_scalars[0], -x_challenge.pow(BigInt::<1>::from((n - 1) as u64))); + assert_eq!( + zeta_x_scalars[0], + -x_challenge.pow(BigInt::<1>::from((n - 1) as u64)) + ); - assert_eq!(zeta_x_scalars[1], -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)) + ); - assert_eq!(zeta_x_scalars[2], -y_challenge * y_challenge * x_challenge.pow(BigInt::<1>::from((n - 3 - 1) as u64))); + 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โฑ @@ -845,14 +793,4 @@ use super::*; assert_eq!(z_x_scalars[k], scalar); } } - - #[test] - fn prove_verify_single() { - assert!(execute_zeromorph(1)); - } - - #[test] - fn prove_and_verify_batched() { - assert!(execute_zeromorph(10)); - } } From f4e3bb5391fc94c972a368b1e8fcd54cde662ab0 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Sun, 14 Jan 2024 23:30:37 -0600 Subject: [PATCH 17/21] rm data_structs --- src/subprotocols/traits.rs | 13 +- src/subprotocols/zeromorph/data_structures.rs | 82 ----------- src/subprotocols/zeromorph/kzg.rs | 88 ++++------- src/subprotocols/zeromorph/mod.rs | 1 - src/subprotocols/zeromorph/zeromorph.rs | 139 ++++++++++++------ 5 files changed, 130 insertions(+), 193 deletions(-) delete mode 100644 src/subprotocols/zeromorph/data_structures.rs diff --git a/src/subprotocols/traits.rs b/src/subprotocols/traits.rs index 22d59e125..671f3f038 100644 --- a/src/subprotocols/traits.rs +++ b/src/subprotocols/traits.rs @@ -1,12 +1,13 @@ use std::borrow::Borrow; +use ark_ff::Field; use merlin::Transcript; -pub trait PCS { +use crate::poly::dense_mlpoly::DensePolynomial; + +pub trait PCS { type Commitment; type Evaluation; - type Polynomial; - type CommitPolynomial; type Challenge; type Proof; type Error; @@ -16,12 +17,12 @@ pub trait PCS { //TODO: convert to impl IntoIterator fn commit( - polys: &[Self::CommitPolynomial], + polys: &[DensePolynomial], ck: &Self::ProverKey, ) -> Result, Self::Error>; fn prove( - polys: &[Self::Polynomial], + polys: &[DensePolynomial], evals: &[Self::Evaluation], challenges: &[Self::Challenge], pk: impl Borrow, @@ -29,7 +30,7 @@ pub trait PCS { ) -> Result; fn open( - polys: &[Self::Polynomial], + polys: &[DensePolynomial], evals: &[Self::Evaluation], challenges: &[Self::Challenge], pk: impl Borrow, diff --git a/src/subprotocols/zeromorph/data_structures.rs b/src/subprotocols/zeromorph/data_structures.rs deleted file mode 100644 index 0dbd87d19..000000000 --- a/src/subprotocols/zeromorph/data_structures.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::subprotocols::zeromorph::kzg::UNIVERSAL_KZG_SRS; -use ark_bn254::Bn254; -use ark_ec::{pairing::Pairing, CurveGroup}; -use ark_ff::UniformRand; -use ark_std::rand::rngs::StdRng; -use lazy_static::lazy_static; -use rand_chacha::{ - rand_core::{RngCore, SeedableRng}, - ChaCha20Rng, -}; -use std::sync::{Arc, Mutex}; - -use super::kzg::KZGProverKey; - -//TODO: The SRS is set with a default value of ____ if this is to be changed (extended) use the cli arg and change it manually. -//TODO: add input specifiying monomial or lagrange basis -const MAX_VARS: usize = 20; -lazy_static! { - pub static ref ZEROMORPH_SRS: Arc>> = - Arc::new(Mutex::new(ZeromorphSRS::setup( - None, - 1 << (MAX_VARS + 1), - &mut ChaCha20Rng::from_seed(*b"11111111111111111111111111111111") - ))); -} - -#[derive(Debug, Clone, Default)] -pub struct ZeromorphSRS(UNIVERSAL_KZG_SRS

); - -impl ZeromorphSRS

{ - pub fn setup( - toxic_waste: Option<&[u8]>, - max_degree: usize, - mut rng: &mut R, - ) -> ZeromorphSRS

{ - ZeromorphSRS(UNIVERSAL_KZG_SRS::

::setup(None, max_degree, rng)) - } - - pub fn get_pk_vk(&self, max_degree: usize) -> (ZeromorphProverKey

, ZeromorphVerifierKey

) { - let offset = self.0.g1_powers.len() - max_degree; - let offset_g1_powers = self.0.g1_powers[offset..].to_vec(); - ( - ZeromorphProverKey { - g1_powers: KZGProverKey { - g1_powers: self.0.g1_powers.clone(), - }, - offset_g1_powers: KZGProverKey { - g1_powers: offset_g1_powers, - }, - }, - ZeromorphVerifierKey { - g1: self.0.g1_powers[0], - g2: self.0.g2_powers[0], - tau_2: self.0.g2_powers[1], - tau_N_max_sub_2_N: self.0.g2_powers[offset], - }, - ) - } -} - -#[derive(Clone, Debug)] -pub struct ZeromorphProverKey { - // generator - pub g1_powers: KZGProverKey

, - pub offset_g1_powers: KZGProverKey

, -} - -#[derive(Copy, Clone, Debug)] -pub struct ZeromorphVerifierKey { - pub g1: P::G1Affine, - pub g2: P::G2Affine, - pub tau_2: P::G2Affine, - pub tau_N_max_sub_2_N: P::G2Affine, -} - -//TODO: can we upgrade the transcript to give not just absorb -#[derive(Clone, Debug)] -pub struct ZeromorphProof { - pub pi: P::G1Affine, - pub q_hat_com: P::G1Affine, - pub q_k_com: Vec, -} diff --git a/src/subprotocols/zeromorph/kzg.rs b/src/subprotocols/zeromorph/kzg.rs index 0bc89f557..52413afce 100644 --- a/src/subprotocols/zeromorph/kzg.rs +++ b/src/subprotocols/zeromorph/kzg.rs @@ -1,7 +1,4 @@ -use ark_bn254::Bn254; use ark_ec::scalar_mul::fixed_base::FixedBase; -use lazy_static::lazy_static; -use std::sync::{Arc, Mutex}; use std::{borrow::Borrow, marker::PhantomData}; use crate::msm::VariableBaseMSM; @@ -9,33 +6,22 @@ use crate::poly::unipoly::UniPoly; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; use ark_ff::PrimeField; use ark_std::One; -use ark_std::{ - rand::{Rng, SeedableRng}, - UniformRand, -}; +use ark_std::UniformRand; use rand_chacha::rand_core::RngCore; use thiserror::Error; -/// `UniversalParams` are the universal parameters for the KZG10 scheme. #[derive(Debug, Clone, Default)] -pub struct UNIVERSAL_KZG_SRS { - /// Group elements of the form `{ ฮฒ^i G }`, where `i` ranges from 0 to - /// `degree`. +pub struct UniversalKzgSrs { pub g1_powers: Vec, - /// Group elements of the form `{ ฮฒ^i H }`, where `i` ranges from 0 to - /// `degree`. pub g2_powers: Vec, } -/// `UnivariateProverKey` is used to generate a proof #[derive(Clone, Debug)] pub struct KZGProverKey { /// generators pub g1_powers: Vec, } -/// `UVKZGVerifierKey` is used to check evaluation proofs for a given -/// commitment. pub struct KZGVerifierKey { /// The generator of G1. pub g1: P::G1Affine, @@ -45,13 +31,13 @@ pub struct KZGVerifierKey { pub tau_2: P::G2Affine, } -impl UNIVERSAL_KZG_SRS

{ - /// Generates a new srs +impl UniversalKzgSrs

{ + // TODO: add logic to have seed/toxic waste pub fn setup( toxic_waste: Option<&[u8]>, max_degree: usize, mut rng: &mut R, - ) -> UNIVERSAL_KZG_SRS

{ + ) -> UniversalKzgSrs

{ let tau = P::ScalarField::rand(rng); let g1 = P::G1::rand(rng); let g2 = P::G2::rand(rng); @@ -68,7 +54,6 @@ impl UNIVERSAL_KZG_SRS

{ let scalar_bits = P::ScalarField::MODULUS_BIT_SIZE as usize; //TODO: gate with rayon - let g1_table = FixedBase::get_window_table(scalar_bits, window_size, g1); let g2_table = FixedBase::get_window_table(scalar_bits, window_size, g2); let g1_powers_projective = FixedBase::msm(scalar_bits, window_size, &g1_table, &tau_powers); @@ -76,23 +61,14 @@ impl UNIVERSAL_KZG_SRS

{ let g1_powers = P::G1::normalize_batch(&g1_powers_projective); let g2_powers = P::G2::normalize_batch(&g2_powers_projective); - UNIVERSAL_KZG_SRS { + UniversalKzgSrs { g1_powers, g2_powers, } } - /// Returns the maximum supported degree - pub fn max_degree(&self) -> usize { - self.g1_powers.len() - } - /// Returns the prover parameters - /// - /// # Panics - /// if `supported_size` is greater than `self.max_degree()` - pub fn extract_prover_key(&self, supported_size: usize) -> KZGProverKey

{ - let g1_powers = self.g1_powers[..=supported_size].to_vec(); - KZGProverKey { g1_powers } + pub fn extract_prover_key(&self, supported_size: usize) -> Vec { + self.g1_powers[..=supported_size].to_vec() } /// Returns the verifier parameters @@ -111,17 +87,10 @@ impl UNIVERSAL_KZG_SRS

{ } } - /// Trim the universal parameters to specialize the public parameters - /// for univariate polynomials to the given `supported_size`, and - /// returns prover key and verifier key. `supported_size` should - /// be in range `1..params.len()` - /// - /// # Panics - /// If `supported_size` is greater than `self.max_degree()`, or `self.max_degree()` is zero. - pub fn get_pk_vk(&self, supported_size: usize) -> (KZGProverKey

, KZGVerifierKey

) { + pub fn trim(&self, supported_size: usize) -> (Vec, KZGVerifierKey

) { let g1_powers = self.g1_powers[..=supported_size].to_vec(); - let pk = KZGProverKey { g1_powers }; + let pk = g1_powers; let vk = KZGVerifierKey { g1: self.g1_powers[0], g2: self.g2_powers[0], @@ -152,50 +121,44 @@ pub struct UVKZGPCS

{ impl UVKZGPCS

{ pub fn commit_offset( - prover_param: impl Borrow>, + prover_param: impl Borrow>, poly: &UniPoly, offset: usize, ) -> Result { let prover_param = prover_param.borrow(); - if poly.degree() > prover_param.g1_powers.len() { + if poly.degree() > prover_param.len() { return Err(KZGError::LengthError); } let scalars = poly.coeffs.as_slice(); - let bases = prover_param.g1_powers.as_slice(); + let bases = prover_param.as_slice(); - // We can avoid some scalar multiplications if 'scalars' contains a lot of leading zeroes using - // offset, that points where non-zero scalars start. - let C = ::msm(&bases[offset..scalars.len()], &poly.coeffs[offset..]) + let c = ::msm(&bases[offset..scalars.len()], &poly.coeffs[offset..]) .unwrap(); - Ok(C.into_affine()) + Ok(c.into_affine()) } - /// Generate a commitment for a polynomial - /// Note that the scheme is not hidding pub fn commit( - prover_param: impl Borrow>, + prover_param: impl Borrow>, poly: &UniPoly, ) -> Result { let prover_param = prover_param.borrow(); - if poly.degree() > prover_param.g1_powers.len() { + if poly.degree() > prover_param.len() { return Err(KZGError::LengthError); } - let C = ::msm( - &prover_param.g1_powers.as_slice()[..poly.coeffs.len()], + let c = ::msm( + &prover_param.as_slice()[..poly.coeffs.len()], poly.coeffs.as_slice(), ) .unwrap(); - Ok(C.into_affine()) + Ok(c.into_affine()) } - /// On input a polynomial `p` and a point `point`, outputs a proof for the - /// same. pub fn open( - prover_param: impl Borrow>, + prover_param: impl Borrow>, polynomial: &UniPoly, point: &P::ScalarField, ) -> Result<(P::G1Affine, P::ScalarField), KZGError> { @@ -208,7 +171,7 @@ impl UVKZGPCS

{ .map(|(q, _r)| q) .ok_or(KZGError::PolyDivisionError)?; let proof = ::msm( - &prover_param.g1_powers.as_slice()[..witness_polynomial.coeffs.len()], + &prover_param.as_slice()[..witness_polynomial.coeffs.len()], witness_polynomial.coeffs.as_slice(), ) .unwrap(); @@ -217,8 +180,6 @@ impl UVKZGPCS

{ Ok((proof.into_affine(), evaluation)) } - /// Verifies that `value` is the evaluation at `x` of the polynomial - /// committed inside `comm`. fn verify( vk: impl Borrow>, commitment: &P::G1Affine, @@ -242,6 +203,7 @@ mod tests { use super::*; use crate::poly::unipoly::UniPoly; use ark_bn254::Bn254; + use ark_std::{ rand::{Rng, SeedableRng}, UniformRand, @@ -261,8 +223,8 @@ mod tests { let mut rng = &mut ChaCha20Rng::from_seed(*seed); let degree = rng.gen_range(2..20); - let pp = UNIVERSAL_KZG_SRS::

::setup(None, degree, &mut rng); - let (ck, vk) = pp.get_pk_vk(degree); + let pp = UniversalKzgSrs::

::setup(None, degree, &mut rng); + let (ck, vk) = pp.trim(degree); let p = random::(degree, rng); let comm = UVKZGPCS::

::commit(&ck, &p)?; let point = P::ScalarField::rand(rng); diff --git a/src/subprotocols/zeromorph/mod.rs b/src/subprotocols/zeromorph/mod.rs index 022013089..3646b6c28 100644 --- a/src/subprotocols/zeromorph/mod.rs +++ b/src/subprotocols/zeromorph/mod.rs @@ -1,3 +1,2 @@ -pub mod data_structures; pub mod kzg; pub mod zeromorph; diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index d6dfb29df..99cfec2db 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -6,12 +6,20 @@ use std::{borrow::Borrow, iter, marker::PhantomData}; use crate::poly::dense_mlpoly::DensePolynomial; use crate::poly::unipoly::UniPoly; use crate::subprotocols::traits::PCS; +use crate::subprotocols::zeromorph::kzg::UniversalKzgSrs; use crate::utils::transcript::ProofTranscript; +use ark_bn254::Bn254; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; use ark_ff::{batch_inversion, Field}; use ark_std::{iterable::Iterable, One, Zero}; use itertools::Itertools; +use lazy_static::lazy_static; use merlin::Transcript; +use rand_chacha::{ + rand_core::{RngCore, SeedableRng}, + ChaCha20Rng, +}; +use std::sync::{Arc, Mutex}; use thiserror::Error; #[cfg(feature = "ark-msm")] @@ -23,10 +31,7 @@ use crate::msm::VariableBaseMSM; #[cfg(feature = "multicore")] use rayon::prelude::*; -use super::{ - data_structures::{ZeromorphProof, ZeromorphProverKey, ZeromorphVerifierKey}, - kzg::UVKZGPCS, -}; +use super::kzg::UVKZGPCS; // Just return vec of P::Scalar // u_challenge = Point @@ -154,6 +159,71 @@ fn eval_and_quotient_scalars( (-vs[0] * z_challenge, q_scalars) } +//TODO: The SRS is set with a default value of ____ if this is to be changed (extended) use the cli arg and change it manually. +//TODO: add input specifiying monomial or lagrange basis +const MAX_VARS: usize = 20; +lazy_static! { + pub static ref ZEROMORPH_SRS: Arc>> = + Arc::new(Mutex::new(ZeromorphSRS::setup( + None, + 1 << (MAX_VARS + 1), + &mut ChaCha20Rng::from_seed(*b"11111111111111111111111111111111") + ))); +} + +#[derive(Debug, Clone, Default)] +pub struct ZeromorphSRS(UniversalKzgSrs

); + +impl ZeromorphSRS

{ + pub fn setup( + toxic_waste: Option<&[u8]>, + max_degree: usize, + mut rng: &mut R, + ) -> ZeromorphSRS

{ + ZeromorphSRS(UniversalKzgSrs::

::setup(None, max_degree, rng)) + } + + pub fn trim(&self, max_degree: usize) -> (ZeromorphProverKey

, ZeromorphVerifierKey

) { + let offset = self.0.g1_powers.len() - max_degree; + let offset_g1_powers = self.0.g1_powers[offset..].to_vec(); + ( + ZeromorphProverKey { + g1_powers: self.0.g1_powers.clone(), + offset_g1_powers: offset_g1_powers, + }, + ZeromorphVerifierKey { + g1: self.0.g1_powers[0], + g2: self.0.g2_powers[0], + tau_2: self.0.g2_powers[1], + tau_N_max_sub_2_N: self.0.g2_powers[offset], + }, + ) + } +} + +#[derive(Clone, Debug)] +pub struct ZeromorphProverKey { + // generator + pub g1_powers: Vec, + pub offset_g1_powers: Vec, +} + +#[derive(Copy, Clone, Debug)] +pub struct ZeromorphVerifierKey { + pub g1: P::G1Affine, + pub g2: P::G2Affine, + pub tau_2: P::G2Affine, + pub tau_N_max_sub_2_N: P::G2Affine, +} + +//TODO: can we upgrade the transcript to give not just absorb +#[derive(Clone, Debug)] +pub struct ZeromorphProof { + pub pi: P::G1Affine, + pub q_hat_com: P::G1Affine, + pub q_k_com: Vec, +} + #[derive(Error, Debug)] pub enum ZeromorphError { #[error("oh no {0}")] @@ -166,12 +236,10 @@ pub struct Zeromorph { /// Compute the powers of a challenge /// -impl PCS for Zeromorph

{ +impl PCS for Zeromorph

{ type Commitment = P::G1Affine; type Evaluation = P::ScalarField; type Challenge = P::ScalarField; - type Polynomial = DensePolynomial; - type CommitPolynomial = UniPoly; type Proof = ZeromorphProof

; type Error = ZeromorphError; @@ -179,21 +247,27 @@ impl PCS for Zeromorph

{ type VerifierKey = ZeromorphVerifierKey

; fn commit( - polys: &[Self::CommitPolynomial], + polys: &[DensePolynomial], pk: &Self::ProverKey, ) -> Result, Self::Error> { + let uni_polys: Vec<_> = polys + .iter() + .map(|poly| UniPoly::from_coeff(poly.Z.clone())) + .collect(); + + // TODO: parallelize Ok( - polys + uni_polys .iter() - .map(|poly| UVKZGPCS::commit(&pk.g1_powers, poly).unwrap()) + .map(|poly| UVKZGPCS::

::commit(&pk.g1_powers, poly).unwrap()) .collect::>(), ) } fn open( - polys: &[Self::Polynomial], + polys: &[DensePolynomial], evals: &[Self::Evaluation], - challenges: &[Self::Challenge], + challenge: &[Self::Challenge], pk: impl Borrow, transcript: &mut Transcript, ) -> Result { @@ -203,7 +277,7 @@ impl PCS for Zeromorph

{ // TODO: assert that the commitments to the polys match the commitments of the polys // TODO: assert that the evals match the poly evlatuated at the point. - let num_vars = challenges.len(); + let num_vars = challenge.len(); let n: usize = 1 << num_vars; let pk = pk.borrow(); @@ -233,15 +307,9 @@ impl PCS for Zeromorph

{ } let mut pi_poly = UniPoly::from_coeff(f_batched.clone()); let f_batched = DensePolynomial::new(f_batched); - /* - let f_batched = &polys[0]; - let batched_evaluation = evals[0]; - */ - - // confirm if these need to be interpolated or not // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) - let (quotients, remainder) = compute_multilinear_quotients::

(&f_batched, challenges); + let (quotients, remainder) = compute_multilinear_quotients::

(&f_batched, challenge); debug_assert_eq!(quotients.len(), f_batched.get_num_vars()); debug_assert_eq!(remainder, batched_evaluation); @@ -251,7 +319,7 @@ impl PCS for Zeromorph

{ let q_k_commitments: Vec<_> = quotients .iter() .map(|q| { - let q_k_commitment = UVKZGPCS::commit(&pk.g1_powers, q).unwrap(); + let q_k_commitment = UVKZGPCS::

::commit(&pk.g1_powers, q).unwrap(); transcript.append_point(label, &q_k_commitment.into_group()); q_k_commitment }) @@ -268,7 +336,7 @@ impl PCS for Zeromorph

{ // Compute and send the commitment C_q = [\hat{q}] // commit at offset let offset = 1 << (quotients.len() - 1); - let q_hat_com = UVKZGPCS::commit_offset(&pk.g1_powers, &q_hat, offset).unwrap(); + let q_hat_com = UVKZGPCS::

::commit_offset(&pk.g1_powers, &q_hat, offset).unwrap(); transcript.append_point(b"ZM: C_q_hat", &q_hat_com.into_group()); // Get challenges x and z @@ -278,9 +346,8 @@ impl PCS for Zeromorph

{ >::challenge_scalar(transcript, b"ZM: z"); let (eval_scalar, (zeta_degree_check_q_scalars, z_zmpoly_q_scalars)) = - eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, challenges); + eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, challenge); // f = z * x * poly.Z + q_hat + (-z * x * ฮฆ_n(x) * e) + x * โˆ‘_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); @@ -296,7 +363,7 @@ impl PCS for Zeromorph

{ debug_assert_eq!(pi_poly.evaluate(&x_challenge), P::ScalarField::zero()); // Compute the KZG opening proof pi_poly; -> TODO should abstract into separate trait - let (pi, _) = UVKZGPCS::open(&pk.offset_g1_powers, &pi_poly, &x_challenge).unwrap(); + let (pi, _) = UVKZGPCS::

::open(&pk.offset_g1_powers, &pi_poly, &x_challenge).unwrap(); Ok(ZeromorphProof { pi, @@ -306,7 +373,7 @@ impl PCS for Zeromorph

{ } fn prove( - polys: &[Self::Polynomial], + polys: &[DensePolynomial], evals: &[Self::Evaluation], challenges: &[Self::Challenge], pk: impl Borrow, @@ -362,11 +429,6 @@ impl PCS for Zeromorph

{ batched_commitment += *commitment * rhos[i]; } - /* - let batched_commitment = commitments[0]; - let batched_evaluation = evals[0]; - */ - // Challenge y let y_challenge = >::challenge_scalar(transcript, b"ZM: y"); @@ -418,7 +480,7 @@ impl PCS for Zeromorph

{ mod test { use super::*; - use crate::{subprotocols::zeromorph::data_structures::ZEROMORPH_SRS, utils::math::Math}; + use crate::utils::math::Math; use ark_bn254::{Bn254, Fr}; use ark_ff::{BigInt, Zero}; use ark_std::{test_rng, UniformRand}; @@ -445,7 +507,7 @@ mod test { // Setup let (pk, vk) = { let poly_size = 1 << (num_vars + 1); - srs.get_pk_vk(poly_size - 1) + srs.trim(poly_size - 1) }; let polys = DensePolynomial::new( (0..(1 << num_vars)) @@ -458,8 +520,7 @@ mod test { let evals = polys.evaluate(&challenges); // Commit and open - let commitments = - Zeromorph::::commit(&[UniPoly::from_coeff(polys.Z.clone())], &pk).unwrap(); + let commitments = Zeromorph::::commit(&[polys.clone()], &pk).unwrap(); let mut prover_transcript = Transcript::new(b"example"); let proof = @@ -492,7 +553,7 @@ mod test { // Setup let (pk, vk) = { let poly_size = 1 << (num_vars + 1); - srs.get_pk_vk(poly_size - 1) + srs.trim(poly_size - 1) }; let polys: Vec> = (0..num_polys) .map(|_| { @@ -515,11 +576,7 @@ mod test { // Commit and open // TODO: move to commit - let uni_polys: Vec<_> = polys - .iter() - .map(|poly| UniPoly::from_coeff(poly.Z.clone())) - .collect(); - let commitments = Zeromorph::::commit(&uni_polys, &pk).unwrap(); + let commitments = Zeromorph::::commit(&polys, &pk).unwrap(); let mut prover_transcript = Transcript::new(b"example"); let proof = From 9aaf168504754bf6a085ccb4bf2d73360c747690 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Tue, 16 Jan 2024 00:35:47 -0600 Subject: [PATCH 18/21] clean up and start abstracting hyrax --- src/benches/bench.rs | 79 +++++ src/poly/dense_mlpoly.rs | 40 ++- src/poly/unipoly.rs | 33 +- src/subprotocols/hyrax.rs | 140 +++++++++ src/subprotocols/mod.rs | 1 + src/subprotocols/traits.rs | 9 +- src/subprotocols/zeromorph/kzg.rs | 131 ++++---- src/subprotocols/zeromorph/zeromorph.rs | 388 +++++++++++------------- 8 files changed, 522 insertions(+), 299 deletions(-) create mode 100644 src/subprotocols/hyrax.rs diff --git a/src/benches/bench.rs b/src/benches/bench.rs index 15653ea69..bb3458f96 100644 --- a/src/benches/bench.rs +++ b/src/benches/bench.rs @@ -76,6 +76,7 @@ macro_rules! single_pass_lasso { pub enum BenchType { JoltDemo, Halo2Comparison, + Zeromorph, } #[allow(unreachable_patterns)] // good errors on new BenchTypes @@ -83,10 +84,88 @@ pub fn benchmarks(bench_type: BenchType) -> Vec<(tracing::Span, fn())> { match bench_type { BenchType::JoltDemo => jolt_demo_benchmarks(), BenchType::Halo2Comparison => halo2_comparison_benchmarks(), + BenchType::Zeromorph => zeromorph(), _ => panic!("BenchType does not have a mapping"), } } +fn zeromorph() -> Vec<(tracing::Span, fn())> { + vec![ + single_pass_lasso!( + "And(2^10)", + Fr, + EdwardsProjective, + AndSubtableStrategy, + /* C= */ 1, + /* M= */ 1 << 16, + /* S= */ 1 << 10 + ), + single_pass_lasso!( + "And(2^12)", + Fr, + EdwardsProjective, + AndSubtableStrategy, + /* C= */ 1, + /* M= */ 1 << 16, + /* S= */ 1 << 12 + ), + single_pass_lasso!( + "And(2^14)", + Fr, + EdwardsProjective, + AndSubtableStrategy, + /* C= */ 1, + /* M= */ 1 << 16, + /* S= */ 1 << 14 + ), + single_pass_lasso!( + "And(2^16)", + Fr, + EdwardsProjective, + AndSubtableStrategy, + /* C= */ 1, + /* M= */ 1 << 16, + /* S= */ 1 << 16 + ), + single_pass_lasso!( + "And(2^18)", + Fr, + EdwardsProjective, + AndSubtableStrategy, + /* C= */ 1, + /* M= */ 1 << 16, + /* S= */ 1 << 18 + ), + single_pass_lasso!( + "And(2^20)", + Fr, + EdwardsProjective, + AndSubtableStrategy, + /* C= */ 1, + /* M= */ 1 << 16, + /* S= */ 1 << 20 + ), + single_pass_lasso!( + "And(2^22)", + Fr, + EdwardsProjective, + AndSubtableStrategy, + /* C= */ 1, + /* M= */ 1 << 16, + /* S= */ 1 << 22 + ), + single_pass_lasso!( + "And(2^24)", + Fr, + EdwardsProjective, + AndSubtableStrategy, + /* C= */ 1, + /* M= */ 1 << 16, + /* S= */ 1 << 24 + ), + ] +} + fn jolt_demo_benchmarks() -> Vec<(tracing::Span, fn())> { vec![ single_pass_lasso!( diff --git a/src/poly/dense_mlpoly.rs b/src/poly/dense_mlpoly.rs index 0c0bfa690..21f5e7f68 100644 --- a/src/poly/dense_mlpoly.rs +++ b/src/poly/dense_mlpoly.rs @@ -13,8 +13,9 @@ use ark_ff::PrimeField; use ark_serialize::*; use ark_std::Zero; use core::ops::Index; +use std::cmp::Ordering; use merlin::Transcript; -use std::ops::IndexMut; +use std::ops::{AddAssign, IndexMut, Mul}; #[cfg(feature = "ark-msm")] use ark_ec::VariableBaseMSM; @@ -409,6 +410,43 @@ impl PolyEvalProof { } } +impl AddAssign for DensePolynomial { + fn add_assign(&mut self, rhs: Self) { + let ordering = self.Z.len().cmp(&rhs.Z.len()); + #[allow(clippy::disallowed_methods)] + for (lhs, rhs) in self.Z.iter_mut().zip(&rhs.Z) { + *lhs += rhs; + } + if matches!(ordering, Ordering::Less) { + self + .Z + .extend(rhs.Z[self.Z.len()..].iter().cloned()); + } + } +} + +impl AddAssign<&F> for DensePolynomial { + fn add_assign(&mut self, rhs: &F) { + #[cfg(feature = "multicore")] + let iter = self.Z.par_iter_mut(); + #[cfg(not(feature = "multicore"))] + let iter = self.Z.iter_mut(); + iter.for_each(|c| *c += rhs); + } +} + +impl Mul for DensePolynomial { + type Output = Self; + + fn mul(self, rhs: F) -> Self { + #[cfg(feature = "multicore")] + let iter = self.Z.into_par_iter(); + #[cfg(not(feature = "multicore"))] + let iter = self.Z.iter(); + Self::new(iter.map(|c| c * rhs).collect::>()) + } +} + #[cfg(test)] mod tests { diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index 4271c9216..295ba2bc2 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -9,6 +9,7 @@ use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_serialize::*; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator, IntoParallelIterator}; // ax^2 + bx + c stored as vec![c,b,a] // ax^3 + bx^2 + cx + d stored as vec![d,c,b,a] @@ -150,15 +151,21 @@ impl UniPoly { 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); + #[cfg(feature = "multicore")] + let iter = self.coeffs.par_iter_mut(); + #[cfg(not(feature = "multicore"))] + let iter = self.coeffs.iter_mut(); + iter.for_each(|c| *c += rhs); } } 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); + #[cfg(feature = "multicore")] + let iter = self.coeffs.par_iter_mut(); + #[cfg(not(feature = "multicore"))] + let iter = self.coeffs.iter_mut(); + iter.for_each(|c| *c *= rhs); } } @@ -166,8 +173,11 @@ impl Mul for UniPoly { type Output = Self; fn mul(self, rhs: F) -> Self { - //TODO: feature gate parallel - Self::from_coeff(self.coeffs.into_iter().map(|c| c * rhs).collect::>()) + #[cfg(feature = "multicore")] + let iter = self.coeffs.into_par_iter(); + #[cfg(not(feature = "multicore"))] + let iter = self.coeffs.iter(); + Self::from_coeff(iter.map(|c| c * rhs).collect::>()) } } @@ -175,8 +185,11 @@ impl Mul<&F> for UniPoly { type Output = Self; fn mul(self, rhs: &F) -> Self { - //TODO: feature gate parallel - Self::from_coeff(self.coeffs.into_iter().map(|c| c * rhs).collect::>()) + #[cfg(feature = "multicore")] + let iter = self.coeffs.into_par_iter(); + #[cfg(not(feature = "multicore"))] + let iter = self.coeffs.iter(); + Self::from_coeff(iter.map(|c| c * rhs).collect::>()) } } @@ -192,10 +205,6 @@ impl AddAssign<&Self> for UniPoly { .coeffs .extend(rhs.coeffs[self.coeffs.len()..].iter().cloned()); } - if matches!(ordering, Ordering::Equal) { - //TODO: truncate leading zeros - self; - } } } diff --git a/src/subprotocols/hyrax.rs b/src/subprotocols/hyrax.rs new file mode 100644 index 000000000..091ad2d25 --- /dev/null +++ b/src/subprotocols/hyrax.rs @@ -0,0 +1,140 @@ +use super::traits::PCS; + + +#[derive(Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct PolyEvalProof { + proof: DotProductProofLog, +} + +impl PolyEvalProof { + fn protocol_name() -> &'static [u8] { + b"polynomial evaluation proof" + } +} + +impl PCS for PolyEvalProof { + type Commitment; + type Evaluation; + type Challenge; + type Proof; + type Error; + + type ProverKey; + type VerifierKey; + + #[tracing::instrument(skip_all, name = "DensePolynomial.commit")] + fn commit( + &self, + gens: &PolyCommitmentGens, + random_tape: Option<&mut RandomTape>, + ) -> (PolyCommitment, PolyCommitmentBlinds) + where + G: CurveGroup, + { + let n = self.Z.len(); + let ell = self.get_num_vars(); + assert_eq!(n, ell.pow2()); + + let (left_num_vars, right_num_vars) = EqPolynomial::::compute_factored_lens(ell); + let L_size = left_num_vars.pow2(); + let R_size = right_num_vars.pow2(); + assert_eq!(L_size * R_size, n); + + let blinds = if let Some(t) = random_tape { + PolyCommitmentBlinds { + blinds: t.random_vector(b"poly_blinds", L_size), + } + } else { + PolyCommitmentBlinds { + blinds: vec![F::zero(); L_size], + } + }; + + + #[tracing::instrument(skip_all, name = "DensePolyEval.prove")] + fn prove( + poly: &DensePolynomial, + blinds_opt: Option<&PolyCommitmentBlinds>, + r: &[G::ScalarField], // point at which the polynomial is evaluated + Zr: &G::ScalarField, // evaluation of \widetilde{Z}(r) + blind_Zr_opt: Option<&G::ScalarField>, // specifies a blind for Zr + gens: &PolyCommitmentGens, + transcript: &mut Transcript, + random_tape: &mut RandomTape, + ) -> (PolyEvalProof, G) { + >::append_protocol_name( + transcript, + PolyEvalProof::::protocol_name(), + ); + + // assert vectors are of the right size + assert_eq!(poly.get_num_vars(), r.len()); + + let (left_num_vars, right_num_vars) = + EqPolynomial::::compute_factored_lens(r.len()); + let L_size = left_num_vars.pow2(); + let R_size = right_num_vars.pow2(); + + let default_blinds = PolyCommitmentBlinds { + blinds: vec![G::ScalarField::zero(); L_size], + }; + let blinds = blinds_opt.map_or(&default_blinds, |p| p); + + assert_eq!(blinds.blinds.len(), L_size); + + let zero = G::ScalarField::zero(); + let blind_Zr = blind_Zr_opt.map_or(&zero, |p| p); + + // compute the L and R vectors + let eq = EqPolynomial::new(r.to_vec()); + let (L, R) = eq.compute_factored_evals(); + assert_eq!(L.len(), L_size); + assert_eq!(R.len(), R_size); + + // compute the vector underneath L*Z and the L*blinds + // compute vector-matrix product between L and Z viewed as a matrix + let LZ = poly.bound(&L); + let LZ_blind: G::ScalarField = (0..L.len()).map(|i| blinds.blinds[i] * L[i]).sum(); + + // a dot product proof of size R_size + let (proof, _C_LR, C_Zr_prime) = DotProductProofLog::prove( + &gens.gens, + transcript, + random_tape, + &LZ, + &LZ_blind, + &R, + Zr, + blind_Zr, + ); + + (PolyEvalProof { proof }, C_Zr_prime) + } + + fn verify( + &self, + gens: &PolyCommitmentGens, + transcript: &mut Transcript, + r: &[G::ScalarField], // point at which the polynomial is evaluated + C_Zr: &G, // commitment to \widetilde{Z}(r) + comm: &PolyCommitment, + ) -> Result<(), ProofVerifyError> { + >::append_protocol_name( + transcript, + PolyEvalProof::::protocol_name(), + ); + + // compute L and R + let eq = EqPolynomial::new(r.to_vec()); + let (L, R) = eq.compute_factored_evals(); + + // compute a weighted sum of commitments and L + let C_affine = G::normalize_batch(&comm.C); + + let C_LZ = VariableBaseMSM::msm(C_affine.as_ref(), L.as_ref()).unwrap(); + + self + .proof + .verify(R.len(), &gens.gens, transcript, &R, &C_LZ, C_Zr) + } +} \ No newline at end of file diff --git a/src/subprotocols/mod.rs b/src/subprotocols/mod.rs index 47ba4c0dc..f9492fad6 100644 --- a/src/subprotocols/mod.rs +++ b/src/subprotocols/mod.rs @@ -8,3 +8,4 @@ pub mod grand_product; pub mod sumcheck; pub mod traits; pub mod zeromorph; +pub mod hyrax; diff --git a/src/subprotocols/traits.rs b/src/subprotocols/traits.rs index 671f3f038..7a34f97c7 100644 --- a/src/subprotocols/traits.rs +++ b/src/subprotocols/traits.rs @@ -25,14 +25,7 @@ pub trait PCS { polys: &[DensePolynomial], evals: &[Self::Evaluation], challenges: &[Self::Challenge], - pk: impl Borrow, - transcript: &mut Transcript, - ) -> Result; - - fn open( - polys: &[DensePolynomial], - evals: &[Self::Evaluation], - challenges: &[Self::Challenge], + commitments: &[Self::Commitment], pk: impl Borrow, transcript: &mut Transcript, ) -> Result; diff --git a/src/subprotocols/zeromorph/kzg.rs b/src/subprotocols/zeromorph/kzg.rs index 52413afce..17cbc2ff3 100644 --- a/src/subprotocols/zeromorph/kzg.rs +++ b/src/subprotocols/zeromorph/kzg.rs @@ -1,3 +1,4 @@ +use ark_bn254::g1; use ark_ec::scalar_mul::fixed_base::FixedBase; use std::{borrow::Borrow, marker::PhantomData}; @@ -10,6 +11,16 @@ use ark_std::UniformRand; use rand_chacha::rand_core::RngCore; use thiserror::Error; +#[derive(Error, Debug)] +pub enum KZGError { + #[error("Length Error: SRS Length: {0}, Key Length: {0}")] + KeyLengthError(usize, usize), + #[error("Length Error: Commitment Key Length: {0}, Polynomial Degree {0}")] + CommitLengthError(usize, usize), + #[error("Failed to compute quotient polynomial due to polynomial division")] + PolyDivisionError, +} + #[derive(Debug, Clone, Default)] pub struct UniversalKzgSrs { pub g1_powers: Vec, @@ -32,12 +43,7 @@ pub struct KZGVerifierKey { } impl UniversalKzgSrs

{ - // TODO: add logic to have seed/toxic waste - pub fn setup( - toxic_waste: Option<&[u8]>, - max_degree: usize, - mut rng: &mut R, - ) -> UniversalKzgSrs

{ + pub fn setup(max_degree: usize, rng: &mut R) -> UniversalKzgSrs

{ let tau = P::ScalarField::rand(rng); let g1 = P::G1::rand(rng); let g2 = P::G2::rand(rng); @@ -67,28 +73,29 @@ impl UniversalKzgSrs

{ } } - pub fn extract_prover_key(&self, supported_size: usize) -> Vec { - self.g1_powers[..=supported_size].to_vec() + pub fn get_prover_key(&self, key_size: usize) -> Result, KZGError> { + if self.g1_powers.len() < key_size { + return Err(KZGError::KeyLengthError(self.g1_powers.len(), key_size)); + } + Ok(self.g1_powers[..=key_size].to_vec()) } - /// Returns the verifier parameters - /// - /// # Panics - /// If self.prover_params is empty. - pub fn extract_verifier_key(&self, supported_size: usize) -> KZGVerifierKey

{ - assert!( - self.g1_powers.len() >= supported_size, - "supported_size is greater than self.max_degree()" - ); - KZGVerifierKey { + pub fn get_verifier_key(&self, key_size: usize) -> Result, KZGError> { + if self.g1_powers.len() < key_size { + return Err(KZGError::KeyLengthError(self.g1_powers.len(), key_size)); + } + Ok(KZGVerifierKey { g1: self.g1_powers[0], g2: self.g2_powers[0], tau_2: self.g2_powers[1], - } + }) } - pub fn trim(&self, supported_size: usize) -> (Vec, KZGVerifierKey

) { - let g1_powers = self.g1_powers[..=supported_size].to_vec(); + pub fn trim(&self, key_size: usize) -> Result<(Vec, KZGVerifierKey

), KZGError> { + if self.g1_powers.len() < key_size { + return Err(KZGError::KeyLengthError(self.g1_powers.len(), key_size)); + } + let g1_powers = self.g1_powers[..=key_size].to_vec(); let pk = g1_powers; let vk = KZGVerifierKey { @@ -96,73 +103,59 @@ impl UniversalKzgSrs

{ g2: self.g2_powers[0], tau_2: self.g2_powers[1], }; - (pk, vk) + Ok((pk, vk)) } } -/// Commitments - -/// Polynomial Evaluation - -#[derive(Error, Debug)] -pub enum KZGError { - #[error("length error")] - LengthError, - #[error("Failed to compute quotient polynomial due to polynomial division")] - PolyDivisionError, -} - -/// KZG Polynomial Commitment Scheme on univariate polynomial. -/// Note: this is non-hiding, which is why we will implement traits on this token struct, -/// as we expect to have several impls for the trait pegged on the same instance of a pairing::Engine. -pub struct UVKZGPCS

{ +pub struct UnivariateKZG

{ phantom: PhantomData

, } -impl UVKZGPCS

{ +impl UnivariateKZG

{ pub fn commit_offset( - prover_param: impl Borrow>, + g1_powers: impl Borrow>, poly: &UniPoly, offset: usize, ) -> Result { - let prover_param = prover_param.borrow(); + let g1_powers = g1_powers.borrow(); - if poly.degree() > prover_param.len() { - return Err(KZGError::LengthError); + if poly.degree() > g1_powers.len() { + return Err(KZGError::CommitLengthError(poly.degree(), g1_powers.len())); } let scalars = poly.coeffs.as_slice(); - let bases = prover_param.as_slice(); + let bases = g1_powers.as_slice(); - let c = ::msm(&bases[offset..scalars.len()], &poly.coeffs[offset..]) - .unwrap(); + let com = + ::msm(&bases[offset..scalars.len()], &poly.coeffs[offset..]) + .unwrap(); - Ok(c.into_affine()) + Ok(com.into_affine()) } pub fn commit( - prover_param: impl Borrow>, + g1_powers: impl Borrow>, poly: &UniPoly, ) -> Result { - let prover_param = prover_param.borrow(); + let g1_powers = g1_powers.borrow(); - if poly.degree() > prover_param.len() { - return Err(KZGError::LengthError); + if poly.degree() > g1_powers.len() { + return Err(KZGError::CommitLengthError(poly.degree(), g1_powers.len())); } - let c = ::msm( - &prover_param.as_slice()[..poly.coeffs.len()], + let com = ::msm( + &g1_powers.as_slice()[..poly.coeffs.len()], poly.coeffs.as_slice(), ) .unwrap(); - Ok(c.into_affine()) + Ok(com.into_affine()) } pub fn open( - prover_param: impl Borrow>, + g1_powers: impl Borrow>, polynomial: &UniPoly, point: &P::ScalarField, ) -> Result<(P::G1Affine, P::ScalarField), KZGError> { - let prover_param = prover_param.borrow(); + let g1_powers = g1_powers.borrow(); let divisor = UniPoly { coeffs: vec![-*point, P::ScalarField::one()], }; @@ -171,7 +164,7 @@ impl UVKZGPCS

{ .map(|(q, _r)| q) .ok_or(KZGError::PolyDivisionError)?; let proof = ::msm( - &prover_param.as_slice()[..witness_polynomial.coeffs.len()], + &g1_powers.as_slice()[..witness_polynomial.coeffs.len()], witness_polynomial.coeffs.as_slice(), ) .unwrap(); @@ -202,7 +195,7 @@ impl UVKZGPCS

{ mod tests { use super::*; use crate::poly::unipoly::UniPoly; - use ark_bn254::Bn254; + use ark_bn254::{Bn254, Fr}; use ark_std::{ rand::{Rng, SeedableRng}, @@ -217,20 +210,21 @@ mod tests { UniPoly::from_coeff(coeffs) } - fn end_to_end_test_template() -> Result<(), KZGError> { + #[test] + fn commit_prove_verify() -> Result<(), KZGError> { let seed = b"11111111111111111111111111111111"; for _ in 0..100 { let mut rng = &mut ChaCha20Rng::from_seed(*seed); let degree = rng.gen_range(2..20); - let pp = UniversalKzgSrs::

::setup(None, degree, &mut rng); - let (ck, vk) = pp.trim(degree); - let p = random::(degree, rng); - let comm = UVKZGPCS::

::commit(&ck, &p)?; - let point = P::ScalarField::rand(rng); - let (proof, value) = UVKZGPCS::

::open(&ck, &p, &point)?; + let pp = UniversalKzgSrs::::setup(degree, &mut rng); + let (ck, vk) = pp.trim(degree).unwrap(); + let p = random::(degree, rng); + let comm = UnivariateKZG::::commit(&ck, &p)?; + let point = Fr::rand(rng); + let (proof, value) = UnivariateKZG::::open(&ck, &p, &point)?; assert!( - UVKZGPCS::

::verify(&vk, &comm, &point, &proof, &value)?, + UnivariateKZG::::verify(&vk, &comm, &point, &proof, &value)?, "proof was incorrect for max_degree = {}, polynomial_degree = {}", degree, p.degree(), @@ -238,9 +232,4 @@ mod tests { } Ok(()) } - - #[test] - fn end_to_end_test() { - end_to_end_test_template::().expect("test failed for Bn256"); - } } diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index 99cfec2db..e691c7f58 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -31,10 +31,80 @@ use crate::msm::VariableBaseMSM; #[cfg(feature = "multicore")] use rayon::prelude::*; -use super::kzg::UVKZGPCS; +use super::kzg::UnivariateKZG; + +const MAX_VARS: usize = 17; + +lazy_static! { + pub static ref ZEROMORPH_SRS: Arc>> = + Arc::new(Mutex::new(ZeromorphSRS::setup( + 1 << (MAX_VARS + 1), + &mut ChaCha20Rng::from_seed(*b"ZEROMORPH_POLY_COMMITMENT_SCHEME") + ))); +} + +#[derive(Debug, Clone, Default)] +pub struct ZeromorphSRS(UniversalKzgSrs

); + +impl ZeromorphSRS

{ + pub fn setup(max_degree: usize, rng: &mut R) -> ZeromorphSRS

{ + ZeromorphSRS(UniversalKzgSrs::

::setup(max_degree, rng)) + } + + pub fn trim( + &self, + max_degree: usize, + ) -> Result<(ZeromorphProverKey

, ZeromorphVerifierKey

), ZeromorphError> { + if max_degree > self.0.g1_powers.len() { + return Err(ZeromorphError::KeyLengthError( + max_degree, + self.0.g1_powers.len(), + )); + } + let offset = self.0.g1_powers.len() - max_degree; + let offset_g1_powers = self.0.g1_powers[offset..].to_vec(); + Ok(( + ZeromorphProverKey { + g1_powers: self.0.g1_powers.clone(), + offset_g1_powers: offset_g1_powers, + }, + ZeromorphVerifierKey { + g1: self.0.g1_powers[0], + g2: self.0.g2_powers[0], + tau_2: self.0.g2_powers[1], + tau_N_max_sub_2_N: self.0.g2_powers[offset], + }, + )) + } +} + +#[derive(Clone, Debug)] +pub struct ZeromorphProverKey { + pub g1_powers: Vec, + pub offset_g1_powers: Vec, +} + +#[derive(Copy, Clone, Debug)] +pub struct ZeromorphVerifierKey { + pub g1: P::G1Affine, + pub g2: P::G2Affine, + pub tau_2: P::G2Affine, + pub tau_N_max_sub_2_N: P::G2Affine, +} + +#[derive(Clone, Debug)] +pub struct ZeromorphProof { + pub pi: P::G1Affine, + pub q_hat_com: P::G1Affine, + pub q_k_com: Vec, +} + +#[derive(Error, Debug)] +pub enum ZeromorphError { + #[error("Length Error: SRS Length: {0}, Key Length: {0}")] + KeyLengthError(usize, usize), +} -// Just return vec of P::Scalar -// u_challenge = Point fn compute_multilinear_quotients( poly: &DensePolynomial, u_challenge: &[P::ScalarField], @@ -49,14 +119,25 @@ fn compute_multilinear_quotients( let (g_lo, g_hi) = g.split_at_mut(1 << (poly.get_num_vars() - 1 - i)); let mut quotient = vec![P::ScalarField::zero(); g_lo.len()]; - quotient - .par_iter_mut() + #[cfg(feature = "multicore")] + let quotient_iter = quotient.par_iter_mut(); + + #[cfg(not(feature = "multicore"))] + let quotient_iter = quotient.iter_mut(); + + quotient_iter .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_eq(g_hi).for_each(|(g_lo, g_hi)| { + + #[cfg(feature = "multicore")] + let g_lo_iter = g_lo.par_iter_mut(); + + #[cfg(not(feature = "multicore"))] + let g_lo_iter = g_lo.iter_mut(); + g_lo_iter.zip_eq(g_hi).for_each(|(g_lo, g_hi)| { *g_lo += (*g_hi - g_lo as &_) * x_i; }); @@ -74,25 +155,29 @@ fn compute_batched_lifted_degree_quotient( quotients: &Vec>, y_challenge: &P::ScalarField, ) -> UniPoly { - // 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() { - // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - - // 1}) then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 - let deg_k = (1 << k) as usize - 1; - let offset = n - deg_k - 1; - for i in 0..(deg_k + 1) { - res[offset + i] += scalar * quotient[i]; - } - scalar *= y_challenge; // update batching scalar y^k - } + // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - 1}) + // then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 + let q_hat = + quotients + .iter() + .enumerate() + .fold(vec![P::ScalarField::zero(); n], |mut q_hat, (idx, q)| { + #[cfg(feature = "multicore")] + let q_hat_iter = q_hat[n - (1 << idx)..].par_iter_mut(); + + #[cfg(not(feature = "multicore"))] + let q_hat_iter = q_hat[n - (1 << idx)..].iter_mut(); + q_hat_iter.zip(&q.coeffs).for_each(|(q_hat, q)| { + *q_hat += scalar * q; + }); + scalar *= y_challenge; + q_hat + }); - UniPoly::from_coeff(res) + UniPoly::from_coeff(q_hat) } fn eval_and_quotient_scalars( @@ -125,7 +210,6 @@ fn eval_and_quotient_scalars( let vs = { let v_numer = squares_of_x[num_vars] - P::ScalarField::one(); - // 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()) @@ -137,7 +221,6 @@ fn eval_and_quotient_scalars( .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) @@ -159,83 +242,10 @@ fn eval_and_quotient_scalars( (-vs[0] * z_challenge, q_scalars) } -//TODO: The SRS is set with a default value of ____ if this is to be changed (extended) use the cli arg and change it manually. -//TODO: add input specifiying monomial or lagrange basis -const MAX_VARS: usize = 20; -lazy_static! { - pub static ref ZEROMORPH_SRS: Arc>> = - Arc::new(Mutex::new(ZeromorphSRS::setup( - None, - 1 << (MAX_VARS + 1), - &mut ChaCha20Rng::from_seed(*b"11111111111111111111111111111111") - ))); -} - -#[derive(Debug, Clone, Default)] -pub struct ZeromorphSRS(UniversalKzgSrs

); - -impl ZeromorphSRS

{ - pub fn setup( - toxic_waste: Option<&[u8]>, - max_degree: usize, - mut rng: &mut R, - ) -> ZeromorphSRS

{ - ZeromorphSRS(UniversalKzgSrs::

::setup(None, max_degree, rng)) - } - - pub fn trim(&self, max_degree: usize) -> (ZeromorphProverKey

, ZeromorphVerifierKey

) { - let offset = self.0.g1_powers.len() - max_degree; - let offset_g1_powers = self.0.g1_powers[offset..].to_vec(); - ( - ZeromorphProverKey { - g1_powers: self.0.g1_powers.clone(), - offset_g1_powers: offset_g1_powers, - }, - ZeromorphVerifierKey { - g1: self.0.g1_powers[0], - g2: self.0.g2_powers[0], - tau_2: self.0.g2_powers[1], - tau_N_max_sub_2_N: self.0.g2_powers[offset], - }, - ) - } -} - -#[derive(Clone, Debug)] -pub struct ZeromorphProverKey { - // generator - pub g1_powers: Vec, - pub offset_g1_powers: Vec, -} - -#[derive(Copy, Clone, Debug)] -pub struct ZeromorphVerifierKey { - pub g1: P::G1Affine, - pub g2: P::G2Affine, - pub tau_2: P::G2Affine, - pub tau_N_max_sub_2_N: P::G2Affine, -} - -//TODO: can we upgrade the transcript to give not just absorb -#[derive(Clone, Debug)] -pub struct ZeromorphProof { - pub pi: P::G1Affine, - pub q_hat_com: P::G1Affine, - pub q_k_com: Vec, -} - -#[derive(Error, Debug)] -pub enum ZeromorphError { - #[error("oh no {0}")] - Invalid(String), -} - pub struct Zeromorph { _phantom: PhantomData

, } -/// Compute the powers of a challenge -/// impl PCS for Zeromorph

{ type Commitment = P::G1Affine; type Evaluation = P::ScalarField; @@ -250,63 +260,53 @@ impl PCS for Zeromorph

{ polys: &[DensePolynomial], pk: &Self::ProverKey, ) -> Result, Self::Error> { - let uni_polys: Vec<_> = polys - .iter() - .map(|poly| UniPoly::from_coeff(poly.Z.clone())) - .collect(); - - // TODO: parallelize + #[cfg(feature = "multicore")] + let iter = polys.par_iter(); + #[cfg(not(feature = "multicore"))] + let iter = polys.iter(); Ok( - uni_polys - .iter() - .map(|poly| UVKZGPCS::

::commit(&pk.g1_powers, poly).unwrap()) + iter + .map(|poly| { + UnivariateKZG::

::commit(&pk.g1_powers, &UniPoly::from_coeff(poly.Z.clone())).unwrap() + }) .collect::>(), ) } - fn open( + fn prove( polys: &[DensePolynomial], evals: &[Self::Evaluation], challenge: &[Self::Challenge], + commitments: &[Self::Commitment], pk: impl Borrow, transcript: &mut Transcript, ) -> Result { - // ASSERT evaluations, challenges, and polynomials are the same size - debug_assert_eq!(evals.len(), polys.len()); - - // TODO: assert that the commitments to the polys match the commitments of the polys - // TODO: assert that the evals match the poly evlatuated at the point. - let num_vars = challenge.len(); let n: usize = 1 << num_vars; let pk = pk.borrow(); + for (poly, com) in polys.iter().zip_eq(commitments.iter()) { + debug_assert_eq!( + UnivariateKZG::

::commit(&pk.g1_powers, &UniPoly::from_coeff(poly.Z.clone())).unwrap(), + *com + ); + } + for (poly, eval) in polys.iter().zip_eq(evals.iter()) { + // Note by evaluating we confirm the number of challenges is valid + debug_assert_eq!(poly.evaluate(challenge), *eval); + } + // Generate batching challenge \rho and powers 1,...,\rho^{m-1} - //TODO: condense this into one scan for the entire batching mechanism let rho = >::challenge_scalar(transcript, b"ZM: rho"); - let rhos = (0..evals.len()) - .scan(P::ScalarField::one(), |acc, _| { - let val = *acc; - *acc *= rho; - Some(val) - }) - .collect::>(); - // Compute batching of unshifted polynomials f_i: - // f_batched = sum_{i=0}^{m-1}\rho^i*f_i - // TODO: add += for multilinear - let mut batched_evaluation = P::ScalarField::zero(); - let mut f_batched = vec![P::ScalarField::zero(); n]; - for (i, f_poly) in polys.iter().enumerate() { - // add_scaled - for j in 0..f_batched.len() { - f_batched[j] += f_poly[j] * rhos[i]; - } - - batched_evaluation += rhos[i] * evals[i]; - } - let mut pi_poly = UniPoly::from_coeff(f_batched.clone()); - let f_batched = DensePolynomial::new(f_batched); + let mut scalar = P::ScalarField::one(); + let (f_batched, batched_evaluation) = (0..polys.len()).fold((DensePolynomial::new(vec![P::ScalarField::zero(); n]), P::ScalarField::zero()), |(mut f_batched, mut batched_evaluation), i| { + f_batched += polys[i].clone() * scalar; + batched_evaluation += scalar * evals[i]; + scalar *= rho; + (f_batched, batched_evaluation) + }); + let mut pi_poly = UniPoly::from_coeff(f_batched.Z.clone()); // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) let (quotients, remainder) = compute_multilinear_quotients::

(&f_batched, challenge); @@ -319,7 +319,7 @@ impl PCS for Zeromorph

{ let q_k_commitments: Vec<_> = quotients .iter() .map(|q| { - let q_k_commitment = UVKZGPCS::

::commit(&pk.g1_powers, q).unwrap(); + let q_k_commitment = UnivariateKZG::

::commit(&pk.g1_powers, q).unwrap(); transcript.append_point(label, &q_k_commitment.into_group()); q_k_commitment }) @@ -336,7 +336,7 @@ impl PCS for Zeromorph

{ // Compute and send the commitment C_q = [\hat{q}] // commit at offset let offset = 1 << (quotients.len() - 1); - let q_hat_com = UVKZGPCS::

::commit_offset(&pk.g1_powers, &q_hat, offset).unwrap(); + let q_hat_com = UnivariateKZG::

::commit_offset(&pk.g1_powers, &q_hat, offset).unwrap(); transcript.append_point(b"ZM: C_q_hat", &q_hat_com.into_group()); // Get challenges x and z @@ -363,7 +363,7 @@ impl PCS for Zeromorph

{ debug_assert_eq!(pi_poly.evaluate(&x_challenge), P::ScalarField::zero()); // Compute the KZG opening proof pi_poly; -> TODO should abstract into separate trait - let (pi, _) = UVKZGPCS::

::open(&pk.offset_g1_powers, &pi_poly, &x_challenge).unwrap(); + let (pi, _) = UnivariateKZG::

::open(&pk.offset_g1_powers, &pi_poly, &x_challenge).unwrap(); Ok(ZeromorphProof { pi, @@ -372,16 +372,6 @@ impl PCS for Zeromorph

{ }) } - fn prove( - polys: &[DensePolynomial], - evals: &[Self::Evaluation], - challenges: &[Self::Challenge], - pk: impl Borrow, - transcript: &mut Transcript, - ) -> Result { - Self::open(&polys, evals, challenges, pk, transcript) - } - fn verify( commitments: &[Self::Commitment], evals: &[Self::Evaluation], @@ -412,22 +402,14 @@ impl PCS for Zeromorph

{ // Compute powers of batching challenge rho let rho = >::challenge_scalar(transcript, b"ZM: rho"); - // TODO merge all of batching into one scan/fold - let rhos = (0..evals.len()) - .scan(P::ScalarField::one(), |acc, _| { - let val = *acc; - *acc *= rho; - Some(val) - }) - .collect::>(); - - // Construct batched evaluations v = sum_{i=0}^{m-1}\rho^i*f_i(u) - let mut batched_evaluation = P::ScalarField::zero(); - let mut batched_commitment = P::G1::zero(); - for (i, (eval, commitment)) in evals.iter().zip(commitments.iter()).enumerate() { - batched_evaluation += *eval * rhos[i]; - batched_commitment += *commitment * rhos[i]; - } + // Compute batching of unshifted polynomials f_i: + let mut scalar = P::ScalarField::one(); + let (batched_evaluation, batched_commitment) = evals.iter().zip_eq(commitments.iter()).fold((P::ScalarField::zero(), P::G1::zero()), |(mut batched_evaluation, mut batched_commitment), (eval, commitment)| { + batched_evaluation += scalar * eval; + batched_commitment += *commitment * scalar; + scalar *= rho; + (batched_evaluation, batched_commitment) + }); // Challenge y let y_challenge = @@ -507,7 +489,7 @@ mod test { // Setup let (pk, vk) = { let poly_size = 1 << (num_vars + 1); - srs.trim(poly_size - 1) + srs.trim(poly_size - 1).unwrap() }; let polys = DensePolynomial::new( (0..(1 << num_vars)) @@ -523,9 +505,15 @@ mod test { let commitments = Zeromorph::::commit(&[polys.clone()], &pk).unwrap(); let mut prover_transcript = Transcript::new(b"example"); - let proof = - Zeromorph::::prove(&[polys], &[evals], &challenges, &pk, &mut prover_transcript) - .unwrap(); + let proof = Zeromorph::::prove( + &[polys], + &[evals], + &challenges, + &commitments, + &pk, + &mut prover_transcript, + ) + .unwrap(); let mut verifier_transcript = Transcript::new(b"example"); Zeromorph::::verify( @@ -553,7 +541,7 @@ mod test { // Setup let (pk, vk) = { let poly_size = 1 << (num_vars + 1); - srs.trim(poly_size - 1) + srs.trim(poly_size - 1).unwrap() }; let polys: Vec> = (0..num_polys) .map(|_| { @@ -575,13 +563,18 @@ mod test { .collect::>(); // Commit and open - // TODO: move to commit let commitments = Zeromorph::::commit(&polys, &pk).unwrap(); let mut prover_transcript = Transcript::new(b"example"); - let proof = - Zeromorph::::prove(&polys, &evals, &challenges, &pk, &mut prover_transcript) - .unwrap(); + let proof = Zeromorph::::prove( + &polys, + &evals, + &challenges, + &commitments, + &pk, + &mut prover_transcript, + ) + .unwrap(); let mut verifier_transcript = Transcript::new(b"example"); Zeromorph::::verify( @@ -658,17 +651,14 @@ mod test { let n = 1 << num_vars; // 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![ + let q_0 = UniPoly::from_coeff(vec![Fr::one()]); + let q_1 = UniPoly::from_coeff(vec![Fr::from(2u64), Fr::from(3u64)]); + let q_2 = UniPoly::from_coeff(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, q_1, q_2]; let mut rng = test_rng(); @@ -679,8 +669,7 @@ mod test { compute_batched_lifted_degree_quotient::(n, "ients, &y_challenge); //Explicitly define q_k_lifted = X^{N-2^k} * q_k and compute the expected batched result - //Note: we've hard programmed in the size of these vectors not the best practice - let data_0_lifted = vec![ + let q_0_lifted = UniPoly::from_coeff(vec![ Fr::zero(), Fr::zero(), Fr::zero(), @@ -689,8 +678,8 @@ mod test { Fr::zero(), Fr::zero(), Fr::one(), - ]; - let data_1_lifted = vec![ + ]); + let q_1_lifted = UniPoly::from_coeff(vec![ Fr::zero(), Fr::zero(), Fr::zero(), @@ -699,8 +688,8 @@ mod test { Fr::zero(), Fr::from(2u64), Fr::from(3u64), - ]; - let data_2_lifted = vec![ + ]); + let q_2_lifted = UniPoly::from_coeff(vec![ Fr::zero(), Fr::zero(), Fr::zero(), @@ -709,30 +698,15 @@ mod test { Fr::from(5u64), Fr::from(6u64), Fr::from(7u64), - ]; - let q_0_lifted = UniPoly::from_coeff(data_0_lifted); - let q_1_lifted = UniPoly::from_coeff(data_1_lifted); - let q_2_lifted = UniPoly::from_coeff(data_2_lifted); + ]); //Explicitly compute ฬ‚q i.e. RLC of lifted polys - let mut batched_quotient_expected = DensePolynomial::new(vec![Fr::zero(); n]); - //TODO: implement add and add_scalad - for i in 0..batched_quotient_expected.len() { - batched_quotient_expected[i] += q_0_lifted[i]; - } + let mut batched_quotient_expected = UniPoly::from_coeff(vec![Fr::zero(); n]); - for i in 0..batched_quotient_expected.len() { - batched_quotient_expected[i] += q_1_lifted[i] * y_challenge; - } - - for i in 0..batched_quotient_expected.len() { - batched_quotient_expected[i] += q_2_lifted[i] * (y_challenge * y_challenge); - } - - for i in 0..batched_quotient.len() { - assert_eq!(batched_quotient[i], batched_quotient_expected[i]); - } - // Implement PartialEq in DensePolynomial + batched_quotient_expected += &q_0_lifted; + batched_quotient_expected += &(q_1_lifted * y_challenge); + batched_quotient_expected += &(q_2_lifted * (y_challenge * y_challenge)); + assert_eq!(batched_quotient, batched_quotient_expected); } /// evaluated quotient \zeta_x From 356a387ca94351178a846e7d0abc11f29edc05ca Mon Sep 17 00:00:00 2001 From: PatStiles Date: Wed, 17 Jan 2024 00:44:56 -0600 Subject: [PATCH 19/21] abstracted hyrax impl PCS trait interface --- src/e2e_test.rs | 5 +- src/lasso/densified.rs | 18 +- src/lasso/memory_checking.rs | 41 ++-- src/lasso/surge.rs | 8 +- src/poly/commitments.rs | 2 +- src/poly/dense_mlpoly.rs | 49 +++-- src/poly/unipoly.rs | 2 +- src/subprotocols/bullet.rs | 2 +- src/subprotocols/dot_product.rs | 3 +- src/subprotocols/hyrax.rs | 251 +++++++++++++++++++----- src/subprotocols/mod.rs | 2 +- src/subprotocols/traits.rs | 32 ++- src/subprotocols/zeromorph/zeromorph.rs | 128 ++++++------ src/subtables/mod.rs | 26 ++- 14 files changed, 365 insertions(+), 204 deletions(-) diff --git a/src/e2e_test.rs b/src/e2e_test.rs index 36b2166df..926189de4 100644 --- a/src/e2e_test.rs +++ b/src/e2e_test.rs @@ -15,7 +15,7 @@ use crate::{ }; macro_rules! e2e_test { - ($test_name:ident, $Strategy:ty, $G:ty, $F:ty, $C:expr, $M:expr, $sparsity:expr) => { + ($test_name:ident, $Strategy:ty, $G:ty, $F:ty, $PCS:ty, $CK:expr, $C:expr, $M:expr, $sparsity:expr) => { #[test] fn $test_name() { use crate::utils::test::{gen_indices, gen_random_point}; @@ -36,7 +36,8 @@ macro_rules! e2e_test { DensifiedRepresentation::from_lookup_indices(&nz, log_M); let gens = SparsePolyCommitmentGens::<$G>::new(b"gens_sparse_poly", C, $sparsity, NUM_MEMORIES, log_M); - let commitment = dense.commit::<$G>(&gens); + let ck = + let commitment = dense.commit::<$G, $PCS>(&ck); let r: Vec<$F> = gen_random_point(log_s); diff --git a/src/lasso/densified.rs b/src/lasso/densified.rs index 69555381a..d1bbfe008 100644 --- a/src/lasso/densified.rs +++ b/src/lasso/densified.rs @@ -3,6 +3,8 @@ use ark_ff::PrimeField; use super::surge::{SparsePolyCommitmentGens, SparsePolynomialCommitment}; use crate::poly::dense_mlpoly::DensePolynomial; +use crate::subprotocols::hyrax::Hyrax; +use crate::subprotocols::traits::PolynomialCommitmentScheme; use crate::utils::math::Math; pub struct DensifiedRepresentation { @@ -80,12 +82,16 @@ impl DensifiedRepresentation { &self, gens: &SparsePolyCommitmentGens, ) -> SparsePolynomialCommitment { - let (l_variate_polys_commitment, _) = self - .combined_l_variate_polys - .commit(&gens.gens_combined_l_variate, None); - let (log_m_variate_polys_commitment, _) = self - .combined_log_m_variate_polys - .commit(&gens.gens_combined_log_m_variate, None); + let (l_variate_polys_commitment, _) = Hyrax::commit( + self.combined_l_variate_polys.clone(), + (gens.gens_combined_l_variate.clone(), None), + ) + .unwrap(); + let (log_m_variate_polys_commitment, _) = Hyrax::commit( + self.combined_log_m_variate_polys.clone(), + (gens.gens_combined_log_m_variate, None), + ) + .unwrap(); SparsePolynomialCommitment { l_variate_polys_commitment, diff --git a/src/lasso/memory_checking.rs b/src/lasso/memory_checking.rs index 9965d59bc..6c5f98150 100644 --- a/src/lasso/memory_checking.rs +++ b/src/lasso/memory_checking.rs @@ -2,9 +2,14 @@ #![allow(clippy::type_complexity)] use crate::lasso::densified::DensifiedRepresentation; use crate::lasso::surge::{SparsePolyCommitmentGens, SparsePolynomialCommitment}; -use crate::poly::dense_mlpoly::{DensePolynomial, PolyEvalProof}; +use crate::poly::dense_mlpoly::DensePolynomial; use crate::poly::identity_poly::IdentityPolynomial; -use crate::subprotocols::grand_product::{BatchedGrandProductArgument, GrandProductCircuit}; +use crate::subprotocols::hyrax::Hyrax; +use crate::subprotocols::traits::PolynomialCommitmentScheme; +use crate::subprotocols::{ + grand_product::{BatchedGrandProductArgument, GrandProductCircuit}, + hyrax::PolyEvalProof, +}; use crate::subtables::{ CombinedTableCommitment, CombinedTableEvalProof, SubtableStrategy, Subtables, }; @@ -406,16 +411,14 @@ where &joint_claim_eval_ops, ); - let (proof_ops, _) = PolyEvalProof::prove( - &dense.combined_l_variate_polys, - None, - &r_joint_ops, - &joint_claim_eval_ops, - None, - &gens.gens_combined_l_variate, + let (proof_ops, _) = Hyrax::prove( + dense.combined_l_variate_polys, + Some(joint_claim_eval_ops), + r_joint_ops, + (None, None, gens.gens_combined_l_variate, *random_tape), transcript, - random_tape, - ); + ) + .unwrap(); >::append_scalars(transcript, b"claim_evals_mem", &eval_final); let challenges_mem = >::challenge_vector( @@ -444,16 +447,14 @@ where &joint_claim_eval_mem, ); - let (proof_mem, _) = PolyEvalProof::prove( - &dense.combined_log_m_variate_polys, - None, - &r_joint_mem, - &joint_claim_eval_mem, - None, - &gens.gens_combined_log_m_variate, + let (proof_mem, _) = Hyrax::prove( + dense.combined_log_m_variate_polys, + Some(joint_claim_eval_mem), + r_joint_mem, + (None, None, gens.gens_combined_log_m_variate, *random_tape), transcript, - random_tape, - ); + ) + .unwrap(); HashLayerProof { eval_dim, diff --git a/src/lasso/surge.rs b/src/lasso/surge.rs index ab70e5ac6..6da0b65b1 100644 --- a/src/lasso/surge.rs +++ b/src/lasso/surge.rs @@ -4,9 +4,12 @@ use crate::lasso::densified::DensifiedRepresentation; use crate::lasso::memory_checking::MemoryCheckingProof; -use crate::poly::dense_mlpoly::{DensePolynomial, PolyCommitment, PolyCommitmentGens}; +use crate::poly::dense_mlpoly::DensePolynomial; use crate::poly::eq_poly::EqPolynomial; -use crate::subprotocols::sumcheck::SumcheckInstanceProof; +use crate::subprotocols::{ + hyrax::{PolyCommitment, PolyCommitmentGens}, + sumcheck::SumcheckInstanceProof, +}; use crate::subtables::{ CombinedTableCommitment, CombinedTableEvalProof, SubtableStrategy, Subtables, }; @@ -23,6 +26,7 @@ use merlin::Transcript; use std::marker::Sync; // Public Params +#[derive(Clone)] pub struct SparsePolyCommitmentGens { pub gens_combined_l_variate: PolyCommitmentGens, pub gens_combined_log_m_variate: PolyCommitmentGens, diff --git a/src/poly/commitments.rs b/src/poly/commitments.rs index 52378108c..e16857e6b 100644 --- a/src/poly/commitments.rs +++ b/src/poly/commitments.rs @@ -11,7 +11,7 @@ use ark_ec::VariableBaseMSM; #[cfg(not(feature = "ark-msm"))] use crate::msm::VariableBaseMSM; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MultiCommitGens { pub n: usize, pub G: Vec, diff --git a/src/poly/dense_mlpoly.rs b/src/poly/dense_mlpoly.rs index 21f5e7f68..524e55b32 100644 --- a/src/poly/dense_mlpoly.rs +++ b/src/poly/dense_mlpoly.rs @@ -2,8 +2,7 @@ use crate::poly::eq_poly::EqPolynomial; use crate::utils::{self, compute_dotproduct}; -use super::commitments::{Commitments, MultiCommitGens}; -use crate::subprotocols::dot_product::{DotProductProofGens, DotProductProofLog}; +use crate::subprotocols::dot_product::DotProductProofLog; use crate::utils::errors::ProofVerifyError; use crate::utils::math::Math; use crate::utils::random::RandomTape; @@ -13,8 +12,8 @@ use ark_ff::PrimeField; use ark_serialize::*; use ark_std::Zero; use core::ops::Index; -use std::cmp::Ordering; use merlin::Transcript; +use std::cmp::Ordering; use std::ops::{AddAssign, IndexMut, Mul}; #[cfg(feature = "ark-msm")] @@ -34,6 +33,7 @@ pub struct DensePolynomial { pub Z: Vec, // evaluations of the polynomial in all the 2^num_vars Boolean inputs } +/* pub struct PolyCommitmentGens { pub gens: DotProductProofGens, } @@ -60,7 +60,7 @@ pub struct PolyCommitment { pub struct ConstPolyCommitment { C: G, } - +*/ impl DensePolynomial { pub fn new(Z: Vec) -> Self { assert!( @@ -109,6 +109,7 @@ impl DensePolynomial { ) } + /* #[cfg(feature = "multicore")] fn commit_inner>( &self, @@ -182,6 +183,7 @@ impl DensePolynomial { (self.commit_inner(&blinds.blinds, &gens.gens.gens_n), blinds) } + */ #[tracing::instrument(skip_all, name = "DensePolynomial.bound")] pub fn bound(&self, L: &[F]) -> Vec { @@ -288,6 +290,7 @@ impl IndexMut for DensePolynomial { } } +/* impl AppendToTranscript for PolyCommitment { fn append_to_transcript>(&self, label: &'static [u8], transcript: &mut T) { transcript.append_message(label, b"poly_commitment_begin"); @@ -409,6 +412,7 @@ impl PolyEvalProof { self.verify(gens, transcript, r, &C_Zr, comm) } } +*/ impl AddAssign for DensePolynomial { fn add_assign(&mut self, rhs: Self) { @@ -418,9 +422,7 @@ impl AddAssign for DensePolynomial { *lhs += rhs; } if matches!(ordering, Ordering::Less) { - self - .Z - .extend(rhs.Z[self.Z.len()..].iter().cloned()); + self.Z.extend(rhs.Z[self.Z.len()..].iter().cloned()); } } } @@ -452,6 +454,9 @@ mod tests { use super::*; use crate::subprotocols::dot_product::DotProductProof; + use crate::subprotocols::hyrax::Hyrax; + use crate::subprotocols::hyrax::PolyCommitmentGens; + use crate::subprotocols::traits::PolynomialCommitmentScheme; use ark_curve25519::EdwardsProjective as G1Projective; use ark_curve25519::Fr; use ark_std::test_rng; @@ -649,26 +654,30 @@ mod tests { assert_eq!(eval, G::ScalarField::from(28u64)); let gens = PolyCommitmentGens::::new(poly.get_num_vars(), b"test-two"); - let (poly_commitment, blinds) = poly.commit(&gens, None); + let (poly_commitment, blinds) = Hyrax::commit(poly, &(gens, None)).unwrap(); let mut random_tape = RandomTape::new(b"proof"); let mut prover_transcript = Transcript::new(b"example"); - let (proof, C_Zr) = PolyEvalProof::prove( - &poly, - Some(&blinds), - &r, - &eval, - None, - &gens, + let (proof, C_Zr) = Hyrax::prove( + poly, + Some(eval), + r, + (Some(blinds), None, gens, random_tape), &mut prover_transcript, - &mut random_tape, - ); + ) + .unwrap(); let mut verifier_transcript = Transcript::new(b"example"); - assert!(proof - .verify(&gens, &mut verifier_transcript, &r, &C_Zr, &poly_commitment) - .is_ok()); + assert!(Hyrax::verify( + (poly_commitment, blinds), + None, + r, + &gens, + &mut verifier_transcript, + (proof, C_Zr) + ) + .is_ok()); } #[test] diff --git a/src/poly/unipoly.rs b/src/poly/unipoly.rs index 295ba2bc2..380a14790 100644 --- a/src/poly/unipoly.rs +++ b/src/poly/unipoly.rs @@ -9,7 +9,7 @@ use crate::utils::transcript::{AppendToTranscript, ProofTranscript}; use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_serialize::*; -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator, IntoParallelIterator}; +use rayon::iter::{IntoParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; // ax^2 + bx + c stored as vec![c,b,a] // ax^3 + bx^2 + cx + d stored as vec![d,c,b,a] diff --git a/src/subprotocols/bullet.rs b/src/subprotocols/bullet.rs index fc6a57f4c..386b62833 100644 --- a/src/subprotocols/bullet.rs +++ b/src/subprotocols/bullet.rs @@ -20,7 +20,7 @@ use ark_ec::VariableBaseMSM; #[cfg(not(feature = "ark-msm"))] use super::super::msm::VariableBaseMSM; -#[derive(Debug, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct BulletReductionProof { L_vec: Vec, R_vec: Vec, diff --git a/src/subprotocols/dot_product.rs b/src/subprotocols/dot_product.rs index 670fe8cc7..490925c7f 100644 --- a/src/subprotocols/dot_product.rs +++ b/src/subprotocols/dot_product.rs @@ -136,6 +136,7 @@ impl DotProductProof { } } +#[derive(Clone)] pub struct DotProductProofGens { n: usize, pub gens_n: MultiCommitGens, @@ -149,7 +150,7 @@ impl DotProductProofGens { } } -#[derive(Debug, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct DotProductProofLog { bullet_reduction_proof: BulletReductionProof, delta: G, diff --git a/src/subprotocols/hyrax.rs b/src/subprotocols/hyrax.rs index 091ad2d25..064f9f8eb 100644 --- a/src/subprotocols/hyrax.rs +++ b/src/subprotocols/hyrax.rs @@ -1,7 +1,58 @@ -use super::traits::PCS; +use super::dot_product::DotProductProofGens; +use super::traits::PolynomialCommitmentScheme; +use crate::poly::commitments::{Commitments, MultiCommitGens}; +use crate::poly::dense_mlpoly::DensePolynomial; +use crate::poly::eq_poly::EqPolynomial; +use crate::subprotocols::dot_product::DotProductProofLog; +use crate::utils::errors::ProofVerifyError; +use crate::utils::math::Math; +use crate::utils::random::RandomTape; +use crate::utils::transcript::ProofTranscript; +use crate::{msm::VariableBaseMSM, utils::transcript::AppendToTranscript}; +use ark_ec::CurveGroup; +use ark_serialize::*; +use ark_std::Zero; +use merlin::Transcript; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use std::borrow::Borrow; +use std::marker::PhantomData; -#[derive(Debug, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Debug, CanonicalSerialize, CanonicalDeserialize, Clone)] +pub struct PolyCommitment { + C: Vec, +} + +impl AppendToTranscript for PolyCommitment { + fn append_to_transcript>(&self, label: &'static [u8], transcript: &mut T) { + transcript.append_message(label, b"poly_commitment_begin"); + for i in 0..self.C.len() { + transcript.append_point(b"poly_commitment_share", &self.C[i]); + } + transcript.append_message(label, b"poly_commitment_end"); + } +} + +#[derive(Clone)] +pub struct PolyCommitmentBlinds { + blinds: Vec, +} + +#[derive(Clone)] +pub struct PolyCommitmentGens { + pub gens: DotProductProofGens, +} + +impl PolyCommitmentGens { + // the number of variables in the multilinear polynomial + pub fn new(num_vars: usize, label: &'static [u8]) -> Self { + let (_left, right) = EqPolynomial::::compute_factored_lens(num_vars); + let gens = DotProductProofGens::new(right.pow2(), label); + PolyCommitmentGens { gens } + } +} + +#[derive(Debug, CanonicalSerialize, Clone, CanonicalDeserialize)] pub struct PolyEvalProof { proof: DotProductProofLog, } @@ -10,83 +61,171 @@ impl PolyEvalProof { fn protocol_name() -> &'static [u8] { b"polynomial evaluation proof" } + + pub fn verify_plain( + &self, + gens: &PolyCommitmentGens, + transcript: &mut Transcript, + r: &[G::ScalarField], // point at which the polynomial is evaluated + Zr: &G::ScalarField, // evaluation \widetilde{Z}(r) + comm: &PolyCommitment, + ) -> Result<(), ProofVerifyError> { + // compute a commitment to Zr with a blind of zero + let C_Zr = Zr.commit(&G::ScalarField::zero(), &gens.gens.gens_1); + + // TODO: Make blinds an Option + Hyrax::verify( + ( + comm.clone(), + PolyCommitmentBlinds { + blinds: vec![G::ScalarField::zero()], + }, + ), + None, + r.to_vec(), + gens, + transcript, + (self.clone(), C_Zr), + ) + } } -impl PCS for PolyEvalProof { - type Commitment; - type Evaluation; - type Challenge; - type Proof; - type Error; +#[cfg(feature = "multicore")] +fn commit_inner( + poly: &DensePolynomial, + blinds: &[G::ScalarField], + gens: &MultiCommitGens, +) -> PolyCommitment { + let L_size = blinds.len(); + let R_size = poly.Z.len() / L_size; + assert_eq!(L_size * R_size, poly.Z.len()); + let C = (0..L_size) + .into_par_iter() + .map(|i| { + Commitments::batch_commit( + poly.Z[R_size * i..R_size * (i + 1)].as_ref(), + &blinds[i], + gens, + ) + }) + .collect(); + PolyCommitment { C } +} + +#[cfg(not(feature = "multicore"))] +fn commit_inner( + poly: &DensePolynomial, + blinds: &[F], + gens: &MultiCommitGens, +) -> PolyCommitment { + let L_size = blinds.len(); + let R_size = poly.Z.len() / L_size; + assert_eq!(L_size * R_size, poly.Z.len()); + let C = (0..L_size) + .map(|i| { + Commitments::batch_commit( + poly.Z[R_size * i..R_size * (i + 1)].as_ref(), + &blinds[i], + gens, + ) + }) + .collect(); + PolyCommitment { C } +} + +pub struct Hyrax { + _phantom: PhantomData, +} + +impl PolynomialCommitmentScheme for Hyrax { + // TODO: remove/manage blinds somehow -> Option + type Commitment = (PolyCommitment, PolyCommitmentBlinds); + type Polynomial = DensePolynomial; + type Evaluation = Option; + type Challenge = Vec; + type Proof = (PolyEvalProof, G); + type Error = ProofVerifyError; - type ProverKey; - type VerifierKey; + type ProverKey = ( + Option>, + Option, + PolyCommitmentGens, + RandomTape, + ); + type CommitmentKey = (PolyCommitmentGens, Option>); + type VerifierKey = PolyCommitmentGens; #[tracing::instrument(skip_all, name = "DensePolynomial.commit")] - fn commit( - &self, - gens: &PolyCommitmentGens, - random_tape: Option<&mut RandomTape>, - ) -> (PolyCommitment, PolyCommitmentBlinds) - where - G: CurveGroup, - { - let n = self.Z.len(); - let ell = self.get_num_vars(); + fn commit( + poly: Self::Polynomial, + ck: impl Borrow, + ) -> Result { + let n = poly.Z.len(); + let ell = poly.get_num_vars(); + let ck = ck.borrow(); assert_eq!(n, ell.pow2()); - let (left_num_vars, right_num_vars) = EqPolynomial::::compute_factored_lens(ell); + let (left_num_vars, right_num_vars) = + EqPolynomial::::compute_factored_lens(ell); let L_size = left_num_vars.pow2(); let R_size = right_num_vars.pow2(); assert_eq!(L_size * R_size, n); + let (gens, random_tape) = ck; let blinds = if let Some(t) = random_tape { PolyCommitmentBlinds { blinds: t.random_vector(b"poly_blinds", L_size), } } else { PolyCommitmentBlinds { - blinds: vec![F::zero(); L_size], + blinds: vec![G::ScalarField::zero(); L_size], } }; + Ok(( + commit_inner(&poly, &blinds.blinds, &gens.gens.gens_n), + blinds, + )) + } - - #[tracing::instrument(skip_all, name = "DensePolyEval.prove")] + // Note this excludes commitments which introduces a concern that the proof would generate for polys and commitments not tied to one another fn prove( - poly: &DensePolynomial, - blinds_opt: Option<&PolyCommitmentBlinds>, - r: &[G::ScalarField], // point at which the polynomial is evaluated - Zr: &G::ScalarField, // evaluation of \widetilde{Z}(r) - blind_Zr_opt: Option<&G::ScalarField>, // specifies a blind for Zr - gens: &PolyCommitmentGens, + poly: Self::Polynomial, + //blinds_opt: Option<&PolyCommitmentBlinds>, + evals: Self::Evaluation, // evaluation of \widetilde{Z}(r) + challenges: Self::Challenge, // point at which the polynomial is evaluated + //blind_Zr_opt: Option<&G::ScalarField>, // specifies a blind for Zr + //gens: &PolyCommitmentGens, + //random_tape: &mut RandomTape, + pk: impl Borrow, transcript: &mut Transcript, - random_tape: &mut RandomTape, - ) -> (PolyEvalProof, G) { + ) -> Result { >::append_protocol_name( transcript, PolyEvalProof::::protocol_name(), ); + let (blinds_opt, blind_Zr_opt, gens, random_tape) = pk.borrow(); + // assert vectors are of the right size - assert_eq!(poly.get_num_vars(), r.len()); + assert_eq!(poly.get_num_vars(), challenges.len()); let (left_num_vars, right_num_vars) = - EqPolynomial::::compute_factored_lens(r.len()); + EqPolynomial::::compute_factored_lens(challenges.len()); let L_size = left_num_vars.pow2(); let R_size = right_num_vars.pow2(); let default_blinds = PolyCommitmentBlinds { blinds: vec![G::ScalarField::zero(); L_size], }; - let blinds = blinds_opt.map_or(&default_blinds, |p| p); + let blinds = blinds_opt.map_or(&default_blinds, |p| &p); assert_eq!(blinds.blinds.len(), L_size); let zero = G::ScalarField::zero(); - let blind_Zr = blind_Zr_opt.map_or(&zero, |p| p); + let blind_Zr = blind_Zr_opt.map_or(&zero, |p| &p); // compute the L and R vectors - let eq = EqPolynomial::new(r.to_vec()); + let eq = EqPolynomial::new(challenges.to_vec()); let (L, R) = eq.compute_factored_evals(); assert_eq!(L.len(), L_size); assert_eq!(R.len(), R_size); @@ -97,44 +236,52 @@ impl PCS for PolyEvalProof { let LZ_blind: G::ScalarField = (0..L.len()).map(|i| blinds.blinds[i] * L[i]).sum(); // a dot product proof of size R_size + // TODO: how to remove this unwrap and still maintain clean interface let (proof, _C_LR, C_Zr_prime) = DotProductProofLog::prove( &gens.gens, transcript, - random_tape, + &mut random_tape, &LZ, &LZ_blind, &R, - Zr, + //TODO: fix this nasty unwrap + &evals.unwrap(), blind_Zr, ); - (PolyEvalProof { proof }, C_Zr_prime) + Ok((PolyEvalProof { proof }, C_Zr_prime)) } fn verify( - &self, - gens: &PolyCommitmentGens, + commitments: Self::Commitment, + // Find a better way to handle this... perhaps verifier key??? + evals: Self::Evaluation, + challenges: Self::Challenge, // point at which the polynomial is evaluated + vk: impl Borrow, // C_Zr commitment to \widetilde{Z}(r) transcript: &mut Transcript, - r: &[G::ScalarField], // point at which the polynomial is evaluated - C_Zr: &G, // commitment to \widetilde{Z}(r) - comm: &PolyCommitment, - ) -> Result<(), ProofVerifyError> { + proof: Self::Proof, + ) -> Result<(), Self::Error> { >::append_protocol_name( transcript, PolyEvalProof::::protocol_name(), ); + let (proof, C_Zr) = proof; + let (comm, _) = commitments; + let gens = vk.borrow(); // compute L and R - let eq = EqPolynomial::new(r.to_vec()); + let eq = EqPolynomial::new(challenges); let (L, R) = eq.compute_factored_evals(); // compute a weighted sum of commitments and L let C_affine = G::normalize_batch(&comm.C); let C_LZ = VariableBaseMSM::msm(C_affine.as_ref(), L.as_ref()).unwrap(); - - self - .proof - .verify(R.len(), &gens.gens, transcript, &R, &C_LZ, C_Zr) + Ok( + proof + .proof + .verify(R.len(), &gens.gens, transcript, &R, &C_LZ, &C_Zr) + .unwrap(), + ) } -} \ No newline at end of file +} diff --git a/src/subprotocols/mod.rs b/src/subprotocols/mod.rs index f9492fad6..2cac22e89 100644 --- a/src/subprotocols/mod.rs +++ b/src/subprotocols/mod.rs @@ -5,7 +5,7 @@ mod zk; pub mod dot_product; pub mod grand_product; +pub mod hyrax; pub mod sumcheck; pub mod traits; pub mod zeromorph; -pub mod hyrax; diff --git a/src/subprotocols/traits.rs b/src/subprotocols/traits.rs index 7a34f97c7..5d6ac9087 100644 --- a/src/subprotocols/traits.rs +++ b/src/subprotocols/traits.rs @@ -1,11 +1,9 @@ -use std::borrow::Borrow; - -use ark_ff::Field; use merlin::Transcript; +use std::borrow::Borrow; -use crate::poly::dense_mlpoly::DensePolynomial; - -pub trait PCS { +pub trait PolynomialCommitmentScheme { + // Abstracting over Polynomial allows us to have batched and non-batched PCS + type Polynomial; type Commitment; type Evaluation; type Challenge; @@ -13,29 +11,29 @@ pub trait PCS { type Error; type ProverKey; + type CommitmentKey; type VerifierKey; //TODO: convert to impl IntoIterator fn commit( - polys: &[DensePolynomial], - ck: &Self::ProverKey, - ) -> Result, Self::Error>; + poly: Self::Polynomial, + ck: impl Borrow, + ) -> Result; fn prove( - polys: &[DensePolynomial], - evals: &[Self::Evaluation], - challenges: &[Self::Challenge], - commitments: &[Self::Commitment], + poly: Self::Polynomial, + evals: Self::Evaluation, + challenges: Self::Challenge, pk: impl Borrow, transcript: &mut Transcript, ) -> Result; fn verify( - commitments: &[Self::Commitment], - evals: &[Self::Evaluation], - challenges: &[Self::Challenge], + commitments: Self::Commitment, + evals: Self::Evaluation, + challenges: Self::Challenge, vk: impl Borrow, transcript: &mut Transcript, proof: Self::Proof, - ) -> Result; + ) -> Result<(), Self::Error>; } diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index e691c7f58..0ca5aab9e 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -5,7 +5,7 @@ use std::{borrow::Borrow, iter, marker::PhantomData}; use crate::poly::dense_mlpoly::DensePolynomial; use crate::poly::unipoly::UniPoly; -use crate::subprotocols::traits::PCS; +use crate::subprotocols::traits::PolynomialCommitmentScheme; use crate::subprotocols::zeromorph::kzg::UniversalKzgSrs; use crate::utils::transcript::ProofTranscript; use ark_bn254::Bn254; @@ -155,11 +155,10 @@ fn compute_batched_lifted_degree_quotient( quotients: &Vec>, y_challenge: &P::ScalarField, ) -> UniPoly { - // Compute \hat{q} = \sum_k y^k * X^{N - d_k - 1} * q_k let mut scalar = P::ScalarField::one(); // y^k - // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - 1}) - // then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 + // Rather than explicitly computing the shifts of q_k by N - d_k - 1 (i.e. multiplying q_k by X^{N - d_k - 1}) + // then accumulating them, we simply accumulate y^k*q_k into \hat{q} at the index offset N - d_k - 1 let q_hat = quotients .iter() @@ -246,38 +245,39 @@ pub struct Zeromorph { _phantom: PhantomData

, } -impl PCS for Zeromorph

{ - type Commitment = P::G1Affine; - type Evaluation = P::ScalarField; - type Challenge = P::ScalarField; +impl PolynomialCommitmentScheme for Zeromorph

{ + type Commitment = Vec; + type Polynomial = Vec>; + type Evaluation = Vec; + type Challenge = Vec; type Proof = ZeromorphProof

; type Error = ZeromorphError; type ProverKey = ZeromorphProverKey

; + type CommitmentKey = Self::Commitment; type VerifierKey = ZeromorphVerifierKey

; fn commit( - polys: &[DensePolynomial], - pk: &Self::ProverKey, - ) -> Result, Self::Error> { + polys: Self::Polynomial, + ck: impl Borrow, + ) -> Result { + let ck = ck.borrow(); + // TODO: assert lengths are valid #[cfg(feature = "multicore")] let iter = polys.par_iter(); #[cfg(not(feature = "multicore"))] let iter = polys.iter(); Ok( iter - .map(|poly| { - UnivariateKZG::

::commit(&pk.g1_powers, &UniPoly::from_coeff(poly.Z.clone())).unwrap() - }) + .map(|poly| UnivariateKZG::

::commit(ck, &UniPoly::from_coeff(poly.Z.clone())).unwrap()) .collect::>(), ) } fn prove( - polys: &[DensePolynomial], - evals: &[Self::Evaluation], - challenge: &[Self::Challenge], - commitments: &[Self::Commitment], + polys: Self::Polynomial, + evals: Self::Evaluation, + challenge: Self::Challenge, pk: impl Borrow, transcript: &mut Transcript, ) -> Result { @@ -285,31 +285,31 @@ impl PCS for Zeromorph

{ let n: usize = 1 << num_vars; let pk = pk.borrow(); - for (poly, com) in polys.iter().zip_eq(commitments.iter()) { - debug_assert_eq!( - UnivariateKZG::

::commit(&pk.g1_powers, &UniPoly::from_coeff(poly.Z.clone())).unwrap(), - *com - ); - } for (poly, eval) in polys.iter().zip_eq(evals.iter()) { // Note by evaluating we confirm the number of challenges is valid - debug_assert_eq!(poly.evaluate(challenge), *eval); + debug_assert_eq!(poly.evaluate(&challenge), *eval); } // Generate batching challenge \rho and powers 1,...,\rho^{m-1} let rho = >::challenge_scalar(transcript, b"ZM: rho"); // Compute batching of unshifted polynomials f_i: let mut scalar = P::ScalarField::one(); - let (f_batched, batched_evaluation) = (0..polys.len()).fold((DensePolynomial::new(vec![P::ScalarField::zero(); n]), P::ScalarField::zero()), |(mut f_batched, mut batched_evaluation), i| { - f_batched += polys[i].clone() * scalar; - batched_evaluation += scalar * evals[i]; - scalar *= rho; - (f_batched, batched_evaluation) - }); + let (f_batched, batched_evaluation) = (0..polys.len()).fold( + ( + DensePolynomial::new(vec![P::ScalarField::zero(); n]), + P::ScalarField::zero(), + ), + |(mut f_batched, mut batched_evaluation), i| { + f_batched += polys[i].clone() * scalar; + batched_evaluation += scalar * evals[i]; + scalar *= rho; + (f_batched, batched_evaluation) + }, + ); let mut pi_poly = UniPoly::from_coeff(f_batched.Z.clone()); // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) - let (quotients, remainder) = compute_multilinear_quotients::

(&f_batched, challenge); + let (quotients, remainder) = compute_multilinear_quotients::

(&f_batched, &challenge); debug_assert_eq!(quotients.len(), f_batched.get_num_vars()); debug_assert_eq!(remainder, batched_evaluation); @@ -346,7 +346,7 @@ impl PCS for Zeromorph

{ >::challenge_scalar(transcript, b"ZM: z"); let (eval_scalar, (zeta_degree_check_q_scalars, z_zmpoly_q_scalars)) = - eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, challenge); + eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, &challenge); // f = z * x * poly.Z + q_hat + (-z * x * ฮฆ_n(x) * e) + x * โˆ‘_k (q_scalars_k * q_k) pi_poly *= &z_challenge; pi_poly += &q_hat; @@ -373,13 +373,13 @@ impl PCS for Zeromorph

{ } fn verify( - commitments: &[Self::Commitment], - evals: &[Self::Evaluation], - challenges: &[Self::Challenge], + commitments: Self::Commitment, + evals: Self::Evaluation, + challenges: Self::Challenge, vk: impl Borrow, transcript: &mut Transcript, proof: Self::Proof, - ) -> Result { + ) -> Result<(), Self::Error> { debug_assert_eq!(evals.len(), commitments.len()); let vk = vk.borrow(); let ZeromorphProof { @@ -404,12 +404,15 @@ impl PCS for Zeromorph

{ // Compute batching of unshifted polynomials f_i: let mut scalar = P::ScalarField::one(); - let (batched_evaluation, batched_commitment) = evals.iter().zip_eq(commitments.iter()).fold((P::ScalarField::zero(), P::G1::zero()), |(mut batched_evaluation, mut batched_commitment), (eval, commitment)| { - batched_evaluation += scalar * eval; - batched_commitment += *commitment * scalar; - scalar *= rho; - (batched_evaluation, batched_commitment) - }); + let (batched_evaluation, batched_commitment) = evals.iter().zip_eq(commitments.iter()).fold( + (P::ScalarField::zero(), P::G1::zero()), + |(mut batched_evaluation, mut batched_commitment), (eval, commitment)| { + batched_evaluation += scalar * eval; + batched_commitment += *commitment * scalar; + scalar *= rho; + (batched_evaluation, batched_commitment) + }, + ); // Challenge y let y_challenge = @@ -425,7 +428,7 @@ impl PCS for Zeromorph

{ >::challenge_scalar(transcript, b"ZM: z"); let (eval_scalar, (mut q_scalars, zm_poly_q_scalars)) = - eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, challenges); + eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, &challenges); q_scalars .iter_mut() @@ -454,7 +457,8 @@ impl PCS for Zeromorph

{ // e(pi, [tau]_2 - x * [1]_2) == e(C_{\zeta,Z}, [X^(N_max - 2^n - 1)]_2) <==> e(C_{\zeta,Z} - x * pi, [X^{N_max - 2^n - 1}]_2) * e(-pi, [tau_2]) == 1 let lhs = P::pairing(pi, vk.tau_2.into_group() - (vk.g2 * x_challenge)); let rhs = P::pairing(Zeta_z_com, vk.tau_N_max_sub_2_N); - Ok(lhs == rhs) + assert_eq!(lhs, rhs); + Ok(()) } } @@ -502,14 +506,13 @@ mod test { let evals = polys.evaluate(&challenges); // Commit and open - let commitments = Zeromorph::::commit(&[polys.clone()], &pk).unwrap(); + let commitments = Zeromorph::::commit(vec![polys.clone()], &pk.g1_powers).unwrap(); let mut prover_transcript = Transcript::new(b"example"); let proof = Zeromorph::::prove( - &[polys], - &[evals], - &challenges, - &commitments, + vec![polys], + vec![evals], + challenges, &pk, &mut prover_transcript, ) @@ -517,9 +520,9 @@ mod test { let mut verifier_transcript = Transcript::new(b"example"); Zeromorph::::verify( - &commitments, - &[evals], - &challenges, + commitments, + vec![evals], + challenges, &vk, &mut verifier_transcript, proof, @@ -563,24 +566,17 @@ mod test { .collect::>(); // Commit and open - let commitments = Zeromorph::::commit(&polys, &pk).unwrap(); + let commitments = Zeromorph::::commit(polys, &pk.g1_powers).unwrap(); let mut prover_transcript = Transcript::new(b"example"); - let proof = Zeromorph::::prove( - &polys, - &evals, - &challenges, - &commitments, - &pk, - &mut prover_transcript, - ) - .unwrap(); + let proof = + Zeromorph::::prove(polys, evals, challenges, &pk, &mut prover_transcript).unwrap(); let mut verifier_transcript = Transcript::new(b"example"); Zeromorph::::verify( - &commitments, - &evals, - &challenges, + commitments, + evals, + challenges, &vk, &mut verifier_transcript, proof, diff --git a/src/subtables/mod.rs b/src/subtables/mod.rs index 852b6160e..e860d2bdb 100644 --- a/src/subtables/mod.rs +++ b/src/subtables/mod.rs @@ -8,12 +8,12 @@ use merlin::Transcript; use crate::{ lasso::{densified::DensifiedRepresentation, memory_checking::GrandProducts}, - poly::dense_mlpoly::{DensePolynomial, PolyCommitment, PolyCommitmentGens, PolyEvalProof}, - poly::eq_poly::EqPolynomial, - utils::errors::ProofVerifyError, + poly::{dense_mlpoly::DensePolynomial, eq_poly::EqPolynomial}, + subprotocols::hyrax::{PolyCommitment, PolyCommitmentGens, PolyEvalProof}, + subprotocols::{hyrax::Hyrax, traits::PolynomialCommitmentScheme}, utils::math::Math, - utils::random::RandomTape, utils::transcript::{AppendToTranscript, ProofTranscript}, + utils::{errors::ProofVerifyError, random::RandomTape}, }; #[cfg(feature = "multicore")] @@ -179,7 +179,7 @@ where &self, gens: &PolyCommitmentGens, ) -> CombinedTableCommitment { - let (comm_ops_val, _blinds) = self.combined_poly.commit(gens, None); + let (comm_ops_val, _blinds) = Hyrax::commit(self.combined_poly, (*gens, None)).unwrap(); CombinedTableCommitment { comm_ops_val } } @@ -266,16 +266,14 @@ impl CombinedTableEvalProof { // decommit the joint polynomial at r_joint >::append_scalar(transcript, b"joint_claim_eval", &eval_joint); - let (proof_table_eval, _comm_table_eval) = PolyEvalProof::prove( - joint_poly, - None, - &r_joint, - &eval_joint, - None, - gens, + let (proof_table_eval, _comm_table_eval) = Hyrax::prove( + *joint_poly, + Some(eval_joint), + r_joint, + (None, None, *gens, *random_tape), transcript, - random_tape, - ); + ) + .unwrap(); proof_table_eval } From d3f71ddc20082d9d9e88d3909e32d1cd85de26a7 Mon Sep 17 00:00:00 2001 From: PatStiles Date: Thu, 1 Feb 2024 17:45:56 -0600 Subject: [PATCH 20/21] zm debug --- src/e2e_test.rs | 13 +- src/lasso/densified.rs | 10 +- src/lasso/memory_checking.rs | 16 +- src/poly/dense_mlpoly.rs | 14 +- src/subprotocols/hyrax.rs | 65 ++-- src/subprotocols/traits.rs | 462 +++++++++++++++++++++++- src/subprotocols/zeromorph/kzg.rs | 6 +- src/subprotocols/zeromorph/zeromorph.rs | 124 ++++--- src/subtables/mod.rs | 10 +- 9 files changed, 593 insertions(+), 127 deletions(-) diff --git a/src/e2e_test.rs b/src/e2e_test.rs index 926189de4..b5c95608c 100644 --- a/src/e2e_test.rs +++ b/src/e2e_test.rs @@ -5,17 +5,14 @@ use crate::{ lasso::{ densified::DensifiedRepresentation, surge::{SparsePolyCommitmentGens, SparsePolynomialEvaluationProof}, - }, - subtables::{ + }, subprotocols::{traits::PolynomialCommitmentScheme, hyrax::Hyrax}, subtables::{ and::AndSubtableStrategy, lt::LTSubtableStrategy, range_check::RangeCheckSubtableStrategy, SubtableStrategy, - }, - utils::math::Math, - utils::random::RandomTape, + }, utils::math::Math, utils::random::RandomTape }; macro_rules! e2e_test { - ($test_name:ident, $Strategy:ty, $G:ty, $F:ty, $PCS:ty, $CK:expr, $C:expr, $M:expr, $sparsity:expr) => { + ($test_name:ident, $Strategy:ty, $G:ty, $F:ty, $C:expr, $M:expr, $sparsity:expr) => { #[test] fn $test_name() { use crate::utils::test::{gen_indices, gen_random_point}; @@ -36,8 +33,7 @@ macro_rules! e2e_test { DensifiedRepresentation::from_lookup_indices(&nz, log_M); let gens = SparsePolyCommitmentGens::<$G>::new(b"gens_sparse_poly", C, $sparsity, NUM_MEMORIES, log_M); - let ck = - let commitment = dense.commit::<$G, $PCS>(&ck); + let commitment = dense.commit(&gens); let r: Vec<$F> = gen_random_point(log_s); @@ -62,6 +58,7 @@ macro_rules! e2e_test { }; } +/// Hyrax e2e_test!( prove_4d_lt, LTSubtableStrategy, diff --git a/src/lasso/densified.rs b/src/lasso/densified.rs index d1bbfe008..5e19cd04a 100644 --- a/src/lasso/densified.rs +++ b/src/lasso/densified.rs @@ -82,14 +82,14 @@ impl DensifiedRepresentation { &self, gens: &SparsePolyCommitmentGens, ) -> SparsePolynomialCommitment { - let (l_variate_polys_commitment, _) = Hyrax::commit( - self.combined_l_variate_polys.clone(), + let (l_variate_polys_commitment, _) = Hyrax::::commit( + &self.combined_l_variate_polys, (gens.gens_combined_l_variate.clone(), None), ) .unwrap(); - let (log_m_variate_polys_commitment, _) = Hyrax::commit( - self.combined_log_m_variate_polys.clone(), - (gens.gens_combined_log_m_variate, None), + let (log_m_variate_polys_commitment, _) = Hyrax::::commit( + &self.combined_log_m_variate_polys, + (gens.gens_combined_log_m_variate.clone(), None), ) .unwrap(); diff --git a/src/lasso/memory_checking.rs b/src/lasso/memory_checking.rs index 6c5f98150..d00b01287 100644 --- a/src/lasso/memory_checking.rs +++ b/src/lasso/memory_checking.rs @@ -412,10 +412,10 @@ where ); let (proof_ops, _) = Hyrax::prove( - dense.combined_l_variate_polys, - Some(joint_claim_eval_ops), - r_joint_ops, - (None, None, gens.gens_combined_l_variate, *random_tape), + &dense.combined_l_variate_polys, + &Some(joint_claim_eval_ops), + &r_joint_ops, + (None, None, &gens.gens_combined_l_variate, random_tape), transcript, ) .unwrap(); @@ -448,10 +448,10 @@ where ); let (proof_mem, _) = Hyrax::prove( - dense.combined_log_m_variate_polys, - Some(joint_claim_eval_mem), - r_joint_mem, - (None, None, gens.gens_combined_log_m_variate, *random_tape), + &dense.combined_log_m_variate_polys, + &Some(joint_claim_eval_mem), + &r_joint_mem, + (None, None, &gens.gens_combined_log_m_variate, random_tape), transcript, ) .unwrap(); diff --git a/src/poly/dense_mlpoly.rs b/src/poly/dense_mlpoly.rs index 524e55b32..8a79dd73d 100644 --- a/src/poly/dense_mlpoly.rs +++ b/src/poly/dense_mlpoly.rs @@ -654,15 +654,15 @@ mod tests { assert_eq!(eval, G::ScalarField::from(28u64)); let gens = PolyCommitmentGens::::new(poly.get_num_vars(), b"test-two"); - let (poly_commitment, blinds) = Hyrax::commit(poly, &(gens, None)).unwrap(); + let (poly_commitment, blinds) = Hyrax::commit(&poly, (gens.clone(), None)).unwrap(); let mut random_tape = RandomTape::new(b"proof"); let mut prover_transcript = Transcript::new(b"example"); let (proof, C_Zr) = Hyrax::prove( - poly, - Some(eval), - r, - (Some(blinds), None, gens, random_tape), + &poly, + &Some(eval), + &r, + (Some(&blinds), None, &gens, &mut random_tape), &mut prover_transcript, ) .unwrap(); @@ -670,8 +670,8 @@ mod tests { let mut verifier_transcript = Transcript::new(b"example"); assert!(Hyrax::verify( - (poly_commitment, blinds), - None, + &(poly_commitment, blinds), + &None, r, &gens, &mut verifier_transcript, diff --git a/src/subprotocols/hyrax.rs b/src/subprotocols/hyrax.rs index 064f9f8eb..6ad16d7cb 100644 --- a/src/subprotocols/hyrax.rs +++ b/src/subprotocols/hyrax.rs @@ -66,8 +66,8 @@ impl PolyEvalProof { &self, gens: &PolyCommitmentGens, transcript: &mut Transcript, - r: &[G::ScalarField], // point at which the polynomial is evaluated - Zr: &G::ScalarField, // evaluation \widetilde{Z}(r) + r: &[G::ScalarField], + Zr: &G::ScalarField, comm: &PolyCommitment, ) -> Result<(), ProofVerifyError> { // compute a commitment to Zr with a blind of zero @@ -75,13 +75,13 @@ impl PolyEvalProof { // TODO: Make blinds an Option Hyrax::verify( - ( + &( comm.clone(), PolyCommitmentBlinds { blinds: vec![G::ScalarField::zero()], }, ), - None, + &None, r.to_vec(), gens, transcript, @@ -146,23 +146,24 @@ impl PolynomialCommitmentScheme for Hyrax { type Proof = (PolyEvalProof, G); type Error = ProofVerifyError; - type ProverKey = ( - Option>, - Option, - PolyCommitmentGens, - RandomTape, + type ProverKey<'p> = ( + Option<&'p PolyCommitmentBlinds>, + Option<&'p G::ScalarField>, + &'p PolyCommitmentGens, + &'p mut RandomTape, ); - type CommitmentKey = (PolyCommitmentGens, Option>); + type CommitmentKey<'c> = (PolyCommitmentGens, Option<&'c mut RandomTape>); type VerifierKey = PolyCommitmentGens; #[tracing::instrument(skip_all, name = "DensePolynomial.commit")] - fn commit( - poly: Self::Polynomial, - ck: impl Borrow, - ) -> Result { + fn commit<'a, 'c>( + poly: &'a Self::Polynomial, + ck: Self::CommitmentKey<'c>, + ) -> Result + { let n = poly.Z.len(); let ell = poly.get_num_vars(); - let ck = ck.borrow(); + let (gens, random_tape) = ck; assert_eq!(n, ell.pow2()); let (left_num_vars, right_num_vars) = @@ -171,7 +172,6 @@ impl PolynomialCommitmentScheme for Hyrax { let R_size = right_num_vars.pow2(); assert_eq!(L_size * R_size, n); - let (gens, random_tape) = ck; let blinds = if let Some(t) = random_tape { PolyCommitmentBlinds { blinds: t.random_vector(b"poly_blinds", L_size), @@ -188,23 +188,26 @@ impl PolynomialCommitmentScheme for Hyrax { } // Note this excludes commitments which introduces a concern that the proof would generate for polys and commitments not tied to one another - fn prove( - poly: Self::Polynomial, + fn prove<'a, 'p>( + poly: &'a Self::Polynomial, //blinds_opt: Option<&PolyCommitmentBlinds>, - evals: Self::Evaluation, // evaluation of \widetilde{Z}(r) - challenges: Self::Challenge, // point at which the polynomial is evaluated + evals: &'a Self::Evaluation, // evaluation of \widetilde{Z}(r) + challenges: &'a Self::Challenge, // point at which the polynomial is evaluated //blind_Zr_opt: Option<&G::ScalarField>, // specifies a blind for Zr //gens: &PolyCommitmentGens, //random_tape: &mut RandomTape, - pk: impl Borrow, + pk: Self::ProverKey<'p>, transcript: &mut Transcript, - ) -> Result { + ) -> Result + where + Self::Challenge: 'a, + { >::append_protocol_name( transcript, PolyEvalProof::::protocol_name(), ); - let (blinds_opt, blind_Zr_opt, gens, random_tape) = pk.borrow(); + let (blinds_opt, blind_Zr_opt, gens, random_tape) = pk; // assert vectors are of the right size assert_eq!(poly.get_num_vars(), challenges.len()); @@ -240,7 +243,7 @@ impl PolynomialCommitmentScheme for Hyrax { let (proof, _C_LR, C_Zr_prime) = DotProductProofLog::prove( &gens.gens, transcript, - &mut random_tape, + random_tape, &LZ, &LZ_blind, &R, @@ -252,15 +255,19 @@ impl PolynomialCommitmentScheme for Hyrax { Ok((PolyEvalProof { proof }, C_Zr_prime)) } - fn verify( - commitments: Self::Commitment, + fn verify<'a>( + commitments: &'a Self::Commitment, // Find a better way to handle this... perhaps verifier key??? - evals: Self::Evaluation, + evals: &'a Self::Evaluation, challenges: Self::Challenge, // point at which the polynomial is evaluated - vk: impl Borrow, // C_Zr commitment to \widetilde{Z}(r) + vk: &'a Self::VerifierKey, // C_Zr commitment to \widetilde{Z}(r) transcript: &mut Transcript, proof: Self::Proof, - ) -> Result<(), Self::Error> { + ) -> Result<(), Self::Error> + where + Self::Commitment: 'a, + Self::VerifierKey: 'a, + { >::append_protocol_name( transcript, PolyEvalProof::::protocol_name(), diff --git a/src/subprotocols/traits.rs b/src/subprotocols/traits.rs index 5d6ac9087..497991e47 100644 --- a/src/subprotocols/traits.rs +++ b/src/subprotocols/traits.rs @@ -1,5 +1,4 @@ use merlin::Transcript; -use std::borrow::Borrow; pub trait PolynomialCommitmentScheme { // Abstracting over Polynomial allows us to have batched and non-batched PCS @@ -10,30 +9,459 @@ pub trait PolynomialCommitmentScheme { type Proof; type Error; - type ProverKey; - type CommitmentKey; + type ProverKey<'p>; + type CommitmentKey<'c>; type VerifierKey; //TODO: convert to impl IntoIterator - fn commit( - poly: Self::Polynomial, - ck: impl Borrow, + fn commit<'a, 'c>( + poly: &'a Self::Polynomial, + ck: Self::CommitmentKey<'c>, ) -> Result; - fn prove( - poly: Self::Polynomial, - evals: Self::Evaluation, - challenges: Self::Challenge, - pk: impl Borrow, + fn prove<'a, 'p>( + poly: &'a Self::Polynomial, + evals: &'a Self::Evaluation, + challenges: &'a Self::Challenge, + pk: Self::ProverKey<'p>, transcript: &mut Transcript, - ) -> Result; + ) -> Result + where + Self::Challenge: 'a; - fn verify( - commitments: Self::Commitment, - evals: Self::Evaluation, + fn verify<'a>( + commitments: &'a Self::Commitment, + evals: &'a Self::Evaluation, challenges: Self::Challenge, - vk: impl Borrow, + vk: &'a Self::VerifierKey, transcript: &mut Transcript, proof: Self::Proof, - ) -> Result<(), Self::Error>; + ) -> Result<(), Self::Error> + where + Self::Commitment: 'a, + Self::VerifierKey: 'a; +} + + +/* +/// Describes the interface for a polynomial commitment scheme that allows +/// a sender to commit to multiple polynomials and later provide a succinct proof +/// of evaluation for the corresponding commitments at a query set `Q`, while +/// enforcing per-polynomial degree bounds. +pub trait PolynomialCommitment, S: CryptographicSponge>: + Sized +{ + /// The universal parameters for the commitment scheme. These are "trimmed" + /// down to `Self::CommitterKey` and `Self::VerifierKey` by `Self::trim`. + type UniversalParams: PCUniversalParams; + /// The committer key for the scheme; used to commit to a polynomial and then + /// open the commitment to produce an evaluation proof. + type CommitterKey: PCCommitterKey; + /// The verifier key for the scheme; used to check an evaluation proof. + type VerifierKey: PCVerifierKey; + /// The commitment to a polynomial. + type Commitment: PCCommitment + Default; + /// Auxiliary state of the commitment, output by the `commit` phase. + /// It contains information that can be reused by the committer + /// during the `open` phase, such as the commitment randomness. + /// Not to be shared with the verifier. + type CommitmentState: PCCommitmentState; + /// The evaluation proof for a single point. + type Proof: Clone; + /// The evaluation proof for a query set. + type BatchProof: Clone + + From> + + Into> + + CanonicalSerialize + + CanonicalDeserialize; + /// The error type for the scheme. + type Error: ark_std::error::Error + From; + + /// Constructs public parameters when given as input the maximum degree `degree` + /// for the polynomial commitment scheme. `num_vars` specifies the number of + /// variables for multivariate setup + fn setup( + max_degree: usize, + num_vars: Option, + rng: &mut R, + ) -> Result; + + /// Specializes the public parameters for polynomials up to the given `supported_degree` + /// and for enforcing degree bounds in the range `1..=supported_degree`. + fn trim( + pp: &Self::UniversalParams, + supported_degree: usize, + supported_hiding_bound: usize, + enforced_degree_bounds: Option<&[usize]>, + ) -> Result<(Self::CommitterKey, Self::VerifierKey), Self::Error>; + + /// Outputs a list of commitments to `polynomials`. If `polynomials[i].is_hiding()`, + /// then the `i`-th commitment is hiding up to `polynomials.hiding_bound()` queries. + /// `rng` should not be `None` if `polynomials[i].is_hiding() == true` for any `i`. + /// + /// If for some `i`, `polynomials[i].is_hiding() == false`, then the + /// corresponding randomness is `Self::Randomness::empty()`. + /// + /// If for some `i`, `polynomials[i].degree_bound().is_some()`, then that + /// polynomial will have the corresponding degree bound enforced. + fn commit<'a>( + ck: &Self::CommitterKey, + polynomials: impl IntoIterator>, + rng: Option<&mut dyn RngCore>, + ) -> Result< + ( + Vec>, + Vec, + ), + Self::Error, + > + where + P: 'a; + + /// open but with individual challenges + fn open<'a>( + ck: &Self::CommitterKey, + labeled_polynomials: impl IntoIterator>, + commitments: impl IntoIterator>, + point: &'a P::Point, + sponge: &mut S, + states: impl IntoIterator, + rng: Option<&mut dyn RngCore>, + ) -> Result + where + P: 'a, + Self::CommitmentState: 'a, + Self::Commitment: 'a; + + /// check but with individual challenges + fn check<'a>( + vk: &Self::VerifierKey, + commitments: impl IntoIterator>, + point: &'a P::Point, + values: impl IntoIterator, + proof: &Self::Proof, + sponge: &mut S, + rng: Option<&mut dyn RngCore>, + ) -> Result + where + Self::Commitment: 'a; + + /// Open several polynomials at one or more points each (possibly different + /// for each polynomial). Each entry in the in the query set of points + /// contains the label of the polynomial which should be queried at that + /// point. + /// + /// Behaviour is undefined if `query_set` contains the entries with the + /// same point label but different actual points. + /// + /// The opening challenges are independent for each batch of polynomials. + fn batch_open<'a>( + ck: &Self::CommitterKey, + labeled_polynomials: impl IntoIterator>, + commitments: impl IntoIterator>, + query_set: &QuerySet, + sponge: &mut S, + states: impl IntoIterator, + rng: Option<&mut dyn RngCore>, + ) -> Result + where + P: 'a, + Self::CommitmentState: 'a, + Self::Commitment: 'a, + { + // The default implementation achieves proceeds by rearranging the queries in + // order to gather (i.e. batch) all polynomials that should be queried at + // the same point, then opening their commitments simultaneously with a + // single call to `open` (per point) + let rng = &mut crate::optional_rng::OptionalRng(rng); + let poly_st_comm: BTreeMap<_, _> = labeled_polynomials + .into_iter() + .zip(states) + .zip(commitments.into_iter()) + .map(|((poly, st), comm)| (poly.label(), (poly, st, comm))) + .collect(); + + let open_time = start_timer!(|| format!( + "Opening {} polynomials at query set of size {}", + poly_st_comm.len(), + query_set.len(), + )); + + let mut query_to_labels_map = BTreeMap::new(); + + // `label` is the label of the polynomial the query refers to + // `point_label` is the label of the point being queried + // `point` is the actual point + for (label, (point_label, point)) in query_set.iter() { + // For each point label in `query_set`, we define an entry in + // `query_to_labels_map` containing a pair whose first element is + // the actual point and the second one is the set of labels of the + // polynomials being queried at that point + let labels = query_to_labels_map + .entry(point_label) + .or_insert((point, BTreeSet::new())); + labels.1.insert(label); + } + + let mut proofs = Vec::new(); + for (_point_label, (point, labels)) in query_to_labels_map.into_iter() { + let mut query_polys: Vec<&'a LabeledPolynomial<_, _>> = Vec::new(); + let mut query_states: Vec<&'a Self::CommitmentState> = Vec::new(); + let mut query_comms: Vec<&'a LabeledCommitment> = Vec::new(); + + // Constructing matching vectors with the polynomial, commitment + // randomness and actual commitment for each polynomial being + // queried at `point` + for label in labels { + let (polynomial, state, comm) = + poly_st_comm.get(label).ok_or(Error::MissingPolynomial { + label: label.to_string(), + })?; + + query_polys.push(polynomial); + query_states.push(state); + query_comms.push(comm); + } + + let proof_time = start_timer!(|| "Creating proof"); + + // Simultaneously opening the commitments of all polynomials that + // refer to the the current point using the plain `open` function + let proof = Self::open( + ck, + query_polys, + query_comms, + &point, + sponge, + query_states, + Some(rng), + )?; + + end_timer!(proof_time); + + proofs.push(proof); + } + end_timer!(open_time); + + Ok(proofs.into()) + } + + /// Verify opening proofs for several polynomials at one or more points + /// each (possibly different for each polynomial). Each entry in + /// the query set of points contains the label of the polynomial which + /// was queried at that point. + /// + /// Behaviour is undefined if `query_set` contains the entries with the + /// same point label but different points. + /// + /// Behaviour is also undefined if proofs are not ordered the same way as + /// queries in `query_to_labels_map` (this is the outcome of calling + /// `batch_open` for the same commitment list and query set).H + /// + /// The opening challenges are independent for each batch of polynomials. + fn batch_check<'a, R: RngCore>( + vk: &Self::VerifierKey, + commitments: impl IntoIterator>, + query_set: &QuerySet, + evaluations: &Evaluations, + proof: &Self::BatchProof, + sponge: &mut S, + rng: &mut R, + ) -> Result + where + Self::Commitment: 'a, + { + // The default implementation proceeds by rearranging the queries in + // order to gather (i.e. batch) the proofs of all polynomials that should + // have been opened at the same point, then verifying those proofs + // simultaneously with a single call to `check` (per point). + let commitments: BTreeMap<_, _> = commitments.into_iter().map(|c| (c.label(), c)).collect(); + let mut query_to_labels_map = BTreeMap::new(); + + // `label` is the label of the polynomial the query refers to + // `point_label` is the label of the point being queried + // `point` is the actual point + for (label, (point_label, point)) in query_set.iter() { + // For each point label in `query_set`, we define an entry in + // `query_to_labels_map` containing a pair whose first element is + // the actual point and the second one is the set of labels of the + // polynomials being queried at that point + let labels = query_to_labels_map + .entry(point_label) + .or_insert((point, BTreeSet::new())); + labels.1.insert(label); + } + + // Implicit assumption: proofs are ordered in same manner as queries in + // `query_to_labels_map`. + let proofs: Vec<_> = proof.clone().into(); + assert_eq!(proofs.len(), query_to_labels_map.len()); + + let mut result = true; + for ((_point_label, (point, labels)), proof) in query_to_labels_map.into_iter().zip(proofs) + { + // Constructing matching vectors with the commitment and claimed + // value of each polynomial being queried at `point` + let mut comms: Vec<&'_ LabeledCommitment<_>> = Vec::new(); + let mut values = Vec::new(); + for label in labels.into_iter() { + let commitment = commitments.get(label).ok_or(Error::MissingPolynomial { + label: label.to_string(), + })?; + + let v_i = evaluations.get(&(label.clone(), point.clone())).ok_or( + Error::MissingEvaluation { + label: label.to_string(), + }, + )?; + + comms.push(commitment); + values.push(*v_i); + } + + let proof_time = start_timer!(|| "Checking per-query proof"); + + // Verify all proofs referring to the current point simultaneously + // with a single call to `check` + result &= Self::check(vk, comms, &point, values, &proof, sponge, Some(rng))?; + end_timer!(proof_time); + } + Ok(result) + } + + /// Open commitments to all polynomials involved in a number of linear + /// combinations (LC) simultaneously. + fn open_combinations<'a>( + ck: &Self::CommitterKey, + linear_combinations: impl IntoIterator>, + polynomials: impl IntoIterator>, + commitments: impl IntoIterator>, + query_set: &QuerySet, + sponge: &mut S, + states: impl IntoIterator, + rng: Option<&mut dyn RngCore>, + ) -> Result, Self::Error> + where + Self::CommitmentState: 'a, + Self::Commitment: 'a, + P: 'a, + { + // The default implementation proceeds by batch-opening all polynomials + // appearing in those LC that are queried at the same point. + let linear_combinations: Vec<_> = linear_combinations.into_iter().collect(); + let polynomials: Vec<_> = polynomials.into_iter().collect(); + + // Rearrange the information about queries on linear combinations into + // information about queries on individual polynomials. + let poly_query_set = + lc_query_set_to_poly_query_set(linear_combinations.iter().copied(), query_set); + let poly_evals = evaluate_query_set(polynomials.iter().copied(), &poly_query_set); + + // Batch-open all polynomials that refer to each individual point in `query_set` + let proof = Self::batch_open( + ck, + polynomials, + commitments, + &poly_query_set, + sponge, + states, + rng, + )?; + Ok(BatchLCProof { + proof, + evals: Some(poly_evals.values().copied().collect()), + }) + } + + /// Verify opening proofs for all polynomials involved in a number of + /// linear combinations (LC) simultaneously. + fn check_combinations<'a, R: RngCore>( + vk: &Self::VerifierKey, + linear_combinations: impl IntoIterator>, + commitments: impl IntoIterator>, + eqn_query_set: &QuerySet, + eqn_evaluations: &Evaluations, + proof: &BatchLCProof, + sponge: &mut S, + rng: &mut R, + ) -> Result + where + Self::Commitment: 'a, + { + // The default implementation does this by batch-checking each + // batch-opening proof of polynomials appearing in those LC that were + // queried at the same point, then computing the evaluations of each LC + // using the proved polynomial evaluations. + let BatchLCProof { proof, evals } = proof; + let lc_s = BTreeMap::from_iter(linear_combinations.into_iter().map(|lc| (lc.label(), lc))); + + // Rearrange the information about queries on linear combinations into + // information about queries on individual polynomials. + let poly_query_set = lc_query_set_to_poly_query_set(lc_s.values().copied(), eqn_query_set); + let sorted_by_poly_and_query_label: BTreeSet<_> = poly_query_set + .clone() + .into_iter() + .map(|(poly_label, v)| ((poly_label.clone(), v.1), v.0)) + .collect(); + + let poly_evals = Evaluations::from_iter( + sorted_by_poly_and_query_label + .into_iter() + .zip(evals.clone().unwrap()) + .map(|(((poly_label, point), _query_label), eval)| ((poly_label, point), eval)), + ); + + for &(ref lc_label, (_, ref point)) in eqn_query_set { + if let Some(lc) = lc_s.get(lc_label) { + let claimed_rhs = *eqn_evaluations + .get(&(lc_label.clone(), point.clone())) + .ok_or(Error::MissingEvaluation { + label: lc_label.to_string(), + })?; + + let mut actual_rhs = F::zero(); + + // Compute the value of the linear combination by adding the + // claimed value for each polynomial in it (to be proved later) + // scaled by the corresponding coefficient. + for (coeff, label) in lc.iter() { + let eval = match label { + LCTerm::One => F::one(), + LCTerm::PolyLabel(l) => *poly_evals + .get(&(l.clone().into(), point.clone())) + .ok_or(Error::MissingEvaluation { + label: format!("{}-{:?}", l.clone(), point.clone()), + })?, + }; + + actual_rhs += &(*coeff * eval); + } + + // Checking the computed evaluation matches the claimed one + if claimed_rhs != actual_rhs { + eprintln!("Claimed evaluation of {} is incorrect", lc.label()); + return Ok(false); + } + } + } + + // Verify the claimed evaluation for each polynomial appearing in the + // linear combinations, batched by point + let pc_result = Self::batch_check( + vk, + commitments, + &poly_query_set, + &poly_evals, + proof, + sponge, + rng, + )?; + if !pc_result { + eprintln!("Evaluation proofs failed to verify"); + return Ok(false); + } + + Ok(true) + } } +*/ \ No newline at end of file diff --git a/src/subprotocols/zeromorph/kzg.rs b/src/subprotocols/zeromorph/kzg.rs index 17cbc2ff3..44117bf0e 100644 --- a/src/subprotocols/zeromorph/kzg.rs +++ b/src/subprotocols/zeromorph/kzg.rs @@ -113,11 +113,10 @@ pub struct UnivariateKZG

{ impl UnivariateKZG

{ pub fn commit_offset( - g1_powers: impl Borrow>, + g1_powers: &Vec, poly: &UniPoly, offset: usize, ) -> Result { - let g1_powers = g1_powers.borrow(); if poly.degree() > g1_powers.len() { return Err(KZGError::CommitLengthError(poly.degree(), g1_powers.len())); @@ -134,10 +133,9 @@ impl UnivariateKZG

{ } pub fn commit( - g1_powers: impl Borrow>, + g1_powers: &Vec, poly: &UniPoly, ) -> Result { - let g1_powers = g1_powers.borrow(); if poly.degree() > g1_powers.len() { return Err(KZGError::CommitLengthError(poly.degree(), g1_powers.len())); diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index 0ca5aab9e..0932ae149 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -1,7 +1,7 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] -use std::{borrow::Borrow, iter, marker::PhantomData}; +use std::{iter, marker::PhantomData}; use crate::poly::dense_mlpoly::DensePolynomial; use crate::poly::unipoly::UniPoly; @@ -241,27 +241,64 @@ fn eval_and_quotient_scalars( (-vs[0] * z_challenge, q_scalars) } +pub trait ZMPolynomialCommitmentScheme { + // Abstracting over Polynomial allows us to have batched and non-batched PCS + type Polynomial; + type Commitment; + type Evaluation; + type Challenge; + type Proof; + type Error; + + type ProverKey<'p>; + type CommitmentKey<'c>; + type VerifierKey; + + //TODO: convert to impl IntoIterator + fn commit<'c>( + poly: &[Self::Polynomial], + ck: &Self::CommitmentKey<'c>, + ) -> Result, Self::Error>; + + fn prove<'p>( + poly: &[Self::Polynomial], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + pk: &Self::ProverKey<'p>, + transcript: &mut Transcript, + ) -> Result; + + fn verify( + commitments: &[Self::Commitment], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + vk: &Self::VerifierKey, + transcript: &mut Transcript, + proof: Self::Proof, + ) -> Result<(), Self::Error>; +} + pub struct Zeromorph { _phantom: PhantomData

, } -impl PolynomialCommitmentScheme for Zeromorph

{ - type Commitment = Vec; - type Polynomial = Vec>; - type Evaluation = Vec; - type Challenge = Vec; +impl ZMPolynomialCommitmentScheme for Zeromorph

{ + type Commitment = P::G1Affine; + type Polynomial = DensePolynomial; + type Evaluation = P::ScalarField; + type Challenge = P::ScalarField; type Proof = ZeromorphProof

; type Error = ZeromorphError; - type ProverKey = ZeromorphProverKey

; - type CommitmentKey = Self::Commitment; + type ProverKey<'p> = ZeromorphProverKey

; + type CommitmentKey<'c> = Vec; type VerifierKey = ZeromorphVerifierKey

; - fn commit( - polys: Self::Polynomial, - ck: impl Borrow, - ) -> Result { - let ck = ck.borrow(); + fn commit<'c>( + polys: &[Self::Polynomial], + ck: &Self::CommitmentKey<'c>, + ) -> Result, Self::Error> + { // TODO: assert lengths are valid #[cfg(feature = "multicore")] let iter = polys.par_iter(); @@ -274,20 +311,20 @@ impl PolynomialCommitmentScheme for Zeromorph

{ ) } - fn prove( - polys: Self::Polynomial, - evals: Self::Evaluation, - challenge: Self::Challenge, - pk: impl Borrow, + fn prove<'p>( + polys: &[Self::Polynomial], + evals: &[Self::Evaluation], + challenge: &[Self::Challenge], + pk: &Self::ProverKey<'p>, transcript: &mut Transcript, - ) -> Result { + ) -> Result + { let num_vars = challenge.len(); let n: usize = 1 << num_vars; - let pk = pk.borrow(); for (poly, eval) in polys.iter().zip_eq(evals.iter()) { // Note by evaluating we confirm the number of challenges is valid - debug_assert_eq!(poly.evaluate(&challenge), *eval); + debug_assert_eq!(poly.evaluate(challenge), *eval); } // Generate batching challenge \rho and powers 1,...,\rho^{m-1} @@ -309,7 +346,7 @@ impl PolynomialCommitmentScheme for Zeromorph

{ let mut pi_poly = UniPoly::from_coeff(f_batched.Z.clone()); // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) - let (quotients, remainder) = compute_multilinear_quotients::

(&f_batched, &challenge); + let (quotients, remainder) = compute_multilinear_quotients::

(&f_batched, challenge); debug_assert_eq!(quotients.len(), f_batched.get_num_vars()); debug_assert_eq!(remainder, batched_evaluation); @@ -346,7 +383,7 @@ impl PolynomialCommitmentScheme for Zeromorph

{ >::challenge_scalar(transcript, b"ZM: z"); let (eval_scalar, (zeta_degree_check_q_scalars, z_zmpoly_q_scalars)) = - eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, &challenge); + eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, challenge); // f = z * x * poly.Z + q_hat + (-z * x * ฮฆ_n(x) * e) + x * โˆ‘_k (q_scalars_k * q_k) pi_poly *= &z_challenge; pi_poly += &q_hat; @@ -373,22 +410,21 @@ impl PolynomialCommitmentScheme for Zeromorph

{ } fn verify( - commitments: Self::Commitment, - evals: Self::Evaluation, - challenges: Self::Challenge, - vk: impl Borrow, + commitments: &[Self::Commitment], + evals: &[Self::Evaluation], + challenges: &[Self::Challenge], + vk: &Self::VerifierKey, transcript: &mut Transcript, proof: Self::Proof, - ) -> Result<(), Self::Error> { + ) -> Result<(), Self::Error> + { debug_assert_eq!(evals.len(), commitments.len()); - let vk = vk.borrow(); let ZeromorphProof { pi, q_k_com, q_hat_com, } = proof; 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 @@ -428,7 +464,7 @@ impl PolynomialCommitmentScheme for Zeromorph

{ >::challenge_scalar(transcript, b"ZM: z"); let (eval_scalar, (mut q_scalars, zm_poly_q_scalars)) = - eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, &challenges); + eval_and_quotient_scalars::

(y_challenge, x_challenge, z_challenge, challenges); q_scalars .iter_mut() @@ -457,7 +493,7 @@ impl PolynomialCommitmentScheme for Zeromorph

{ // e(pi, [tau]_2 - x * [1]_2) == e(C_{\zeta,Z}, [X^(N_max - 2^n - 1)]_2) <==> e(C_{\zeta,Z} - x * pi, [X^{N_max - 2^n - 1}]_2) * e(-pi, [tau_2]) == 1 let lhs = P::pairing(pi, vk.tau_2.into_group() - (vk.g2 * x_challenge)); let rhs = P::pairing(Zeta_z_com, vk.tau_N_max_sub_2_N); - assert_eq!(lhs, rhs); + assert!(lhs == rhs); Ok(()) } } @@ -506,13 +542,13 @@ mod test { let evals = polys.evaluate(&challenges); // Commit and open - let commitments = Zeromorph::::commit(vec![polys.clone()], &pk.g1_powers).unwrap(); + let commitments = Zeromorph::::commit(&[polys.clone()], &pk.g1_powers).unwrap(); let mut prover_transcript = Transcript::new(b"example"); let proof = Zeromorph::::prove( - vec![polys], - vec![evals], - challenges, + &[polys], + &[evals], + &challenges, &pk, &mut prover_transcript, ) @@ -520,9 +556,9 @@ mod test { let mut verifier_transcript = Transcript::new(b"example"); Zeromorph::::verify( - commitments, - vec![evals], - challenges, + &commitments, + &[evals], + &challenges, &vk, &mut verifier_transcript, proof, @@ -566,17 +602,17 @@ mod test { .collect::>(); // Commit and open - let commitments = Zeromorph::::commit(polys, &pk.g1_powers).unwrap(); + let commitments = Zeromorph::::commit(&polys, &pk.g1_powers).unwrap(); let mut prover_transcript = Transcript::new(b"example"); let proof = - Zeromorph::::prove(polys, evals, challenges, &pk, &mut prover_transcript).unwrap(); + Zeromorph::::prove(&polys, &evals, &challenges, &pk, &mut prover_transcript).unwrap(); let mut verifier_transcript = Transcript::new(b"example"); Zeromorph::::verify( - commitments, - evals, - challenges, + &commitments, + &evals, + &challenges, &vk, &mut verifier_transcript, proof, diff --git a/src/subtables/mod.rs b/src/subtables/mod.rs index e860d2bdb..165f6382e 100644 --- a/src/subtables/mod.rs +++ b/src/subtables/mod.rs @@ -179,7 +179,7 @@ where &self, gens: &PolyCommitmentGens, ) -> CombinedTableCommitment { - let (comm_ops_val, _blinds) = Hyrax::commit(self.combined_poly, (*gens, None)).unwrap(); + let (comm_ops_val, _blinds) = Hyrax::::commit(&self.combined_poly, (gens.clone(), None)).unwrap(); CombinedTableCommitment { comm_ops_val } } @@ -267,10 +267,10 @@ impl CombinedTableEvalProof { >::append_scalar(transcript, b"joint_claim_eval", &eval_joint); let (proof_table_eval, _comm_table_eval) = Hyrax::prove( - *joint_poly, - Some(eval_joint), - r_joint, - (None, None, *gens, *random_tape), + &joint_poly, + &Some(eval_joint), + &r_joint, + (None, None, &gens, random_tape), transcript, ) .unwrap(); From 53971a4d925054baf759e54ca4915a61d7d123de Mon Sep 17 00:00:00 2001 From: PatStiles Date: Sat, 17 Feb 2024 21:06:02 -0600 Subject: [PATCH 21/21] fix offset powers --- src/subprotocols/zeromorph/zeromorph.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/subprotocols/zeromorph/zeromorph.rs b/src/subprotocols/zeromorph/zeromorph.rs index 0932ae149..7155b906c 100644 --- a/src/subprotocols/zeromorph/zeromorph.rs +++ b/src/subprotocols/zeromorph/zeromorph.rs @@ -11,7 +11,7 @@ use crate::utils::transcript::ProofTranscript; use ark_bn254::Bn254; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; use ark_ff::{batch_inversion, Field}; -use ark_std::{iterable::Iterable, One, Zero}; +use ark_std::{iterable::Iterable, One, Zero, ops::Neg}; use itertools::Itertools; use lazy_static::lazy_static; use merlin::Transcript; @@ -55,14 +55,15 @@ impl ZeromorphSRS

{ &self, max_degree: usize, ) -> Result<(ZeromorphProverKey

, ZeromorphVerifierKey

), ZeromorphError> { - if max_degree > self.0.g1_powers.len() { + let offset = self.0.g1_powers.len() - max_degree; + if self.0.g1_powers.len() >= max_degree + offset { return Err(ZeromorphError::KeyLengthError( max_degree, self.0.g1_powers.len(), )); } let offset = self.0.g1_powers.len() - max_degree; - let offset_g1_powers = self.0.g1_powers[offset..].to_vec(); + let offset_g1_powers = self.0.g1_powers[offset..(offset + max_degree)].to_vec(); Ok(( ZeromorphProverKey { g1_powers: self.0.g1_powers.clone(), @@ -282,7 +283,9 @@ pub struct Zeromorph { _phantom: PhantomData

, } -impl ZMPolynomialCommitmentScheme for Zeromorph

{ +impl ZMPolynomialCommitmentScheme for Zeromorph

+where

::G2Affine: Neg +{ type Commitment = P::G1Affine; type Polynomial = DensePolynomial; type Evaluation = P::ScalarField; @@ -484,16 +487,15 @@ impl ZMPolynomialCommitmentScheme for Zeromorph

{ .concat(); let bases = [ - vec![q_hat_com.into_affine(), batched_commitment.into(), vk.g1], + vec![q_hat_com.into(), batched_commitment.into(), vk.g1], q_k_com, ] .concat(); let Zeta_z_com = ::msm(&bases, &scalars).unwrap(); - // e(pi, [tau]_2 - x * [1]_2) == e(C_{\zeta,Z}, [X^(N_max - 2^n - 1)]_2) <==> e(C_{\zeta,Z} - x * pi, [X^{N_max - 2^n - 1}]_2) * e(-pi, [tau_2]) == 1 - let lhs = P::pairing(pi, vk.tau_2.into_group() - (vk.g2 * x_challenge)); - let rhs = P::pairing(Zeta_z_com, vk.tau_N_max_sub_2_N); - assert!(lhs == rhs); + // e(pi, [tau]_2 - x * [1]_2) == e(C_{\zeta,Z}, -[X^(N_max - 2^n - 1)]_2) <==> e(C_{\zeta,Z} - x * pi, [X^{N_max - 2^n - 1}]_2) * e(-pi, [tau_2]) == 1 + let e = P::multi_pairing(&[Zeta_z_com, pi], &[vk.tau_2.into_group() - (vk.g2 * x_challenge), -vk.tau_N_max_sub_2_N.into_group()]); + assert!(e.is_zero()); Ok(()) } } @@ -521,7 +523,7 @@ mod test { #[test] fn prove_verify_single() { - let max_vars = 16; + let max_vars = 8; let mut rng = test_rng(); let srs = ZEROMORPH_SRS.lock().unwrap();