Skip to content

Commit

Permalink
IPA Solidity unit-test generator (#341)
Browse files Browse the repository at this point in the history
* 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
storojs72 authored Mar 4, 2024
1 parent 4cb69de commit 59caba3
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
10 changes: 5 additions & 5 deletions src/provider/ipa_pc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pub struct ProverKey<E: Engine> {
#[derive(Debug, Serialize)]
#[serde(bound = "")]
pub struct VerifierKey<E: Engine> {
ck_v: Arc<CommitmentKey<E>>,
ck_s: CommitmentKey<E>,
pub(in crate::provider) ck_v: Arc<CommitmentKey<E>>,
pub(in crate::provider) ck_s: CommitmentKey<E>,
}

impl<E: Engine> SimpleDigestible for VerifierKey<E> {}
Expand Down Expand Up @@ -149,9 +149,9 @@ impl<E: Engine> InnerProductWitness<E> {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct InnerProductArgument<E: Engine> {
L_vec: Vec<CompressedCommitment<E>>,
R_vec: Vec<CompressedCommitment<E>>,
a_hat: E::Scalar,
pub(in crate::provider) L_vec: Vec<CompressedCommitment<E>>,
pub(in crate::provider) R_vec: Vec<CompressedCommitment<E>>,
pub(in crate::provider) a_hat: E::Scalar,
}

impl<E> InnerProductArgument<E>
Expand Down
3 changes: 2 additions & 1 deletion src/provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) mod util;

// crate-private modules
mod keccak;
mod tests;

use crate::{
provider::{
Expand Down Expand Up @@ -166,7 +167,7 @@ impl CurveCycleEquipped for PallasEngine {
}

#[cfg(test)]
mod tests {
mod test {
use crate::provider::{
bn256_grumpkin::{bn256, grumpkin},
secp_secq::{secp256k1, secq256k1},
Expand Down
4 changes: 2 additions & 2 deletions src/provider/pedersen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<<E::GE as PrimeCurve>::Affine>,
pub(in crate::provider) ck: Vec<<E::GE as PrimeCurve>::Affine>,
}

impl<E> Len for CommitmentKey<E>
Expand Down Expand Up @@ -65,7 +65,7 @@ where
E: Engine,
E::GE: DlogGroup<ScalarExt = E::Scalar>,
{
comm: <E::GE as DlogGroup>::Compressed,
pub(crate) comm: <E::GE as DlogGroup>::Compressed,
}

impl<E> CommitmentTrait<E> for Commitment<E>
Expand Down
129 changes: 129 additions & 0 deletions src/provider/tests/ipa_pc.rs
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);
}
}
124 changes: 124 additions & 0 deletions src/provider/tests/mod.rs
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
}
}
2 changes: 1 addition & 1 deletion src/provider/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub mod test_utils {
use std::sync::Arc;

/// Returns a random polynomial, a point and calculate its evaluation.
fn random_poly_with_eval<E: Engine, R: RngCore + CryptoRng>(
pub(crate) fn random_poly_with_eval<E: Engine, R: RngCore + CryptoRng>(
num_vars: usize,
mut rng: &mut R,
) -> (
Expand Down

1 comment on commit 59caba3

@github-actions
Copy link
Contributor

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

Please sign in to comment.