From bd503112f527ba183b40a7be1bd8745927728138 Mon Sep 17 00:00:00 2001 From: Artem Storozhuk Date: Thu, 25 Jan 2024 17:58:16 -0500 Subject: [PATCH] Optimising proving by avoiding recomputing final `Pi` polynomial in MLKZG (#242) * feat: Avoid computing final 'Pi' polynomial which is constant * chore: Cleanup * chore: Latest suggestions --- src/provider/hyperkzg.rs | 91 ++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index 874539d40..fd531e6cf 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -122,10 +122,10 @@ where ck: &UniversalKZGParam, _pk: &Self::ProverKey, transcript: &mut ::TE, - C: &Commitment, + _C: &Commitment, hat_P: &[E::Fr], point: &[E::Fr], - eval: &E::Fr, + _eval: &E::Fr, ) -> Result { let x: Vec = point.to_vec(); @@ -164,8 +164,7 @@ where .preprocessed() }; - let kzg_open_batch = |C: &[E::G1Affine], - f: &[Vec], + let kzg_open_batch = |f: &[Vec], u: &[E::Fr], transcript: &mut ::TE| -> (Vec, Vec>) { @@ -194,7 +193,6 @@ where let k = f.len(); let t = u.len(); - assert!(C.len() == k); // The verifier needs f_i(u_j), so we compute them here // (V will compute B(u_j) itself) @@ -202,7 +200,7 @@ where v.par_iter_mut().enumerate().for_each(|(i, v_i)| { // for each point u v_i.par_iter_mut().zip_eq(f).for_each(|(v_ij, f)| { - // for each poly f + // for each poly f (except the last one - since it is constant) *v_ij = UniPoly::ref_cast(f).evaluate(&u[i]); }); }); @@ -216,7 +214,6 @@ where // The prover computes the challenge to keep the transcript in the same // state as that of the verifier let _d_0 = Self::verifier_second_challenge(&w, transcript); - (w, v) }; @@ -229,7 +226,10 @@ where // Phase 1 -- create commitments com_1, ..., com_\ell let mut polys: Vec> = Vec::new(); polys.push(hat_P.to_vec()); - for i in 0..ell { + + // We don't compute final Pi (and its commitment) as it is constant and equals to 'eval' + // also known to verifier, so can be derived on its side as well + for i in 0..x.len() - 1 { let Pi_len = polys[i].len() / 2; let mut Pi = vec![E::Fr::ZERO; Pi_len]; @@ -238,10 +238,6 @@ where *Pi_j = x[ell - i - 1] * (polys[i][2 * j + 1] - polys[i][2 * j]) + polys[i][2 * j]; }); - if i == ell - 1 && *eval != Pi[0] { - return Err(NovaError::UnSat); - } - polys.push(Pi); } @@ -263,9 +259,7 @@ where let u = vec![r, -r, r * r]; // Phase 3 -- create response - let mut com_all = comms.clone(); - com_all.insert(0, C.comm.preprocessed()); - let (w, evals) = kzg_open_batch(&com_all, &polys, &u, transcript); + let (w, evals) = kzg_open_batch(&polys, &u, transcript); Ok(EvaluationArgument { comms, w, evals }) } @@ -380,8 +374,7 @@ where // we do not need to add x to the transcript, because in our context x was // obtained from the transcript let r = Self::compute_challenge(&com, transcript); - - if r == E::Fr::ZERO || C.comm == E::G1::zero() { + if r == E::Fr::ZERO || C.comm == E::G1::identity() { return Err(NovaError::ProofVerifyError); } com.insert(0, C.comm.preprocessed()); // set com_0 = C, shifts other commitments to the right @@ -393,18 +386,15 @@ where if v.len() != 3 { return Err(NovaError::ProofVerifyError); } - if v[0].len() != ell + 1 || v[1].len() != ell + 1 || v[2].len() != ell + 1 { + if v[0].len() != ell || v[1].len() != ell || v[2].len() != ell { return Err(NovaError::ProofVerifyError); } let ypos = &v[0]; let yneg = &v[1]; - let Y = &v[2]; + let mut Y = v[2].to_vec(); + Y.push(*y); // Check consistency of (Y, ypos, yneg) - if Y[ell] != *y { - return Err(NovaError::ProofVerifyError); - } - let two = E::Fr::from(2u64); for i in 0..ell { if two * r * Y[i + 1] @@ -474,15 +464,54 @@ mod tests { let point = vec![Fr::from(2), Fr::from(2)]; let eval = Fr::from(9); assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_ok()); + } - // Try a couple incorrect evaluations and expect failure - let point = vec![Fr::from(2), Fr::from(2)]; - let eval = Fr::from(50); - assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_err()); + #[test] + fn test_mlkzg_alternative() { + fn test_inner(n: usize, poly: &[Fr], point: &[Fr], eval: Fr) -> Result<(), NovaError> { + let ck: CommitmentKey = + as CommitmentEngineTrait>::setup(b"test", n); + let (pk, vk): (KZGProverKey, KZGVerifierKey) = EvaluationEngine::::setup(&ck); + + // make a commitment + let C = KZGCommitmentEngine::commit(&ck, poly); + + // prove an evaluation + let mut prover_transcript = Keccak256Transcript::new(b"TestEval"); + let proof = + EvaluationEngine::::prove(&ck, &pk, &mut prover_transcript, &C, poly, point, &eval) + .unwrap(); + + // verify the evaluation + let mut verifier_transcript = Keccak256Transcript::::new(b"TestEval"); + EvaluationEngine::::verify(&vk, &mut verifier_transcript, &C, point, &eval, &proof) + } - let point = vec![Fr::from(0), Fr::from(2)]; - let eval = Fr::from(4); - assert!(EvaluationEngine::::prove(&ck, &pk, &mut tr, &C, &poly, &point, &eval).is_err()); + 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); + + assert!(test_inner(n, &poly, &point, eval).is_ok()); + + // wrong eval + let eval = Fr::from(56); + assert!(test_inner(n, &poly, &point, eval).is_err()); } #[test] @@ -541,7 +570,7 @@ mod tests { // Change the proof and expect verification to fail let mut bad_proof = proof.clone(); - bad_proof.comms[0] = (bad_proof.comms[0] + bad_proof.comms[1]).to_affine(); + bad_proof.comms[0] = (bad_proof.comms[0] + bad_proof.comms[0] * Fr::from(123)).to_affine(); let mut verifier_transcript2 = Keccak256Transcript::::new(b"TestEval"); assert!(EvaluationEngine::::verify( &vk,