From 021ceab62d6b9c874bc5a0ccdd271d60bfd41136 Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Mon, 1 Sep 2025 09:09:39 +0530 Subject: [PATCH 1/9] feat: add verifier server --- tooling/verifier-server/Cargo.toml | 3 + tooling/verifier-server/Dockerfile | 7 + tooling/verifier-server/README.md | 179 +++++++++++++++++++++ tooling/verifier-server/docker-compose.yml | 7 + 4 files changed, 196 insertions(+) diff --git a/tooling/verifier-server/Cargo.toml b/tooling/verifier-server/Cargo.toml index 88415604..e0804be9 100644 --- a/tooling/verifier-server/Cargo.toml +++ b/tooling/verifier-server/Cargo.toml @@ -22,7 +22,10 @@ serde.workspace = true serde_json.workspace = true sha2.workspace = true tokio.workspace = true +<<<<<<< HEAD tokio-util.workspace = true +======= +>>>>>>> 8764374 (feat: add verifier server) tower.workspace = true tower-http.workspace = true tracing.workspace = true diff --git a/tooling/verifier-server/Dockerfile b/tooling/verifier-server/Dockerfile index e1d6fce9..30354003 100644 --- a/tooling/verifier-server/Dockerfile +++ b/tooling/verifier-server/Dockerfile @@ -35,7 +35,11 @@ FROM rust:1.85-alpine AS rust-builder RUN apk add --no-cache \ musl-dev \ pkgconfig \ +<<<<<<< HEAD libressl-dev \ +======= + openssl-dev \ +>>>>>>> 8764374 (feat: add verifier server) git WORKDIR /rust-app @@ -48,11 +52,14 @@ COPY provekit/ ./provekit/ COPY skyscraper/ ./skyscraper/ COPY tooling/ ./tooling/ +<<<<<<< HEAD # Set environment variables for LibreSSL static linking ENV OPENSSL_STATIC=1 ENV OPENSSL_LIB_DIR=/usr/lib ENV OPENSSL_INCLUDE_DIR=/usr/include +======= +>>>>>>> 8764374 (feat: add verifier server) # Build the verifier server in release mode RUN cargo build --release --bin verifier-server diff --git a/tooling/verifier-server/README.md b/tooling/verifier-server/README.md index b852c9a7..45079984 100644 --- a/tooling/verifier-server/README.md +++ b/tooling/verifier-server/README.md @@ -1,14 +1,43 @@ # ProveKit Verifier Server +<<<<<<< HEAD HTTP server combining Rust (API) + Go (verifier binary) for WHIR-based proof verification. ## Quick Start +======= +A containerized verifier server that combines a Rust HTTP server with a Go-based verifier binary for processing WHIR-based proof verification requests. + +## Architecture + +The verifier server consists of two main components: + +1. **Rust HTTP Server** (`verifier-server`): Handles HTTP requests, downloads artifacts, and orchestrates verification +2. **Go Verifier Binary** (`verifier`): Performs the actual WHIR proof verification using gnark + +## Building + +### Prerequisites + +- Docker and Docker Compose +- Alternatively: Rust 1.85+ and Go 1.23.3+ for local development + +### Using Docker (Recommended) + +#### Option 1: Using the build script +```bash +cd tooling/verifier-server +./build.sh +``` + +#### Option 2: Using docker-compose +>>>>>>> 8764374 (feat: add verifier server) ```bash cd tooling/verifier-server docker-compose up --build ``` +<<<<<<< HEAD Server runs at `http://localhost:3000` ## API @@ -29,11 +58,85 @@ curl -X POST http://localhost:3000/verify \ "vkUrl": "https://example.com/verification_key.bin", (optional) "np": { /* NoirProof JSON */ }, }' +======= +#### Option 3: Manual Docker build +```bash +# From the project root +docker build -f tooling/verifier-server/Dockerfile -t provekit-verifier-server . +``` + +### Local Development + +#### Build Rust server +```bash +cargo build --release --bin verifier-server +``` + +#### Build Go verifier binary +```bash +cd recursive-verifier +go build -o verifier ./cmd/cli +``` + +## Running + +### Using Docker Compose (Recommended) +```bash +cd tooling/verifier-server +docker-compose up +``` + +The server will be available at `http://localhost:3000` + +### Using Docker directly +```bash +docker run -p 3000:3000 provekit-verifier-server:latest +``` + +### Local Development +```bash +# Make sure the Go verifier binary is available in the PATH or same directory +./target/release/verifier-server +``` + +## API Endpoints + +### Health Check +```bash +GET /health +``` + +Returns server status and version information. + +### Proof Verification +```bash +POST /verify +``` + +Verifies a Noir proof using the WHIR verification system. + +**Request Body:** +```json +{ + "nps_url": "https://example.com/scheme.nps", + "r1cs_url": "https://example.com/r1cs.json", + "pk_url": "https://example.com/proving_key.bin", + "vk_url": "https://example.com/verification_key.bin", + "noir_proof": "", + "verification_params": { + "max_verification_time": 300 + }, + "metadata": { + "request_id": "unique-request-id" + } +} +>>>>>>> 8764374 (feat: add verifier server) ``` **Response:** ```json { +<<<<<<< HEAD "isValid": true, "result": { "status": "valid", @@ -82,3 +185,79 @@ cargo run --bin verifier-server - **Rust HTTP Server**: Handles requests, downloads artifacts, orchestrates verification - **Go Verifier Binary**: Performs WHIR proof verification using gnark - **Artifact Caching**: Downloads cached by URL hash for performance +======= + "status": "success", + "verification_time_ms": 1500, + "request_id": "unique-request-id", + "timestamp": "2024-01-01T12:00:00Z" +} +``` + +## Configuration + +The server can be configured using environment variables: + +- `RUST_LOG`: Log level (default: `info`) +- `RUST_BACKTRACE`: Enable backtraces (default: `1`) + +## File Structure + +``` +tooling/verifier-server/ +├── src/ +│ ├── main.rs # Server entry point +│ ├── handlers.rs # HTTP request handlers +│ ├── models.rs # Data models +│ └── error.rs # Error handling +├── Dockerfile # Multi-stage Docker build +├── docker-compose.yml # Docker Compose configuration +├── build.sh # Build script +├── README.md # This file +└── Cargo.toml # Rust dependencies +``` + +## Troubleshooting + +### Common Issues + +1. **Port already in use**: Change the port mapping in docker-compose.yml or use `-p 3001:3000` instead +2. **Build failures**: Ensure Docker has enough memory allocated (at least 4GB recommended) +3. **Go binary not found**: The Docker build automatically includes the Go verifier binary + +### Logs + +To view logs: +```bash +docker-compose logs -f verifier-server +``` + +### Health Check + +The container includes a health check that pings `/health` every 30 seconds. Check container health: +```bash +docker ps +``` + +Look for the "STATUS" column to see health status. + +## Development + +### Local Testing + +1. Build both components locally +2. Ensure the Go `verifier` binary is in your PATH or the same directory as the Rust server +3. Run the Rust server: `cargo run --bin verifier-server` + +### Debugging + +Enable debug logging: +```bash +RUST_LOG=debug cargo run --bin verifier-server +``` + +Or in Docker: +```yaml +environment: + - RUST_LOG=debug +``` +>>>>>>> 8764374 (feat: add verifier server) diff --git a/tooling/verifier-server/docker-compose.yml b/tooling/verifier-server/docker-compose.yml index feaec807..aa60af36 100644 --- a/tooling/verifier-server/docker-compose.yml +++ b/tooling/verifier-server/docker-compose.yml @@ -7,7 +7,11 @@ services: dockerfile: tooling/verifier-server/Dockerfile args: TARGETOS: linux +<<<<<<< HEAD TARGETARCH: arm64 +======= + TARGETARCH: amd64 +>>>>>>> 8764374 (feat: add verifier server) ports: - "3000:3000" environment: @@ -16,7 +20,10 @@ services: volumes: # Mount artifacts directory for persistence (optional) - ./artifacts:/app/artifacts +<<<<<<< HEAD user: "1001:1001" # Match the appuser UID/GID from Dockerfile +======= +>>>>>>> 8764374 (feat: add verifier server) restart: unless-stopped healthcheck: test: From 1b32cbcc2e6cb23a8d029bf6972b2f69c89eb9ee Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Tue, 23 Sep 2025 00:02:44 +0530 Subject: [PATCH 2/9] feat(ffi): add provekit-ffi --- Cargo.toml | 3 +- tooling/provekit-ffi/Cargo.toml | 34 +++ tooling/provekit-ffi/README.md | 301 ++++++++++++++++++++ tooling/provekit-ffi/include/provekit_ffi.h | 80 ++++++ tooling/provekit-ffi/module.modulemap | 4 + tooling/provekit-ffi/src/ffi.rs | 163 +++++++++++ tooling/provekit-ffi/src/lib.rs | 31 ++ tooling/provekit-ffi/src/types.rs | 59 ++++ tooling/provekit-ffi/src/utils.rs | 19 ++ tooling/verifier-server/Cargo.toml | 3 - tooling/verifier-server/Dockerfile | 7 - tooling/verifier-server/README.md | 179 ------------ tooling/verifier-server/docker-compose.yml | 9 +- 13 files changed, 694 insertions(+), 198 deletions(-) create mode 100644 tooling/provekit-ffi/Cargo.toml create mode 100644 tooling/provekit-ffi/README.md create mode 100644 tooling/provekit-ffi/include/provekit_ffi.h create mode 100644 tooling/provekit-ffi/module.modulemap create mode 100644 tooling/provekit-ffi/src/ffi.rs create mode 100644 tooling/provekit-ffi/src/lib.rs create mode 100644 tooling/provekit-ffi/src/types.rs create mode 100644 tooling/provekit-ffi/src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index d1f78c19..be8ac9f8 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", @@ -57,7 +58,6 @@ missing_docs_in_private_items = { level = "allow", priority = 1 } missing_safety_doc = { level = "deny", priority = 1 } [profile.release] -debug = true # Generate symbol info for profiling opt-level = 3 codegen-units = 1 lto = "fat" @@ -82,6 +82,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/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..7ac1e422 --- /dev/null +++ b/tooling/provekit-ffi/README.md @@ -0,0 +1,301 @@ +# 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.nps", + "/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.nps", + "/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( + schemePath, + 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( + schemePath, + 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( + schemePath, + 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( + schemePath, + 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 + +# 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( + scheme_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( + scheme_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_last_error()` - Get last error message (currently returns static message) + +### 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 +- **Scheme files**: `.nps` (binary) or `.json` (JSON format) +- **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. + +## 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. + +## Features + +The FFI library is built with JSON support by default, providing the `pk_prove_to_json` function. diff --git a/tooling/provekit-ffi/include/provekit_ffi.h b/tooling/provekit-ffi/include/provekit_ffi.h new file mode 100644 index 00000000..8a24641d --- /dev/null +++ b/tooling/provekit-ffi/include/provekit_ffi.h @@ -0,0 +1,80 @@ +#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 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 (.nps 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 (.nps 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); + + /// Get the last error message as a C string. + /// + /// @return A null-terminated C string containing the last error message, + /// or NULL if no error occurred. The returned string is static and + /// does not need to be freed. + const char *pk_last_error(void); + +#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..3edaf4ec --- /dev/null +++ b/tooling/provekit-ffi/src/ffi.rs @@ -0,0 +1,163 @@ +//! 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}, + path::Path, + }, +}; + +/// Prove a Noir program and write the proof to a file. +/// +/// # Arguments +/// +/// * `prover_path` - Path to the prepared proof scheme (.nps 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 { + 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)?; + + // Read the scheme file (.nps or .json) + let mut prover: Prover = + read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; + + // Generate the proof + let proof = prover.prove(&input_path).map_err(|_| PKError::ProofError)?; + + // Write the proof to file + 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 (.nps 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 { + // Validate inputs + if out_buf.is_null() { + return PKError::InvalidInput.into(); + } + + let out_buf = match out_buf.as_mut() { + Some(buf) => buf, + None => return PKError::InvalidInput.into(), + }; + + // Initialize output buffer to empty state + *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)?; + + // Read the scheme file (.pkp or .json) + let mut prover: Prover = + read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; + + // Generate the proof + let proof = prover.prove(&input_path).map_err(|_| PKError::ProofError)?; + + // Serialize to JSON + 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/lib.rs b/tooling/provekit-ffi/src/lib.rs new file mode 100644 index 00000000..658fdecf --- /dev/null +++ b/tooling/provekit-ffi/src/lib.rs @@ -0,0 +1,31 @@ +//! 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; +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..073b1156 --- /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) +} diff --git a/tooling/verifier-server/Cargo.toml b/tooling/verifier-server/Cargo.toml index e0804be9..88415604 100644 --- a/tooling/verifier-server/Cargo.toml +++ b/tooling/verifier-server/Cargo.toml @@ -22,10 +22,7 @@ serde.workspace = true serde_json.workspace = true sha2.workspace = true tokio.workspace = true -<<<<<<< HEAD tokio-util.workspace = true -======= ->>>>>>> 8764374 (feat: add verifier server) tower.workspace = true tower-http.workspace = true tracing.workspace = true diff --git a/tooling/verifier-server/Dockerfile b/tooling/verifier-server/Dockerfile index 30354003..e1d6fce9 100644 --- a/tooling/verifier-server/Dockerfile +++ b/tooling/verifier-server/Dockerfile @@ -35,11 +35,7 @@ FROM rust:1.85-alpine AS rust-builder RUN apk add --no-cache \ musl-dev \ pkgconfig \ -<<<<<<< HEAD libressl-dev \ -======= - openssl-dev \ ->>>>>>> 8764374 (feat: add verifier server) git WORKDIR /rust-app @@ -52,14 +48,11 @@ COPY provekit/ ./provekit/ COPY skyscraper/ ./skyscraper/ COPY tooling/ ./tooling/ -<<<<<<< HEAD # Set environment variables for LibreSSL static linking ENV OPENSSL_STATIC=1 ENV OPENSSL_LIB_DIR=/usr/lib ENV OPENSSL_INCLUDE_DIR=/usr/include -======= ->>>>>>> 8764374 (feat: add verifier server) # Build the verifier server in release mode RUN cargo build --release --bin verifier-server diff --git a/tooling/verifier-server/README.md b/tooling/verifier-server/README.md index 45079984..b852c9a7 100644 --- a/tooling/verifier-server/README.md +++ b/tooling/verifier-server/README.md @@ -1,43 +1,14 @@ # ProveKit Verifier Server -<<<<<<< HEAD HTTP server combining Rust (API) + Go (verifier binary) for WHIR-based proof verification. ## Quick Start -======= -A containerized verifier server that combines a Rust HTTP server with a Go-based verifier binary for processing WHIR-based proof verification requests. - -## Architecture - -The verifier server consists of two main components: - -1. **Rust HTTP Server** (`verifier-server`): Handles HTTP requests, downloads artifacts, and orchestrates verification -2. **Go Verifier Binary** (`verifier`): Performs the actual WHIR proof verification using gnark - -## Building - -### Prerequisites - -- Docker and Docker Compose -- Alternatively: Rust 1.85+ and Go 1.23.3+ for local development - -### Using Docker (Recommended) - -#### Option 1: Using the build script -```bash -cd tooling/verifier-server -./build.sh -``` - -#### Option 2: Using docker-compose ->>>>>>> 8764374 (feat: add verifier server) ```bash cd tooling/verifier-server docker-compose up --build ``` -<<<<<<< HEAD Server runs at `http://localhost:3000` ## API @@ -58,85 +29,11 @@ curl -X POST http://localhost:3000/verify \ "vkUrl": "https://example.com/verification_key.bin", (optional) "np": { /* NoirProof JSON */ }, }' -======= -#### Option 3: Manual Docker build -```bash -# From the project root -docker build -f tooling/verifier-server/Dockerfile -t provekit-verifier-server . -``` - -### Local Development - -#### Build Rust server -```bash -cargo build --release --bin verifier-server -``` - -#### Build Go verifier binary -```bash -cd recursive-verifier -go build -o verifier ./cmd/cli -``` - -## Running - -### Using Docker Compose (Recommended) -```bash -cd tooling/verifier-server -docker-compose up -``` - -The server will be available at `http://localhost:3000` - -### Using Docker directly -```bash -docker run -p 3000:3000 provekit-verifier-server:latest -``` - -### Local Development -```bash -# Make sure the Go verifier binary is available in the PATH or same directory -./target/release/verifier-server -``` - -## API Endpoints - -### Health Check -```bash -GET /health -``` - -Returns server status and version information. - -### Proof Verification -```bash -POST /verify -``` - -Verifies a Noir proof using the WHIR verification system. - -**Request Body:** -```json -{ - "nps_url": "https://example.com/scheme.nps", - "r1cs_url": "https://example.com/r1cs.json", - "pk_url": "https://example.com/proving_key.bin", - "vk_url": "https://example.com/verification_key.bin", - "noir_proof": "", - "verification_params": { - "max_verification_time": 300 - }, - "metadata": { - "request_id": "unique-request-id" - } -} ->>>>>>> 8764374 (feat: add verifier server) ``` **Response:** ```json { -<<<<<<< HEAD "isValid": true, "result": { "status": "valid", @@ -185,79 +82,3 @@ cargo run --bin verifier-server - **Rust HTTP Server**: Handles requests, downloads artifacts, orchestrates verification - **Go Verifier Binary**: Performs WHIR proof verification using gnark - **Artifact Caching**: Downloads cached by URL hash for performance -======= - "status": "success", - "verification_time_ms": 1500, - "request_id": "unique-request-id", - "timestamp": "2024-01-01T12:00:00Z" -} -``` - -## Configuration - -The server can be configured using environment variables: - -- `RUST_LOG`: Log level (default: `info`) -- `RUST_BACKTRACE`: Enable backtraces (default: `1`) - -## File Structure - -``` -tooling/verifier-server/ -├── src/ -│ ├── main.rs # Server entry point -│ ├── handlers.rs # HTTP request handlers -│ ├── models.rs # Data models -│ └── error.rs # Error handling -├── Dockerfile # Multi-stage Docker build -├── docker-compose.yml # Docker Compose configuration -├── build.sh # Build script -├── README.md # This file -└── Cargo.toml # Rust dependencies -``` - -## Troubleshooting - -### Common Issues - -1. **Port already in use**: Change the port mapping in docker-compose.yml or use `-p 3001:3000` instead -2. **Build failures**: Ensure Docker has enough memory allocated (at least 4GB recommended) -3. **Go binary not found**: The Docker build automatically includes the Go verifier binary - -### Logs - -To view logs: -```bash -docker-compose logs -f verifier-server -``` - -### Health Check - -The container includes a health check that pings `/health` every 30 seconds. Check container health: -```bash -docker ps -``` - -Look for the "STATUS" column to see health status. - -## Development - -### Local Testing - -1. Build both components locally -2. Ensure the Go `verifier` binary is in your PATH or the same directory as the Rust server -3. Run the Rust server: `cargo run --bin verifier-server` - -### Debugging - -Enable debug logging: -```bash -RUST_LOG=debug cargo run --bin verifier-server -``` - -Or in Docker: -```yaml -environment: - - RUST_LOG=debug -``` ->>>>>>> 8764374 (feat: add verifier server) diff --git a/tooling/verifier-server/docker-compose.yml b/tooling/verifier-server/docker-compose.yml index aa60af36..7ee94374 100644 --- a/tooling/verifier-server/docker-compose.yml +++ b/tooling/verifier-server/docker-compose.yml @@ -7,11 +7,7 @@ services: dockerfile: tooling/verifier-server/Dockerfile args: TARGETOS: linux -<<<<<<< HEAD TARGETARCH: arm64 -======= - TARGETARCH: amd64 ->>>>>>> 8764374 (feat: add verifier server) ports: - "3000:3000" environment: @@ -20,10 +16,7 @@ services: volumes: # Mount artifacts directory for persistence (optional) - ./artifacts:/app/artifacts -<<<<<<< HEAD - user: "1001:1001" # Match the appuser UID/GID from Dockerfile -======= ->>>>>>> 8764374 (feat: add verifier server) + user: "1001:1001" # Match the appuser UID/GID from Dockerfile restart: unless-stopped healthcheck: test: From bdbe7012824bb627a7827a74a8e0817f95247d5c Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Thu, 30 Oct 2025 09:56:19 +0530 Subject: [PATCH 3/9] chore: update README --- tooling/provekit-ffi/README.md | 21 ++++++++++----------- tooling/provekit-ffi/include/provekit_ffi.h | 13 ++++--------- tooling/provekit-ffi/src/ffi.rs | 12 +++++------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/tooling/provekit-ffi/README.md b/tooling/provekit-ffi/README.md index 7ac1e422..cc740953 100644 --- a/tooling/provekit-ffi/README.md +++ b/tooling/provekit-ffi/README.md @@ -83,7 +83,7 @@ int main() { // Option 1: Prove and write to file int result = pk_prove_to_file( - "/path/to/scheme.nps", + "/path/to/scheme.pkp", "/path/to/input.toml", "/path/to/output.np" ); @@ -95,7 +95,7 @@ int main() { // Option 2: Prove and get JSON in memory PKBuf proof_buf; result = pk_prove_to_json( - "/path/to/scheme.nps", + "/path/to/scheme.pkp", "/path/to/input.toml", &proof_buf ); @@ -125,7 +125,7 @@ guard pk_init() == PK_SUCCESS else { // Option 1: Prove and write to file let fileResult = pk_prove_to_file( - schemePath, + proverPath, inputPath, outputPath ) @@ -137,7 +137,7 @@ guard fileResult == PK_SUCCESS else { // Option 2: Prove and get JSON in memory var proofBuf = PKBuf(ptr: nil, len: 0) let jsonResult = pk_prove_to_json( - schemePath, + proverPath, inputPath, &proofBuf ) @@ -172,7 +172,7 @@ if (pk_init() != PK_SUCCESS) { // Option 1: Prove and write to file val fileResult = pk_prove_to_file( - schemePath, + proverPath, inputPath, outputPath ) @@ -184,7 +184,7 @@ if (fileResult != PK_SUCCESS) { // Option 2: Prove and get JSON in memory val proofBuf = PKBuf() val jsonResult = pk_prove_to_json( - schemePath, + proverPath, inputPath, proofBuf ) @@ -210,7 +210,7 @@ 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 +lib = ctypes.CDLL('./libprovekit_ffi.so') # or .dylib on macOS, .dll on Windows # Define structures class PKBuf(Structure): @@ -230,7 +230,7 @@ if lib.pk_init() != 0: # PK_SUCCESS = 0 # Option 1: Prove and write to file file_result = lib.pk_prove_to_file( - scheme_path.encode('utf-8'), + prover_path.encode('utf-8'), input_path.encode('utf-8'), output_path.encode('utf-8') ) @@ -241,7 +241,7 @@ if file_result != 0: # Option 2: Prove and get JSON in memory proof_buf = PKBuf() json_result = lib.pk_prove_to_json( - scheme_path.encode('utf-8'), + prover_path.encode('utf-8'), input_path.encode('utf-8'), ctypes.byref(proof_buf) ) @@ -266,7 +266,6 @@ lib.pk_free_buf(proof_buf) - `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_last_error()` - Get last error message (currently returns static message) ### Error Codes @@ -282,7 +281,7 @@ lib.pk_free_buf(proof_buf) ## File Formats ### Input Files -- **Scheme files**: `.nps` (binary) or `.json` (JSON format) +- **Prover files**: `.pkp` (binary) or `.json` (JSON format) - prepared proof scheme - **Witness files**: `.toml` (TOML format with input values) ### Output Files diff --git a/tooling/provekit-ffi/include/provekit_ffi.h b/tooling/provekit-ffi/include/provekit_ffi.h index 8a24641d..511f3895 100644 --- a/tooling/provekit-ffi/include/provekit_ffi.h +++ b/tooling/provekit-ffi/include/provekit_ffi.h @@ -28,6 +28,8 @@ extern "C" 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 @@ -47,7 +49,7 @@ extern "C" /// Prove a Noir program and write the proof to a file. /// - /// @param prover_path Path to the prepared proof scheme (.nps 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 @@ -57,7 +59,7 @@ extern "C" /// /// This function is only available when the library is built with JSON support. /// - /// @param prover_path Path to the prepared proof scheme (.nps 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_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 @@ -68,13 +70,6 @@ extern "C" /// @param buf The buffer to free void pk_free_buf(PKBuf buf); - /// Get the last error message as a C string. - /// - /// @return A null-terminated C string containing the last error message, - /// or NULL if no error occurred. The returned string is static and - /// does not need to be freed. - const char *pk_last_error(void); - #ifdef __cplusplus } #endif diff --git a/tooling/provekit-ffi/src/ffi.rs b/tooling/provekit-ffi/src/ffi.rs index 3edaf4ec..b4b2ca56 100644 --- a/tooling/provekit-ffi/src/ffi.rs +++ b/tooling/provekit-ffi/src/ffi.rs @@ -18,7 +18,7 @@ use { /// /// # Arguments /// -/// * `prover_path` - Path to the prepared proof scheme (.nps file) +/// * `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) /// @@ -42,9 +42,8 @@ pub unsafe extern "C" fn pk_prove_to_file( let input_path = c_str_to_str(input_path)?; let out_path = c_str_to_str(out_path)?; - // Read the scheme file (.nps or .json) - let mut prover: Prover = - read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; + // Read the scheme file (.pkp or .json) + let prover: Prover = read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; // Generate the proof let proof = prover.prove(&input_path).map_err(|_| PKError::ProofError)?; @@ -68,7 +67,7 @@ pub unsafe extern "C" fn pk_prove_to_file( /// /// # Arguments /// -/// * `scheme_path` - Path to the prepared proof scheme (.nps file) +/// * `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 /// @@ -107,8 +106,7 @@ pub unsafe extern "C" fn pk_prove_to_json( let input_path = c_str_to_str(input_path)?; // Read the scheme file (.pkp or .json) - let mut prover: Prover = - read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; + let prover: Prover = read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; // Generate the proof let proof = prover.prove(&input_path).map_err(|_| PKError::ProofError)?; From 8ef2a97069b628da2c0b436fe9e55f2c0a2662be Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Sat, 20 Dec 2025 18:47:21 +0530 Subject: [PATCH 4/9] docs(ffi): fix doc comment formatting --- tooling/provekit-ffi/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/provekit-ffi/src/types.rs b/tooling/provekit-ffi/src/types.rs index 073b1156..4447310c 100644 --- a/tooling/provekit-ffi/src/types.rs +++ b/tooling/provekit-ffi/src/types.rs @@ -21,7 +21,7 @@ impl PKBuf { } } - /// Create a buffer from a Vec, transferring ownership + /// 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(); From c3e590fe1003e352f9201c61972241aba5faa171 Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Sat, 20 Dec 2025 18:54:18 +0530 Subject: [PATCH 5/9] revert: undo unrelated docker-compose formatting change --- tooling/verifier-server/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/verifier-server/docker-compose.yml b/tooling/verifier-server/docker-compose.yml index 7ee94374..feaec807 100644 --- a/tooling/verifier-server/docker-compose.yml +++ b/tooling/verifier-server/docker-compose.yml @@ -16,7 +16,7 @@ services: volumes: # Mount artifacts directory for persistence (optional) - ./artifacts:/app/artifacts - user: "1001:1001" # Match the appuser UID/GID from Dockerfile + user: "1001:1001" # Match the appuser UID/GID from Dockerfile restart: unless-stopped healthcheck: test: From b5da517dcd84a1efe5bfb337f269d97b9b6f3999 Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Sat, 20 Dec 2025 19:12:53 +0530 Subject: [PATCH 6/9] fix: restore debug symbols and bench harness settings --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index be8ac9f8..e3f08eb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ missing_docs_in_private_items = { level = "allow", priority = 1 } missing_safety_doc = { level = "deny", priority = 1 } [profile.release] +debug = true # Generate symbol info for profiling opt-level = 3 codegen-units = 1 lto = "fat" From b499388a03e448c3d7c1548def26564f290f1c58 Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Tue, 13 Jan 2026 20:41:29 +0530 Subject: [PATCH 7/9] fix: update CI toolchain and resolve clippy warnings - Update CI workflow to use nightly-2025-10-20 matching rust-toolchain.toml - Fix MSRV warning: remove const from num_entries (requires Rust 1.87+) - Fix doc-lazy-continuation warning in witness_builder.rs - Fix needless_borrows_for_generic_args in FFI prove functions --- .github/workflows/ci.yml | 2 +- provekit/common/src/sparse_matrix.rs | 2 +- provekit/common/src/witness/witness_builder.rs | 4 ++-- tooling/provekit-ffi/src/ffi.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) 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/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/src/ffi.rs b/tooling/provekit-ffi/src/ffi.rs index b4b2ca56..7119951e 100644 --- a/tooling/provekit-ffi/src/ffi.rs +++ b/tooling/provekit-ffi/src/ffi.rs @@ -46,7 +46,7 @@ pub unsafe extern "C" fn pk_prove_to_file( let prover: Prover = read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; // Generate the proof - let proof = prover.prove(&input_path).map_err(|_| PKError::ProofError)?; + let proof = prover.prove(input_path).map_err(|_| PKError::ProofError)?; // Write the proof to file provekit_common::file::write(&proof, Path::new(out_path)) @@ -109,7 +109,7 @@ pub unsafe extern "C" fn pk_prove_to_json( let prover: Prover = read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; // Generate the proof - let proof = prover.prove(&input_path).map_err(|_| PKError::ProofError)?; + let proof = prover.prove(input_path).map_err(|_| PKError::ProofError)?; // Serialize to JSON let json_string = serde_json::to_string(&proof).map_err(|_| PKError::SerializationError)?; From 35ccf1369a88daac67b62c81767a5608376a1f81 Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Tue, 13 Jan 2026 20:47:07 +0530 Subject: [PATCH 8/9] perf(ffi): add panic catching for FFI safety Wrap FFI functions with catch_panic to prevent undefined behavior from stack unwinding across FFI boundary. This is critical for mobile platforms where panics must not propagate to C/Swift/Kotlin. --- tooling/provekit-ffi/src/ffi.rs | 92 ++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/tooling/provekit-ffi/src/ffi.rs b/tooling/provekit-ffi/src/ffi.rs index 7119951e..0af6f8de 100644 --- a/tooling/provekit-ffi/src/ffi.rs +++ b/tooling/provekit-ffi/src/ffi.rs @@ -10,10 +10,21 @@ use { 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 @@ -37,28 +48,28 @@ pub unsafe extern "C" fn pk_prove_to_file( input_path: *const c_char, out_path: *const c_char, ) -> c_int { - 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)?; + 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)?; - // Read the scheme file (.pkp or .json) - let prover: Prover = read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; + let prover: Prover = + read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; - // Generate the proof - let proof = prover.prove(input_path).map_err(|_| PKError::ProofError)?; + let proof = prover.prove(input_path).map_err(|_| PKError::ProofError)?; - // Write the proof to file - provekit_common::file::write(&proof, Path::new(out_path)) - .map_err(|_| PKError::FileWriteError)?; + provekit_common::file::write(&proof, Path::new(out_path)) + .map_err(|_| PKError::FileWriteError)?; - Ok(()) - })(); + Ok(()) + })(); - match result { - Ok(()) => PKError::Success.into(), - Err(error) => error.into(), - } + match result { + Ok(()) => PKError::Success.into(), + Err(error) => error.into(), + } + }) } /// Prove a Noir program and return the proof as JSON string. @@ -88,42 +99,41 @@ pub unsafe extern "C" fn pk_prove_to_json( input_path: *const c_char, out_buf: *mut PKBuf, ) -> c_int { - // Validate inputs if out_buf.is_null() { return PKError::InvalidInput.into(); } - let out_buf = match out_buf.as_mut() { - Some(buf) => buf, - None => 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(), + }; - // Initialize output buffer to empty state - *out_buf = PKBuf::empty(); + *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 result = (|| -> Result, PKError> { + let prover_path = c_str_to_str(prover_path)?; + let input_path = c_str_to_str(input_path)?; - // Read the scheme file (.pkp or .json) - let prover: Prover = read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; + let prover: Prover = + read(Path::new(prover_path)).map_err(|_| PKError::SchemeReadError)?; - // Generate the proof - let proof = prover.prove(input_path).map_err(|_| PKError::ProofError)?; + let proof = prover.prove(input_path).map_err(|_| PKError::ProofError)?; - // Serialize to JSON - let json_string = serde_json::to_string(&proof).map_err(|_| PKError::SerializationError)?; + let json_string = + serde_json::to_string(&proof).map_err(|_| PKError::SerializationError)?; - Ok(json_string.into_bytes()) - })(); + Ok(json_string.into_bytes()) + })(); - match result { - Ok(json_bytes) => { - *out_buf = PKBuf::from_vec(json_bytes); - PKError::Success.into() + match result { + Ok(json_bytes) => { + *out_buf = PKBuf::from_vec(json_bytes); + PKError::Success.into() + } + Err(error) => error.into(), } - Err(error) => error.into(), - } + }) } /// Free a buffer allocated by ProveKit FFI functions. From 934c00e2e4b310add84a08319b06452f2a502089 Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Wed, 14 Jan 2026 19:49:14 +0530 Subject: [PATCH 9/9] feat(ffi): add custom allocator support via pk_set_allocator Allows host applications to provide custom memory allocation functions for memory tracking and management. Falls back to system allocator when not configured. Optimized for minimal overhead in the default case. --- tooling/provekit-ffi/README.md | 22 +++++- tooling/provekit-ffi/include/provekit_ffi.h | 10 +++ tooling/provekit-ffi/src/ffi_allocator.rs | 78 +++++++++++++++++++++ tooling/provekit-ffi/src/lib.rs | 1 + 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 tooling/provekit-ffi/src/ffi_allocator.rs diff --git a/tooling/provekit-ffi/README.md b/tooling/provekit-ffi/README.md index cc740953..00555224 100644 --- a/tooling/provekit-ffi/README.md +++ b/tooling/provekit-ffi/README.md @@ -266,6 +266,7 @@ lib.pk_free_buf(proof_buf) - `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 @@ -291,10 +292,27 @@ lib.pk_free_buf(proof_buf) 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. -## Features -The FFI library is built with JSON support by default, providing the `pk_prove_to_json` function. diff --git a/tooling/provekit-ffi/include/provekit_ffi.h b/tooling/provekit-ffi/include/provekit_ffi.h index 511f3895..7879d884 100644 --- a/tooling/provekit-ffi/include/provekit_ffi.h +++ b/tooling/provekit-ffi/include/provekit_ffi.h @@ -70,6 +70,16 @@ extern "C" /// @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/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 index 658fdecf..e277b804 100644 --- a/tooling/provekit-ffi/src/lib.rs +++ b/tooling/provekit-ffi/src/lib.rs @@ -24,6 +24,7 @@ //! to ensure proper memory management and valid pointer usage. pub mod ffi; +mod ffi_allocator; pub mod types; pub mod utils;