Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPA Solidity unit-test generator #341

Merged
merged 4 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
124 changes: 124 additions & 0 deletions src/provider/ipa_pc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,132 @@ 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<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);
}

#[test]
fn test_multiple_polynomial_size() {
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
125 changes: 124 additions & 1 deletion src/provider/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 Expand Up @@ -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<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
}
}