diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 496f0232..8838ff9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - name: Setup rust toolchain, cache and bins uses: moonrepo/setup-rust@v1 with: - channel: nightly-2025-04-05 + channel: nightly-2025-10-20 cache-base: main components: rustfmt, clippy - run: cargo fmt --all --check diff --git a/Cargo.toml b/Cargo.toml index d1f78c19..e3f08eb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "provekit/verifier", "tooling/cli", "tooling/provekit-bench", + "tooling/provekit-ffi", "tooling/provekit-gnark", "tooling/verifier-server", "ntt", @@ -82,6 +83,7 @@ skyscraper = { path = "skyscraper/core" } provekit-bench = { path = "tooling/provekit-bench" } provekit-cli = { path = "tooling/cli" } provekit-common = { path = "provekit/common" } +provekit-ffi = { path = "tooling/provekit-ffi" } provekit-gnark = { path = "tooling/provekit-gnark" } provekit-prover = { path = "provekit/prover" } provekit-r1cs-compiler = { path = "provekit/r1cs-compiler" } diff --git a/provekit/common/src/sparse_matrix.rs b/provekit/common/src/sparse_matrix.rs index e61f6826..b6228940 100644 --- a/provekit/common/src/sparse_matrix.rs +++ b/provekit/common/src/sparse_matrix.rs @@ -52,7 +52,7 @@ impl SparseMatrix { } } - pub const fn num_entries(&self) -> usize { + pub fn num_entries(&self) -> usize { self.values.len() } diff --git a/provekit/common/src/witness/witness_builder.rs b/provekit/common/src/witness/witness_builder.rs index d212c9bc..6d964a2e 100644 --- a/provekit/common/src/witness/witness_builder.rs +++ b/provekit/common/src/witness/witness_builder.rs @@ -116,8 +116,8 @@ pub enum WitnessBuilder { /// occurs in the bin op. MultiplicitiesForBinOp(usize, Vec<(ConstantOrR1CSWitness, ConstantOrR1CSWitness)>), /// U32 addition with carry: computes result = (a + b) % 2^32 and carry = (a - /// + b) / 2^32 Arguments: (result_witness_index, carry_witness_index, - /// a, b) + /// + b) / 2^32. Arguments: (result_witness_index, carry_witness_index, a, + /// b) U32Addition(usize, usize, ConstantOrR1CSWitness, ConstantOrR1CSWitness), /// AND operation: computes result = a & b /// Arguments: (result_witness_index, a, b) diff --git a/tooling/provekit-ffi/Cargo.toml b/tooling/provekit-ffi/Cargo.toml new file mode 100644 index 00000000..7d3853fc --- /dev/null +++ b/tooling/provekit-ffi/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "provekit-ffi" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lib] +crate-type = ["staticlib"] + +[dependencies] +# Workspace crates +provekit-common.workspace = true +provekit-prover.workspace = true + +# Noir language +acir.workspace = true +noirc_abi.workspace = true + +# 3rd party +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true +postcard.workspace = true +tracing.workspace = true + +[lints] +workspace = true + +[features] +default = [] diff --git a/tooling/provekit-ffi/README.md b/tooling/provekit-ffi/README.md new file mode 100644 index 00000000..00555224 --- /dev/null +++ b/tooling/provekit-ffi/README.md @@ -0,0 +1,318 @@ +# ProveKit FFI + +This crate provides C-compatible FFI bindings for ProveKit, enabling integration with multiple programming languages and platforms including mobile (iOS, Android), desktop, web, and embedded systems. + +## Features + +- **C ABI Compatibility**: All functions use C-compatible types and calling conventions +- **Memory Management**: Safe buffer management with explicit allocation/deallocation +- **Multiple Output Formats**: Support for binary, JSON, and file outputs +- **Error Handling**: Comprehensive error codes and messages +- **Cross-Platform**: Can be compiled as a static library for mobile, desktop, and embedded platforms + +## Building + +### For Development (Host Platform) +```bash +cargo build --release -p provekit-ffi +``` + +### For Mobile Platforms + +#### iOS +```bash +# Install iOS targets +rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios + +# Build for device (ARM64) +cargo build --release --target aarch64-apple-ios -p provekit-ffi + +# Build for simulator (ARM64) +cargo build --release --target aarch64-apple-ios-sim -p provekit-ffi + +# Build for simulator (x86_64, Intel Macs) +cargo build --release --target x86_64-apple-ios -p provekit-ffi +``` + +#### Android +```bash +# Install Android targets +rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android + +# Build for ARM64 +cargo build --release --target aarch64-linux-android -p provekit-ffi + +# Build for ARM32 +cargo build --release --target armv7-linux-androideabi -p provekit-ffi + +# Build for x86_64 +cargo build --release --target x86_64-linux-android -p provekit-ffi +``` + +### Create Platform-Specific Packages + +#### iOS XCFramework +```bash +xcodebuild -create-xcframework \ + -library target/aarch64-apple-ios/release/libprovekit_ffi.a -headers tooling/provekit-ffi/include \ + -library target/aarch64-apple-ios-sim/release/libprovekit_ffi.a -headers tooling/provekit-ffi/include \ + -library target/x86_64-apple-ios/release/libprovekit_ffi.a -headers tooling/provekit-ffi/include \ + -output ProvekitFFI.xcframework +``` + +#### Android AAR (requires additional setup) +```bash +# Copy libraries to Android project structure +mkdir -p android/src/main/jniLibs/{arm64-v8a,armeabi-v7a,x86_64} +cp target/aarch64-linux-android/release/libprovekit_ffi.a android/src/main/jniLibs/arm64-v8a/ +cp target/armv7-linux-androideabi/release/libprovekit_ffi.a android/src/main/jniLibs/armeabi-v7a/ +cp target/x86_64-linux-android/release/libprovekit_ffi.a android/src/main/jniLibs/x86_64/ +``` + +## Usage + +### C/C++ +```c +#include "provekit_ffi.h" + +int main() { + // Initialize the library + if (pk_init() != PK_SUCCESS) { + return 1; + } + + // Option 1: Prove and write to file + int result = pk_prove_to_file( + "/path/to/scheme.pkp", + "/path/to/input.toml", + "/path/to/output.np" + ); + + if (result == PK_SUCCESS) { + printf("Proof written to file successfully\n"); + } + + // Option 2: Prove and get JSON in memory + PKBuf proof_buf; + result = pk_prove_to_json( + "/path/to/scheme.pkp", + "/path/to/input.toml", + &proof_buf + ); + + if (result == PK_SUCCESS) { + // Use proof_buf.ptr and proof_buf.len as JSON string + printf("JSON proof generated: %zu bytes\n", proof_buf.len); + printf("Proof JSON: %.*s\n", (int)proof_buf.len, proof_buf.ptr); + + // Free the buffer + pk_free_buf(proof_buf); + } + + return 0; +} +``` + +### Swift +```swift +import Foundation +import ProvekitFFI + +// Initialize ProveKit +guard pk_init() == PK_SUCCESS else { + fatalError("Failed to initialize ProveKit") +} + +// Option 1: Prove and write to file +let fileResult = pk_prove_to_file( + proverPath, + inputPath, + outputPath +) + +guard fileResult == PK_SUCCESS else { + fatalError("File proving failed with error: \(fileResult)") +} + +// Option 2: Prove and get JSON in memory +var proofBuf = PKBuf(ptr: nil, len: 0) +let jsonResult = pk_prove_to_json( + proverPath, + inputPath, + &proofBuf +) + +guard jsonResult == PK_SUCCESS else { + fatalError("JSON proving failed with error: \(jsonResult)") +} + +// Convert to Swift String (JSON) +let jsonString = String( + bytesNoCopy: proofBuf.ptr, + length: proofBuf.len, + encoding: .utf8, + freeWhenDone: false +) + +print("Proof JSON: \(jsonString ?? "Invalid UTF-8")") + +// Free the buffer +pk_free_buf(proofBuf) +``` + +### Kotlin (Android) +```kotlin +// Load the native library +System.loadLibrary("provekit_ffi") + +// Initialize ProveKit +if (pk_init() != PK_SUCCESS) { + throw RuntimeException("Failed to initialize ProveKit") +} + +// Option 1: Prove and write to file +val fileResult = pk_prove_to_file( + proverPath, + inputPath, + outputPath +) + +if (fileResult != PK_SUCCESS) { + throw RuntimeException("File proving failed with error: $fileResult") +} + +// Option 2: Prove and get JSON in memory +val proofBuf = PKBuf() +val jsonResult = pk_prove_to_json( + proverPath, + inputPath, + proofBuf +) + +if (jsonResult != PK_SUCCESS) { + throw RuntimeException("JSON proving failed with error: $jsonResult") +} + +// Convert to String (JSON) +val jsonBytes = ByteArray(proofBuf.len.toInt()) +// Copy memory from native buffer to Java byte array +// (implementation depends on JNI wrapper) +val jsonString = String(jsonBytes, Charsets.UTF_8) +println("Proof JSON: $jsonString") + +// Free the buffer +pk_free_buf(proofBuf) +``` + +### Python (via ctypes) +```python +import ctypes +from ctypes import Structure, c_char_p, c_int, c_size_t, POINTER + +# Load the library +lib = ctypes.CDLL('./libprovekit_ffi.so') # or .dylib on macOS, .dll on Windows + +# Define structures +class PKBuf(Structure): + _fields_ = [("ptr", POINTER(ctypes.c_uint8)), ("len", c_size_t)] + +# Define function signatures +lib.pk_init.restype = c_int +lib.pk_prove_to_file.argtypes = [c_char_p, c_char_p, c_char_p] +lib.pk_prove_to_file.restype = c_int +lib.pk_prove_to_json.argtypes = [c_char_p, c_char_p, POINTER(PKBuf)] +lib.pk_prove_to_json.restype = c_int +lib.pk_free_buf.argtypes = [PKBuf] + +# Initialize ProveKit +if lib.pk_init() != 0: # PK_SUCCESS = 0 + raise RuntimeError("Failed to initialize ProveKit") + +# Option 1: Prove and write to file +file_result = lib.pk_prove_to_file( + prover_path.encode('utf-8'), + input_path.encode('utf-8'), + output_path.encode('utf-8') +) + +if file_result != 0: + raise RuntimeError(f"File proving failed with error: {file_result}") + +# Option 2: Prove and get JSON in memory +proof_buf = PKBuf() +json_result = lib.pk_prove_to_json( + prover_path.encode('utf-8'), + input_path.encode('utf-8'), + ctypes.byref(proof_buf) +) + +if json_result != 0: + raise RuntimeError(f"JSON proving failed with error: {json_result}") + +# Convert to string (JSON) +json_bytes = ctypes.string_at(proof_buf.ptr, proof_buf.len) +json_string = json_bytes.decode('utf-8') +print(f"Proof JSON: {json_string}") + +# Free the buffer +lib.pk_free_buf(proof_buf) +``` + +## API Reference + +### Functions + +- `pk_init()` - Initialize the library (call once) +- `pk_prove_to_file()` - Generate proof and write to file +- `pk_prove_to_json()` - Generate proof and return as JSON string in memory buffer +- `pk_free_buf()` - Free buffers returned by ProveKit functions +- `pk_set_allocator()` - Set custom allocator functions for memory management (optional) + +### Error Codes + +- `PK_SUCCESS` (0) - Operation successful +- `PK_INVALID_INPUT` (1) - Invalid input parameters +- `PK_SCHEME_READ_ERROR` (2) - Failed to read scheme file +- `PK_WITNESS_READ_ERROR` (3) - Failed to read witness/input file +- `PK_PROOF_ERROR` (4) - Failed to generate proof +- `PK_SERIALIZATION_ERROR` (5) - Failed to serialize output +- `PK_UTF8_ERROR` (6) - UTF-8 conversion error +- `PK_FILE_WRITE_ERROR` (7) - File write error + +## File Formats + +### Input Files +- **Prover files**: `.pkp` (binary) or `.json` (JSON format) - prepared proof scheme +- **Witness files**: `.toml` (TOML format with input values) + +### Output Files +- **Proof files**: `.np` (binary) or `.json` (JSON format) + +## Memory Management + +All buffers returned by ProveKit functions must be freed using `pk_free_buf()`. Failure to do so will result in memory leaks. + +### Custom Allocator + +By default, ProveKit uses the system allocator. To use a custom allocator (e.g., for iOS memory tracking), call `pk_set_allocator()` before any other ProveKit functions: + +```c +void* my_alloc(size_t size, size_t align) { + // Custom allocation logic +} + +void my_dealloc(void* ptr, size_t size, size_t align) { + // Custom deallocation logic +} + +// Set custom allocator (call once, before pk_init) +pk_set_allocator(my_alloc, my_dealloc); +``` + +If `pk_set_allocator()` is not called, the system allocator is used. + +## Thread Safety + +The FFI functions are not guaranteed to be thread-safe. If you need to call ProveKit functions from multiple threads, ensure proper synchronization. + + diff --git a/tooling/provekit-ffi/include/provekit_ffi.h b/tooling/provekit-ffi/include/provekit_ffi.h new file mode 100644 index 00000000..7879d884 --- /dev/null +++ b/tooling/provekit-ffi/include/provekit_ffi.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + /// Buffer structure for returning data from ProveKit functions. + /// The caller is responsible for freeing buffers using pk_free_buf. + typedef struct + { + /// Pointer to the data + uint8_t *ptr; + /// Length of the data in bytes + size_t len; + } PKBuf; + + /// Error codes returned by ProveKit functions + typedef enum + { + /// Success + PK_SUCCESS = 0, + /// Invalid input parameters (null pointers, etc.) + PK_INVALID_INPUT = 1, + /// Failed to read scheme file + PK_SCHEME_READ_ERROR = 2, + /// Failed to read witness/input file + PK_WITNESS_READ_ERROR = 3, + /// Failed to generate proof + PK_PROOF_ERROR = 4, + /// Failed to serialize output + PK_SERIALIZATION_ERROR = 5, + /// UTF-8 conversion error + PK_UTF8_ERROR = 6, + /// File write error + PK_FILE_WRITE_ERROR = 7, + } PKError; + + /// Initialize the ProveKit library. + /// + /// This function should be called once before using any other ProveKit functions. + /// + /// @return PK_SUCCESS on success + int pk_init(void); + + /// Prove a Noir program and write the proof to a file. + /// + /// @param prover_path Path to the prepared proof scheme (.pkp file) + /// @param input_path Path to the witness/input values (.toml file) + /// @param out_path Path where to write the proof file (.np or .json) + /// @return PK_SUCCESS on success, or an appropriate error code on failure + int pk_prove_to_file(const char *prover_path, const char *input_path, const char *out_path); + + /// Prove a Noir program and return the proof as JSON string. + /// + /// This function is only available when the library is built with JSON support. + /// + /// @param prover_path Path to the prepared proof scheme (.pkp file) + /// @param input_path Path to the witness/input values (.toml file) + /// @param out_buf Output buffer to store the JSON string (must be freed with pk_free_buf) + /// @return PK_SUCCESS on success, or an appropriate error code on failure + int pk_prove_to_json(const char *prover_path, const char *input_path, PKBuf *out_buf); + + /// Free a buffer allocated by ProveKit FFI functions. + /// + /// @param buf The buffer to free + void pk_free_buf(PKBuf buf); + + /// Set custom allocator functions for memory management. + /// + /// When set, all allocations will be delegated to the provided functions. + /// Pass NULL for both to use the system allocator (default). + /// + /// @param alloc_fn Function to allocate memory (size, align) -> ptr, or NULL + /// @param dealloc_fn Function to deallocate memory (ptr, size, align), or NULL + void pk_set_allocator(void *(*_Nullable alloc_fn)(size_t size, size_t align), + void (*_Nullable dealloc_fn)(void *ptr, size_t size, size_t align)); + +#ifdef __cplusplus +} +#endif diff --git a/tooling/provekit-ffi/module.modulemap b/tooling/provekit-ffi/module.modulemap new file mode 100644 index 00000000..e2934bf4 --- /dev/null +++ b/tooling/provekit-ffi/module.modulemap @@ -0,0 +1,4 @@ +module ProvekitFFI [system] { + header "include/provekit_ffi.h" + export * +} diff --git a/tooling/provekit-ffi/src/ffi.rs b/tooling/provekit-ffi/src/ffi.rs new file mode 100644 index 00000000..0af6f8de --- /dev/null +++ b/tooling/provekit-ffi/src/ffi.rs @@ -0,0 +1,171 @@ +//! Main FFI functions for ProveKit. + +use { + crate::{ + types::{PKBuf, PKError}, + utils::c_str_to_str, + }, + anyhow::Result, + provekit_common::{file::read, Prover}, + provekit_prover::Prove, + std::{ + os::raw::{c_char, c_int}, + panic, + path::Path, + }, +}; + +/// Catches panics and converts them to error codes to prevent unwinding across +/// FFI boundary. +#[inline] +fn catch_panic(default: T, f: F) -> T +where + F: FnOnce() -> T + panic::UnwindSafe, +{ + panic::catch_unwind(f).unwrap_or(default) +} + +/// Prove a Noir program and write the proof to a file. +/// +/// # Arguments +/// +/// * `prover_path` - Path to the prepared proof scheme (.pkp file) +/// * `input_path` - Path to the witness/input values (.toml file) +/// * `out_path` - Path where to write the proof file (.np or .json) +/// +/// # Returns +/// +/// Returns `PKError::Success` on success, or an appropriate error code on +/// failure. +/// +/// # Safety +/// +/// The caller must ensure that all path parameters are valid null-terminated C +/// strings. +#[no_mangle] +pub unsafe extern "C" fn pk_prove_to_file( + prover_path: *const c_char, + input_path: *const c_char, + out_path: *const c_char, +) -> c_int { + catch_panic(PKError::ProofError.into(), || { + let result = (|| -> Result<(), PKError> { + let prover_path = c_str_to_str(prover_path)?; + let input_path = c_str_to_str(input_path)?; + let out_path = c_str_to_str(out_path)?; + + let prover: Prover = + read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; + + let proof = prover.prove(input_path).map_err(|_| PKError::ProofError)?; + + provekit_common::file::write(&proof, Path::new(out_path)) + .map_err(|_| PKError::FileWriteError)?; + + Ok(()) + })(); + + match result { + Ok(()) => PKError::Success.into(), + Err(error) => error.into(), + } + }) +} + +/// Prove a Noir program and return the proof as JSON string. +/// +/// This function is only available when the "json" feature is enabled. +/// +/// # Arguments +/// +/// * `scheme_path` - Path to the prepared proof scheme (.pkp file) +/// * `input_path` - Path to the witness/input values (.toml file) +/// * `out_buf` - Output buffer to store the JSON string +/// +/// # Returns +/// +/// Returns `PKError::Success` on success, or an appropriate error code on +/// failure. The caller must free the returned buffer using `pk_free_buf`. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `prover_path` and `input_path` are valid null-terminated C strings +/// - `out_buf` is a valid pointer to a `PKBuf` structure +/// - The returned buffer is freed using `pk_free_buf` +#[no_mangle] +pub unsafe extern "C" fn pk_prove_to_json( + prover_path: *const c_char, + input_path: *const c_char, + out_buf: *mut PKBuf, +) -> c_int { + if out_buf.is_null() { + return PKError::InvalidInput.into(); + } + + catch_panic(PKError::ProofError.into(), || { + let out_buf = match out_buf.as_mut() { + Some(buf) => buf, + None => return PKError::InvalidInput.into(), + }; + + *out_buf = PKBuf::empty(); + + let result = (|| -> Result, PKError> { + let prover_path = c_str_to_str(prover_path)?; + let input_path = c_str_to_str(input_path)?; + + let prover: Prover = + read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; + + let proof = prover.prove(input_path).map_err(|_| PKError::ProofError)?; + + let json_string = + serde_json::to_string(&proof).map_err(|_| PKError::SerializationError)?; + + Ok(json_string.into_bytes()) + })(); + + match result { + Ok(json_bytes) => { + *out_buf = PKBuf::from_vec(json_bytes); + PKError::Success.into() + } + Err(error) => error.into(), + } + }) +} + +/// Free a buffer allocated by ProveKit FFI functions. +/// +/// # Arguments +/// +/// * `buf` - The buffer to free +/// +/// # Safety +/// +/// The caller must ensure that: +/// - The buffer was allocated by a ProveKit FFI function +/// - The buffer is not used after calling this function +/// - This function is called exactly once for each allocated buffer +#[no_mangle] +pub unsafe extern "C" fn pk_free_buf(buf: PKBuf) { + if !buf.ptr.is_null() && buf.len > 0 { + drop(Vec::from_raw_parts(buf.ptr, buf.len, buf.len)); + } +} + +/// Initialize the ProveKit library. +/// +/// This function should be called once before using any other ProveKit +/// functions. It sets up logging and other global state. +/// +/// # Returns +/// +/// Returns `PKError::Success` on success. +#[no_mangle] +pub extern "C" fn pk_init() -> c_int { + // Initialize tracing/logging if needed + // For now, we'll keep it simple and just return success + PKError::Success.into() +} diff --git a/tooling/provekit-ffi/src/ffi_allocator.rs b/tooling/provekit-ffi/src/ffi_allocator.rs new file mode 100644 index 00000000..85b65f8a --- /dev/null +++ b/tooling/provekit-ffi/src/ffi_allocator.rs @@ -0,0 +1,78 @@ +//! Callback-based allocator that delegates to host via FFI. +//! +//! SAFETY: pk_set_allocator must be called before any allocations occur. +//! This is guaranteed by calling it in Swift's init() before pk_init(). + +use std::{ + alloc::{GlobalAlloc, Layout}, + ffi::c_void, + ptr, +}; + +type AllocFn = unsafe extern "C" fn(size: usize, align: usize) -> *mut c_void; +type DeallocFn = unsafe extern "C" fn(ptr: *mut c_void, size: usize, align: usize); + +static mut ALLOC_FN: Option = None; +static mut DEALLOC_FN: Option = None; + +#[no_mangle] +pub unsafe extern "C" fn pk_set_allocator( + alloc_fn: Option, + dealloc_fn: Option, +) { + ALLOC_FN = alloc_fn; + DEALLOC_FN = dealloc_fn; +} + +struct FfiAllocator; + +unsafe impl GlobalAlloc for FfiAllocator { + #[inline(always)] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + match ALLOC_FN { + Some(f) => f(layout.size(), layout.align()) as *mut u8, + None => std::alloc::System.alloc(layout), + } + } + + #[inline(always)] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + match DEALLOC_FN { + Some(f) => f(ptr as *mut c_void, layout.size(), layout.align()), + None => std::alloc::System.dealloc(ptr, layout), + } + } + + #[inline(always)] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + match ALLOC_FN { + Some(f) => { + let ptr = f(layout.size(), layout.align()) as *mut u8; + if !ptr.is_null() { + ptr::write_bytes(ptr, 0, layout.size()); + } + ptr + } + None => std::alloc::System.alloc_zeroed(layout), + } + } + + #[inline(always)] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + match (ALLOC_FN, DEALLOC_FN) { + (Some(alloc), Some(dealloc)) => { + let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); + let new_ptr = alloc(new_layout.size(), new_layout.align()) as *mut u8; + if !new_ptr.is_null() { + ptr::copy_nonoverlapping(ptr, new_ptr, layout.size().min(new_size)); + dealloc(ptr as *mut c_void, layout.size(), layout.align()); + } + new_ptr + } + _ => std::alloc::System.realloc(ptr, layout, new_size), + } + } +} + +#[global_allocator] +static ALLOCATOR: FfiAllocator = FfiAllocator; diff --git a/tooling/provekit-ffi/src/lib.rs b/tooling/provekit-ffi/src/lib.rs new file mode 100644 index 00000000..e277b804 --- /dev/null +++ b/tooling/provekit-ffi/src/lib.rs @@ -0,0 +1,32 @@ +//! FFI bindings for ProveKit, enabling integration with multiple programming +//! languages and platforms. +//! +//! This crate provides C-compatible functions for loading Noir proof schemes, +//! reading witness inputs, and generating proofs that can be called from any +//! language that supports C FFI (Swift, Kotlin, Python, JavaScript, etc.). +//! +//! # Architecture +//! +//! The FFI bindings are organized into several modules: +//! - `types`: Type definitions (PKBuf, PKError, etc.) +//! - `ffi`: Main FFI functions exposed via C ABI +//! - `utils`: Internal utility functions +//! +//! # Usage +//! +//! 1. Call `pk_init()` once before using any other functions +//! 2. Use `pk_prove_to_file()` or `pk_prove_to_json()` to generate proofs +//! 3. Free any returned buffers using `pk_free_buf()` +//! +//! # Safety +//! +//! All FFI functions are marked as `unsafe extern "C"` and require the caller +//! to ensure proper memory management and valid pointer usage. + +pub mod ffi; +mod ffi_allocator; +pub mod types; +pub mod utils; + +// Re-export public types and functions for convenience +pub use {ffi::*, types::*}; diff --git a/tooling/provekit-ffi/src/types.rs b/tooling/provekit-ffi/src/types.rs new file mode 100644 index 00000000..4447310c --- /dev/null +++ b/tooling/provekit-ffi/src/types.rs @@ -0,0 +1,59 @@ +//! Type definitions for ProveKit FFI bindings. + +use std::{os::raw::c_int, ptr}; + +/// Buffer structure for returning data to foreign languages. +/// The caller is responsible for freeing the buffer using `pk_free_buf`. +#[repr(C)] +pub struct PKBuf { + /// Pointer to the data + pub ptr: *mut u8, + /// Length of the data in bytes + pub len: usize, +} + +impl PKBuf { + /// Create an empty buffer + pub fn empty() -> Self { + Self { + ptr: ptr::null_mut(), + len: 0, + } + } + + /// Create a buffer from a `Vec`, transferring ownership + pub fn from_vec(mut v: Vec) -> Self { + let ptr = v.as_mut_ptr(); + let len = v.len(); + std::mem::forget(v); // Transfer ownership to caller + Self { ptr, len } + } +} + +/// Error codes returned by FFI functions +#[repr(C)] +#[derive(Debug)] +pub enum PKError { + /// Success + Success = 0, + /// Invalid input parameters (null pointers, etc.) + InvalidInput = 1, + /// Failed to read scheme file + SchemeReadError = 2, + /// Failed to read witness/input file + WitnessReadError = 3, + /// Failed to generate proof + ProofError = 4, + /// Failed to serialize output + SerializationError = 5, + /// UTF-8 conversion error + Utf8Error = 6, + /// File write error + FileWriteError = 7, +} + +impl From for c_int { + fn from(error: PKError) -> Self { + error as c_int + } +} diff --git a/tooling/provekit-ffi/src/utils.rs b/tooling/provekit-ffi/src/utils.rs new file mode 100644 index 00000000..052604b7 --- /dev/null +++ b/tooling/provekit-ffi/src/utils.rs @@ -0,0 +1,19 @@ +//! Utility functions for ProveKit FFI bindings. + +use { + crate::types::PKError, + anyhow::Result, + std::{ffi::CStr, os::raw::c_char}, +}; + +/// Internal helper to convert C string to Rust string +/// +/// # Safety +/// +/// The caller must ensure that `ptr` is a valid null-terminated C string. +pub unsafe fn c_str_to_str(ptr: *const c_char) -> Result<&'static str, PKError> { + if ptr.is_null() { + return Err(PKError::InvalidInput); + } + CStr::from_ptr(ptr).to_str().map_err(|_| PKError::Utf8Error) +}