Merge pull request #1 from zkmopro/refactor

feat: wrap up `groth16_prover_zkey_file`, build for macOS, iOS device…
This commit is contained in:
Ya-wen, Jeng 2025-02-01 22:10:50 +08:00 committed by GitHub
commit 1d32c928c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 1774 additions and 2397 deletions

2
.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[env]
RUST_RAPIDSNARK_LINK_TEST_WITNESS="1"

View File

@ -29,7 +29,46 @@ jobs:
- name: Check formatting
run: cargo fmt --all -- --check
test:
build-ios:
runs-on: macos-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
strategy:
matrix:
target:
- aarch64-apple-ios
- aarch64-apple-ios-sim
- x86_64-apple-ios
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.81.0"
targets: ${{ matrix.target }}
- name: Build
run: cargo build --target ${{ matrix.target }}
build-android:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
strategy:
matrix:
target:
- x86_64-linux-android
- aarch64-linux-android
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.81.0"
targets: ${{ matrix.target }}
- name: Install Android NDK
run: cargo install cargo-ndk
- name: Build
run: cargo ndk -t ${{ matrix.target }} build
test-linux:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
@ -37,7 +76,31 @@ jobs:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: "1.81.0"
override: true
- name: Run tests
run: cargo test
test-macOS:
runs-on: macos-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: "1.81.0"
override: true
- name: Run tests
run: cargo test
test-macOS-x86_64:
runs-on: macos-13
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: "1.81.0"
override: true
- name: Run tests
run: cargo test

2250
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,11 @@ edition = "2021"
[dependencies]
anyhow = "1.0.95"
ark-bn254 = "0.4.0"
ark-circom = { git = "https://github.com/zkmopro/circom-compat.git", version = "0.1.0", branch = "wasm-delete" }
num-bigint = "0.4.6"
rust-witness = "0.1.2"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.135"
num-traits = "0.2.19"
[build-dependencies]
cc = "1.0"

View File

