diff --git a/benches/pcs.rs b/benches/pcs.rs index 764b306de..af12efcba 100644 --- a/benches/pcs.rs +++ b/benches/pcs.rs @@ -1,7 +1,7 @@ use arecibo::provider::{ hyperkzg::EvaluationEngine as MLEvaluationEngine, - ipa_pc::EvaluationEngine as IPAEvaluationEngine, non_hiding_zeromorph::ZMPCS, Bn256Engine, - Bn256EngineKZG, Bn256EngineZM, + ipa_pc::EvaluationEngine as IPAEvaluationEngine, non_hiding_zeromorph::ZMPCS, + shplonk::EvaluationEngine as Shplonk, Bn256Engine, Bn256EngineKZG, Bn256EngineZM, }; use arecibo::spartan::polys::multilinear::MultilinearPolynomial; use arecibo::traits::{ @@ -41,7 +41,7 @@ criterion_main!(pcs); const NUM_VARS_TEST_VECTOR: [usize; 6] = [10, 12, 14, 16, 18, 20]; -struct BenchAssests> { +struct BenchAssets> { poly: MultilinearPolynomial<::Scalar>, point: Vec<::Scalar>, eval: ::Scalar, @@ -73,7 +73,7 @@ pub fn random_poly_with_eval( (poly, point, eval) } -impl> BenchAssests { +impl> BenchAssets { pub(crate) fn from_num_vars(num_vars: usize, rng: &mut R) -> Self { let (poly, point, eval) = random_poly_with_eval::(num_vars, rng); @@ -117,7 +117,7 @@ macro_rules! benchmark_all_engines { let mut rng = rand::rngs::StdRng::seed_from_u64(*num_vars as u64); $( - let $assets: BenchAssests<_, $eval_engine> = BenchAssests::from_num_vars::(*num_vars, &mut rng); + let $assets: BenchAssets<_, $eval_engine> = BenchAssets::from_num_vars::(*num_vars, &mut rng); )* // Proving group @@ -159,13 +159,14 @@ fn bench_pcs(c: &mut Criterion) { bench_pcs_verifying_internal, (ipa_assets, IPAEvaluationEngine), (hyperkzg_assets, MLEvaluationEngine), - (zm_assets, ZMPCS) + (zm_assets, ZMPCS), + (shplonk_assets, Shplonk) ); } fn bench_pcs_proving_internal>( b: &mut Bencher<'_>, - bench_assets: &BenchAssests, + bench_assets: &BenchAssets, ) { // Bench generate proof. b.iter(|| { @@ -184,7 +185,7 @@ fn bench_pcs_proving_internal>( fn bench_pcs_verifying_internal>( b: &mut Bencher<'_>, - bench_assets: &BenchAssests, + bench_assets: &BenchAssets, ) { // Bench verify proof. b.iter(|| { diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index 7fbbda9cb..7ba1e7505 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -61,18 +61,20 @@ where E::Fr: TranscriptReprTrait, E::G1Affine: TranscriptReprTrait, // TODO: this bound on DlogGroup is really unusable! { - fn compute_challenge( + /// TODO: write doc + pub fn compute_challenge( com: &[E::G1Affine], transcript: &mut impl TranscriptEngineTrait, ) -> E::Fr { - transcript.absorb(b"c", &com.to_vec().as_slice()); + transcript.absorb(b"c", &com); transcript.squeeze(b"c").unwrap() } + /// TODO: write doc // Compute challenge q = Hash(vk, C0, ..., C_{k-1}, u0, ...., u_{t-1}, // (f_i(u_j))_{i=0..k-1,j=0..t-1}) // It is assumed that both 'C' and 'u' are already absorbed by the transcript - fn get_batch_challenge( + pub fn get_batch_challenge( v: &[Vec], transcript: &mut impl TranscriptEngineTrait, ) -> E::Fr { @@ -88,14 +90,15 @@ where transcript.squeeze(b"r").unwrap() } - fn batch_challenge_powers(q: E::Fr, k: usize) -> Vec { - // Compute powers of q : (1, q, q^2, ..., q^(k-1)) + /// Compute powers of q : (1, q, q^2, ..., q^(k-1)) + pub fn batch_challenge_powers(q: E::Fr, k: usize) -> Vec { std::iter::successors(Some(E::Fr::ONE), |&x| Some(x * q)) .take(k) .collect() } - fn verifier_second_challenge( + /// TODO: write doc + pub fn verifier_second_challenge( W: &[E::G1Affine], transcript: &mut impl TranscriptEngineTrait, ) -> E::Fr { diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 5e4724485..60611ee6f 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -4,6 +4,7 @@ pub mod hyperkzg; pub mod ipa_pc; pub mod non_hiding_zeromorph; +pub mod shplonk; // crate-public modules, made crate-public mostly for tests pub(crate) mod bn256_grumpkin; diff --git a/src/provider/non_hiding_kzg.rs b/src/provider/non_hiding_kzg.rs index c1be79f9b..b6b28dc34 100644 --- a/src/provider/non_hiding_kzg.rs +++ b/src/provider/non_hiding_kzg.rs @@ -222,7 +222,7 @@ pub struct UVKZGProof { } /// Polynomial and its associated types -pub type UVKZGPoly = crate::spartan::polys::univariate::UniPoly; +type UVKZGPoly = crate::spartan::polys::univariate::UniPoly; #[derive(Debug, Eq, PartialEq, Default)] /// KZG Polynomial Commitment Scheme on univariate polynomial. diff --git a/src/provider/non_hiding_zeromorph.rs b/src/provider/non_hiding_zeromorph.rs index f0c150fdc..1c4ee0137 100644 --- a/src/provider/non_hiding_zeromorph.rs +++ b/src/provider/non_hiding_zeromorph.rs @@ -7,7 +7,7 @@ use crate::{ errors::{NovaError, PCSError}, provider::{ non_hiding_kzg::{ - KZGProverKey, KZGVerifierKey, UVKZGCommitment, UVKZGEvaluation, UVKZGPoly, UVKZGProof, + KZGProverKey, KZGVerifierKey, UVKZGCommitment, UVKZGEvaluation, UVKZGProof, UniversalKZGParam, UVKZGPCS, }, traits::DlogGroup, @@ -33,6 +33,7 @@ use std::sync::Arc; use std::{borrow::Borrow, iter, marker::PhantomData}; use crate::provider::kzg_commitment::KZGCommitmentEngine; +use crate::spartan::polys::univariate::UniPoly; /// `ZMProverKey` is used to generate a proof #[derive(Clone, Debug, Eq, PartialEq)] @@ -156,7 +157,7 @@ where if pp.commit_pp.powers_of_g().len() < poly.Z.len() { return Err(PCSError::LengthError.into()); } - UVKZGPCS::commit(&pp.commit_pp, UVKZGPoly::ref_cast(&poly.Z)).map(|c| c.into()) + UVKZGPCS::commit(&pp.commit_pp, UniPoly::ref_cast(&poly.Z)).map(|c| c.into()) } /// On input a polynomial `poly` and a point `point`, outputs a proof for the @@ -184,10 +185,7 @@ where debug_assert_eq!(remainder, eval.0); // Compute the multilinear quotients q_k = q_k(X_0, ..., X_{k-1}) - let quotients_polys = quotients - .into_iter() - .map(UVKZGPoly::new) - .collect::>(); + let quotients_polys = quotients.into_iter().map(UniPoly::new).collect::>(); // Compute and absorb commitments C_{q_k} = [q_k], k = 0,...,d-1 let q_comms = quotients_polys @@ -215,7 +213,7 @@ where let (eval_scalar, (degree_check_q_scalars, zmpoly_q_scalars)) = eval_and_quotient_scalars(y, x, z, point); // f = z * poly.Z + q_hat + (-z * Φ_n(x) * e) + ∑_k (q_scalars_k * q_k) - let mut f = UVKZGPoly::new(poly.Z.clone()); + let mut f = UniPoly::new(poly.Z.clone()); f *= &z; f += &q_hat; f[0] += eval_scalar * eval.0; @@ -360,8 +358,8 @@ fn quotients(poly: &MultilinearPolynomial, point: &[F]) -> (Ve // Compute the batched, lifted-degree quotient `\hat{q}` fn batched_lifted_degree_quotient( y: F, - quotients_polys: &[UVKZGPoly], -) -> (UVKZGPoly, usize) { + quotients_polys: &[UniPoly], +) -> (UniPoly, usize) { let num_vars = quotients_polys.len(); let powers_of_y = (0..num_vars) @@ -390,7 +388,7 @@ fn batched_lifted_degree_quotient( }, ); - (UVKZGPoly::new(q_hat), 1 << (num_vars - 1)) + (UniPoly::new(q_hat), 1 << (num_vars - 1)) } /// Computes some key terms necessary for computing the partially evaluated univariate ZM polynomial @@ -523,12 +521,11 @@ mod test { use super::quotients; + use crate::spartan::polys::univariate::UniPoly; use crate::{ errors::PCSError, provider::{ - non_hiding_kzg::{ - trim, KZGProverKey, UVKZGCommitment, UVKZGPoly, UniversalKZGParam, UVKZGPCS, - }, + non_hiding_kzg::{trim, KZGProverKey, UVKZGCommitment, UniversalKZGParam, UVKZGPCS}, non_hiding_zeromorph::{batched_lifted_degree_quotient, eval_and_quotient_scalars, ZMPCS}, traits::DlogGroup, util::test_utils::prove_verify_from_num_vars, @@ -598,9 +595,9 @@ mod test { let n = 1 << num_vars; // Assuming N = 2^num_vars // Define mock q_k with deg(q_k) = 2^k - 1 - let q_0 = UVKZGPoly::new(vec![Scalar::one()]); - let q_1 = UVKZGPoly::new(vec![Scalar::from(2), Scalar::from(3)]); - let q_2 = UVKZGPoly::new(vec![ + let q_0 = UniPoly::new(vec![Scalar::one()]); + let q_1 = UniPoly::new(vec![Scalar::from(2), Scalar::from(3)]); + let q_2 = UniPoly::new(vec![ Scalar::from(4), Scalar::from(5), Scalar::from(6), @@ -644,10 +641,7 @@ mod test { }); // Compare the computed and expected batched quotients - assert_eq!( - batched_quotient.0, - UVKZGPoly::new(batched_quotient_expected) - ); + assert_eq!(batched_quotient.0, UniPoly::new(batched_quotient_expected)); } #[test] @@ -657,9 +651,9 @@ mod test { let num_vars = 3; // Define some mock q_k with deg(q_k) = 2^k - 1 - let _q_0 = UVKZGPoly::new(vec![Scalar::one()]); - let _q_1 = UVKZGPoly::new(vec![Scalar::from(2), Scalar::from(3)]); - let _q_2 = UVKZGPoly::new(vec![ + let _q_0 = UniPoly::new(vec![Scalar::one()]); + let _q_1 = UniPoly::new(vec![Scalar::from(2), Scalar::from(3)]); + let _q_2 = UniPoly::new(vec![ Scalar::from(4), Scalar::from(5), Scalar::from(6), @@ -713,9 +707,9 @@ mod test { let mut rng = ChaCha20Rng::from_seed([0u8; 32]); // Define some mock q_k with deg(q_k) = 2^k - 1 - let _q_0 = UVKZGPoly::new(vec![Scalar::one()]); - let _q_1 = UVKZGPoly::new(vec![Scalar::from(2), Scalar::from(3)]); - let _q_2 = UVKZGPoly::new(vec![ + let _q_0 = UniPoly::new(vec![Scalar::one()]); + let _q_1 = UniPoly::new(vec![Scalar::from(2), Scalar::from(3)]); + let _q_2 = UniPoly::new(vec![ Scalar::from(4), Scalar::from(5), Scalar::from(6), @@ -755,7 +749,7 @@ mod test { fn commit_filtered( prover_param: impl Borrow>, - poly: &UVKZGPoly, + poly: &UniPoly, ) -> Result, NovaError> where E: MultiMillerLoop, @@ -802,10 +796,7 @@ mod test { } let (quotients, _remainder) = quotients(&multilinear_poly, random_points.as_slice()); - let quotients_polys = quotients - .into_iter() - .map(UVKZGPoly::new) - .collect::>(); + let quotients_polys = quotients.into_iter().map(UniPoly::new).collect::>(); let (q_hat, offset) = batched_lifted_degree_quotient(E::Fr::random(&mut rng), "ients_polys); diff --git a/src/provider/shplonk.rs b/src/provider/shplonk.rs new file mode 100644 index 000000000..e0c75af86 --- /dev/null +++ b/src/provider/shplonk.rs @@ -0,0 +1,652 @@ +//! Shplonk PCS +use crate::provider::kzg_commitment::KZGCommitmentEngine; +use crate::provider::non_hiding_kzg::{trim, KZGProverKey, KZGVerifierKey, UniversalKZGParam}; +use crate::provider::pedersen::Commitment; +use crate::provider::traits::DlogGroup; +use crate::provider::util::iterators::DoubleEndedIteratorExt; +use crate::spartan::polys::univariate::UniPoly; +use crate::traits::commitment::Len; +use crate::traits::evaluation::EvaluationEngineTrait; +use crate::traits::{Engine as NovaEngine, Group, TranscriptEngineTrait, TranscriptReprTrait}; +use crate::{CommitmentEngineTrait, NovaError}; +use ff::{Field, PrimeFieldBits}; +use group::{Curve, Group as group_Group}; +use pairing::{Engine, MillerLoopResult, MultiMillerLoop}; +use rayon::iter::{ + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefMutIterator, ParallelIterator, +}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::marker::PhantomData; + +use crate::provider::hyperkzg::EvaluationEngine as HyperKZG; +use group::prime::PrimeCurveAffine; +use itertools::Itertools; +use ref_cast::RefCast as _; +use std::sync::Arc; + +/// EvaluationArgument of Shplonk +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound( + serialize = "E::G1Affine: Serialize, E::Fr: Serialize", + deserialize = "E::G1Affine: Deserialize<'de>, E::Fr: Deserialize<'de>" +))] +pub struct EvaluationArgument { + comms: Vec, + evals: Vec>, + R_x: Vec, + C_Q: E::G1Affine, + C_H: E::G1Affine, +} + +/// EvaluationEngine of Shplonk +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EvaluationEngine { + _p: PhantomData<(E, NE)>, +} + +impl EvaluationEngine +where + E: Engine, + NE: NovaEngine>, + E::Fr: Serialize + DeserializeOwned, + E::G1Affine: Serialize + DeserializeOwned, + E::G2Affine: Serialize + DeserializeOwned, + E::G1: DlogGroup, + E::Fr: PrimeFieldBits, + ::Base: TranscriptReprTrait, + E::Fr: TranscriptReprTrait, + E::G1Affine: TranscriptReprTrait, +{ + fn compute_a(c_q: &E::G1Affine, transcript: &mut impl TranscriptEngineTrait) -> E::Fr { + transcript.absorb(b"C_Q", c_q); + transcript.squeeze(b"a").unwrap() + } + + fn compute_pi_polynomials(hat_P: &[E::Fr], point: &[E::Fr], eval: &E::Fr) -> Vec> { + let mut polys: Vec> = Vec::new(); + polys.push(hat_P.to_vec()); + + for i in 0..point.len() - 1 { + let Pi_len = polys[i].len() / 2; + let mut Pi = vec![E::Fr::ZERO; Pi_len]; + + (0..Pi_len) + .into_par_iter() + .map(|j| { + point[point.len() - i - 1] * (polys[i][2 * j + 1] - polys[i][2 * j]) + polys[i][2 * j] + }) + .collect_into_vec(&mut Pi); + + polys.push(Pi); + } + + // TODO avoid including last constant polynomial, known to verifier + polys.push(vec![*eval]); + + assert_eq!(polys.len(), 1 + (hat_P.len() as f32).log2().ceil() as usize); + + polys + } + + fn compute_commitments( + ck: &UniversalKZGParam, + C: &Commitment, + polys: &[Vec], + ) -> Vec { + // TODO avoid computing commitment to constant polynomial + let mut comms: Vec = (1..polys.len()) + .into_par_iter() + .map(|i| >::commit(ck, &polys[i]).comm) + .collect(); + // TODO avoid inserting commitment known to verifier + comms.insert(0, C.comm); + + let mut comms_affine: Vec = vec![E::G1Affine::identity(); comms.len()]; + NE::GE::batch_normalize(&comms, &mut comms_affine); + comms_affine + } + + fn compute_evals(polys: &[Vec], u: &[E::Fr]) -> Vec> { + // TODO: avoid computing eval to a constant polynomial + let mut v = vec![vec!(E::Fr::ZERO; polys.len()); u.len()]; + v.par_iter_mut().enumerate().for_each(|(i, v_i)| { + // for each point u + v_i.par_iter_mut().zip_eq(polys).for_each(|(v_ij, f)| { + // for each poly f (except the last one - since it is constant) + *v_ij = UniPoly::ref_cast(f).evaluate(&u[i]); + }); + }); + v + } + + fn compute_k_polynomial( + batched_Pi: &UniPoly, + Q_x: &UniPoly, + D: &UniPoly, + R_x: &UniPoly, + a: E::Fr, + ) -> UniPoly { + let mut tmp = Q_x.clone(); + tmp *= &D.evaluate(&a); + tmp[0] += &R_x.evaluate(&a); + tmp = UniPoly::new( + tmp + .coeffs + .into_iter() + .map(|coeff| -coeff) + .collect::>(), + ); + let mut K_x = batched_Pi.clone(); + K_x += &tmp; + K_x + } +} + +impl EvaluationEngineTrait for EvaluationEngine +where + E: MultiMillerLoop, + NE: NovaEngine>, + E::Fr: Serialize + DeserializeOwned, + E::G1Affine: Serialize + DeserializeOwned, + E::G2Affine: Serialize + DeserializeOwned, + E::G1: DlogGroup, + ::Base: TranscriptReprTrait, // Note: due to the move of the bound TranscriptReprTrait on G::Base from Group to Engine + E::Fr: PrimeFieldBits, // TODO due to use of gen_srs_for_testing, make optional + E::Fr: TranscriptReprTrait, + E::G1Affine: TranscriptReprTrait, +{ + type ProverKey = KZGProverKey; + type VerifierKey = KZGVerifierKey; + type EvaluationArgument = EvaluationArgument; + + fn setup(ck: Arc>) -> (KZGProverKey, KZGVerifierKey) { + let len = ck.length() - 1; + trim(ck, len) + } + + fn prove( + ck: &UniversalKZGParam, + _pk: &KZGProverKey, + transcript: &mut ::TE, + C: &Commitment, + hat_P: &[E::Fr], + point: &[E::Fr], + eval: &E::Fr, + ) -> Result, NovaError> { + let x: Vec = point.to_vec(); + let ell = x.len(); + let n = hat_P.len(); + assert_eq!(n, 1 << ell); + + // Phase 1 (similar to hyperkzg) + let polys = Self::compute_pi_polynomials(hat_P, point, eval); + let comms = Self::compute_commitments(ck, C, &polys); + + // Phase 2 (similar to hyperkzg) + let r = HyperKZG::::compute_challenge(&comms, transcript); + let u = vec![r, -r, r * r]; + + let evals = Self::compute_evals(&polys, &u); + + // Phase 3 + // Compute B(x) = f_0(x) + q * f_1(x) + ... + q^(k-1) * f_{k-1}(x) + let q = HyperKZG::::get_batch_challenge(&evals, transcript); + let batched_Pi: UniPoly = polys.into_iter().map(UniPoly::new).rlc(&q); + + // Q(x), R(x) = P(x) / D(x), where D(x) = (x - r) * (x + r) * (x - r^2) = 1 * x^3 - r^2 * x^2 - r^2 * x + r^4 + let D = UniPoly::new(vec![u[2] * u[2], -u[2], -u[2], E::Fr::from(1)]); + let (Q_x, R_x) = batched_Pi.divide_with_q_and_r(&D).unwrap(); + + let C_Q = >::commit(ck, &Q_x.coeffs) + .comm + .to_affine(); + + let a = Self::compute_a(&C_Q, transcript); + + // K(x) = P(x) - Q(x) * D(a) - R(a), note that R(a) should be subtracted from a free term of polynomial + let K_x = Self::compute_k_polynomial(&batched_Pi, &Q_x, &D, &R_x, a); + + // TODO: since this is a usual KZG10 we should use it as utility instead + // H(x) = K(x) / (x - a) + let divisor = UniPoly::new(vec![-a, E::Fr::from(1)]); + let (H_x, _) = K_x.divide_with_q_and_r(&divisor).unwrap(); + + let C_H = >::commit(ck, &H_x.coeffs) + .comm + .to_affine(); + + Ok(EvaluationArgument:: { + comms, + evals, + R_x: R_x.coeffs, + C_Q, + C_H, + }) + } + + /// A method to verify purported evaluations of a batch of polynomials + fn verify( + vk: &KZGVerifierKey, + transcript: &mut ::TE, + _C: &Commitment, + point: &[E::Fr], + _P_of_x: &E::Fr, + pi: &EvaluationArgument, + ) -> Result<(), NovaError> { + let r = HyperKZG::::compute_challenge(&pi.comms, transcript); + let u = [r, -r, r * r]; + + if pi.evals.len() != u.len() { + return Err(NovaError::ProofVerifyError); + } + if pi.R_x.len() != u.len() { + return Err(NovaError::ProofVerifyError); + } + + // TODO: + // insert _P_of_x into every pi.evals_i[last] + // insert _C into pi.comms[0] + // compute commitment for eval and insert it into pi.comms[last] + + let q = HyperKZG::::get_batch_challenge(&pi.evals, transcript); + //let q_powers = HyperKZG::::batch_challenge_powers(q, pi.comms.len()); + + let R_x = UniPoly::new(pi.R_x.clone()); + + let mut evals_at_r = vec![]; + let mut evals_at_minus_r = vec![]; + let mut evals_at_r_squared = vec![]; + for (i, evals_i) in pi.evals.iter().enumerate() { + if i == 0 { + evals_at_r = evals_i.clone(); + } + if i == 1 { + evals_at_minus_r = evals_i.clone(); + } + if i == 2 { + evals_at_r_squared = evals_i.clone(); + } + + let batched_eval = UniPoly::ref_cast(evals_i).evaluate(&q); + + // here we check correlation between R polynomial and batched evals, e.g.: + // 1) R(r) == eval at r + // 2) R(-r) == eval at -r + // 3) R(r^2) == eval at r^2 + if batched_eval != R_x.evaluate(&u[i]) { + return Err(NovaError::ProofVerifyError); + } + } + + // here we check that Pi polynomials were correctly constructed by the prover, using 'r' as a random point, e.g: + // P_i_even = P_i(r) + P_i(-r) * 1/2 + // P_i_odd = P_i(r) - P_i(-r) * 1/2*r + // P_i+1(r^2) == (1 - point_i) * P_i_even + point_i * P_i_odd -> should hold, according to Gemini transformation + let mut point = point.to_vec(); + point.reverse(); + #[allow(clippy::disallowed_methods)] + for (index, ((eval_r, eval_minus_r), eval_r_squared)) in evals_at_r + .iter() + .zip_eq(evals_at_minus_r.iter()) + // TODO: Ask Adrian if we need evals_at_r_squared[0] for some additional checks + .zip(evals_at_r_squared[1..].iter()) + .enumerate() + { + let even = (*eval_r + eval_minus_r) * (E::Fr::from(2).invert().unwrap()); + let odd = (*eval_r - eval_minus_r) * ((E::Fr::from(2) * r).invert().unwrap()); + + if *eval_r_squared != ((E::Fr::ONE - point[index]) * even) + (point[index] * odd) { + return Err(NovaError::ProofVerifyError); + } + } + + let C_P: E::G1 = pi.comms.iter().map(|comm| comm.to_curve()).rlc(&q); + let C_Q = pi.C_Q; + let C_H = pi.C_H; + let r_squared = u[2]; + + // D = (x - r) * (x + r) * (x - r^2) = 1 * x^3 - r^2 * x^2 - r^2 * x + r^4 + let D = UniPoly::new(vec![ + r_squared * r_squared, + -r_squared, + -r_squared, + E::Fr::from(1), + ]); + + let a = Self::compute_a(&C_Q, transcript); + + let C_K = C_P - (C_Q * D.evaluate(&a) + vk.g * R_x.evaluate(&a)); + + let pairing_inputs: Vec<(E::G1Affine, E::G2Prepared)> = vec![ + (C_H, vk.beta_h.into()), + ((C_H * (-a) - C_K).to_affine(), vk.h.into()), + ]; + + #[allow(clippy::map_identity)] + let pairing_input_refs = pairing_inputs + .iter() + .map(|(a, b)| (a, b)) + .collect::>(); + + let pairing_result = E::multi_miller_loop(pairing_input_refs.as_slice()).final_exponentiation(); + if pairing_result.is_identity().unwrap_u8() == 0x00 { + return Err(NovaError::ProofVerifyError); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::TranscriptEngineTrait; + use crate::{provider::keccak::Keccak256Transcript, CommitmentEngineTrait, CommitmentKey}; + use halo2curves::bn256::G1; + use itertools::Itertools; + + type E = halo2curves::bn256::Bn256; + type NE = crate::provider::Bn256EngineKZG; + type Fr = ::Scalar; + + fn test_commitment_to_k_polynomial_correctness( + ck: &CommitmentKey, + C: &Commitment, + poly: &[Fr], + point: &[Fr], + eval: &Fr, + ) { + let polys = EvaluationEngine::::compute_pi_polynomials(poly, point, eval); + let comms = EvaluationEngine::::compute_commitments(ck, C, &polys); + + let q = Fr::from(8165763); + let q_powers = HyperKZG::::batch_challenge_powers(q, polys.len()); + let batched_Pi: UniPoly = polys.clone().into_iter().map(UniPoly::new).rlc(&q); + + let r = Fr::from(1354678); + let r_squared = r * r; + + let divident = batched_Pi.clone(); + let D = UniPoly::new(vec![ + r_squared * r_squared, + -r_squared, + -r_squared, + Fr::from(1), + ]); + let (Q_x, R_x) = divident.divide_with_q_and_r(&D).unwrap(); + + let a = Fr::from(938576); + + let K_x = EvaluationEngine::::compute_k_polynomial(&batched_Pi, &Q_x, &D, &R_x, a); + + let mut C_P = G1::identity(); + q_powers.iter().zip_eq(comms.iter()).for_each(|(q_i, C_i)| { + C_P += *C_i * q_i; + }); + + let C_Q = + <::CE as CommitmentEngineTrait>::commit( + ck, + &Q_x.coeffs, + ) + .comm + .to_affine(); + + // Check that Cp - Cq * D(a) - g1 * R(a) == MSM(ck, K(x)) + let C_K = C_P - C_Q * D.evaluate(&a) - ck.powers_of_g[0] * R_x.evaluate(&a); + + let C_K_expected = + <::CE as CommitmentEngineTrait>::commit( + ck, + &K_x.coeffs, + ) + .comm + .to_affine(); + + assert_eq!(C_K_expected, C_K.to_affine()); + } + + fn test_k_polynomial_correctness(poly: &[Fr], point: &[Fr], eval: &Fr) { + let polys = EvaluationEngine::::compute_pi_polynomials(poly, point, eval); + let q = Fr::from(8165763); + let batched_Pi: UniPoly = polys.clone().into_iter().map(UniPoly::new).rlc(&q); + + let r = Fr::from(56263); + let r_squared = r * r; + + let divident = batched_Pi.clone(); + let D = UniPoly::new(vec![ + r_squared * r_squared, + -r_squared, + -r_squared, + Fr::from(1), + ]); + let (Q_x, R_x) = divident.divide_with_q_and_r(&D).unwrap(); + + let a = Fr::from(190837645); + + let K_x = EvaluationEngine::::compute_k_polynomial(&batched_Pi, &Q_x, &D, &R_x, a); + + assert_eq!(Fr::from(0), K_x.evaluate(&a)); + } + + fn test_d_polynomial_correctness(poly: &[Fr], point: &[Fr], eval: &Fr) { + let polys = EvaluationEngine::::compute_pi_polynomials(poly, point, eval); + let q = Fr::from(8165763); + let batched_Pi: UniPoly = polys.clone().into_iter().map(UniPoly::new).rlc(&q); + + let r = Fr::from(2895776832); + let r_squared = r * r; + + let divident = batched_Pi.clone(); + // D(x) = (x - r) * (x + r) * (x - r^2) + let D = UniPoly::new(vec![ + r_squared * r_squared, + -r_squared, + -r_squared, + Fr::from(1), + ]); + let (Q_x, R_x) = divident.divide_with_q_and_r(&D).unwrap(); + + let evaluation_scalar = Fr::from(182746); + assert_eq!( + batched_Pi.evaluate(&evaluation_scalar), + D.evaluate(&evaluation_scalar) * Q_x.evaluate(&evaluation_scalar) + + R_x.evaluate(&evaluation_scalar) + ); + + // Check that Q(x) = (P(x) - R(x)) / D(x) + let mut P_x = batched_Pi.clone(); + let minus_R_x = UniPoly::new( + R_x + .clone() + .coeffs + .into_iter() + .map(|coeff| -coeff) + .collect::>(), + ); + P_x += &minus_R_x; + + let divident = P_x.clone(); + let (Q_x_recomputed, _) = divident.divide_with_q_and_r(&D).unwrap(); + + assert_eq!(Q_x, Q_x_recomputed); + } + + fn test_batching_property_on_evaluation(poly: &[Fr], point: &[Fr], eval: &Fr) { + let polys = EvaluationEngine::::compute_pi_polynomials(poly, point, eval); + + let q = Fr::from(97652); + let u = [Fr::from(10), Fr::from(20), Fr::from(50)]; + + let batched_Pi: UniPoly = polys.clone().into_iter().map(UniPoly::new).rlc(&q); + + let q_powers = HyperKZG::::batch_challenge_powers(q, polys.len()); + for evaluation_scalar in u.iter() { + let evals = polys + .clone() + .into_iter() + .map(|poly| UniPoly::new(poly).evaluate(evaluation_scalar)) + .collect::>(); + + let expected = evals + .iter() + .zip_eq(q_powers.iter()) + .map(|(eval, q)| eval * q) + .collect::>() + .into_iter() + .sum::(); + + let actual = batched_Pi.evaluate(evaluation_scalar); + assert_eq!(expected, actual); + } + } + + #[test] + fn test_shplonk_unit_tests() { + // poly = [1, 2, 1, 4, 1, 2, 1, 4] + let poly = vec![ + Fr::ONE, + Fr::from(2), + Fr::from(1), + Fr::from(4), + Fr::ONE, + Fr::from(2), + Fr::from(1), + Fr::from(4), + ]; + + // point = [4,3,8] + let point = vec![Fr::from(4), Fr::from(3), Fr::from(8)]; + + // eval = 57 + let eval = Fr::from(57); + + let ck: CommitmentKey = + as CommitmentEngineTrait>::setup(b"test", poly.len()); + + let ck = Arc::new(ck); + let C: Commitment = KZGCommitmentEngine::commit(&ck, &poly); + + test_batching_property_on_evaluation(&poly, &point, &eval); + test_d_polynomial_correctness(&poly, &point, &eval); + test_k_polynomial_correctness(&poly, &point, &eval); + test_commitment_to_k_polynomial_correctness(&ck, &C, &poly, &point, &eval); + } + + #[test] + fn test_shplonk_pcs() { + let n = 8; + + // poly = [1, 2, 1, 4, 1, 2, 1, 4] + let poly = vec![ + Fr::ONE, + Fr::from(2), + Fr::from(1), + Fr::from(4), + Fr::ONE, + Fr::from(2), + Fr::from(1), + Fr::from(4), + ]; + + // point = [4,3,8] + let point = vec![Fr::from(4), Fr::from(3), Fr::from(8)]; + + // eval = 57 + let eval = Fr::from(57); + + let ck: CommitmentKey = + as CommitmentEngineTrait>::setup(b"test", n); + let ck = Arc::new(ck); + let (pk, vk): (KZGProverKey, KZGVerifierKey) = + EvaluationEngine::::setup(ck.clone()); + + // make a commitment + let C: Commitment = KZGCommitmentEngine::commit(&ck, &poly); + + let mut prover_transcript = Keccak256Transcript::new(b"TestEval"); + let proof = + EvaluationEngine::::prove(&ck, &pk, &mut prover_transcript, &C, &poly, &point, &eval) + .unwrap(); + + let mut verifier_transcript = Keccak256Transcript::::new(b"TestEval"); + assert!(EvaluationEngine::::verify( + &vk, + &mut verifier_transcript, + &C, + &point, + &eval, + &proof + ) + .is_ok()); + } + + #[test] + fn test_shplonk_pcs_negative() { + let n = 8; + // poly = [1, 2, 1, 4, 1, 2, 1, 4] + let poly = vec![ + Fr::ONE, + Fr::from(2), + Fr::from(1), + Fr::from(4), + Fr::ONE, + Fr::from(2), + Fr::from(1), + Fr::from(4), + ]; + // point = [4,3,8] + let point = vec![Fr::from(4), Fr::from(3), Fr::from(8)]; + // eval = 57 + let eval = Fr::from(57); + + // eval = 57 + let eval1 = Fr::from(56); // wrong eval + test_negative_inner(n, &poly, &point, &eval1); + + // point = [4,3,8] + let point1 = vec![Fr::from(4), Fr::from(3), Fr::from(7)]; // wrong point + test_negative_inner(n, &poly, &point1, &eval); + + // poly = [1, 2, 1, 4, 1, 2, 1, 4] + let poly1 = vec![ + Fr::ONE, + Fr::from(2), + Fr::from(1), + Fr::from(4), + Fr::ONE, + Fr::from(2), + Fr::from(200), + Fr::from(100), + ]; // wrong poly + test_negative_inner(n, &poly1, &point, &eval); + } + + fn test_negative_inner(n: usize, poly: &[Fr], point: &[Fr], eval: &Fr) { + let ck: CommitmentKey = + as CommitmentEngineTrait>::setup(b"test", n); + let ck = Arc::new(ck); + let (pk, vk): (KZGProverKey, KZGVerifierKey) = + EvaluationEngine::::setup(ck.clone()); + + // make a commitment + let C: Commitment = KZGCommitmentEngine::commit(&ck, poly); + + let mut prover_transcript = Keccak256Transcript::new(b"TestEval"); + let mut verifier_transcript = Keccak256Transcript::::new(b"TestEval"); + + let proof = + EvaluationEngine::::prove(&ck, &pk, &mut prover_transcript, &C, poly, point, eval) + .unwrap(); + + assert!(EvaluationEngine::::verify( + &vk, + &mut verifier_transcript, + &C, + point, + eval, + &proof + ) + .is_err()); + } +}