Skip to content

Commit

Permalink
Groth16 solidity verifier (0xEigenLabs#224)
Browse files Browse the repository at this point in the history
* feat: groth16 solidity verifier

* fix: build err

* chore: code review

* feat: add groth16 verifier test

* chore: add groth16 verifier test script

* chore: snark_verifier.sh

* chore: ci clippy & RustFmt fix

* chore: code polish

* chore: rm solidity verifier

* chore: use --grep to assign different test case

* fix: update test_single.sh

---------

Co-authored-by: Stephen <[email protected]>
  • Loading branch information
JangoCCC and eigmax authored Mar 7, 2024
1 parent cfed713 commit 810ed8a
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 18 deletions.
1 change: 1 addition & 0 deletions groth16/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ num = { version = "0.4.0" }
algebraic = { path = "../algebraic", default-features=false }
rand = "0.4"
anyhow = "1.0.79"
regex = "0.2"

profiler_macro = {git = "https://github.com/ChengYueJia/profiler-rs", rev = "6d6a77f8"}
ark-std = { version = "0.4.0", optional = true }
Expand Down
282 changes: 282 additions & 0 deletions groth16/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
},
groth16::Groth16,
json_utils::*,
template,
};
use algebraic::{
bellman_ce::Engine,
Expand All @@ -18,6 +19,8 @@ use algebraic::{
};
use anyhow::{anyhow, bail, Result};
use num_traits::Zero;
use regex::Regex;
use template::CONTRACT_TEMPLATE;

pub fn groth16_setup(
curve_type: &str,
Expand Down Expand Up @@ -149,6 +152,118 @@ pub fn groth16_verify(
Ok(())
}

// Acknowledgement: The Solidity verifier template was modified from ZoKrates implementation.
pub fn generate_verifier(vk_file_path: &str, sol_file_path: &str) -> Result<()> {
let json_data = std::fs::read_to_string(vk_file_path)?;
let vk_file: VerifyingKeyFile =
serde_json::from_str(&json_data).expect("Error during deserialization of the JSON data");

let vk_alpha = vk_file.alpha_g1.to_string();
let vk_beta = vk_file.beta_g2.to_string();
let vk_gamma = vk_file.gamma_g2.to_string();
let vk_delta = vk_file.delta_g2.to_string();
let vk_gamma_abc = vk_file.ic;

let (mut template_text, solidity_pairing_lib_sans_bn256g2) =
(String::from(CONTRACT_TEMPLATE), solidity_pairing_lib(false));

let vk_regex = Regex::new(r#"(<%vk_[^i%]*%>)"#).unwrap();
let vk_gamma_abc_len_regex = Regex::new(r#"(<%vk_gamma_abc_length%>)"#).unwrap();
let vk_gamma_abc_repeat_regex = Regex::new(r#"(<%vk_gamma_abc_pts%>)"#).unwrap();
let vk_input_len_regex = Regex::new(r#"(<%vk_input_length%>)"#).unwrap();
let input_loop = Regex::new(r#"(<%input_loop%>)"#).unwrap();
let input_argument = Regex::new(r#"(<%input_argument%>)"#).unwrap();

template_text = vk_regex
.replace(template_text.as_str(), vk_alpha.as_str())
.into_owned();

template_text = vk_regex
.replace(template_text.as_str(), vk_beta.to_string().as_str())
.into_owned();

template_text = vk_regex
.replace(template_text.as_str(), vk_gamma.to_string().as_str())
.into_owned();

template_text = vk_regex
.replace(template_text.as_str(), vk_delta.to_string().as_str())
.into_owned();

let gamma_abc_count: usize = vk_gamma_abc.len();
template_text = vk_gamma_abc_len_regex
.replace(
template_text.as_str(),
format!("{}", gamma_abc_count).as_str(),
)
.into_owned();

template_text = vk_input_len_regex
.replace(
template_text.as_str(),
format!("{}", gamma_abc_count - 1).as_str(),
)
.into_owned();

// feed input values only if there are any
template_text = if gamma_abc_count > 1 {
input_loop.replace(
template_text.as_str(),
r#"
for(uint i = 0; i < input.length; i++){
inputValues[i] = input[i];
}"#,
)
} else {
input_loop.replace(template_text.as_str(), "")
}
.to_string();

// take input values as argument only if there are any
template_text = if gamma_abc_count > 1 {
input_argument.replace(
template_text.as_str(),
format!(", uint[{}] memory input", gamma_abc_count - 1).as_str(),
)
} else {
input_argument.replace(template_text.as_str(), "")
}
.to_string();

let mut gamma_abc_repeat_text = String::new();
for (i, g1) in vk_gamma_abc.iter().enumerate() {
gamma_abc_repeat_text.push_str(
format!(
"vk.gamma_abc[{}] = Pairing.G1Point({});",
i,
g1.to_string().as_str()
)
.as_str(),
);
if i < gamma_abc_count - 1 {
gamma_abc_repeat_text.push_str("\n ");
}
}

template_text = vk_gamma_abc_repeat_regex
.replace(template_text.as_str(), gamma_abc_repeat_text.as_str())
.into_owned();

let re = Regex::new(r"(?P<v>0[xX][0-9a-fA-F]{64})").unwrap();
template_text = re.replace_all(&template_text, "uint256($v)").to_string();

match std::fs::write(
sol_file_path,
format!("{}{}", solidity_pairing_lib_sans_bn256g2, template_text),
) {
Ok(()) => println!("Generate solidity verifier successfully!"),
Err(e) => {
bail!("write sol file failed, {:?}", e)
}
}
Ok(())
}

fn create_circuit_from_file<E: Engine>(
circuit_file: &str,
witness: Option<Vec<E::Fr>>,
Expand Down Expand Up @@ -197,3 +312,170 @@ fn write_pk_vk_to_files<P: Parser>(
std::fs::write(vk_file, vk_json)?;
Ok(())
}

fn solidity_pairing_lib(with_g2_addition: bool) -> String {
let pairing_lib_beginning = r#"// This file is MIT Licensed.
//
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pragma solidity ^0.8.16;
library Pairing {
struct G1Point {
uint X;
uint Y;
}
// Encoding of field elements is: X[0] * z + X[1]
struct G2Point {
uint[2] X;
uint[2] Y;
}
/// @return the generator of G1
function P1() pure internal returns (G1Point memory) {
return G1Point(1, 2);
}
/// @return the generator of G2
function P2() pure internal returns (G2Point memory) {
return G2Point(
[10857046999023057135944570762232829481370756359578518086990519993285655852781,
11559732032986387107991004021392285783925812861821192530917403151452391805634],
[8495653923123431417604973247489272438418190587263600148770280649306958101930,
4082367875863433681332203403145435568316851327593401208105741076214120093531]
);
}
/// @return the negation of p, i.e. p.addition(p.negate()) should be zero.
function negate(G1Point memory p) pure internal returns (G1Point memory) {
// The prime q in the base field F_q for G1
uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
if (p.X == 0 && p.Y == 0)
return G1Point(0, 0);
return G1Point(p.X, q - (p.Y % q));
}
/// @return r the sum of two points of G1
function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
uint[4] memory input;
input[0] = p1.X;
input[1] = p1.Y;
input[2] = p2.X;
input[3] = p2.Y;
bool success;
assembly {
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require(success);
}
"#;

let pairing_lib_g2_addition = r#"
/// @return r the sum of two points of G2
function addition(G2Point memory p1, G2Point memory p2) internal view returns (G2Point memory r) {
(r.X[0], r.X[1], r.Y[0], r.Y[1]) = BN256G2.ECTwistAdd(p1.X[0],p1.X[1],p1.Y[0],p1.Y[1],p2.X[0],p2.X[1],p2.Y[0],p2.Y[1]);
}
"#;

let pairing_lib_ending = r#"
/// @return r the product of a point on G1 and a scalar, i.e.
/// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
function scalar_mul(G1Point memory p, uint s) internal view returns (G1Point memory r) {
uint[3] memory input;
input[0] = p.X;
input[1] = p.Y;
input[2] = s;
bool success;
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require (success);
}
/// @return the result of computing the pairing check
/// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
/// For example pairing([P1(), P1().negate()], [P2(), P2()]) should
/// return true.
function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) {
require(p1.length == p2.length);
uint elements = p1.length;
uint inputSize = elements * 6;
uint[] memory input = new uint[](inputSize);
for (uint i = 0; i < elements; i++)
{
input[i * 6 + 0] = p1[i].X;
input[i * 6 + 1] = p1[i].Y;
input[i * 6 + 2] = p2[i].X[1];
input[i * 6 + 3] = p2[i].X[0];
input[i * 6 + 4] = p2[i].Y[1];
input[i * 6 + 5] = p2[i].Y[0];
}
uint[1] memory out;
bool success;
assembly {
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
// Use "invalid" to make gas estimation work
switch success case 0 { invalid() }
}
require(success);
return out[0] != 0;
}
/// Convenience method for a pairing check for two pairs.
function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal view returns (bool) {
G1Point[] memory p1 = new G1Point[](2);
G2Point[] memory p2 = new G2Point[](2);
p1[0] = a1;
p1[1] = b1;
p2[0] = a2;
p2[1] = b2;
return pairing(p1, p2);
}
/// Convenience method for a pairing check for three pairs.
function pairingProd3(
G1Point memory a1, G2Point memory a2,
G1Point memory b1, G2Point memory b2,
G1Point memory c1, G2Point memory c2
) internal view returns (bool) {
G1Point[] memory p1 = new G1Point[](3);
G2Point[] memory p2 = new G2Point[](3);
p1[0] = a1;
p1[1] = b1;
p1[2] = c1;
p2[0] = a2;
p2[1] = b2;
p2[2] = c2;
return pairing(p1, p2);
}
/// Convenience method for a pairing check for four pairs.
function pairingProd4(
G1Point memory a1, G2Point memory a2,
G1Point memory b1, G2Point memory b2,
G1Point memory c1, G2Point memory c2,
G1Point memory d1, G2Point memory d2
) internal view returns (bool) {
G1Point[] memory p1 = new G1Point[](4);
G2Point[] memory p2 = new G2Point[](4);
p1[0] = a1;
p1[1] = b1;
p1[2] = c1;
p1[3] = d1;
p2[0] = a2;
p2[1] = b2;
p2[2] = c2;
p2[3] = d2;
return pairing(p1, p2);
}
}
"#;

if !with_g2_addition {
[pairing_lib_beginning, pairing_lib_ending].join("\n")
} else {
[
pairing_lib_beginning,
pairing_lib_g2_addition,
pairing_lib_ending,
]
.join("\n")
}
}
17 changes: 17 additions & 0 deletions groth16/src/json_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use num_bigint::BigUint;
use num_traits::Num;
use serde::{Deserialize, Serialize};
use serde_json::to_string;
use std::fmt;
#[derive(Debug, Serialize, Deserialize)]
pub struct G1 {
pub x: String,
Expand All @@ -25,6 +26,22 @@ pub struct G2 {
pub y: [String; 2],
}

impl fmt::Display for G1 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}, {}", self.x, self.y)
}
}

impl fmt::Display for G2 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"[{}, {}], [{}, {}]",
self.x[0], self.x[1], self.y[0], self.y[1]
)
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct VerifyingKeyFile {
#[serde(rename = "protocol")]
Expand Down
1 change: 1 addition & 0 deletions groth16/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod api;
pub mod groth16;
pub mod json_utils;
mod template;

pub use bellman_ce::pairing::ff;
pub use ff::*;
Expand Down
Loading

0 comments on commit 810ed8a

Please sign in to comment.