From 5a522239a62262f6b0fefa1f1d54a22b72da5b38 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 19 Jan 2024 12:43:40 -0600 Subject: [PATCH] initial commit --- .github/workflows/ci.yml | 24 +++ .gitignore | 16 ++ Cargo.toml | 21 +++ LICENSE | 21 +++ README.md | 2 + cbindgen.toml | 32 ++++ fixtures/mycircuit.r1cs | Bin 0 -> 264 bytes fixtures/mycircuit.wasm | Bin 0 -> 30625 bytes src/ffi.rs | 370 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 10 files changed, 487 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cbindgen.toml create mode 100644 fixtures/mycircuit.r1cs create mode 100644 fixtures/mycircuit.wasm create mode 100644 src/ffi.rs create mode 100644 src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..73f802c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: Cargo Build & Test + +on: + push: + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + build_and_test: + name: Rust project - latest + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - stable + - beta + - nightly + steps: + - uses: actions/checkout@v3 + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: cargo build --verbose + - run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ea5efc --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +.vscode/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..04edd18 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "circom-compat-ffi" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +ark-circom = { git = "https://github.com/codex-storage/circom-compat.git" } + +ark-crypto-primitives = { version = "=0.4.0" } +ark-ec = { version = "=0.4.1", default-features = false, features = ["parallel"] } +ark-ff = { version = "=0.4.1", default-features = false, features = ["parallel", "asm"] } +ark-std = { version = "=0.4.0", default-features = false, features = ["parallel"] } +ark-bn254 = { version = "=0.4.0" } +ark-groth16 = { version = "=0.4.0", default-features = false, features = ["parallel"] } +ark-poly = { version = "=0.4.1", default-features = false, features = ["parallel"] } +ark-relations = { version = "=0.4.0", default-features = false } +ark-serialize = { version = "=0.4.1", default-features = false } +ruint = { version = "1.7.0", features = ["serde", "num-bigint", "ark-ff"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..774aba1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Codex Storage + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..63da5ae --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# circom-compat-ffi +circom-compat (ark-circom) ffi diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..ab815ea --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,32 @@ +[parse] +parse_deps = false +expand = ["rust-circom-compat-ffi"] + +# Configuration for name mangling +[export.mangle] +# Whether the types should be renamed during mangling, for example +# c_char -> CChar, etc. +rename_types = "CamelCase" +# Whether the underscores from the mangled name should be omitted. +remove_underscores = true + +[struct] +# A rule to use to rename struct field names. The renaming assumes the input is +# the Rust standard snake_case, however it acccepts all the different rename_args +# inputs. This means many options here are no-ops or redundant. +# +# possible values (that actually do something): +# * "CamelCase": my_arg => myArg +# * "PascalCase": my_arg => MyArg +# * "GeckoCase": my_arg => mMyArg +# * "ScreamingSnakeCase": my_arg => MY_ARG +# * "None": apply no renaming +# +# technically possible values (that shouldn't have a purpose here): +# * "SnakeCase": apply no renaming +# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?) +# * "UpperCase": same as ScreamingSnakeCase in this context +# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context +# +# default: "None" +rename_fields = "CamelCase" diff --git a/fixtures/mycircuit.r1cs b/fixtures/mycircuit.r1cs new file mode 100644 index 0000000000000000000000000000000000000000..0aebe9115cde7c8b8287014e293f9dff14dedf6c GIT binary patch literal 264 zcmXRiOfF_*U|?VdVi4^B#2}ym#6bRs$zLBjPb}D3>G8r?BQ~P3ahq$v4$TFI4~zU# z3|Js)fFuJ06A**=AOJE4#IFExfB?n^GVz)RQU`MfNFOc$QU}67fY&^bSqdOkNB|@T L0#N;6cY^o;&hIT^ literal 0 HcmV?d00001 diff --git a/fixtures/mycircuit.wasm b/fixtures/mycircuit.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4a28f0c11c19939b3d70aae10d311b9d7078fb1a GIT binary patch literal 30625 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 literal 0 HcmV?d00001 diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..d9e945f --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,370 @@ +use std::{ + any::Any, + ffi::{c_char, CStr}, + fs::File, + os::raw::c_void, + panic::{catch_unwind, AssertUnwindSafe}, +}; + +use ark_bn254::{Bn254, Fr}; +use ark_circom::{read_zkey, CircomBuilder, CircomConfig}; +use ark_crypto_primitives::snark::SNARK; +use ark_groth16::{prepare_verifying_key, Groth16, Proof, ProvingKey}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::rand::{rngs::ThreadRng, thread_rng}; + +use ruint::aliases::U256; + +type GrothBn = Groth16; + +pub const ERR_UNKNOWN: i32 = -1; +pub const ERR_OK: i32 = 0; +pub const ERR_WASM_PATH: i32 = 1; +pub const ERR_R1CS_PATH: i32 = 2; +pub const ERR_ZKEY_PATH: i32 = 3; +pub const ERR_INPUT_NAME: i32 = 4; +pub const ERR_INVALID_INPUT: i32 = 5; +pub const ERR_CANT_READ_ZKEY: i32 = 6; +pub const ERR_CIRCOM_BUILDER: i32 = 7; +pub const ERR_FAILED_TO_DESERIALIZE_PROOF: i32 = 8; +pub const ERR_FAILED_TO_DESERIALIZE_INPUTS: i32 = 9; + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct Buffer { + data: *const u8, + len: usize, +} + +#[derive(Debug, Clone)] +// #[repr(C)] +struct CircomBn254 { + builder: *mut CircomBuilder, + proving_key: *mut ProvingKey, + _marker: core::marker::PhantomData<(*mut CircomBn254, core::marker::PhantomPinned)>, +} + +#[derive(Debug, Clone)] +struct CircomCompatCtx { + circom: *mut c_void, + rng: ThreadRng, + _marker: core::marker::PhantomData<(*mut CircomCompatCtx, core::marker::PhantomPinned)>, +} + +fn to_err_code(result: Result<(), Box>) -> i32 { + match result { + Ok(_) => ERR_OK, + Err(e) => match e.downcast_ref::() { + Some(e) => *e, + None => ERR_UNKNOWN, + }, + } +} + +/// # Safety +/// +#[no_mangle] +#[allow(private_interfaces)] +pub unsafe extern "C" fn init_circom_compat( + r1cs_path: *const c_char, + wasm_path: *const c_char, + zkey_path: *const c_char, + ctx_ptr: &mut *mut CircomCompatCtx, +) -> i32 { + let result = catch_unwind(AssertUnwindSafe(|| { + let mut rng = thread_rng(); // TODO: use a shared rng - how? + let builder = CircomBuilder::new( + CircomConfig::::new( + CStr::from_ptr(wasm_path) + .to_str() + .map_err(|_| ERR_WASM_PATH) + .unwrap(), + CStr::from_ptr(r1cs_path) + .to_str() + .map_err(|_| ERR_R1CS_PATH) + .unwrap(), + ) + .map_err(|_| ERR_CIRCOM_BUILDER) + .unwrap(), + ); + + let proving_key = if !zkey_path.is_null() { + let mut file = File::open( + CStr::from_ptr(zkey_path) + .to_str() + .map_err(|_| ERR_ZKEY_PATH) + .unwrap(), + ) + .unwrap(); + + read_zkey(&mut file) + .map_err(|_| ERR_CANT_READ_ZKEY) + .unwrap() + .0 + } else { + Groth16::::generate_random_parameters_with_reduction::<_>( + builder.setup(), + &mut rng, + ) + .map_err(|_| ERR_UNKNOWN) + .unwrap() + }; + + let circom_bn254 = CircomBn254 { + builder: Box::into_raw(Box::new(builder)), + proving_key: Box::into_raw(Box::new(proving_key)), + _marker: core::marker::PhantomData, + }; + + let circom_compat_ctx = CircomCompatCtx { + circom: Box::into_raw(Box::new(circom_bn254)) as *mut c_void, + rng: rng, + _marker: core::marker::PhantomData, + }; + + *ctx_ptr = Box::into_raw(Box::new(circom_compat_ctx)); + })); + + to_err_code(result) +} + +#[no_mangle] +#[allow(private_interfaces)] +pub unsafe extern "C" fn release_circom_compat(ctx_ptr: &mut *mut CircomCompatCtx) { + if !ctx_ptr.is_null() { + let ctx = &mut Box::from_raw(*ctx_ptr); + if !ctx.circom.is_null() { + let circom = &mut Box::from_raw(ctx.circom as *mut CircomBn254); + let _ = Box::from_raw(circom.builder); + let _ = Box::from_raw(circom.proving_key); + if !circom.builder.is_null() { + circom.builder = std::ptr::null_mut() + }; + if !circom.proving_key.is_null() { + circom.proving_key = std::ptr::null_mut() + }; + ctx.circom = std::ptr::null_mut(); + *ctx_ptr = std::ptr::null_mut(); + } + } +} + +pub unsafe extern "C" fn release_buffer(buff_ptr: &mut *mut Buffer) { + if !buff_ptr.is_null() { + let buff = &mut Box::from_raw(*buff_ptr); + let _ = Box::from_raw(buff.data as *mut u8); + buff.data = std::ptr::null_mut(); + buff.len = 0; + *buff_ptr = std::ptr::null_mut(); + } +} + +unsafe fn to_circom(ctx_ptr: *mut CircomCompatCtx) -> *mut CircomBn254 { + (*ctx_ptr).circom as *mut CircomBn254 +} + +/// # Safety +/// +#[no_mangle] +#[allow(private_interfaces)] +pub unsafe extern "C" fn push_input_u256_array( + ctx_ptr: *mut CircomCompatCtx, + name_ptr: *const c_char, + input_ptr: *const u8, + len: usize, +) -> i32 { + let result = catch_unwind(AssertUnwindSafe(|| { + let name = CStr::from_ptr(name_ptr) + .to_str() + .map_err(|_| ERR_INPUT_NAME) + .unwrap(); + + let input = { + let slice = std::slice::from_raw_parts(input_ptr, len); + slice + .chunks(U256::BYTES) + .map(|c| U256::try_from_le_slice(c).ok_or(ERR_INVALID_INPUT).unwrap()) + .collect::>() + }; + + let circom = &mut *to_circom(ctx_ptr); + input + .iter() + .for_each(|c| (*circom.builder).push_input(name, *c)); + })); + + to_err_code(result) +} + +/// # Safety +/// +#[no_mangle] +#[allow(private_interfaces)] +pub unsafe extern "C" fn prove_circuit( + ctx_ptr: *mut CircomCompatCtx, + proof_bytes_ptr: &mut *mut Buffer, + inputs_bytes_ptr: &mut *mut Buffer, +) -> i32 { + let result = catch_unwind(AssertUnwindSafe(|| { + let circom = &mut *to_circom(ctx_ptr); + + let proving_key = &(*circom.proving_key); + let rng = &mut (*ctx_ptr).rng; + + let circuit = (*circom.builder).clone().build().unwrap(); + + let inputs = circuit.get_public_inputs().unwrap(); + let proof = GrothBn::prove(&proving_key, circuit, rng).unwrap(); + + let mut proof_bytes = Vec::new(); + proof.serialize_compressed(&mut proof_bytes).unwrap(); + + let mut public_inputs_bytes = Vec::new(); + inputs + .serialize_compressed(&mut public_inputs_bytes) + .unwrap(); + + // leak the buffers to avoid rust from freeing the pointed to data, + // clone to avoid bytes from being freed + let proof_slice = Box::leak(Box::new(proof_bytes.clone())).as_slice(); + let proof_buff = Buffer { + data: proof_slice.as_ptr() as *const u8, + len: proof_bytes.len(), + }; + + // leak the buffers to avoid rust from freeing the pointed to data, + // clone to avoid bytes from being freed + let input_slice = Box::leak(Box::new(public_inputs_bytes.clone())).as_slice(); + let input_buff = Buffer { + data: input_slice.as_ptr() as *const u8, + len: public_inputs_bytes.len(), + }; + + *proof_bytes_ptr = Box::into_raw(Box::new(proof_buff)); + *inputs_bytes_ptr = Box::into_raw(Box::new(input_buff)); + })); + + to_err_code(result) +} + +/// # Safety +/// +#[no_mangle] +#[allow(private_interfaces)] +pub unsafe extern "C" fn verify_circuit( + ctx_ptr: *mut CircomCompatCtx, + proof_bytes_ptr: *const Buffer, + inputs_bytes_ptr: *const Buffer, +) -> i32 { + let result = catch_unwind(AssertUnwindSafe(|| { + let proof_bytes = + std::slice::from_raw_parts((*proof_bytes_ptr).data, (*proof_bytes_ptr).len); + + let proof = Proof::::deserialize_compressed(proof_bytes) + .map_err(|_| ERR_FAILED_TO_DESERIALIZE_PROOF) + .unwrap(); + + let public_inputs_bytes = + std::slice::from_raw_parts((*inputs_bytes_ptr).data, (*inputs_bytes_ptr).len); + let public_inputs: Vec = + CanonicalDeserialize::deserialize_compressed(public_inputs_bytes) + .map_err(|_| ERR_FAILED_TO_DESERIALIZE_INPUTS) + .unwrap(); + + let circom = &mut *to_circom(ctx_ptr); + + let proving_key = &(*circom.proving_key); + let pvk = prepare_verifying_key(&proving_key.vk); + + GrothBn::verify_proof(&pvk, &proof, &public_inputs) + .map_err(|e| e.to_string()) + .unwrap(); + })); + + to_err_code(result) +} + +macro_rules! build_fn +{ + ($name:tt, $($v:ident: $t:ty),*) => { + #[no_mangle] + #[allow(private_interfaces)] + pub unsafe extern "C" fn $name( + ctx_ptr: *mut CircomCompatCtx, + name_ptr: *const c_char, + input: $($t),* + ) -> i32 { + let result = catch_unwind(AssertUnwindSafe(|| { + let name = CStr::from_ptr(name_ptr).to_str().unwrap(); + let input = U256::from(input); + + let circom = &mut *to_circom(ctx_ptr); + (*circom.builder).push_input(name, input); + })); + + to_err_code(result) + } + }; +} + +build_fn!(push_input_numeric_i8, x: i8); +build_fn!(push_input_numeric_u8, x: u8); +build_fn!(push_input_numeric_i16, x: i16); +build_fn!(push_input_numeric_u16, x: u16); +build_fn!(push_input_numeric_i32, x: i32); +build_fn!(push_input_numeric_u32, x: u32); +build_fn!(push_input_numeric_u64, x: u64); + +#[cfg(test)] +mod test { + use std::ffi::CString; + + use super::*; + + #[test] + fn groth16_proof() { + let r1cs_path = CString::new("./fixtures/mycircuit.r1cs".as_bytes()).unwrap(); + let wasm_path = CString::new("./fixtures/mycircuit.wasm".as_bytes()).unwrap(); + + unsafe { + let mut ctx_ptr: *mut CircomCompatCtx = std::ptr::null_mut(); + init_circom_compat( + r1cs_path.as_ptr(), + wasm_path.as_ptr(), + std::ptr::null(), + &mut ctx_ptr, + ); + + assert!(ctx_ptr != std::ptr::null_mut()); + + let a = CString::new("a".as_bytes()).unwrap(); + push_input_numeric_i8(ctx_ptr, a.as_ptr(), 3); + + let b = CString::new("b".as_bytes()).unwrap(); + push_input_numeric_i8(ctx_ptr, b.as_ptr(), 3); + + let mut proof_bytes_ptr: *mut Buffer = std::ptr::null_mut(); + let mut inputs_bytes_ptr: *mut Buffer = std::ptr::null_mut(); + + assert!(prove_circuit(ctx_ptr, &mut proof_bytes_ptr, &mut inputs_bytes_ptr) == ERR_OK); + + assert!(proof_bytes_ptr != std::ptr::null_mut()); + assert!((*proof_bytes_ptr).data != std::ptr::null()); + assert!((*proof_bytes_ptr).len > 0); + + assert!(inputs_bytes_ptr != std::ptr::null_mut()); + assert!((*inputs_bytes_ptr).data != std::ptr::null()); + assert!((*inputs_bytes_ptr).len > 0); + + assert!(verify_circuit(ctx_ptr, &(*proof_bytes_ptr), &(*inputs_bytes_ptr)) == ERR_OK); + + release_buffer(&mut proof_bytes_ptr); + release_buffer(&mut inputs_bytes_ptr); + release_circom_compat(&mut ctx_ptr); + + assert!(ctx_ptr == std::ptr::null_mut()); + assert!(proof_bytes_ptr == std::ptr::null_mut()); + assert!(inputs_bytes_ptr == std::ptr::null_mut()); + }; + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d8a989e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod ffi;