@ -1,8 +1,12 @@
use std::env;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
fn main() {
// #[cfg(test)]
rust_witness::transpile::transpile_wasm("./test-vectors".to_string());
if std::env::var("RUST_RAPIDSNARK_LINK_TEST_WITNESS").is_ok() {
rust_witness::transpile::transpile_wasm("./test-vectors".to_string());
}
let target = std::env::var("TARGET").unwrap();
let arch = target.split('-').next().unwrap();
@ -39,4 +43,29 @@ fn main() {
println!("cargo:rustc-link-lib=static=fr");
println!("cargo:rustc-link-lib=static=fq");
println!("cargo:rustc-link-lib=static=gmp");
// refer to https://github.com/bbqsrc/cargo-ndk to see how to link the libc++_shared.so file in Android
if env::var("CARGO_CFG_TARGET_OS").unwrap() == "android" {
android();
}
}
fn android() {
println!("cargo:rustc-link-lib=c++_shared");
if let Ok(output_path) = env::var("CARGO_NDK_OUTPUT_PATH") {
let sysroot_libs_path = PathBuf::from(env::var_os("CARGO_NDK_SYSROOT_LIBS_PATH").unwrap());
let lib_path = sysroot_libs_path.join("libc++_shared.so");
assert!(
lib_path.exists(),
"Error: Source file {:?} does not exist",
lib_path
);
let dest_dir = Path::new(&output_path).join(env::var("CARGO_NDK_ANDROID_TARGET").unwrap());
println!("cargo:rerun-if-changed={}", dest_dir.display());
if !dest_dir.exists() {
fs::create_dir_all(&dest_dir).unwrap();
}
fs::copy(lib_path, Path::new(&dest_dir).join("libc++_shared.so")).unwrap();
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -14,177 +14,185 @@
//!
use std::collections::HashMap;
use std::ffi::CString;
use std::fs::File;
use std::os::raw::c_char;
use std::os::raw::c_uint;
use std::str::FromStr;
use anyhow::Context;
use anyhow::Result;
use ark_bn254::Bn254;
use ark_circom::read_proving_key;
use ark_circom::ZkeyHeaderReader;
use num_bigint::BigInt;
use serde::Deserialize;
use serde::Serialize;
/// A function that converts named inputs to a full witness. This should be generated using e.g.
/// [rust-witness](https://crates.io/crates/rust-witness).
pub type WtnsFn = fn(HashMap<String, Vec<BigInt>>) -> Vec<BigInt>;
// match what rapidsnark expects
#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
struct VerificationKey {
protocol: String,
curve: String,
nPublic: u32,
vk_alpha_1: [String; 3],
vk_beta_2: [[String; 2]; 3],
vk_gamma_2: [[String; 2]; 3],
vk_delta_2: [[String; 2]; 3],
IC: Vec<[String; 3]>,
}
/// A structure representing a proof and public signals.
#[repr(C)]
#[derive(Debug)]
pub struct ProofResult {
proof: *mut c_char,
public_signals: *mut c_char,
pub proof: String,
pub public_signals: String,
}
extern "C" {
fn groth16_api_prove(
zkeyFilename: *const c_char,
wtnsData: *mut u8,
wtnsDataLen: c_uint,
) -> *mut ProofResult;
fn groth16_api_verify(proof: *mut ProofResult, key_json: *const c_char) -> bool;
fn free_proof_result(result: *mut ProofResult);
pub fn groth16_prover_zkey_file(
zkey_file_path: *const std::os::raw::c_char,
wtns_buffer: *const std::os::raw::c_void,
wtns_size: std::ffi::c_ulong,
proof_buffer: *mut std::os::raw::c_char,
proof_size: *mut std::ffi::c_ulong,
public_buffer: *mut std::os::raw::c_char,
public_size: *mut std::ffi::c_ulong,
error_msg: *mut std::os::raw::c_char,
error_msg_maxsize: std::ffi::c_ulong,
) -> i32;
pub fn groth16_verify(
proof: *const std::os::raw::c_char,
inputs: *const std::os::raw::c_char,
verification_key: *const std::os::raw::c_char,
error_msg: *mut std::os::raw::c_char,
error_msg_maxsize: std::ffi::c_ulong,
) -> i32;
}
/// Verify a proof using a zkey. The proof is expected to be encoded as json.
pub fn verify_proof(zkey_path: &str, proof: String) -> Result<bool> {
let mut header_reader = ZkeyHeaderReader::new(zkey_path);
header_reader.read();
let file = File::open(zkey_path)?;
let mut reader = std::io::BufReader::new(file);
let proving_key = read_proving_key::<_, Bn254>(&mut reader)?;
// convert out proving key to json so we can
// use it with rapidsnark
let vk = proving_key.vk;
// let v = proving_key.vk.alpha_g1.to_string();
let vkey = VerificationKey {
protocol: "groth16".to_string(),
curve: "bn128".to_string(),
nPublic: 0, // this is unused in the rapidsnark verifier
vk_alpha_1: [
vk.alpha_g1.x.to_string(),
vk.alpha_g1.y.to_string(),
"1".to_string(),
],
vk_beta_2: [
[vk.beta_g2.x.c0.to_string(), vk.beta_g2.x.c1.to_string()],
[vk.beta_g2.y.c0.to_string(), vk.beta_g2.y.c1.to_string()],
["1".to_string(), "0".to_string()],
],
vk_gamma_2: [
[vk.gamma_g2.x.c0.to_string(), vk.gamma_g2.x.c1.to_string()],
[vk.gamma_g2.y.c0.to_string(), vk.gamma_g2.y.c1.to_string()],
["1".to_string(), "0".to_string()],
],
vk_delta_2: [
[vk.delta_g2.x.c0.to_string(), vk.delta_g2.x.c1.to_string()],
[vk.delta_g2.y.c0.to_string(), vk.delta_g2.y.c1.to_string()],
["1".to_string(), "0".to_string()],
],
IC: vk
.gamma_abc_g1
.iter()
.map(|p| [p.x.to_string(), p.y.to_string(), "1".to_string()])
.collect(),
};
let vkey_json = serde_json::to_string(&vkey)?;
let vkey_json_cstr = CString::new(vkey_json)?;
let v: serde_json::Value = serde_json::from_str(&proof)?;
let proof = v["proof"].to_string();
let signals = v["signals"].to_string();
use num_traits::ops::bytes::ToBytes;
use std::io::{self};
/// Parse bigints to `wtns` format.<br/>
/// Reference: [witnesscalc/src/witnesscalc.cpp](https://github.com/0xPolygonID/witnesscalc/blob/4a789880727aa0df50f1c4ef78ec295f5a30a15e/src/witnesscalc.cpp)
pub fn parse_bigints_to_witness(bigints: Vec<BigInt>) -> io::Result<Vec<u8>> {
let mut buffer = Vec::new();
let version: u32 = 2;
let n_sections: u32 = 2;
let n8: u32 = 32;
let q = BigInt::from_str(
"21888242871839275222246405745257275088548364400416034343698204186575808495617",
)
.unwrap();
let n_witness_values: u32 = bigints.len() as u32;
// Write the format bytes (4 bytes)
let wtns_format = "wtns".as_bytes();
buffer.extend_from_slice(wtns_format);
// Write version (4 bytes)
buffer.extend_from_slice(&version.to_le_bytes());
// Write number of sections (4 bytes)
buffer.extend_from_slice(&n_sections.to_le_bytes());
// Iterate through sections to write the data
// Section 1 (Field parameters)
let section_id_1: u32 = 1;
let section_length_1: u64 = 8 + n8 as u64;
buffer.extend_from_slice(&section_id_1.to_le_bytes());
buffer.extend_from_slice(&section_length_1.to_le_bytes());
// Write n8 (4 bytes), q (32 bytes), and n_witness_values (4 bytes)
buffer.extend_from_slice(&n8.to_le_bytes());
buffer.extend_from_slice(&q.to_signed_bytes_le());
buffer.extend_from_slice(&n_witness_values.to_le_bytes());
// Section 2 (Witness data)
let section_id_2: u32 = 2;
let section_length_2: u64 = bigints.len() as u64 * n8 as u64; // Witness data size
buffer.extend_from_slice(&section_id_2.to_le_bytes());
buffer.extend_from_slice(&section_length_2.to_le_bytes());
// Write the witness data (each BigInt to n8 bytes)
for bigint in bigints {
let mut bytes = bigint.to_le_bytes();
bytes.resize(n8 as usize, 0); // Ensure each BigInt is padded to n8 bytes
buffer.extend_from_slice(&bytes);
}
// Return the buffer containing the complete witness data
Ok(buffer)
}
/// Wrapper for `groth16_prover_zkey_file`
pub fn groth16_prover_zkey_file_wrapper(
zkey_path: &str,
wtns_buffer: Vec<u8>,
) -> Result<ProofResult> {
let wtns_size = wtns_buffer.len() as u64;
let mut proof_buffer = vec![0u8; 4 * 1024 * 1024]; // Adjust size as needed
let mut proof_size: u64 = 4 * 1024 * 1024;
let proof_ptr = proof_buffer.as_mut_ptr() as *mut std::ffi::c_char;
let mut public_buffer = vec![0u8; 4 * 1024 * 1024]; // Adjust size as needed
let mut public_size: u64 = 4 * 1024 * 1024;
let public_ptr = public_buffer.as_mut_ptr() as *mut std::ffi::c_char;
let mut error_msg = vec![0u8; 256]; // Error message buffer
let error_msg_ptr = error_msg.as_mut_ptr() as *mut std::ffi::c_char;
unsafe {
let result = groth16_api_verify(
&mut ProofResult {
proof: CString::new(proof).unwrap().into_raw(),
public_signals: CString::new(signals).unwrap().into_raw(),
},
vkey_json_cstr.as_ptr(),
let result = groth16_prover_zkey_file(
zkey_path.as_ptr() as *const std::ffi::c_char,
wtns_buffer.as_ptr() as *const std::os::raw::c_void, // Witness buffer
wtns_size,
proof_ptr,
&mut proof_size,
public_ptr,
&mut public_size,
error_msg_ptr,
error_msg.len() as u64,
);
Ok(result)
if result != 0 {
let error_string = std::ffi::CStr::from_ptr(error_msg_ptr)
.to_string_lossy()
.into_owned();
return Err(anyhow::anyhow!("Proof generation failed: {}", error_string));
}
// Convert both strings
let proof = std::ffi::CStr::from_ptr(proof_ptr)
.to_string_lossy()
.into_owned();
let public_signals = std::ffi::CStr::from_ptr(public_ptr)
.to_string_lossy()
.into_owned();
Ok(ProofResult {
proof,
public_signals,
})
}
}
/// Generate a groth16 proof using a specific zkey. Inputs are expected to be base 10 encoded
/// strings. Returns a json encoded proof and public signals.
pub fn generate_proof(
zkey_path: &str,
inputs: std::collections::HashMap<String, Vec<String>>,
witness_fn: WtnsFn,
) -> Result<String> {
// Form the inputs
let bigint_inputs = inputs
.into_iter()
.map(|(k, v)| {
(
k,
v.into_iter()
.map(|i| BigInt::from_str(&i).unwrap())
.collect(),
)
})
.collect();
let mut wtns = witness_fn(bigint_inputs)
.into_iter()
.map(|w| w.to_biguint().unwrap())
.flat_map(|v| {
let mut bytes = v.to_bytes_le();
bytes.resize(32, 0);
bytes
})
.collect::<Vec<_>>();
// Convert Rust strings to C strings
let zkey_cstr = CString::new(zkey_path).context("Failed to create CString for zkey path")?;
/// Wrapper for `groth16_verify`
pub fn groth16_verify_wrapper(proof: &str, inputs: &str, verification_key: &str) -> Result<bool> {
let proof_cstr = std::ffi::CString::new(proof).unwrap();
let inputs_cstr = std::ffi::CString::new(inputs).unwrap();
let verification_key_cstr = std::ffi::CString::new(verification_key).unwrap();
let mut error_msg = vec![0u8; 256]; // Error message buffer
let error_msg_ptr = error_msg.as_mut_ptr() as *mut std::ffi::c_char;
unsafe {
let proof_ptr =
groth16_api_prove(zkey_cstr.as_ptr(), wtns.as_mut_ptr(), wtns.len() as c_uint);
if proof_ptr.is_null() {
return Err(anyhow::anyhow!("Proof generation failed"));
let result = groth16_verify(
proof_cstr.as_ptr() as *const std::ffi::c_char,
inputs_cstr.as_ptr() as *const std::ffi::c_char,
verification_key_cstr.as_ptr() as *const std::ffi::c_char,
error_msg_ptr,
error_msg.len() as u64,
);
if result == 2 {
let error_string = std::ffi::CStr::from_ptr(error_msg_ptr)
.to_string_lossy()
.into_owned();
return Err(anyhow::anyhow!(
"Proof verification failed: {}",
error_string
));
}
// Convert both strings
let result = &*proof_ptr;
let proof = std::ffi::CStr::from_ptr(result.proof)
.to_string_lossy()
.into_owned();
let public_signals = std::ffi::CStr::from_ptr(result.public_signals)
.to_string_lossy()
.into_owned();
free_proof_result(proof_ptr);
Ok(format!(
"{{ \"proof\": {proof},\"signals\": {public_signals}}}"
))
Ok(result == 0)
}
}
#[cfg(test)]
mod tests {
use anyhow::bail;
use anyhow::Result;
use num_bigint::BigInt;
use std::collections::HashMap;
use std::str::FromStr;
use std::{collections::HashMap, str::FromStr};
use crate::{parse_bigints_to_witness, WtnsFn};
rust_witness::witness!(multiplier2);
rust_witness::witness!(keccak256256test);
@ -211,6 +219,28 @@ mod tests {
inputs
}
fn compute_witness(
inputs: HashMap<String, Vec<String>>,
witness_fn: WtnsFn,
) -> Result<Vec<u8>> {
// Form the inputs
let bigint_inputs = inputs
.into_iter()
.map(|(k, v)| {
(
k,
v.into_iter()
.map(|i| BigInt::from_str(&i).unwrap())
.collect(),
)
})
.collect();
let wtns: Vec<BigInt> = witness_fn(bigint_inputs);
let witnesscalc_wtns = parse_bigints_to_witness(wtns)?;
Ok(witnesscalc_wtns)
}
#[test]
fn test_prove_rapidsnark() -> Result<()> {
// Create a new MoproCircom instance
@ -222,15 +252,22 @@ mod tests {
)
.unwrap();
let b = BigInt::from(1u8);
// let c = a.clone() * b.clone();
inputs.insert("a".to_string(), vec![a.to_string()]);
inputs.insert("b".to_string(), vec![b.to_string()]);
let proof_json = super::generate_proof(&zkey_path, inputs, multiplier2_witness)?;
let valid = super::verify_proof(&zkey_path, proof_json)?;
if !valid {
bail!("Proof is invalid");
}
// Generate Witness Buffer
let wtns_buffer = compute_witness(inputs, multiplier2_witness)?;
// Generate Proof
let proof_result = super::groth16_prover_zkey_file_wrapper(&zkey_path, wtns_buffer)?;
let vkey = std::fs::read_to_string("./test-vectors/multiplier2.vkey.json")?;
let valid = super::groth16_verify_wrapper(
&proof_result.proof,
&proof_result.public_signals,
&vkey,
)?;
assert!(valid);
Ok(())
}
@ -246,12 +283,21 @@ mod tests {
let inputs = bytes_to_circuit_inputs(&input_vec);
// Generate Witness Buffer
let wtns_buffer = compute_witness(inputs, keccak256256test_witness)?;
let wtns_data = std::fs::read("./test-vectors/keccak256_256_test.wtns")?;
assert_eq!(wtns_buffer, wtns_data);
// Generate Proof
let proof_json = super::generate_proof(&zkey_path, inputs, keccak256256test_witness)?;
let valid = super::verify_proof(&zkey_path, proof_json)?;
if !valid {
bail!("Proof is invalid");
}
let proof_result = super::groth16_prover_zkey_file_wrapper(&zkey_path, wtns_buffer)?;
let vkey = std::fs::read_to_string("./test-vectors/keccak256_256_test.vkey.json")?;
let valid = super::groth16_verify_wrapper(
&proof_result.proof,
&proof_result.public_signals,
&vkey,
)?;
assert!(valid);
Ok(())
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,99 @@
{
"protocol": "groth16",
"curve": "bn128",
"nPublic": 2,
"vk_alpha_1": [
"20491192805390485299153009773594534940189261866228447918068658471970481763042",
"9383485363053290200918347156157836566562967994039712273449902621266178545958",
"1"
],
"vk_beta_2": [
[
"6375614351688725206403948262868962793625744043794305715222011528459656738731",
"4252822878758300859123897981450591353533073413197771768651442665752259397132"
],
[
"10505242626370262277552901082094356697409835680220590971873171140371331206856",
"21847035105528745403288232691147584728191162732299865338377159692350059136679"
],
[
"1",
"0"
]
],
"vk_gamma_2": [
[
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
],
[
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
],
[
"1",
"0"
]
],
"vk_delta_2": [
[
"21433406528933179909930745994334927660178418579094216084923767796468271186424",
"13751094566666250787453117075390489314032734336035585479837046065555811806277"
],
[
"16699850258000131247573555544305336535932817397817319735498422046083998163923",
"12877331500166317036475448636193806012569545897268783945479551162026182231936"
],
[
"1",
"0"
]
],
"vk_alphabeta_12": [
[
[
"2029413683389138792403550203267699914886160938906632433982220835551125967885",
"21072700047562757817161031222997517981543347628379360635925549008442030252106"
],
[
"5940354580057074848093997050200682056184807770593307860589430076672439820312",
"12156638873931618554171829126792193045421052652279363021382169897324752428276"
],
[
"7898200236362823042373859371574133993780991612861777490112507062703164551277",
"7074218545237549455313236346927434013100842096812539264420499035217050630853"
]
],
[
[
"7077479683546002997211712695946002074877511277312570035766170199895071832130",
"10093483419865920389913245021038182291233451549023025229112148274109565435465"
],
[
"4595479056700221319381530156280926371456704509942304414423590385166031118820",
"19831328484489333784475432780421641293929726139240675179672856274388269393268"
],
[
"11934129596455521040620786944827826205713621633706285934057045369193958244500",
"8037395052364110730298837004334506829870972346962140206007064471173334027475"
]
]
],
"IC": [
[
"6819801395408938350212900248749732364821477541620635511814266536599629892365",
"9092252330033992554755034971584864587974280972948086568597554018278609861372",
"1"
],
[
"17882351432929302592725330552407222299541667716607588771282887857165175611387",
"18907419617206324833977586007131055763810739835484972981819026406579664278293",
"1"
],
[
"15838138634521468894153380932528531886891906022296751863057552941301429532008",
"10499496224041775125547926627482656159317436804293654376137218419558038465083",
"1"
]
]
}