|
| 1 | +# Rust Integration |
| 2 | + |
| 3 | +This document explains how to use Fully Homomorphic Encryption (FHE) modules developed with **concrete-python** directly in Rust programs using the **Concrete** toolchain. |
| 4 | + |
| 5 | +This workflow enables rapid prototyping in Python and seamless deployment in Rust, combining the flexibility of Python with the safety and performance of Rust. |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +- Write and compile FHE modules in Python using `concrete-python`. |
| 10 | +- Import the compiled module artifact into Rust using the `concrete-macro` crate. |
| 11 | +- Use the generated Rust APIs for encryption, evaluation, and decryption. |
| 12 | + |
| 13 | +## Prerequisites |
| 14 | + |
| 15 | +- Python 3.8+ |
| 16 | +- Rust 1.70+ |
| 17 | +- `concrete-python` (>=2.10) |
| 18 | +- `concrete` and `concrete-macro` Rust crates (>=2.10.1-rc1) |
| 19 | + |
| 20 | +## Regular example |
| 21 | + |
| 22 | +### Step 1: Define and Compile a Module in Python |
| 23 | + |
| 24 | +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: |
| 25 | + |
| 26 | +```python |
| 27 | +from concrete import fhe |
| 28 | + |
| 29 | +@fhe.module() |
| 30 | +class MyModule: |
| 31 | + @fhe.function({"x": "encrypted"}) |
| 32 | + def inc(x): |
| 33 | + return (x + 1) % 256 |
| 34 | + |
| 35 | + @fhe.function({"x": "encrypted"}) |
| 36 | + def dec(x): |
| 37 | + return (x - 1) % 256 |
| 38 | + |
| 39 | +inputset = fhe.inputset(fhe.uint8) |
| 40 | +module = MyModule.compile({"inc": inputset, "dec": inputset}) |
| 41 | + |
| 42 | +module.server.save(path="MyModule.zip", via_mlir=True) |
| 43 | +``` |
| 44 | + |
| 45 | +This produces a `MyModule.zip` artifact containing the compiled FHE module. |
| 46 | + |
| 47 | +### Step 2: Set Up the Rust Project |
| 48 | + |
| 49 | +Initialize a new Rust project and add the required dependencies. |
| 50 | + |
| 51 | +```shell |
| 52 | +cargo init |
| 53 | +cargo add concrete@=2.10.1-rc1 concrete-macro@=2.10.1-rc1 |
| 54 | +``` |
| 55 | + |
| 56 | +Place the `MyModule.zip` artifact in your project directory. |
| 57 | + |
| 58 | +### Step 3: Import the Python-Compiled Module in Rust |
| 59 | + |
| 60 | +Use the `concrete_macro::from_concrete_python_export_zip!` macro to import the module at build time. |
| 61 | + |
| 62 | +```rust |
| 63 | +mod my_module { |
| 64 | + use concrete_macro::from_concrete_python_export_zip; |
| 65 | + from_concrete_python_export_zip!("MyModule.zip"); |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +This macro unpacks the artifact, triggers recompilation, reads metadata, and generates Rust APIs for the module's functions. |
| 70 | + |
| 71 | +### Step 4: Use the Module in Rust |
| 72 | + |
| 73 | +You can now use the FHE functions in Rust. The following example demonstrates a full FHE workflow: |
| 74 | + |
| 75 | +```rust |
| 76 | +use concrete::common::Tensor; |
| 77 | + |
| 78 | +fn main() { |
| 79 | + // Prepare input and expected output tensors |
| 80 | + let input = Tensor::new(vec![5], vec![]); |
| 81 | + let expected_output = Tensor::new(vec![6], vec![]); |
| 82 | + |
| 83 | + // Key generation |
| 84 | + let mut secret_csprng = concrete::common::SecretCsprng::new(0u128); |
| 85 | + let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128); |
| 86 | + let keyset = my_module::new_keyset(secret_csprng.pin_mut(), encryption_csprng.pin_mut()); |
| 87 | + let client_keyset = keyset.get_client(); |
| 88 | + |
| 89 | + // Create client stub for the 'inc' function |
| 90 | + let mut inc_client = my_module::client::inc::ClientFunction::new(&client_keyset, encryption_csprng); |
| 91 | + |
| 92 | + // Encrypt input and obtain evaluation keys |
| 93 | + let encrypted_input = inc_client.prepare_inputs(input); |
| 94 | + let evaluation_keys = keyset.get_server(); |
| 95 | + |
| 96 | + // Create server stub for the 'inc' function |
| 97 | + let mut inc_server = my_module::server::inc::ServerFunction::new(); |
| 98 | + |
| 99 | + // Evaluate the function on encrypted data |
| 100 | + let encrypted_output = inc_server.invoke(&evaluation_keys, encrypted_input); |
| 101 | + |
| 102 | + // Decrypt the output |
| 103 | + let decrypted_output = inc_client.process_outputs(encrypted_output); |
| 104 | + |
| 105 | + // Check correctness |
| 106 | + assert_eq!(decrypted_output.values(), expected_output.values()); |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +## TFHE-rs Ciphertext Interoperability |
| 111 | + |
| 112 | +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. |
| 113 | + |
| 114 | +### Step 1: Define and Compile a Module with TFHE-rs Types in Python |
| 115 | + |
| 116 | +You can define a module in Python that uses TFHE-rs integer types as arguments and outputs. For example: |
| 117 | + |
| 118 | +```python |
| 119 | +from concrete import fhe |
| 120 | +from concrete.fhe import tfhers |
| 121 | + |
| 122 | +TFHERS_UINT_8_3_2_4096 = tfhers.TFHERSIntegerType( |
| 123 | + False, |
| 124 | + bit_width=8, |
| 125 | + carry_width=3, |
| 126 | + msg_width=2, |
| 127 | + params=tfhers.CryptoParams( |
| 128 | + lwe_dimension=909, |
| 129 | + glwe_dimension=1, |
| 130 | + polynomial_size=4096, |
| 131 | + pbs_base_log=15, |
| 132 | + pbs_level=2, |
| 133 | + lwe_noise_distribution=0, |
| 134 | + glwe_noise_distribution=2.168404344971009e-19, |
| 135 | + encryption_key_choice=tfhers.EncryptionKeyChoice.BIG, |
| 136 | + ), |
| 137 | +) |
| 138 | + |
| 139 | +@fhe.module() |
| 140 | +class MyModule: |
| 141 | + |
| 142 | + @fhe.function({"x": "encrypted", "y": "encrypted"}) |
| 143 | + def my_func(x, y): |
| 144 | + x = tfhers.to_native(x) |
| 145 | + y = tfhers.to_native(y) |
| 146 | + return tfhers.from_native(x + y, TFHERS_UINT_8_3_2_4096) |
| 147 | + |
| 148 | +def t(v): |
| 149 | + return tfhers.TFHERSInteger(TFHERS_UINT_8_3_2_4096, v) |
| 150 | + |
| 151 | +inputset = [(t(0), t(0)), (t(2**6), t(2**6))] |
| 152 | +my_module = MyModule.compile({"my_func": inputset}) |
| 153 | +my_module.server.save("test_tfhers.zip", via_mlir=True) |
| 154 | +``` |
| 155 | + |
| 156 | +This produces a `test_tfhers.zip` artifact compatible with Rust and TFHE-rs. |
| 157 | + |
| 158 | +### Step 2: Use the Module with TFHE-rs Ciphertexts in Rust |
| 159 | + |
| 160 | +You can import and use the module in Rust, passing and receiving native TFHE-rs ciphertexts: |
| 161 | + |
| 162 | +```rust |
| 163 | +mod precompile { |
| 164 | + use concrete_macro::from_concrete_python_export_zip; |
| 165 | + from_concrete_python_export_zip!("src/test_tfhers.zip"); |
| 166 | +} |
| 167 | + |
| 168 | +use tfhe::prelude::{FheDecrypt, FheEncrypt}; |
| 169 | +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; |
| 170 | +use tfhe::{generate_keys, FheUint8}; |
| 171 | + |
| 172 | +fn main() { |
| 173 | + // Key generation for TFHE-rs |
| 174 | + let config = tfhe::ConfigBuilder::with_custom_parameters(V0_10_PARAM_MESSAGE_2_CARRY_3_KS_PBS_GAUSSIAN_2M64); |
| 175 | + let (client_key, _) = generate_keys(config); |
| 176 | + |
| 177 | + // Build Concrete keyset with TFHE-rs client key |
| 178 | + let mut secret_csprng = concrete::common::SecretCsprng::new(0u128); |
| 179 | + let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128); |
| 180 | + let keyset = precompile::KeysetBuilder::new() |
| 181 | + .with_key_for_my_func_0_arg(&client_key) |
| 182 | + .generate(secret_csprng.pin_mut(), encryption_csprng.pin_mut()); |
| 183 | + let server_keyset = keyset.get_server(); |
| 184 | + |
| 185 | + // Encrypt inputs using TFHE-rs |
| 186 | + let arg_0 = FheUint8::encrypt(6u8, &client_key); |
| 187 | + let arg_1 = FheUint8::encrypt(4u8, &client_key); |
| 188 | + |
| 189 | + // Evaluate the Concrete circuit on TFHE-rs ciphertexts |
| 190 | + let mut server = precompile::server::my_func::ServerFunction::new(); |
| 191 | + let output = server.invoke(&server_keyset, arg_0, arg_1); |
| 192 | + |
| 193 | + // Decrypt the result using TFHE-rs |
| 194 | + let decrypted: u8 = output.decrypt(&client_key); |
| 195 | + assert_eq!(decrypted, 10); |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +This workflow allows you to combine the high-level graph optimizations of Concrete with the operator-level flexibility of TFHE-rs, all within Rust. |
| 200 | + |
| 201 | +## Notes |
| 202 | + |
| 203 | +- The module must be compiled with `via_mlir=True` to be loaded in the Rust program. |
| 204 | +- The Rust API is currently in beta and may evolve in future releases. |
| 205 | +- The Python and Rust environments must use compatible versions of the Concrete toolchain. |
| 206 | +- 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. |
0 commit comments