-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
IPA Solidity unit-test generator (#341)
* feat: Initial experiments with Solidity templating in Rust (IPA) * feat: Dynamic compatibility uni-test for IPA * chore: Apply suggestions * chore: Move solidity-specific unit-test for IPA into separate module
- Loading branch information
Showing
7 changed files
with
265 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<GrumpkinEngine>>(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::<GrumpkinEngine>::reinterpret_commitments_as_ck(&proof.R_vec) | ||
.expect("can't reinterpred R_vec"); | ||
let l_vec = CommitmentKey::<GrumpkinEngine>::reinterpret_commitments_as_ck(&proof.L_vec) | ||
.expect("can't reinterpred L_vec"); | ||
|
||
let r_vec_array = ec_points_to_json::<GrumpkinEngine>(&r_vec.ck); | ||
let l_vec_array = ec_points_to_json::<GrumpkinEngine>(&l_vec.ck); | ||
let point_array = field_elements_to_json::<GrumpkinEngine>(&point); | ||
let ckv_array = ec_points_to_json::<GrumpkinEngine>(&vk.ck_v.ck); | ||
let cks_array = ec_points_to_json::<GrumpkinEngine>(&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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<E: Engine, EE: EvaluationEngineTrait<E>>( | ||
num_vars: usize, | ||
) -> ( | ||
<E::CE as CommitmentEngineTrait<E>>::Commitment, | ||
Vec<E::Scalar>, | ||
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::<E, StdRng>(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::<E, EE>(ck_arc, &commitment, &poly, &point, &eval); | ||
|
||
(commitment, point, eval, proof, vk) | ||
} | ||
|
||
fn prove_verify_solidity<E: Engine, EE: EvaluationEngineTrait<E>>( | ||
ck: Arc<<<E as Engine>::CE as CommitmentEngineTrait<E>>::CommitmentKey>, | ||
commitment: &<<E as Engine>::CE as CommitmentEngineTrait<E>>::Commitment, | ||
poly: &MultilinearPolynomial<<E as Engine>::Scalar>, | ||
point: &[<E as Engine>::Scalar], | ||
eval: &<E as Engine>::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<E: Engine>(field_elements: &[E::Scalar]) -> Vec<Value> { | ||
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<E>(ec_points: &[<E::GE as PrimeCurve>::Affine]) -> Vec<Value> | ||
where | ||
E: Engine, | ||
E::GE: DlogGroup<ScalarExt = E::Scalar>, | ||
{ | ||
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59caba3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Benchmarks
Table of Contents
Overview
This benchmark report shows the Arecibo GPU benchmarks.
NVIDIA L4
Intel(R) Xeon(R) CPU @ 2.20GHz
32 vCPUs
125 GB RAM
Workflow run: https://github.com/lurk-lab/arecibo/actions/runs/8146803478
Benchmark Results
RecursiveSNARK-NIVC-2
ref=4cb69de
ref=59caba3
Prove-NumCons-6540
44.23 ms
(✅ 1.00x)44.30 ms
(✅ 1.00x slower)Verify-NumCons-6540
34.50 ms
(✅ 1.00x)34.29 ms
(✅ 1.01x faster)Prove-NumCons-1028888
317.65 ms
(✅ 1.00x)318.31 ms
(✅ 1.00x slower)Verify-NumCons-1028888
249.07 ms
(✅ 1.00x)249.73 ms
(✅ 1.00x slower)CompressedSNARK-NIVC-Commitments-2
ref=4cb69de
ref=59caba3
Prove-NumCons-6540
10.76 s
(✅ 1.00x)10.78 s
(✅ 1.00x slower)Verify-NumCons-6540
50.28 ms
(✅ 1.00x)50.72 ms
(✅ 1.01x slower)Prove-NumCons-1028888
53.49 s
(✅ 1.00x)53.48 s
(✅ 1.00x faster)Verify-NumCons-1028888
50.77 ms
(✅ 1.00x)50.64 ms
(✅ 1.00x faster)Made with criterion-table