-
Notifications
You must be signed in to change notification settings - Fork 34
Add prover-client prototype #907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| [package] | ||
| name = "binius-prover-client" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| authors = ["Irreducible Team <opensource@irreducible.com>"] | ||
| description = "Simplified open-source interface to the Binius ZK prover using serialization" | ||
| license = "MIT OR Apache-2.0" | ||
| build = "build.rs" | ||
|
|
||
| [lib] | ||
| # This is a Rust crate (rlib). The cdylib is only built for internal testing purposes. | ||
| crate-type = ["rlib", "cdylib"] | ||
|
|
||
| [dependencies] | ||
| # Core dependencies for serialization | ||
| binius-core = { path = "../core" } | ||
| binius-utils = { path = "../utils" } | ||
| bytes = "1.7" | ||
|
|
||
| # Error handling | ||
| thiserror = "2.0" | ||
| anyhow = "1.0" | ||
|
|
||
| # Needed for the FFI implementation (only when building cdylib) | ||
| binius-prover = { path = "../prover", optional = true } | ||
|
|
||
| [features] | ||
| default = [] | ||
| # Enable building the FFI implementation (for cdylib) | ||
| ffi-impl = ["binius-prover"] | ||
|
|
||
| [dev-dependencies] | ||
| # For testing and benchmarking | ||
| criterion = "0.6" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| # Binius Prover Client | ||
|
|
||
| A Rust crate providing a clean API for Binius ZK proof generation. | ||
|
|
||
| ## Overview | ||
|
|
||
| This is a standard Rust library that provides a type-safe interface for generating Binius proofs. It uses FFI to communicate with the prover via serialized data, enabling the prover to be distributed as a closed-source binary while keeping the interface open-source. | ||
|
|
||
| ## Architecture | ||
|
|
||
| ``` | ||
| ┌─────────────────────┐ ┌─────────────────────┐ ┌──────────────────────┐ | ||
| │ Your Rust App │────│ Prover Client │────│ Binius Prover │ | ||
| │ │ │ (This Crate) │ │ (C Library) │ | ||
| │ - ConstraintSystem │ │ - ProverClient │ │ - binius_prove() │ | ||
| │ - ValuesData │ │ - Serialization │ │ - Proof generation │ | ||
| │ - Proof │ │ - Error handling │ │ │ | ||
| └─────────────────────┘ └─────────────────────┘ └──────────────────────┘ | ||
| ``` | ||
|
|
||
| ## Installation | ||
|
|
||
| Add this crate to your `Cargo.toml`: | ||
|
|
||
| ```toml | ||
| [dependencies] | ||
| binius-prover-client = { path = "../prover-client" } | ||
| ``` | ||
|
|
||
| ## Requirements | ||
|
|
||
| This crate requires the Binius prover to be available as a C library: | ||
|
|
||
| ```bash | ||
| export BINIUS_PROVER_LIB_PATH=/path/to/prover/library | ||
| ``` | ||
|
|
||
| Without this, the crate will compile but return runtime errors when attempting to generate proofs. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```rust | ||
| use binius_prover_client::ProverClient; | ||
| use binius_core::constraint_system::{ConstraintSystem, ValuesData}; | ||
|
|
||
| // Create a prover client instance | ||
| let prover = ProverClient::new(1); // log_inv_rate = 1 | ||
|
|
||
| // Generate proof from constraint system and witness data | ||
| let proof = prover.prove(&constraint_system, &public_witness, &private_witness)?; | ||
| ``` | ||
|
|
||
| The crate provides three API methods: | ||
| - `prove()` - Takes Rust types, handles serialization internally | ||
| - `prove_serialized()` - Takes pre-serialized bytes, returns deserialized `Proof` | ||
| - `prove_serialized_raw()` - Takes and returns raw bytes for maximum efficiency | ||
|
|
||
| ## FFI Interface Details | ||
|
|
||
| The FFI boundary uses a single C function with serialized inputs/outputs: | ||
|
|
||
| ```c | ||
| // Returns proof size on success, negative error code on failure | ||
| int32_t binius_prove( | ||
| const uint8_t* cs_bytes, // Serialized ConstraintSystem | ||
| size_t cs_len, | ||
| const uint8_t* pub_witness_bytes, // Serialized public ValuesData | ||
| size_t pub_witness_len, | ||
| const uint8_t* priv_witness_bytes,// Serialized private ValuesData | ||
| size_t priv_witness_len, | ||
| uint32_t log_inv_rate, // Proof generation parameter | ||
| uint8_t* proof_out, // Output buffer for serialized Proof | ||
| size_t proof_capacity // Size of output buffer | ||
| ); | ||
| ``` | ||
|
|
||
| ### Error Codes | ||
|
|
||
| - **Positive number**: Size of the proof written to `proof_out` (success) | ||
| - **-1**: Null pointer error | ||
| - **-2**: Invalid input data | ||
| - **-3**: Proving error | ||
| - **-4**: Serialization error | ||
| - **-5**: Output buffer too small | ||
|
|
||
| ## Testing and Development | ||
|
|
||
| ### Running the Test Suite | ||
|
|
||
| The crate includes a focused test suite with automatic FFI library management: | ||
|
|
||
| ```bash | ||
| # Quick test - builds FFI library and runs all tests | ||
| ./test_prover_client.sh | ||
| ``` | ||
|
|
||
| This script will: | ||
| 1. Build the FFI library with the current implementation | ||
| 2. Set up library paths automatically | ||
| 3. Run integration tests for all API variants | ||
| 4. Verify FFI boundary crossing works correctly | ||
|
|
||
| ### Manual Testing | ||
|
|
||
| ```bash | ||
| # Build the FFI library | ||
| cargo build --release --features ffi-impl | ||
|
|
||
| # Set library path and run tests | ||
| export BINIUS_PROVER_LIB_PATH=$(pwd)/target/release | ||
| cargo test | ||
| ``` | ||
|
|
||
| ### Test Coverage | ||
|
|
||
| The test suite focuses on interface correctness: | ||
| - **API methods**: All three variants (`prove`, `prove_serialized`, `prove_serialized_raw`) | ||
| - **FFI boundary**: Verifies data crosses the FFI boundary correctly | ||
| - **Serialization**: Ensures proper serialization/deserialization | ||
| - **Trait implementation**: Tests Default trait and accessor methods | ||
|
|
||
| ## Implementation Notes | ||
|
|
||
| ### Library Detection | ||
|
|
||
| The crate's build script automatically detects the external prover library: | ||
|
|
||
| - Checks `BINIUS_PROVER_LIB_PATH` environment variable | ||
| - Sets up linking when library is found | ||
| - Provides graceful fallback when library is unavailable | ||
|
|
||
| ### FFI Implementation | ||
|
|
||
| The file `src/ffi_impl.rs` contains the Binius prover wrapped in a C-compatible FFI interface. This is used to test the FFI boundary. In a closed-source deployment, this code would be compiled as a proprietary C library. | ||
|
|
||
| ## Advanced Usage | ||
|
|
||
| ### Error Handling | ||
|
|
||
| The interface provides detailed error information: | ||
|
|
||
| ```rust | ||
| use binius_prover_client::{ProverClient, ProverError}; | ||
|
|
||
| match prover.prove(&cs, &pub_witness, &priv_witness) { | ||
| Ok(proof) => println!("Proof generated: {} bytes", proof.data().len()), | ||
| Err(ProverError::LibraryNotAvailable(msg)) => { | ||
| eprintln!("FFI library not found: {}", msg); | ||
| // Handle library not available case | ||
| } | ||
| Err(ProverError::FfiError(code)) => { | ||
| eprintln!("FFI error code: {}", code); | ||
| // Handle specific FFI error codes | ||
| } | ||
| Err(e) => eprintln!("Other error: {}", e), | ||
| } | ||
| ``` | ||
|
|
||
| ### Performance Considerations | ||
|
|
||
| - **Pre-serialized data**: Use `prove_serialized_raw()` when you already have serialized inputs | ||
| - **Library linking**: Dynamic linking adds minimal overhead compared to proof generation time | ||
| - **Memory management**: The FFI boundary uses copying; consider this for very large constraint systems | ||
|
|
||
| ### Integration with Existing Code | ||
|
|
||
| The interface is designed to integrate easily with existing Binius workflows: | ||
|
|
||
| ```rust | ||
| // Works with existing constraint system construction | ||
| let cs = constraint_system_builder.build(); | ||
| let witness = witness_builder.build(); | ||
|
|
||
| // Drop-in replacement for direct prover usage | ||
| let prover = ProverClient::new(log_inv_rate); | ||
| let proof = prover.prove(&cs.constraint_system, &witness.public, &witness.private)?; | ||
|
|
||
| // Use proof with existing verification code | ||
| verify_proof(&proof, &public_inputs)?; | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| use std::env; | ||
|
Check warning on line 1 in crates/prover-client/build.rs
|
||
| use std::path::{Path, PathBuf}; | ||
|
|
||
| fn main() { | ||
| // Tell Cargo about our custom cfg flags | ||
| println!("cargo::rustc-check-cfg=cfg(has_binius_prover)"); | ||
| println!("cargo::rustc-check-cfg=cfg(no_binius_prover)"); | ||
|
|
||
| // Skip library detection when we're building the FFI implementation itself | ||
| // This avoids circular dependency when building as cdylib | ||
| if env::var("CARGO_FEATURE_FFI_IMPL").is_ok() { | ||
| println!("cargo:rustc-cfg=no_binius_prover"); | ||
| return; | ||
| } | ||
|
|
||
| // Try to find the closed-source library via environment variable | ||
| if let Ok(path) = env::var("BINIUS_PROVER_LIB_PATH") { | ||
| let path = PathBuf::from(path); | ||
|
|
||
| if verify_library_exists(&path) { | ||
| // Set up linking | ||
| println!("cargo:rustc-link-search={}", path.display()); | ||
| println!("cargo:rustc-link-lib=binius_prover"); | ||
|
|
||
| // Store the library info for runtime access | ||
| println!("cargo:rustc-env=LINKED_BINIUS_LIB_PATH={}", path.display()); | ||
| if let Some(file_name) = find_library_file(&path) { | ||
| println!("cargo:rustc-env=LINKED_BINIUS_LIB_NAME={}", file_name); | ||
| } | ||
|
|
||
| // Set a cfg flag to enable integration tests | ||
| println!("cargo:rustc-cfg=has_binius_prover"); | ||
| } else { | ||
| println!("cargo:rustc-cfg=no_binius_prover"); | ||
| } | ||
| } else { | ||
| // Library not found - this is OK for development | ||
| println!("cargo:rustc-cfg=no_binius_prover"); | ||
| } | ||
|
|
||
| // Always rerun if these change | ||
| println!("cargo:rerun-if-env-changed=BINIUS_PROVER_LIB_PATH"); | ||
| println!("cargo:rerun-if-changed=build.rs"); | ||
| } | ||
|
|
||
| fn find_library_file(dir: &Path) -> Option<String> { | ||
| let lib_names = [ | ||
|
Check warning on line 47 in crates/prover-client/build.rs
|
||
| "libbinius_prover.so", // Linux dynamic | ||
| "libbinius_prover.dylib", // macOS dynamic | ||
| "binius_prover.dll", // Windows dynamic | ||
| "libbinius_prover.a", // Static library | ||
| ]; | ||
|
|
||
| for name in &lib_names { | ||
| if dir.join(name).exists() { | ||
| return Some(name.to_string()); | ||
| } | ||
| } | ||
| None | ||
| } | ||
|
|
||
| fn verify_library_exists(dir: &Path) -> bool { | ||
| if !dir.exists() { | ||
|
Check warning on line 63 in crates/prover-client/build.rs
|
||
| return false; | ||
| } | ||
|
|
||
| // Check for different library naming conventions | ||
| let lib_names = [ | ||
| "libbinius_prover.so", // Linux | ||
| "libbinius_prover.dylib", // macOS | ||
| "binius_prover.dll", // Windows | ||
| "libbinius_prover.a", // Static library | ||
| ]; | ||
|
|
||
| for name in &lib_names { | ||
| if dir.join(name).exists() { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| false | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| use thiserror::Error; | ||
|
|
||
| /// Error types for the prover interface | ||
|
Check warning on line 3 in crates/prover-client/src/error.rs
|
||
| #[derive(Debug, Error)] | ||
| pub enum ProverError { | ||
| /// Invalid input data | ||
| #[error("Invalid input: {0}")] | ||
| InvalidInput(String), | ||
|
|
||
| /// Serialization error | ||
| #[error("Serialization error: {0}")] | ||
| Serialization(#[from] binius_utils::serialization::SerializationError), | ||
|
|
||
| /// FFI error from the closed-source prover | ||
| #[error("FFI error (code {0})")] | ||
| FFIError(i32), | ||
|
|
||
| /// Library not available | ||
| #[error("Library not available: {0}")] | ||
| LibraryNotAvailable(String), | ||
|
|
||
| /// Prover operation failed | ||
| #[error("Prover failed: {0}")] | ||
| ProverFailed(String), | ||
|
|
||
| /// IO error | ||
| #[error("IO error: {0}")] | ||
| Io(#[from] std::io::Error), | ||
| } | ||
|
|
||
| impl ProverError { | ||
| /// Create an error from an FFI error code | ||
|
Check warning on line 32 in crates/prover-client/src/error.rs
|
||
| pub fn from_ffi_code(code: i32) -> Self { | ||
| match code { | ||
| -1 => ProverError::ProverFailed("General prover failure".to_string()), | ||
| -2 => ProverError::InvalidInput("Invalid constraint system".to_string()), | ||
| -3 => ProverError::InvalidInput("Invalid witness data".to_string()), | ||
| -4 => ProverError::ProverFailed("Out of memory".to_string()), | ||
| _ => ProverError::FFIError(code), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Result type for prover operations | ||
| pub type Result<T> = std::result::Result<T, ProverError>; | ||
|
Check warning on line 45 in crates/prover-client/src/error.rs
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workspace configuration requires all crates to reference dependencies declared in the workspace root
Cargo.toml. The dependencybytes = "1.7"should be declared in the workspace root and referenced here asbytes.workspace = true.Spotted by Diamond (based on custom rule: Irreducible Rust and Cargo)

Is this helpful? React 👍 or 👎 to let us know.