docs(frontend): add documentation for concrete-rust

This commit is contained in:
Alexandre Péré
2025-06-16 11:24:35 +02:00
committed by Quentin Bourgerie
parent 2420cad5b8
commit 5da71398a2
4 changed files with 209 additions and 2 deletions

View File

@@ -38,6 +38,7 @@
* [Debugging and artifact](execution-analysis/debug.md)
* [Performance](optimization/summary.md)
* [GPU acceleration](execution-analysis/gpu_acceleration.md)
* [Rust integration](execution-analysis/rust_integration.md)
* Other
* [Statistics](compilation/statistics.md)
* [Progressbar](execution-analysis/progressbar.md)

View File

@@ -0,0 +1,206 @@
# Rust Integration
This document explains how to use Fully Homomorphic Encryption (FHE) modules developed with **concrete-python** directly in Rust programs using the **Concrete** toolchain.
This workflow enables rapid prototyping in Python and seamless deployment in Rust, combining the flexibility of Python with the safety and performance of Rust.
## Overview
- Write and compile FHE modules in Python using `concrete-python`.
- Import the compiled module artifact into Rust using the `concrete-macro` crate.
- Use the generated Rust APIs for encryption, evaluation, and decryption.
## Prerequisites
- Python 3.8+
- Rust 1.70+
- `concrete-python` (>=2.10)
- `concrete` and `concrete-macro` Rust crates (>=2.10.1-rc1)
## Regular example
### Step 1: Define and Compile a Module in Python
Write your FHE logic in Python and compile it to an artifact compatible with the Rust toolchain. Here is an example of a small and simple module:
```python
from concrete import fhe
@fhe.module()
class MyModule:
@fhe.function({"x": "encrypted"})
def inc(x):
return (x + 1) % 256
@fhe.function({"x": "encrypted"})
def dec(x):
return (x - 1) % 256
inputset = fhe.inputset(fhe.uint8)
module = MyModule.compile({"inc": inputset, "dec": inputset})
module.server.save(path="MyModule.zip", via_mlir=True)
```
This produces a `MyModule.zip` artifact containing the compiled FHE module.
### Step 2: Set Up the Rust Project
Initialize a new Rust project and add the required dependencies.
```shell
cargo init
cargo add concrete@=2.10.1-rc1 concrete-macro@=2.10.1-rc1
```
Place the `MyModule.zip` artifact in your project directory.
### Step 3: Import the Python-Compiled Module in Rust
Use the `concrete_macro::from_concrete_python_export_zip!` macro to import the module at build time.
```rust
mod my_module {
use concrete_macro::from_concrete_python_export_zip;
from_concrete_python_export_zip!("MyModule.zip");
}
```
This macro unpacks the artifact, triggers recompilation, reads metadata, and generates Rust APIs for the module's functions.
### Step 4: Use the Module in Rust
You can now use the FHE functions in Rust. The following example demonstrates a full FHE workflow:
```rust
use concrete::common::Tensor;
fn main() {
// Prepare input and expected output tensors
let input = Tensor::new(vec![5], vec![]);
let expected_output = Tensor::new(vec![6], vec![]);
// Key generation
let mut secret_csprng = concrete::common::SecretCsprng::new(0u128);
let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128);
let keyset = my_module::new_keyset(secret_csprng.pin_mut(), encryption_csprng.pin_mut());
let client_keyset = keyset.get_client();
// Create client stub for the 'inc' function
let mut inc_client = my_module::client::inc::ClientFunction::new(&client_keyset, encryption_csprng);
// Encrypt input and obtain evaluation keys
let encrypted_input = inc_client.prepare_inputs(input);
let evaluation_keys = keyset.get_server();
// Create server stub for the 'inc' function
let mut inc_server = my_module::server::inc::ServerFunction::new();
// Evaluate the function on encrypted data
let encrypted_output = inc_server.invoke(&evaluation_keys, encrypted_input);
// Decrypt the output
let decrypted_output = inc_client.process_outputs(encrypted_output);
// Check correctness
assert_eq!(decrypted_output.values(), expected_output.values());
}
```
## TFHE-rs Ciphertext Interoperability
Starting from Concrete v2.11, you can define and use modules that operate directly on TFHE-rs ciphertexts, enabling seamless interoperability between Concrete and TFHE-rs in Rust.
### Step 1: Define and Compile a Module with TFHE-rs Types in Python
You can define a module in Python that uses TFHE-rs integer types as arguments and outputs. For example:
```python
from concrete import fhe
from concrete.fhe import tfhers
TFHERS_UINT_8_3_2_4096 = tfhers.TFHERSIntegerType(
False,
bit_width=8,
carry_width=3,
msg_width=2,
params=tfhers.CryptoParams(
lwe_dimension=909,
glwe_dimension=1,
polynomial_size=4096,
pbs_base_log=15,
pbs_level=2,
lwe_noise_distribution=0,
glwe_noise_distribution=2.168404344971009e-19,
encryption_key_choice=tfhers.EncryptionKeyChoice.BIG,
),
)
@fhe.module()
class MyModule:
@fhe.function({"x": "encrypted", "y": "encrypted"})
def my_func(x, y):
x = tfhers.to_native(x)
y = tfhers.to_native(y)
return tfhers.from_native(x + y, TFHERS_UINT_8_3_2_4096)
def t(v):
return tfhers.TFHERSInteger(TFHERS_UINT_8_3_2_4096, v)
inputset = [(t(0), t(0)), (t(2**6), t(2**6))]
my_module = MyModule.compile({"my_func": inputset})
my_module.server.save("test_tfhers.zip", via_mlir=True)
```
This produces a `test_tfhers.zip` artifact compatible with Rust and TFHE-rs.
### Step 2: Use the Module with TFHE-rs Ciphertexts in Rust
You can import and use the module in Rust, passing and receiving native TFHE-rs ciphertexts:
```rust
mod precompile {
use concrete_macro::from_concrete_python_export_zip;
from_concrete_python_export_zip!("src/test_tfhers.zip");
}
use tfhe::prelude::{FheDecrypt, FheEncrypt};
use tfhe::shortint::parameters::v0_10::classic::gaussian::p_fail_2_minus_64::ks_pbs::V0_10_PARAM_MESSAGE_2_CARRY_3_KS_PBS_GAUSSIAN_2M64;
use tfhe::{generate_keys, FheUint8};
fn main() {
// Key generation for TFHE-rs
let config = tfhe::ConfigBuilder::with_custom_parameters(V0_10_PARAM_MESSAGE_2_CARRY_3_KS_PBS_GAUSSIAN_2M64);
let (client_key, _) = generate_keys(config);
// Build Concrete keyset with TFHE-rs client key
let mut secret_csprng = concrete::common::SecretCsprng::new(0u128);
let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128);
let keyset = precompile::KeysetBuilder::new()
.with_key_for_my_func_0_arg(&client_key)
.generate(secret_csprng.pin_mut(), encryption_csprng.pin_mut());
let server_keyset = keyset.get_server();
// Encrypt inputs using TFHE-rs
let arg_0 = FheUint8::encrypt(6u8, &client_key);
let arg_1 = FheUint8::encrypt(4u8, &client_key);
// Evaluate the Concrete circuit on TFHE-rs ciphertexts
let mut server = precompile::server::my_func::ServerFunction::new();
let output = server.invoke(&server_keyset, arg_0, arg_1);
// Decrypt the result using TFHE-rs
let decrypted: u8 = output.decrypt(&client_key);
assert_eq!(decrypted, 10);
}
```
This workflow allows you to combine the high-level graph optimizations of Concrete with the operator-level flexibility of TFHE-rs, all within Rust.
## Notes
- The module must be compiled with `via_mlir=True` to be loaded in the Rust program.
- The Rust API is currently in beta and may evolve in future releases.
- The Python and Rust environments must use compatible versions of the Concrete toolchain.
- When using TFHE-rs ciphertext interoperability, ensure that the TFHE-rs client key used for encryption matches the one registered in the Concrete keyset for the corresponding argument.

