initial commit

This commit is contained in:
vimwitch 2025-01-29 14:42:27 -08:00
commit a261813f77
48 changed files with 2890 additions and 0 deletions

43
.github/workflows/build-and-test.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Build and Test
on:
push:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
env:
CARGO_TERM_COLOR: always
jobs:
clippy_check:
runs-on: ubuntu-latest
env:
RUSTFLAGS: "-Dwarnings" # Make sure CI fails on all warnings, including Clippy lints
steps:
- uses: actions/checkout@v4
- name: Run Clippy
run: cargo clippy --all-targets --all-features
lint:
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:
- uses: actions/checkout@v4
- name: Check formatting
run: cargo fmt --all -- --check
test:
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:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Run tests
run: cargo test

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
.DS_Store
*.tmp
*.swp

2530
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "rust-rapidsnark"
version = "0.1.0"
edition = "2021"
[lib]
[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"
[build-dependencies]
cc = "1.0"
rust-witness = "0.1.2"

42
build.rs Normal file
View File

@ -0,0 +1,42 @@
use std::path::PathBuf;
fn main() {
// #[cfg(test)]
rust_witness::transpile::transpile_wasm("./test-vectors".to_string());
let target = std::env::var("TARGET").unwrap();
let arch = target.split('-').next().unwrap();
// Try to list contents of the target directory
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let rapidsnark_dir = manifest_dir.join("rapidsnark");
let absolute_lib_path = if rapidsnark_dir.join(&target).exists() {
rapidsnark_dir.join(&target)
} else {
rapidsnark_dir.join(arch)
};
let compiler = cc::Build::new().get_compiler();
let cpp_stdlib = if compiler.is_like_clang() {
"c++"
} else {
"stdc++"
};
println!(
"cargo:rustc-link-search=native={}",
absolute_lib_path.clone().display()
);
println!("cargo:rustc-link-lib=static=rapidsnark");
println!("cargo:rustc-link-lib={}", cpp_stdlib);
if target.contains("android") {
// pthread is included in libc in android
println!("cargo:rustc-link-lib=c");
} else {
println!("cargo:rustc-link-lib=pthread");
}
println!("cargo:rustc-link-lib=static=fr");
println!("cargo:rustc-link-lib=static=fq");
println!("cargo:rustc-link-lib=static=gmp");
}

3
rapidsnark/README.md Normal file
View File

@ -0,0 +1,3 @@
Built from [here](https://github.com/chancehudson/rapidsnark/tree/rust-ffi). This is built into static libraries and then linked into the rust project. Importantly we need to compile static libraries for each platform we intend to support.
Linking static libraries instead of compiling from c++ source should make it easier to maintain the `build.rs` script.

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.

BIN
rapidsnark/aarch64/libfq.a Normal file

Binary file not shown.

BIN
rapidsnark/aarch64/libfr.a Normal file

Binary file not shown.

BIN
rapidsnark/aarch64/libgmp.a Normal file

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.

BIN
rapidsnark/x86_64/libfq.a Normal file

Binary file not shown.

BIN
rapidsnark/x86_64/libfr.a Normal file

Binary file not shown.

BIN
rapidsnark/x86_64/libgmp.a Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,13 @@
#!/bin/sh
set -e
BUILD_DIR=$(mktemp -d)
git clone https://github.com/chancehudson/rapidsnark.git $BUILD_DIR
cd $BUILD_DIR
git submodule init
git submodule update
build_gmp.sh

236
src/lib.rs Normal file
View File

@ -0,0 +1,236 @@
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 std::collections::HashMap;
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;
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]>,
}
#[repr(C)]
pub struct ProofResult {
proof: *mut c_char,
public_signals: *mut c_char,
}
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 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();
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(),
);
Ok(result)
}
}
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")?;
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"));
}
// 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}}}"
))
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use num_bigint::BigInt;
use std::str::FromStr;
use anyhow::Result;
use anyhow::bail;
rust_witness::witness!(multiplier2);
rust_witness::witness!(keccak256256test);
fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
let mut bits = Vec::new();
for &byte in bytes {
for j in 0..8 {
let bit = (byte >> j) & 1;
bits.push(bit == 1);
}
}
bits
}
fn bytes_to_circuit_inputs(input_vec: &[u8]) -> HashMap<String, Vec<String>> {
let bits = bytes_to_bits(input_vec);
let converted_vec: Vec<String> = bits
.into_iter()
.map(|bit| (bit as i32).to_string())
.collect();
let mut inputs = HashMap::new();
inputs.insert("in".to_string(), converted_vec);
inputs
}
#[test]
fn test_prove_rapidsnark() -> Result<()> {
// Create a new MoproCircom instance
let zkey_path = "./test-vectors/multiplier2_final.zkey".to_string();
let mut inputs = HashMap::new();
let a = BigInt::from_str(
"21888242871839275222246405745257275088548364400416034343698204186575808495616",
)
.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");
}
Ok(())
}
#[test]
fn test_prove_rapidsnark_keccak() -> Result<()> {
// Create a new MoproCircom instance
let zkey_path = "./test-vectors/keccak256_256_test_final.zkey".to_string();
// Prepare inputs
let input_vec = vec![
116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
];
let inputs = bytes_to_circuit_inputs(&input_vec);
// 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");
}
Ok(())
}
}

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.