diff --git a/Cargo.toml b/Cargo.toml index 78ba2ccca..ef6bf8523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,8 @@ anyhow = "1.0.72" tap = "1.0.1" tracing-texray = "0.2.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +handlebars = "5.1.0" +serde_json = "1.0.1" [build-dependencies] vergen = { version = "8", features = ["build", "git", "gitcl"] } diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index f0a9df871..fb2f4b195 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -28,8 +28,8 @@ pub struct ProverKey { #[derive(Debug, Serialize)] #[serde(bound = "")] pub struct VerifierKey { - ck_v: Arc>, - ck_s: CommitmentKey, + pub(in crate::provider) ck_v: Arc>, + pub(in crate::provider) ck_s: CommitmentKey, } impl SimpleDigestible for VerifierKey {} @@ -149,9 +149,9 @@ impl InnerProductWitness { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] pub struct InnerProductArgument { - L_vec: Vec>, - R_vec: Vec>, - a_hat: E::Scalar, + pub(in crate::provider) L_vec: Vec>, + pub(in crate::provider) R_vec: Vec>, + pub(in crate::provider) a_hat: E::Scalar, } impl InnerProductArgument diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 87ca35203..12f82ae46 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -19,6 +19,7 @@ pub(crate) mod util; // crate-private modules mod keccak; +mod tests; use crate::{ provider::{ @@ -167,7 +168,7 @@ impl CurveCycleEquipped for PallasEngine { } #[cfg(test)] -mod tests { +mod test { use crate::provider::{ bn256_grumpkin::{bn256, grumpkin}, secp_secq::{secp256k1, secq256k1}, diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index be16b3f24..cb2a68f69 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -33,7 +33,7 @@ where // this is a hack; we just assume the size of the element. // Look for the static assertions in provider macros for a justification #[abomonate_with(Vec<[u64; 8]>)] - ck: Vec<::Affine>, + pub(in crate::provider) ck: Vec<::Affine>, } impl Len for CommitmentKey @@ -65,7 +65,7 @@ where E: Engine, E::GE: DlogGroup, { - comm: ::Compressed, + pub(crate) comm: ::Compressed, } impl CommitmentTrait for Commitment diff --git a/src/provider/tests/ipa_pc.rs b/src/provider/tests/ipa_pc.rs new file mode 100644 index 000000000..bc61206dd --- /dev/null +++ b/src/provider/tests/ipa_pc.rs @@ -0,0 +1,129 @@ +#[cfg(test)] +mod test { + use crate::provider::ipa_pc::EvaluationEngine; + use crate::provider::tests::solidity_compatibility_utils::{ + ec_points_to_json, field_elements_to_json, generate_pcs_solidity_unit_test_data, + }; + + use crate::provider::GrumpkinEngine; + use group::Curve; + + use crate::provider::pedersen::{CommitmentKey, CommitmentKeyExtTrait}; + use handlebars::Handlebars; + use serde_json::json; + use serde_json::{Map, Value}; + + static IPA_COMPATIBILITY_UNIT_TESTING_TEMPLATE: &str = " +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.16; +import \"@std/Test.sol\"; +import \"src/blocks/grumpkin/Grumpkin.sol\"; +import \"src/blocks/EqPolynomial.sol\"; +import \"src/Utilities.sol\"; +import \"src/blocks/IpaPcs.sol\"; + +contract IpaTest is Test { +function composeIpaInput() public pure returns (InnerProductArgument.IpaInputGrumpkin memory) { +Grumpkin.GrumpkinAffinePoint[] memory ck_v = new Grumpkin.GrumpkinAffinePoint[]({{ len ck_v }}); +{{ #each ck_v }} ck_v[{{ i }}]=Grumpkin.GrumpkinAffinePoint({{ x }}, {{y}});\n {{ /each }} + +Grumpkin.GrumpkinAffinePoint[] memory ck_s = new Grumpkin.GrumpkinAffinePoint[]({{ len ck_s }}); +{{ #each ck_s }} ck_s[{{ i }}]=Grumpkin.GrumpkinAffinePoint({{ x }}, {{y}});\n {{ /each }} + +uint256[] memory point = new uint256[]({{ len point }}); +{{ #each point }} point[{{ i }}]={{ val }};\n {{ /each }} + +Grumpkin.GrumpkinAffinePoint[] memory L_vec = new Grumpkin.GrumpkinAffinePoint[]({{ len L_vec }}); +{{ #each L_vec }} L_vec[{{ i }}]=Grumpkin.GrumpkinAffinePoint({{ x }}, {{y}});\n {{ /each }} + +Grumpkin.GrumpkinAffinePoint[] memory R_vec = new Grumpkin.GrumpkinAffinePoint[]({{ len R_vec }}); +{{ #each R_vec }} R_vec[{{ i }}]=Grumpkin.GrumpkinAffinePoint({{ x }}, {{y}});\n {{ /each }} + +uint256 a_hat = {{ a_hat }}; + +// InnerProductInstance +Grumpkin.GrumpkinAffinePoint memory commitment = Grumpkin.GrumpkinAffinePoint({{ commitment_x }}, {{ commitment_y }}); + +uint256 eval = {{ eval }}; + +return InnerProductArgument.IpaInputGrumpkin(ck_v, ck_s, point, L_vec, R_vec, commitment, eval, a_hat); +} + +function testIpaGrumpkinVerification_{{ num_vars }}_Variables() public { +InnerProductArgument.IpaInputGrumpkin memory input = composeIpaInput(); +assertTrue(InnerProductArgument.verifyGrumpkin(input, getTranscript())); +} + +function getTranscript() public pure returns (KeccakTranscriptLib.KeccakTranscript memory) { +// b\"TestEval\" in Rust +uint8[] memory label = new uint8[](8); +label[0] = 0x54; +label[1] = 0x65; +label[2] = 0x73; +label[3] = 0x74; +label[4] = 0x45; +label[5] = 0x76; +label[6] = 0x61; +label[7] = 0x6c; + +KeccakTranscriptLib.KeccakTranscript memory keccak_transcript = KeccakTranscriptLib.instantiate(label); +return keccak_transcript; +} +} +"; + + // To generate Solidity unit-test: + // cargo test test_solidity_compatibility_ipa --release -- --ignored --nocapture > ipa.t.sol + #[test] + #[ignore] + fn test_solidity_compatibility_ipa() { + let num_vars = 2; + + // Secondary part of verification is IPA over Grumpkin + let (commitment, point, eval, proof, vk) = + generate_pcs_solidity_unit_test_data::<_, EvaluationEngine>(num_vars); + + let num_vars_string = format!("{}", num_vars); + let eval_string = format!("{:?}", eval); + let commitment_x_string = format!("{:?}", commitment.comm.to_affine().x); + let commitment_y_string = format!("{:?}", commitment.comm.to_affine().y); + let proof_a_hat_string = format!("{:?}", proof.a_hat); + + let r_vec = CommitmentKey::::reinterpret_commitments_as_ck(&proof.R_vec) + .expect("can't reinterpred R_vec"); + let l_vec = CommitmentKey::::reinterpret_commitments_as_ck(&proof.L_vec) + .expect("can't reinterpred L_vec"); + + let r_vec_array = ec_points_to_json::(&r_vec.ck); + let l_vec_array = ec_points_to_json::(&l_vec.ck); + let point_array = field_elements_to_json::(&point); + let ckv_array = ec_points_to_json::(&vk.ck_v.ck); + let cks_array = ec_points_to_json::(&vk.ck_s.ck); + + let mut map = Map::new(); + map.insert("num_vars".to_string(), Value::String(num_vars_string)); + map.insert("eval".to_string(), Value::String(eval_string)); + map.insert( + "commitment_x".to_string(), + Value::String(commitment_x_string), + ); + map.insert( + "commitment_y".to_string(), + Value::String(commitment_y_string), + ); + map.insert("R_vec".to_string(), Value::Array(r_vec_array)); + map.insert("L_vec".to_string(), Value::Array(l_vec_array)); + map.insert("a_hat".to_string(), Value::String(proof_a_hat_string)); + map.insert("point".to_string(), Value::Array(point_array)); + map.insert("ck_v".to_string(), Value::Array(ckv_array)); + map.insert("ck_s".to_string(), Value::Array(cks_array)); + + let mut reg = Handlebars::new(); + reg + .register_template_string("ipa.t.sol", IPA_COMPATIBILITY_UNIT_TESTING_TEMPLATE) + .expect("can't register template"); + + let solidity_unit_test_source = reg.render("ipa.t.sol", &json!(map)).expect("can't render"); + println!("{}", solidity_unit_test_source); + } +} diff --git a/src/provider/tests/mod.rs b/src/provider/tests/mod.rs new file mode 100644 index 000000000..39fafa52a --- /dev/null +++ b/src/provider/tests/mod.rs @@ -0,0 +1,124 @@ +mod ipa_pc; + +#[cfg(test)] +pub mod solidity_compatibility_utils { + use crate::provider::traits::DlogGroup; + use crate::spartan::polys::multilinear::MultilinearPolynomial; + use crate::traits::{ + commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait, Engine, + }; + use group::prime::PrimeCurve; + use group::prime::PrimeCurveAffine; + use rand::rngs::StdRng; + use serde_json::{Map, Value}; + use std::sync::Arc; + + pub(crate) fn generate_pcs_solidity_unit_test_data>( + num_vars: usize, + ) -> ( + >::Commitment, + Vec, + E::Scalar, + EE::EvaluationArgument, + EE::VerifierKey, + ) { + use rand_core::SeedableRng; + + let mut rng = rand::rngs::StdRng::seed_from_u64(num_vars as u64); + + let (poly, point, eval) = + crate::provider::util::test_utils::random_poly_with_eval::(num_vars, &mut rng); + + // Mock commitment key. + let ck = E::CE::setup(b"test", 1 << num_vars); + let ck_arc = Arc::new(ck.clone()); + // Commits to the provided vector using the provided generators. + let commitment = E::CE::commit(&ck_arc, poly.evaluations()); + + let (proof, vk) = prove_verify_solidity::(ck_arc, &commitment, &poly, &point, &eval); + + (commitment, point, eval, proof, vk) + } + + fn prove_verify_solidity>( + ck: Arc<<::CE as CommitmentEngineTrait>::CommitmentKey>, + commitment: &<::CE as CommitmentEngineTrait>::Commitment, + poly: &MultilinearPolynomial<::Scalar>, + point: &[::Scalar], + eval: &::Scalar, + ) -> (EE::EvaluationArgument, EE::VerifierKey) { + use crate::traits::TranscriptEngineTrait; + + // Generate Prover and verifier key for given commitment key. + let ock = ck.clone(); + let (prover_key, verifier_key) = EE::setup(ck); + + // Generate proof. + let mut prover_transcript = E::TE::new(b"TestEval"); + let proof: EE::EvaluationArgument = EE::prove( + &*ock, + &prover_key, + &mut prover_transcript, + commitment, + poly.evaluations(), + point, + eval, + ) + .unwrap(); + let pcp = prover_transcript.squeeze(b"c").unwrap(); + + // Verify proof. + let mut verifier_transcript = E::TE::new(b"TestEval"); + EE::verify( + &verifier_key, + &mut verifier_transcript, + commitment, + point, + eval, + &proof, + ) + .unwrap(); + let pcv = verifier_transcript.squeeze(b"c").unwrap(); + + // Check if the prover transcript and verifier transcript are kept in the same state. + assert_eq!(pcp, pcv); + + (proof, verifier_key) + } + + pub(crate) fn field_elements_to_json(field_elements: &[E::Scalar]) -> Vec { + let mut value_vector = vec![]; + field_elements.iter().enumerate().for_each(|(i, fe)| { + let mut value = Map::new(); + value.insert("i".to_string(), Value::String(i.to_string())); + value.insert("val".to_string(), Value::String(format!("{:?}", fe))); + value_vector.push(Value::Object(value)); + }); + value_vector + } + + pub(crate) fn ec_points_to_json(ec_points: &[::Affine]) -> Vec + where + E: Engine, + E::GE: DlogGroup, + { + let mut value_vector = vec![]; + ec_points.iter().enumerate().for_each(|(i, ec_point)| { + let mut value = Map::new(); + let coordinates_info = ec_point.to_curve().to_coordinates(); + let not_infinity = !coordinates_info.2; + assert!(not_infinity); + value.insert("i".to_string(), Value::String(i.to_string())); + value.insert( + "x".to_string(), + Value::String(format!("{:?}", coordinates_info.0)), + ); + value.insert( + "y".to_string(), + Value::String(format!("{:?}", coordinates_info.1)), + ); + value_vector.push(Value::Object(value)); + }); + value_vector + } +} diff --git a/src/provider/util/mod.rs b/src/provider/util/mod.rs index d097ffe4f..c2d3483ae 100644 --- a/src/provider/util/mod.rs +++ b/src/provider/util/mod.rs @@ -94,7 +94,7 @@ pub mod test_utils { use std::sync::Arc; /// Returns a random polynomial, a point and calculate its evaluation. - fn random_poly_with_eval( + pub(crate) fn random_poly_with_eval( num_vars: usize, mut rng: &mut R, ) -> (