From 2700848563e38db86e02e5f7e1eb5cbed5a7c008 Mon Sep 17 00:00:00 2001 From: Artem Storozhuk Date: Mon, 12 Feb 2024 19:35:21 +0200 Subject: [PATCH 1/4] feat: Initial experiments with Solidity templating in Rust (IPA) --- Cargo.toml | 4 ++ src/provider/ipa_pc.rs | 110 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 78ba2ccca..f509d7087 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,10 @@ derive_more = "0.99.17" # lightens impl macros for pasta static_assertions = "1.1.0" rayon-scan = "0.1.0" +handlebars = "5.1.0" +serde_json = "1.0.1" + + [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] # grumpkin-msm has been patched to support MSMs for the pasta curve cycle # see: https://github.com/lurk-lab/grumpkin-msm/pull/3 diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index f0a9df871..30f7dd446 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -404,6 +404,116 @@ mod test { use crate::provider::util::test_utils::prove_verify_from_num_vars; use crate::provider::GrumpkinEngine; + use handlebars::Handlebars; + use serde_json::json; + + static TEMPLATE: &'static 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_2_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; +} +} +"; + + #[test] + fn test_ipa_debug() { + //prove_verify_from_num_vars::<_, EvaluationEngine>(2); + + let mut reg = Handlebars::new(); + reg + .register_template_string("ipa.t.sol", TEMPLATE) + .expect("can't register template"); + + println!( + "{}", + reg + .render( + "ipa.t.sol", + &json!( + { + "ck_v": [ + {"i": "0", "x": "0x15afa1c1de43e186ee615ee76389d1ca9de572d426869ab062a03f1ba65808a2", "y": "0x28d6d43cb5ba89778111ceaa56cb8bf2c34a5fb6013988513d5798a60846d423"}, + {"i": "1", "x": "0x132126b357d7299c5c18e04cbe13c4206b763dbc56a8d19900270cd0c59f3981", "y": "0x169077205c0ed8e9f2738a9f04d064e17c457a531a93e9ec5131e35d587cd381"}, + {"i": "2", "x": "0x20c9d6e3d55f0142ce09b6d1cd8b86c8eaecf8f204bce4c9b88a75c720e34b74", "y": "0x227f66a87a7649e8a76a2314e14c0c44877e1eca54015d5ecd8b1da08ccbb779"}, + {"i": "3", "x": "0x1300fe5112d72be0b65d1d365f294a136df15671e4f56e2fbf65be2ffec64e4f", "y": "0x0c93e3b91eeead0adf19f228e2a361b3b6055d1b89e699196c6a5550be5824b9"}, + ], + "ck_s": [ + {"i": "0", "x": "0x2e8facd7beb3da0e505fa1e33ee77b0b19fa1dfc1c5e04537cda07bf56cc248b", "y": "0x11a32df7bf180b18e526371ee2e21bb42ee2d9a7ac875f0816be6effda4e3dfb"}, + ], + "point": [ + {"i": "0", "val": "0x1fe29a0b699fa3cbc723126c4ad0e4a5f410c5f699f3599e92c4f0e99c1abd97"}, + {"i": "1", "val": "0x0ed4861fc966ff194c23744c2e6f63139211dc3550a28a9c8e0979427ff9c677"}, + ], + "L_vec": [ + {"i": "0", "x": "0x1aedd46eb53cfded07f7c3710015340b8cb21983fe71d24f0e7d9f5ab4854e2d", "y": "0x06d42154bbf58e193faa5443312aa938c3fc88648f1a0912d890ea1f7edc3ade"}, + {"i": "1", "x": "0x1c95cbc06044e13eca63f164a8d2dbd3bfc7ed470dd244154e2ae5f83592b649", "y": "0x0abde1d3428cfe8b21442f486b010f14042f5d84b54a811d06307104c4755a2c"}, + ], + "R_vec": [ + {"i": "0", "x": "0x2f1727ea1ac3c3862caa797261db6a9b0714f7d8e65adb97e5f4da457044ccfe", "y": "0x185e59b83d3e903a804f6dcfd68a3e34b5cb9d048aca562e7e89c77b5c7db13e"}, + {"i": "1", "x": "0x08adac48b78bbb3435da3efc7162332b5693f5db927e184c0d1faaeaaf60fdbd", "y": "0x1770ed9ec1f5ed7815a86ec6a5acc1b66d6c89d9bbbb53a2663ce292f7fe48b0"}, + ], + "a_hat": "0x144237bc694bfa4f625dab1f8bfc854e3e7b9a612027e16bcd840383d088e190", + "commitment_x": "0x1e7268591a2b38be3ff689fe1eb31600f9161a2163a08ee9842d458ac0bddf05", + "commitment_y": "0x1f3070c0592c3f0135e1aba5100d43785490023f9536025b119bf9c0f96d5281", + "eval": "0x2514662a7e8e9a7a4ab7ea7c8e6a3423e7a47fca5105e6f3264d20d88e6d33bf", + } + ) + ) + .expect("can't render") + ); + } + #[test] fn test_multiple_polynomial_size() { for num_vars in [4, 5, 6] { From 3a384d7dfb953e035ec261ddbcd92bdeb16ed4f1 Mon Sep 17 00:00:00 2001 From: Artem Storozhuk Date: Thu, 22 Feb 2024 17:18:36 +0200 Subject: [PATCH 2/4] feat: Dynamic compatibility uni-test for IPA --- src/provider/ipa_pc.rs | 98 +++++++++++++++++------------- src/provider/pedersen.rs | 4 +- src/provider/util/mod.rs | 125 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 182 insertions(+), 45 deletions(-) diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 30f7dd446..36aab9edc 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -401,13 +401,20 @@ where #[cfg(test)] mod test { use crate::provider::ipa_pc::EvaluationEngine; + use crate::provider::util::solidity_compatibility_utils::{ + ec_points_to_json, field_elements_to_json, generate_pcs_solidity_unit_test_data, + }; use crate::provider::util::test_utils::prove_verify_from_num_vars; + 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 TEMPLATE: &'static str = " + static IPA_COMPATIBILITY_UNIT_TESTING_TEMPLATE: &str = " // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.16; import \"@std/Test.sol\"; @@ -443,7 +450,7 @@ uint256 eval = {{ eval }}; return InnerProductArgument.IpaInputGrumpkin(ck_v, ck_s, point, L_vec, R_vec, commitment, eval, a_hat); } -function testIpaGrumpkinVerification_2_Variables() public { +function testIpaGrumpkinVerification_{{ num_vars }}_Variables() public { InnerProductArgument.IpaInputGrumpkin memory input = composeIpaInput(); assertTrue(InnerProductArgument.verifyGrumpkin(input, getTranscript())); } @@ -466,52 +473,59 @@ return keccak_transcript; } "; + // To generate Solidity unit-test: + // cargo test test_solidity_compatibility_ipa --release -- --ignored --nocapture > ipa.t.sol #[test] - fn test_ipa_debug() { - //prove_verify_from_num_vars::<_, EvaluationEngine>(2); + #[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", TEMPLATE) + .register_template_string("ipa.t.sol", IPA_COMPATIBILITY_UNIT_TESTING_TEMPLATE) .expect("can't register template"); - println!( - "{}", - reg - .render( - "ipa.t.sol", - &json!( - { - "ck_v": [ - {"i": "0", "x": "0x15afa1c1de43e186ee615ee76389d1ca9de572d426869ab062a03f1ba65808a2", "y": "0x28d6d43cb5ba89778111ceaa56cb8bf2c34a5fb6013988513d5798a60846d423"}, - {"i": "1", "x": "0x132126b357d7299c5c18e04cbe13c4206b763dbc56a8d19900270cd0c59f3981", "y": "0x169077205c0ed8e9f2738a9f04d064e17c457a531a93e9ec5131e35d587cd381"}, - {"i": "2", "x": "0x20c9d6e3d55f0142ce09b6d1cd8b86c8eaecf8f204bce4c9b88a75c720e34b74", "y": "0x227f66a87a7649e8a76a2314e14c0c44877e1eca54015d5ecd8b1da08ccbb779"}, - {"i": "3", "x": "0x1300fe5112d72be0b65d1d365f294a136df15671e4f56e2fbf65be2ffec64e4f", "y": "0x0c93e3b91eeead0adf19f228e2a361b3b6055d1b89e699196c6a5550be5824b9"}, - ], - "ck_s": [ - {"i": "0", "x": "0x2e8facd7beb3da0e505fa1e33ee77b0b19fa1dfc1c5e04537cda07bf56cc248b", "y": "0x11a32df7bf180b18e526371ee2e21bb42ee2d9a7ac875f0816be6effda4e3dfb"}, - ], - "point": [ - {"i": "0", "val": "0x1fe29a0b699fa3cbc723126c4ad0e4a5f410c5f699f3599e92c4f0e99c1abd97"}, - {"i": "1", "val": "0x0ed4861fc966ff194c23744c2e6f63139211dc3550a28a9c8e0979427ff9c677"}, - ], - "L_vec": [ - {"i": "0", "x": "0x1aedd46eb53cfded07f7c3710015340b8cb21983fe71d24f0e7d9f5ab4854e2d", "y": "0x06d42154bbf58e193faa5443312aa938c3fc88648f1a0912d890ea1f7edc3ade"}, - {"i": "1", "x": "0x1c95cbc06044e13eca63f164a8d2dbd3bfc7ed470dd244154e2ae5f83592b649", "y": "0x0abde1d3428cfe8b21442f486b010f14042f5d84b54a811d06307104c4755a2c"}, - ], - "R_vec": [ - {"i": "0", "x": "0x2f1727ea1ac3c3862caa797261db6a9b0714f7d8e65adb97e5f4da457044ccfe", "y": "0x185e59b83d3e903a804f6dcfd68a3e34b5cb9d048aca562e7e89c77b5c7db13e"}, - {"i": "1", "x": "0x08adac48b78bbb3435da3efc7162332b5693f5db927e184c0d1faaeaaf60fdbd", "y": "0x1770ed9ec1f5ed7815a86ec6a5acc1b66d6c89d9bbbb53a2663ce292f7fe48b0"}, - ], - "a_hat": "0x144237bc694bfa4f625dab1f8bfc854e3e7b9a612027e16bcd840383d088e190", - "commitment_x": "0x1e7268591a2b38be3ff689fe1eb31600f9161a2163a08ee9842d458ac0bddf05", - "commitment_y": "0x1f3070c0592c3f0135e1aba5100d43785490023f9536025b119bf9c0f96d5281", - "eval": "0x2514662a7e8e9a7a4ab7ea7c8e6a3423e7a47fca5105e6f3264d20d88e6d33bf", - } - ) - ) - .expect("can't render") - ); + let solidity_unit_test_source = reg.render("ipa.t.sol", &json!(map)).expect("can't render"); + println!("{}", solidity_unit_test_source); } #[test] diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index be16b3f24..83669655c 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(crate) 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/util/mod.rs b/src/provider/util/mod.rs index d097ffe4f..0d07304ac 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, ) -> ( @@ -205,3 +205,126 @@ pub mod test_utils { } } } + +#[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 + } +} From 2d189aaf2269c85e46f40f938ed87972a78726fd Mon Sep 17 00:00:00 2001 From: Artem Storozhuk Date: Fri, 23 Feb 2024 17:04:12 +0200 Subject: [PATCH 3/4] chore: Apply suggestions --- Cargo.toml | 6 ++---- src/provider/pedersen.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f509d7087..ef6bf8523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,6 @@ derive_more = "0.99.17" # lightens impl macros for pasta static_assertions = "1.1.0" rayon-scan = "0.1.0" -handlebars = "5.1.0" -serde_json = "1.0.1" - - [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] # grumpkin-msm has been patched to support MSMs for the pasta curve cycle # see: https://github.com/lurk-lab/grumpkin-msm/pull/3 @@ -76,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/pedersen.rs b/src/provider/pedersen.rs index 83669655c..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]>)] - pub(crate) ck: Vec<::Affine>, + pub(in crate::provider) ck: Vec<::Affine>, } impl Len for CommitmentKey From b7f6d5fc517735ac846726df8169850ac6ca1ccb Mon Sep 17 00:00:00 2001 From: Artem Storozhuk Date: Wed, 28 Feb 2024 17:21:50 +0200 Subject: [PATCH 4/4] chore: Move solidity-specific unit-test for IPA into separate module --- src/provider/ipa_pc.rs | 134 ++--------------------------------- src/provider/mod.rs | 3 +- src/provider/tests/ipa_pc.rs | 129 +++++++++++++++++++++++++++++++++ src/provider/tests/mod.rs | 124 ++++++++++++++++++++++++++++++++ src/provider/util/mod.rs | 123 -------------------------------- 5 files changed, 260 insertions(+), 253 deletions(-) create mode 100644 src/provider/tests/ipa_pc.rs create mode 100644 src/provider/tests/mod.rs diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 36aab9edc..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 @@ -401,132 +401,8 @@ where #[cfg(test)] mod test { use crate::provider::ipa_pc::EvaluationEngine; - use crate::provider::util::solidity_compatibility_utils::{ - ec_points_to_json, field_elements_to_json, generate_pcs_solidity_unit_test_data, - }; use crate::provider::util::test_utils::prove_verify_from_num_vars; - 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); - } #[test] fn test_multiple_polynomial_size() { 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/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 0d07304ac..c2d3483ae 100644 --- a/src/provider/util/mod.rs +++ b/src/provider/util/mod.rs @@ -205,126 +205,3 @@ pub mod test_utils { } } } - -#[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 - } -}