View File

@@ -107,7 +107,7 @@ inputset = [(2, 3), (0, 0), (1, 6), (7, 7), (7, 1), (3, 2), (6, 1), (1, 7), (4,
Here, our inputset consists of 10 integer pairs, ranging from a minimum of `(0, 0)` to a maximum of `(7, 7)`.
{% hint style="warning" %}
Choosing a representative inputset is critical to allow the compiler to find accurate bounds of all the intermediate values (see more details [here](https://docs.zama.ai/concrete/explanations/compilation#bounds-measurement)). Evaluating the circuit with input values under or over the bounds may result in undefined behavior.
Choosing a representative inputset is critical to allow the compiler to find accurate bounds of all the intermediate values (see more details [here](https://docs.zama.ai/concrete/explanations/compiler_workflow#bounds-measurement)). Evaluating the circuit with input values under or over the bounds may result in undefined behavior.
{% endhint %}
{% hint style="warning" %}

View File

@@ -22,7 +22,7 @@ implementation.
In our implementation, only the compression function is implemented in FHE, corresponding to the
`_process_encrypted_chunk_server_side function`. The rest is done client-side in the clear,
including the message expansion. While more of the process could be done in FHE, this tutorial
focuses on demonstrating the use of [Modules](https://docs.zama.ai/concrete/compilation/modules).
focuses on demonstrating the use of [Modules](https://docs.zama.ai/concrete/compilation/combining/composing_functions_with_modules).
Our Module contains 7 functions which can be combined together:
- `xor3` XORs three values together