chore(docs): proofreading and design update

This commit is contained in:
yuxizama
2024-04-15 17:52:03 +02:00
committed by Arthur Meyre
parent b55bae1e0f
commit 08e4e4f993
47 changed files with 703 additions and 636 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -22,13 +22,13 @@ layout:
Learn the basics of TFHE-rs, set it up, and make it run with ease.
<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>What is TFHE-rs?</strong></td><td><a href="getting_started/">getting_started</a></td><td><a href=".gitbook/assets/yellow1.png">yellow1.png</a></td></tr><tr><td><strong>Installation</strong></td><td><a href="getting_started/installation.md">installation.md</a></td><td><a href=".gitbook/assets/yellow2.png">yellow2.png</a></td></tr><tr><td><strong>Quick start</strong></td><td><a href="getting_started/quick_start.md">quick_start.md</a></td><td><a href=".gitbook/assets/yellow3.png">yellow3.png</a></td></tr></tbody></table>
<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>What is TFHE-rs?</strong></td><td>Understand TFHE-rs library and basic cryptographic concepts</td><td><a href="getting_started/readme.md">getting_started.md</a></td><td><a href=".gitbook/assets/start1.png">start1.png</a></td></tr><tr><td><strong>Installation</strong></td><td>Follow the step by step guide to import TFHE-rs in your project</td><td><a href="getting_started/installation.md">installation.md</a></td><td><a href=".gitbook/assets/start2.png">start2.png</a></td></tr><tr><td><strong>Quick start</strong></td><td>See a full example of using TFHE-rs to compute on encrypted data</td><td><a href="getting_started/quick_start.md">quick_start.md</a></td><td><a href=".gitbook/assets/start3.png">start3.png</a></td></tr></tbody></table>
## Build with TFHE-rs
Start building with TFHE-rs by exploring its core features, discovering essential guides, and learning more with user-friendly tutorials.
<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>Fundamentals</strong></td><td>Explore the core features and basics of TFHE-rs.<br><br></td><td><ul><li><a href="fundamentals/configure-and-generate-keys.md">Configure and generate keys</a></li><li><a href="fundamentals/set-the-server-key.md">Set the server key</a></li><li><a href="fundamentals/encrypt-data.md">Encrypt data</a></li><li><a href="fundamentals/compute.md">Compute on encrypted data</a></li><li><a href="fundamentals/decrypt-data.md">Decrypt data</a></li></ul></td><td><a href=".gitbook/assets/orange1.png">orange1.png</a></td></tr><tr><td><strong>Guides</strong></td><td>Discover essential guides to work with TFHE-rs.<br><br></td><td><ul><li><a href="guides/run_on_gpu.md">Run on GPU</a></li><li><a href="guides/rust_configuration.md">Configure Rust</a></li><li><a href="guides/overflow_operations.md">Detect overflow</a></li><li><a href="guides/c_api.md">Use the C API</a></li></ul></td><td><a href=".gitbook/assets/orange2.png">orange2.png</a></td></tr><tr><td><strong>Tutorials</strong></td><td>Learn more about TFHE-rs with our tutorials.<br><br></td><td><ul><li><a href="tutorials/see-all-tutorials.md#start-here">Start here</a></li><li><a href="tutorials/see-all-tutorials.md#go-further">Go further</a></li><li><a href="tutorials/see-all-tutorials.md">See all tutorials</a></li></ul></td><td><a href=".gitbook/assets/orange3.png">orange3.png</a></td></tr></tbody></table>
<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>Fundamentals</strong></td><td>Explore the core features.</td><td><ul><li><a href="fundamentals/configure-and-generate-keys.md">Configure</a></li><li><a href="fundamentals/encrypt-data.md">Encrypt data</a></li></ul></td><td><a href=".gitbook/assets/build1.png">build1.png</a></td></tr><tr><td><strong>Guides</strong></td><td>Deploy your project.</td><td><ul><li><a href="guides/run_on_gpu.md">Run on GPU</a></li><li><a href="guides/rust_configuration.md">Configure Rust</a></li></ul></td><td><a href=".gitbook/assets/build2.png">build2.png</a></td></tr><tr><td><strong>Tutorials</strong></td><td>Learn more with tutorials.</td><td><ul><li><a href="tutorials/see-all-tutorials.md#start-here">Start here</a></li><li><a href="tutorials/see-all-tutorials.md#go-further">Go further</a></li></ul></td><td><a href=".gitbook/assets/build3.png">build3.png</a></td></tr></tbody></table>
## Explore more

View File

@@ -2,7 +2,7 @@
* [Welcome to TFHE-rs](README.md)
## Getting Started
## Get Started
* [What is TFHE-rs?](getting\_started/readme.md)
* [Installation](getting\_started/installation.md)
@@ -13,34 +13,35 @@
## Fundamentals
* [Configure and generate keys](fundamentals/configure-and-generate-keys.md)
* [Set the server key](fundamentals/set-the-server-key.md)
* [Encrypt data](fundamentals/encrypt-data.md)
* [Compute on encrypted data](fundamentals/compute.md)
* [Decrypt data](fundamentals/decrypt-data.md)
* [Generate encrypted pseudo random values](fundamentals/encrypted-prf.md)
* [Serialize/Deserialize](fundamentals/serialization.md)
* [Compress ciphertexts/Keys](fundamentals/compress.md)
* [Debug](fundamentals/debug.md)
* [Configuration and key generation](fundamentals/configure-and-generate-keys.md)
* [Server key](fundamentals/set-the-server-key.md)
* [Encryption](fundamentals/encrypt-data.md)
* [Computation on encrypted data](fundamentals/compute.md)
* [Decryption](fundamentals/decrypt-data.md)
* [Encrypted pseudo random values](fundamentals/encrypted-prf.md)
* [Serialization/deserialization](fundamentals/serialization.md)
* [Compressing ciphertexts/keys](fundamentals/compress.md)
* [Debugging](fundamentals/debug.md)
## Guides
* [Configure Rust](guides/rust\_configuration.md)
* [Run on GPU](guides/run\_on\_gpu.md)
* [Detect overflow](guides/overflow\_operations.md)
* [Migrate data to newer versions of TFHE-rs](guides/migrate\_data.md)
* [Use public key encryption](guides/public\_key.md)
* [Generic function bounds](guides/trait\_bounds.md)
* [Use parallelized PBS](guides/parallelized\_pbs.md)
* [Use the C API](guides/c\_api.md)
* [Use the JS on WASM API](guides/js\_on\_wasm\_api.md)
* [Use multi-threading using the rayon crate](guides/rayon\_crate.md)
* [Use trivial ciphertexts](guides/trivial\_ciphertext.md)
* [Rust configuration](guides/rust\_configuration.md)
* [GPU acceleration](guides/run\_on\_gpu.md)
* [Overflow detection](guides/overflow\_operations.md)
* [Data migration to newer versions of TFHE-rs](guides/migrate\_data.md)
* [Public key encryption](guides/public\_key.md)
* [Zero-knowledge proofs](guides/zk-pok.md)
* [Generic trait bounds](guides/trait\_bounds.md)
* [Parallelized PBS](guides/parallelized\_pbs.md)
* [High-level API in C](guides/c\_api.md)
* [JS on WASM API](guides/js\_on\_wasm\_api.md)
* [Multi-threading with Rayon crate](guides/rayon\_crate.md)
* [Trivial ciphertexts](guides/trivial\_ciphertext.md)
* [PBS statistics](guides/pbs-stats.md)
## Tutorials
* [See all tutorials](tutorials/see-all-tutorials.md)
* [All tutorials](tutorials/see-all-tutorials.md)
* [Homomorphic parity bit](tutorials/parity\_bit.md)
* [Homomorphic case changing on Ascii string](tutorials/ascii\_fhe\_string.md)
* [SHA256 with Boolean API](tutorials/sha256\_bool.md)

View File

@@ -13,6 +13,6 @@ Here are a series of articles that guide you to go deeper into the understanding
The article [Guide to Fully Homomorphic Encryption over the Discretized Torus](https://eprint.iacr.org/2021/1402.pdf) gives more mathematical details about the TFHE scheme.
You can also watch the video record of the original talk by Ilaria Chillotti for FHE.org:&#x20;
You can also watch the video record of the original talk by Ilaria Chillotti for FHE.org:
{% embed url="https://youtu.be/npoHSR6-oRw" %}

View File

@@ -1,12 +1,18 @@
# Reducing the size of keys and ciphertexts
TFHE-rs includes features to reduce the size of both keys and ciphertexts, by compressing them. Most TFHE-rs entities contain random numbers generated by a Pseudo Random Number Generator (PRNG). A PRNG is deterministic, therefore storing only the random seed used to generate those numbers is enough to keep all the required information: using the same PRNG and the same seed, the full chain of random values can be reconstructed when decompressing the entity.
# Compressing ciphertexts/keys
In the library, entities that can be compressed are prefixed by `Compressed`. For instance, the type of a compressed `FheUint256` is `CompressedFheUint256`.
This document explains the mechanism and steps to compress ciphertext and keys.
**TFHE-rs** includes features to compress both keys and ciphertexts, reducing their storage and transmission sizes.
Most **TFHE-rs** entities contain random numbers generated by a Pseudo Random Number Generator (PRNG). Since a PRNG is deterministic, storing only the random seed used to generate those numbers preserves all necessary information. When decompressing the entity, using the same PRNG and the same seed will reconstruct the full chain of random values.
In **TFHE-rs**, compressible entities are prefixed with `Compressed`. For instance, a compressed `FheUint256` is declared as `CompressedFheUint256`.
In the following example code, we use the `bincode` crate dependency to serialize in a binary format and compare serialized sizes.
## Compressed ciphertexts
This example shows how to compress a ciphertext encypting messages over 16 bits.
This example shows how to compress a ciphertext encrypting messages over 16 bits:
```rust
use tfhe::prelude::*;
@@ -35,9 +41,9 @@ fn main() {
}
```
## Compressed server keys
This example shows how to compress the server keys.
This example shows how to compress the server keys:
```rust
use tfhe::prelude::*;
@@ -75,12 +81,14 @@ fn main() {
```
## Compressed public keys
This example shows how to compress the classical public keys.
This example shows how to compress the classical public keys:
{% hint style="warning" %}
It is not currently recommended to use the CompressedPublicKey to encrypt ciphertexts without first decompressing it. In case the resulting PublicKey is too large to fit in memory the encryption with the CompressedPublicKey will be very slow, this is a known problem and will be addressed in future releases.
It is not currently recommended to use the `CompressedPublicKey` to encrypt ciphertexts without first decompressing them. If the resulting PublicKey is too large to fit in memory, it may result in significant slowdowns.
This issue has been identified and will be addressed in future releases.
{% endhint %}
```rust
@@ -106,10 +114,9 @@ fn main() {
}
```
## Compressed compact public key
This example shows how to use compressed compact public keys.
This example shows how to use compressed compact public keys:
```rust
use tfhe::prelude::*;

View File

@@ -1,8 +1,10 @@
# Compute on encrypted data
Computations on encrypted data should be as easy to write as normal Rust, thanks to the usage of operator overloading.
This document describes how to perform computation on encrypted data.
In the following example, the full encryption, computation with Rust's built-in operators and decryption flow is described:
With **TFHE-rs,** the program can be as straightforward as conventional Rust coding by using operator overloading.
The following example illustrates the complete process of encryption, computation using Rusts built-in operators, and decryption:
```rust
use tfhe::prelude::*;

View File

@@ -1,12 +1,12 @@
# Configure and create keys
# Configuration and key generation
The first step is the creation of the configuration. The configuration is used to declare which type you will (or will not) use, as well as enabling you to use custom crypto-parameters for these types. Custom parameters should only be used for more advanced usage and/or testing.
This document explains how to initialize the configuration and generate keys.
A configuration can be created by using the ConfigBuilder type.
The configuration specifies the selected data types and their custom crypto-parameters. You should only use custom parameters for advanced usage and/or testing.
In this example, 8-bit unsigned integers with default parameters are used. The `integers` feature must also be enabled, as per the table on [this page](../guides/rust\_configuration.md#homomorphic-types).
To create a configuration, use the `ConfigBuilder` type. The following example shows the setup using 8-bit unsigned integers with default parameters. Additionally, ensure the `integers` feature is enabled, as indicated in the table on [this page](../guides/rust\_configuration.md#homomorphic-types).
The config is generated by first creating a builder with all types deactivated. Then, the integer types with default parameters are activated, since we are going to use FheUint8 values.
The configuration is initialized by creating a builder with all types deactivated. Then, the integer types with default parameters are activated, for using `FheUint8` values.
```rust
use tfhe::{ConfigBuilder, generate_keys};
@@ -19,6 +19,7 @@ fn main() {
}
```
The `generate_keys` command returns a client key and a server key.
The `generate_keys` command returns a client key and a server key:
The `client_key` is meant to stay private and not leave the client, whereas the `server_key` can be made public and sent to a server for it to enable FHE computations.
* **Client\_key**: this key should remain private and never leave the client.
* **Server\_key**: this key can be public and sent to a server to enable FHE computations.

View File

@@ -1,16 +1,14 @@
# Debug
# Debugging
Since tfhe-rs 0.5, [trivial ciphertexts](../fundamentals/trivial\_ciphertext.md) have another application. They can be used to allow debugging via a debugger or print statements as well as speeding-up execution time so that you won't have to spend minutes waiting for execution to progress.
This document explains a feature to facilitate debugging.
This can greatly improve the pace at which one develops FHE applications.
Starting from **TFHE-rs 0.5**, [trivial ciphertexts](../guides/trivial\_ciphertext.md) introduce a new feature to facilitate debugging. This feature supports a debugger, print statements, and faster execution, significantly reducing waiting time and enhancing the development pace of FHE applications.
{% hint style="warning" %}
Keep in mind that trivial ciphertexts are not secure at all, thus an application released/deployed in production must never receive trivial ciphertext from a client.
Trivial ciphertexts are not secure. An application released/deployed in production must never receive trivial ciphertext from a client.
{% endhint %}
## Example
To use this feature, simply call your circuits/functions with trivially encrypted values (made using `encrypt_trivial`) instead of real encryptions (made using `encrypt`)
To use this feature, simply call your circuits/functions with trivially encrypted values that are created using `encrypt_trivial`(instead of real encryptions that are created using `encrypt`):
```rust
use tfhe::prelude::*;
@@ -49,18 +47,18 @@ fn main() {
}
```
This example is going to print.
This example is going to print:
```console
a: Ok(1234), b: Ok(4567), c: Ok(89101112)
a * b = Ok(5635678)
```
If any input to `mul_all` is not a trivial ciphertexts, the computations would be done 100% in FHE, and the program would output:
If any input to `mul_all` is not a trivial ciphertexts, the computations will be done 100% in FHE, and the program will output:
```console
a: Err(NotTrivialCiphertextError), b: Err(NotTrivialCiphertextError), c: Err(NotTrivialCiphertextError)
a * b = Err(NotTrivialCiphertextError)
```
Using trivial encryptions as input, the example runs in **980 ms** on a standard 12 cores laptop, using real encryptions it would run in **7.5 seconds** on a 128-core machine.
Using trivial encryptions as input, the example runs in **980 ms** on a standard 12-core laptop, compared to **7.5 seconds** on a 128-core machine using real encryptions.

View File

@@ -1,6 +1,8 @@
# Decrypt data
Decrypting data is achieved by using the `decrypt` method, which comes from the FheDecrypt trait.
This document provides instructions on how to decrypt data.
To decrypt data, use the `decrypt` method from the `FheDecrypt` trait:
```rust
use tfhe::prelude::*;

View File

@@ -1,8 +1,10 @@
# Encrypt data
Encrypting data is achieved via the `encrypt` associated function of the FheEncrypt trait.
This document explains how to encrypt data.
Types exposed by this crate implement at least one of FheEncrypt or FheTryEncrypt to allow encryption.
To encrypt data, use the `encrypt` method from the `FheEncrypt` trait. This crate provides types that implement either `FheEncrypt` or `FheTryEncrypt` or both, to enable encryption.
Here is an example:
```rust
use tfhe::prelude::*;

View File

@@ -1,6 +1,6 @@
# Generate encrypted pseudo random values
TFHE-rs supports generating pseudo random values in FHE that are not known by the server.
This document gives an example of generating pseudo random values in FHE that are not known by the server.
```rust
use tfhe::prelude::FheDecrypt;

View File

@@ -1,10 +1,14 @@
# Serialization/Deserialization
# Serialization/deserialization
As explained in the Introduction, most types are meant to be shared with the server that performs the computations.
This document explains the `serialization` and `deserialization` features that are useful to send data to a server to perform the computations.
The easiest way to send these data to a server is to use the `serialization` and `deserialization` features. `tfhe` uses the [serde](https://crates.io/crates/serde) framework. Serde's `Serialize` and `Deserialize` functions are implemented on TFHE's types.
## Serialization/deserialization
To serialize our data, a [data format](https://serde.rs/#data-formats) should be picked. Here, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is a binary format.
**TFHE-rs** uses the [Serde](https://crates.io/crates/serde) framework and implements Serde's `Serialize` and `Deserialize` traits.
To serialize the data, you need to choose a [data format](https://serde.rs/#data-formats). In the following example, we use [bincode](https://crates.io/crates/bincode) for its binary format.
Here is a full example:
```toml
# Cargo.toml
@@ -68,29 +72,26 @@ fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error
}
```
## Safe serialization/deserialization
# Safe Serialization/Deserialization
When dealing with sensitive types, it's important to implement safe serialization and safe deserialization functions to prevent runtime errors and enhance security. The safe serialization and deserialization use `bincode` internally.
For some types, safe serialization and deserialization functions are available.
Bincode is used internally.
The safe deserialization must take the output of a safe-serialization as input. During the process, the following validation occurs:
Safe-deserialization must take as input the output of a safe-serialization.
On this condition, validation of the following is done:
- type: trying to deserialize `type A` from a serialized `type B` raises an error along the lines of *On deserialization, expected type A, got type B* instead of a generic deserialization error (or less likely a meaningless result of `type A`)
- version: trying to deserialize `type A` (version 0.2) from a serialized `type A` (incompatible version 0.1) raises an error along the lines of *On deserialization, expected serialization version 0.2, got version 0.1* instead of a generic deserialization error (or less likely a meaningless result of `type A` (version 0.2))
- parameter compatibility: trying to deserialize into an object of `type A` with some crypto parameters from a an object of `type A` with other crypto parameters raises an error along the lines of *Deserialized object of type A not conformant with given parameter set*.
If both parameters sets 1 and 2 have the same lwe dimension for ciphertexts, a ciphertext from param 1 may not fail this deserialization check with param 2 even if doing this deserialization may not make sense.
Also, this check can't distinguish ciphertexts/server keys from independent client keys with the same parameters (which makes no sense combining to do homomorphic operations).
This check is meant to prevent runtime errors in server homomorphic operations by checking that server keys and ciphertexts are compatible with the same parameter set.
* **Type match**: deserializing `type A` from a serialized `type B` raises an error indicating "On deserialization, expected type A, got type B".
* **Version compatibility**: deserializing `type A` of a newer version (for example, version 0.2) from a serialized `type A` of an older version (for example, version 0.1) raises an error indicating "On deserialization, expected serialization version 0.2, got version 0.1".
* **Parameter compatibility**: deserializing an object of `type A` with one set of crypto parameters from an object of `type A` with another set of crypto parameters raises an error indicating "Deserialized object of type A not conformant with given parameter set"
* If both parameter sets have the same LWE dimension for ciphertexts, a ciphertext from param 1 may not fail this deserialization check with param 2.
* This check can't distinguish ciphertexts/server keys from independent client keys with the same parameters.
* This check is meant to prevent runtime errors in server homomorphic operations by checking that server keys and ciphertexts are compatible with the same parameter set.
* You can use the standalone `is_conformant` method to check parameter compatibility. Besides, the `safe_deserialize_conformant` function includes the parameter compatibility check, and the `safe_deserialize` function does not include the compatibility check.
* **Size limit**: both serialization and deserialization processes expect a size limit (measured in bytes) for the serialized data:
* On serialization, an error is raised if the serialized output exceeds the specific limit.
* On deserialization, an error is raised if the serialized input exceeds the specific limit.
Moreover, a size limit (in number of bytes) for the serialized data is expected on both serialization and deserialization.
On serialization, an error is raised if the serialized output would be bigger than the given limit.
On deserialization, an error is raised if the serialized input is bigger than the given limit.
It is meant to gracefully return an error in case of an attacker trying to cause an out of memory error on deserialization.
This feature aims to gracefully return an error in case of an attacker trying to cause an out-of-memory error on deserialization.
A standalone `is_conformant` method is also available on those types to do a parameter compatibility check.
Parameter compatibility check is done by `safe_deserialize_conformant` function but a `safe_deserialize` function without this check is also available.
Here is an example:
```rust
// main.rs

View File

@@ -1,8 +1,10 @@
# Set the server key
The next step is to call `set_server_key`
This document explains how to call the function `set_server_key`.
This function will **move** the server key to an internal state of the crate and manage the details to give a simpler interface.
This function will **move** the server key to an internal state of the crate and manage the details for a simpler interface.
Here is an example:
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key};

View File

@@ -1,16 +1,18 @@
# Benchmarks
Due to their nature, homomorphic operations are naturally slower than their cleartext equivalents. Some timings are exposed for basic operations. For completeness, benchmarks for other libraries are also given.
This document details the performance benchmarks of homomorphic operations using **TFHE-rs**.
By their nature, homomorphic operations run slower than their cleartext equivalents. The following are the timings for basic operations, including benchmarks from other libraries for comparison.
{% hint style="info" %}
All CPU benchmarks were launched on an AWS hpc7a.96xlarge instance with the following specifications: AMD EPYC 9R14 CPU @ 2.60GHz and 740GB of RAM.
All CPU benchmarks were launched on an `AWS hpc7a.96xlarge` instance equipped with an `AMD EPYC 9R14 CPU @ 2.60GHz` and 740GB of RAM.
{% endhint %}
## Integer
## Integer operations
This measures the execution time for some operation sets of tfhe-rs::integer (the unsigned version). Note that the timings for `FheInt` (i.e., the signed integers) are similar.
The following tables benchmark the execution time of some operation sets using `FheUint` (unsigned integers). The `FheInt` (signed integers) performs similarly.
The table below reports the timing on CPU when the inputs of the benchmarked operation are encrypted.
The next table shows the operation timings on CPU when all inputs are encrypted:
| Operation \ Size | `FheUint8` | `FheUint16` | `FheUint32` | `FheUint64` | `FheUint128` | `FheUint256` |
| ------------------------------------------------------ | ---------- | ----------- | ----------- | ----------- | ------------ | ------------ |
@@ -18,39 +20,39 @@ The table below reports the timing on CPU when the inputs of the benchmarked ope
| Add / Sub (`+`,`-`) | 57.7 ms | 82.1 ms | 105 ms | 128 ms | 155 ms | 195 ms |
| Mul (`x`) | 80.8 ms | 149 ms | 211 ms | 366 ms | 961 ms | 3.2 s |
| Equal / Not Equal (`eq`, `ne`) | 31.9 ms | 31.3 ms | 48.7 ms | 50.9 ms | 51.4 ms | 52.8 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 48.1 ms | 68.4 ms | 83.2 ms | 102 ms | 121 ms | 145 ms |
| Max / Min (`max`,`min`) | 81.1 ms | 96.4 ms | 114 ms | 133 ms | 154 ms | 198 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 48.1 ms | 68.4 ms | 83.2 ms | 102 ms | 121 ms | 145 ms |
| Max / Min (`max`,`min`) | 81.1 ms | 96.4 ms | 114 ms | 133 ms | 154 ms | 198 ms |
| Bitwise operations (`&`, `\|`, `^`) | 15.9 ms | 16.1 ms | 16.7 ms | 17.8 ms | 19.1 ms | 21.9 ms |
| Div / Rem (`/`, `%`) | 613 ms | 1.56 s | 3.73 s | 8.83 s | 20.6 s | 53.8 s |
| Div / Rem (`/`, `%`) | 613 ms | 1.56 s | 3.73 s | 8.83 s | 20.6 s | 53.8 s |
| Left / Right Shifts (`<<`, `>>`) | 88.1 ms | 108 ms | 133 ms | 160 ms | 199 ms | 403 ms |
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 83.6 ms | 101 ms | 127 ms | 158 ms | 198 ms | 402 ms |
| Leading / Trailing zeros/ones | 85.7 ms | 135 ms | 151 ms | 206 ms | 250 ms | 308 ms |
| Log2 | 98.0 ms | 151 ms | 173 ms | 231 ms | 279 ms | 333 ms |
The table below reports the timing on CPU when the left input of the benchmarked operation is encrypted and the other is a clear scalar of the same size.
The next table shows the operation timings on CPU when the left input is encrypted and the right is a clear scalar of the same size:
| Operation \ Size | `FheUint8` | `FheUint16` | `FheUint32` | `FheUint64` | `FheUint128` | `FheUint256` |
| ------------------------------------------------------ | ---------- | ----------- | ----------- | ----------- | ------------ | ------------ |
| Add / Sub (`+`,`-`) | 61.7 ms | 81.0 ms | 99.3 ms | 117 ms | 144 ms | 189 ms |
| Mul (`x`) | 62.7 ms | 133 ms | 173 ms | 227 ms | 371 ms | 917 ms |
| Equal / Not Equal (`eq`, `ne`) | 33.2 ms | 32.2 ms | 31.4 ms | 49.1 ms | 49.8 ms | 51.6 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 32.1 ms | 51.9 ms | 70.8 ms | 89.2 ms | 110 ms | 130 ms |
| Max / Min (`max`,`min`) | 69.9 ms | 88.8 ms | 107 ms | 130 ms | 153 ms | 188 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 32.1 ms | 51.9 ms | 70.8 ms | 89.2 ms | 110 ms | 130 ms |
| Max / Min (`max`,`min`) | 69.9 ms | 88.8 ms | 107 ms | 130 ms | 153 ms | 188 ms |
| Bitwise operations (`&`, `\|`, `^`) | 16.1 ms | 16.3 ms | 17.2 ms | 18.2 ms | 19.6 ms | 22.1 ms |
| Div (`/`) | 160 ms | 194 ms | 275 ms | 391 ms | 749 ms | 2.02 s |
| Rem (`%`) | 281 ms | 404 ms | 533 ms | 719 ms | 1.18 s | 2.76 s |
| Div (`/`) | 160 ms | 194 ms | 275 ms | 391 ms | 749 ms | 2.02 s |
| Rem (`%`) | 281 ms | 404 ms | 533 ms | 719 ms | 1.18 s | 2.76 s |
| Left / Right Shifts (`<<`, `>>`) | 16.0 ms | 16.2 ms | 16.7 ms | 17.9 ms | 19.2 ms | 21.8 ms |
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 16.4 ms | 16.6 ms | 17.2 ms | 18.4 ms | 19.7 ms | 22.2 ms |
All timings are related to parallelized Radix-based integer operations, where each block is encrypted using the default parameters (i.e., PARAM\_MESSAGE\_2\_CARRY\_2\_KS\_PBS, more information about parameters can be found [here](../references/fine-grained-apis/shortint/parameters.md)). To ensure predictable timings, the operation flavor is the `default` one: the carry is propagated if needed. The operation costs may be reduced by using `unchecked`, `checked`, or `smart`.
All timings are based on parallelized Radix-based integer operations where each block is encrypted using the default parameters `PARAM_MESSAGE_2_CARRY_2_KS_PBS`. To ensure predictable timings, we perform operations in the `default` mode, which propagates the carry bit as needed. You can minimize operational costs by selecting from 'unchecked', 'checked', or 'smart' modes, each balancing performance and security differently.
Benchmark results on GPU for all these operations can be consulted [here](../guides/run_on_gpu.md#benchmarks).
For more details about parameters, see [here](../references/fine-grained-apis/shortint/parameters.md). You can find the benchmark results on GPU for all these operations [here](../guides/run\_on\_gpu.md#benchmarks).
## Shortint
## Shortint operations
This measures the execution time for some operations using various parameter sets of tfhe-rs::shortint. Except for `unchecked_add`, all timings are related to the `default` operations. This flavor ensures predictable timings for an operation along the entire circuit by clearing the carry space after each operation.
This uses the Concrete FFT + AVX-512 configuration.
The next table shows the execution time of some operations using various parameter sets of tfhe-rs::shortint. Except for `unchecked_add`, we perform all the operations in the `default` mode. This mode ensures predictable timings along the entire circuit by clearing the carry space after each operation. The configuration is Concrete FFT + AVX-512.
| Parameter set | PARAM\_MESSAGE\_1\_CARRY\_1 | PARAM\_MESSAGE\_2\_CARRY\_2 | PARAM\_MESSAGE\_3\_CARRY\_3 | PARAM\_MESSAGE\_4\_CARRY\_4 |
| ---------------------------------- | --------------------------- | --------------------------- | --------------------------- | --------------------------- |
@@ -59,11 +61,11 @@ This uses the Concrete FFT + AVX-512 configuration.
| mul\_lsb | 5.99 ms | 12.3 ms | 101 ms | 500 ms |
| keyswitch\_programmable\_bootstrap | 6.40 ms | 12.9 ms | 104 ms | 489 ms |
## Boolean
## Boolean operations
This measures the execution time of a single binary Boolean gate.
The next table shows the execution time of a single binary Boolean gate.
### tfhe-rs::boolean.
### tfhe-rs::boolean
| Parameter set | Concrete FFT + AVX-512 |
| ---------------------------------------------------- | ---------------------- |
@@ -71,19 +73,19 @@ This measures the execution time of a single binary Boolean gate.
| PARAMETERS\_ERROR\_PROB\_2\_POW\_MINUS\_165\_KS\_PBS | 13.7 ms |
| TFHE\_LIB\_PARAMETERS | 9.90 ms |
### tfhe-lib.
#### tfhe-lib
Using the same hpc7a.96xlarge machine as the one for tfhe-rs, the timings are:
Using the same hpc7a.96xlarge machine as the one for tfhe-rs, the timings are as follows:
| Parameter set | spqlios-fma |
| ------------------------------------------------ | ----------- |
| default\_128bit\_gate\_bootstrapping\_parameters | 13.5 ms |
### OpenFHE (v1.1.2).
### OpenFHE (v1.1.2)
Following the official instructions from OpenFHE, `clang14` and the following command are used to setup the project: `cmake -DNATIVE_SIZE=32 -DWITH_NATIVEOPT=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DWITH_OPENMP=OFF ..`
Following the official instructions from OpenFHE, we use `clang14` and the following command to setup the project: `cmake -DNATIVE_SIZE=32 -DWITH_NATIVEOPT=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DWITH_OPENMP=OFF ..`
To use the HEXL library, the configuration used is as follows:
The following example shows how to initialize the configuration to use the HEXL library:
```bash
export CXX=clang++
@@ -96,21 +98,23 @@ hexl -> y
scripts/build-openfhe-development-hexl.sh
```
Using the same hpc7a.96xlarge machine as the one for tfhe-rs, the timings are:
Using the same hpc7a.96xlarge machine as the one for tfhe-rs, the timings are as follows:
| Parameter set | GINX | GINX w/ Intel HEXL |
| --------------------------------- | ------- | ------------------ |
| FHEW\_BINGATE/STD128\_OR | 25.5 ms | 21,6 ms |
| FHEW\_BINGATE/STD128\_LMKCDEY\_OR | 25.4 ms | 19.9 ms |
## How to reproduce TFHE-rs benchmarks
## Reproducing TFHE-rs benchmarks
TFHE-rs benchmarks can be easily reproduced from [source](https://github.com/zama-ai/tfhe-rs).
**TFHE-rs** benchmarks can be easily reproduced from the [source](https://github.com/zama-ai/tfhe-rs).
{% hint style="info" %}
AVX512 is now enabled by default for benchmarks when available
{% endhint %}
The following example shows how to reproduce **TFHE-rs** benchmarks:
```shell
#Boolean benchmarks:
make bench_boolean

View File

@@ -1,44 +1,43 @@
# Installation
This document provides instructions to set up **TFHE-rs** in your project.
## Importing
## Importing into your project
First, add **TFHE-rs** as a dependency in your `Cargo.toml`.
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`.
**For `x86_64` machine running a Unix-like OS:**
If you are using an `x86_64` machine running a Unix-like OS:
```toml
tfhe = { version = "0.6.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
```
If you are using an `ARM` machine running a Unix-like OS:
**For `ARM` machine running a Unix-like OS:**
```toml
tfhe = { version = "0.6.0", features = [ "boolean", "shortint", "integer", "aarch64-unix" ] }
```
If you are using an `x86_64` machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
**For `x86_64` machines with the** [**`rdseed instruction`**](https://en.wikipedia.org/wiki/RDRAND) **running Windows:**
```toml
tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64"] }
```
{% hint style="info" %}
You need to use a Rust version >= 1.73 to compile TFHE-rs.
**Rust version**: a minimum Rust version of 1.73 is required to compile **TFHE-rs**.
{% endhint %}
{% hint style="success" %}
When running code that uses `TFHE-rs`, it is highly recommended to run in release mode with cargo's `--release` flag to have the best possible performance
**Performance**: for optimal performance, it is highly recommended to run code that uses **`TFHE-rs`** in release mode with cargo's `--release` flag.
{% endhint %}
## Supported platforms
TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (x86 with `RDSEED` instruction).
**TFHE-rs** currently supports the following platforms:
| OS | x86 | aarch64 |
| ------- | ------------- | ---------------- |
| Linux | `x86_64-unix` | `aarch64-unix`\* |
| macOS | `x86_64-unix` | `aarch64-unix`\* |
| Windows | `x86_64` | Unsupported |
| OS | x86 | aarch64 |
| ------- | ---------------------------------- | ---------------- |
| Linux | `x86_64-unix` | `aarch64-unix`\* |
| macOS | `x86_64-unix` | `aarch64-unix`\* |
| Windows | `x86_64` with `RDSEED` instruction | Unsupported |

View File

@@ -1,11 +1,19 @@
# Homomorphic Types and Operations
# Types & Operations
This document explains the encryption types and operations supported by **TFHE-rs.**
## Types
`TFHE-rs` includes two main types to represent encrypted data:
- `FheUint`: this is the homomorphic equivalent of Rust unsigned integers `u8, u16, ...`
- `FheInt`: this is the homomorphic equivalent of Rust (signed) integers `i8, i16, ...`
In the same manner as many programming languages, the number of bits used to represent the data must be chosen when declaring a variable. For instance:
**TFHE-rs** supports two main types of encrypted data:
* `FheUint`: homomorphic equivalent of Rust unsigned integers `u8, u16, ...`
* `FheInt`: homomorphic equivalent of Rust signed integers `i8, i16, ...`
### Integer
**TFHE-rs** uses integers to encrypt all messages which are larger than 4 bits.
Similar to Rust integers, you need to specify the bit size of data when declaring a variable:
```Rust
// let clear_a: u64 = 7;
@@ -18,60 +26,56 @@ In the same manner as many programming languages, the number of bits used to rep
let mut c = FheUint128::try_encrypt(clear_c, &keys)?;
```
## Operation list
The table below contains an overview of the available operations in `TFHE-rs`. The notation `Enc` (for Encypted) either refers to `FheInt` or `FheUint`, for any size between 1 and 256-bits.
## Operations
More details, and further examples, are given in the following sections.
**TFHE-rs** supports various operations on encrypted integers (`Enc`) of any size between 1 and 256 bits. These operations can also work between encrypted integers and clear integers (`Int`).
| name | symbol | `Enc`/`Enc` | `Enc`/ `Int` |
|-----------------------|----------------|--------------------|--------------------------|
| Neg | `-` | :heavy_check_mark: | :heavy_check_mark: |
| Add | `+` | :heavy_check_mark: | :heavy_check_mark: |
| Sub | `-` | :heavy_check_mark: | :heavy_check_mark: |
| Mul | `*` | :heavy_check_mark: | :heavy_check_mark: |
| Div | `/` | :heavy_check_mark: | :heavy_check_mark: |
| Rem | `%` | :heavy_check_mark: | :heavy_check_mark: |
| Not | `!` | :heavy_check_mark: | :heavy_check_mark: |
| BitAnd | `&` | :heavy_check_mark: | :heavy_check_mark: |
| BitOr | `\|` | :heavy_check_mark: | :heavy_check_mark: |
| BitXor | `^` | :heavy_check_mark: | :heavy_check_mark: |
| Shr | `>>` | :heavy_check_mark: | :heavy_check_mark: |
| Shl | `<<` | :heavy_check_mark: | :heavy_check_mark: |
| Min | `min` | :heavy_check_mark: | :heavy_check_mark: |
| Max | `max` | :heavy_check_mark: | :heavy_check_mark: |
| Greater than | `gt` | :heavy_check_mark: | :heavy_check_mark: |
| Greater or equal than | `ge` | :heavy_check_mark: | :heavy_check_mark: |
| Lower than | `lt` | :heavy_check_mark: | :heavy_check_mark: |
| Lower or equal than | `le` | :heavy_check_mark: | :heavy_check_mark: |
| Equal | `eq` | :heavy_check_mark: | :heavy_check_mark: |
| Cast (into dest type) | `cast_into` | :heavy_check_mark: | :heavy_multiplication_x: |
| Cast (from src type) | `cast_from` | :heavy_check_mark: | :heavy_multiplication_x: |
| Ternary operator | `if_then_else` | :heavy_check_mark: | :heavy_multiplication_x: |
| name | symbol | `Enc`/`Enc` | `Enc`/ `Int` |
| --------------------- | -------------- | -------------------- | -------------------------- |
| Neg | `-` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Add | `+` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Sub | `-` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Mul | `*` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Div | `/` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Rem | `%` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Not | `!` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| BitAnd | `&` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| BitOr | `\|` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| BitXor | `^` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Shr | `>>` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Shl | `<<` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Min | `min` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Max | `max` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Greater than | `gt` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Greater or equal than | `ge` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Less than | `lt` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Less or equal than | `le` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Equal | `eq` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Cast (into dest type) | `cast_into` | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
| Cast (from src type) | `cast_from` | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
| Ternary operator | `if_then_else` | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
### Arithmetic operations
## Integer
Homomorphic integer types (`FheUint` and `FheInt`) support the following arithmetic operations:
In `TFHE-rs`, integers are used to encrypt all messages which are larger than 4 bits. All supported operations are listed below.
| name | symbol | type |
| --------------------------------------------------------- | ------ | ------ |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `-` | Unary |
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
| [Div](https://doc.rust-lang.org/std/ops/trait.Div.html)\* | `/` | Binary |
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html)\* | `%` | Binary |
### Arithmetic operations.
Specifications for operations with zero:
Homomorphic integer types support arithmetic operations.
* **Division by zero**: returns modulus - 1.
* Example: for FheUint8 (modulus = $$2^8=256$$), dividing by zero returns an ecryption of 255.
* **Remainder operator**: returns the first input unchanged.
* Example: if `ct1 = FheUint8(63)` and `ct2 = FheUint8(0)`, then ct1 % ct2 returns FheUint8(63).
The list of supported operations is:
| name | symbol | type |
|----------------------------------------------------------|--------|--------|
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `-` | Unary |
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
| [Div](https://doc.rust-lang.org/std/ops/trait.Div.html)* | `/` | Binary |
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html)* | `%` | Binary |
For division by 0, the convention is to return `modulus - 1`. For instance, for `FheUint8`, the modulus is $$2^8=256$$, so a division by 0 will return an encryption of 255.
For the remainder operator, the convention is to return the first input without any modification. For instance, if `ct1 = FheUint8(63)` and `ct2 = FheUint8(0)` then `ct1 % ct2` will return `FheUint8(63)`.
A simple example of how to use these operations:
The following example shows how to perform arithmetic operations:
```rust
use tfhe::prelude::*;
@@ -110,24 +114,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```
### Bitwise operations.
### Bitwise operations
Homomorphic integer types support some bitwise operations.
Homomorphic integer types support the following bitwise operations:
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------------------------------------- | -------------- | ------ |
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
| [Rotate Right](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate\_right) | `rotate_right` | Binary |
| [Rotate Left](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate\_left) | `rotate_left` | Binary |
| name | symbol | type |
|--------------------------------------------------------------------------------------|----------------|--------|
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
| [Rotate Right](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_right) | `rotate_right` | Binary |
| [Rotate Left](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_left) | `rotate_left` | Binary |
A simple example of how to use these operations:
The following example shows how to perform bitwise operations:
```rust
use tfhe::prelude::*;
@@ -159,29 +161,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```
### Comparisons.
### Comparison operations
Homomorphic integers support comparison operations.
Due to some Rust limitations, it is not possible to overload the comparison symbols because of the inner definition of the operations. This is because Rust expects to have a Boolean as an output, whereas a ciphertext is returned when using homomorphic types.
You will need to use different methods instead of using symbols for the comparisons. These methods follow the same naming conventions as the two standard Rust traits:
Homomorphic integers support comparison operations. However, due to Rust's limitations, you cannot overload comparison symbols. This is because Rust requires Boolean outputs from such operations, but homomorphic types return ciphertexts. Therefore, you should use the following methods, which conform to the naming conventions of Rusts standard traits:
* [PartialOrd](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html)
* [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
The list of supported operations is:
Supported operations:
| name | symbol | type |
|-----------------------------------------------------------------------------|--------|--------|
| [Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `eq` | Binary |
| [Not Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `ne` | Binary |
| [Greater Than ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `gt` | Binary |
| --------------------------------------------------------------------------- | ------ | ------ |
| [Equal](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `eq` | Binary |
| [Not Equal](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `ne` | Binary |
| [Greater Than](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `gt` | Binary |
| [Greater or Equal](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `ge` | Binary |
| [Lower ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `lt` | Binary |
| [Lower or Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `le` | Binary |
| [Lower](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `lt` | Binary |
| [Lower or Equal](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `le` | Binary |
A simple example of how to use these operations:
The following example shows how to perform comparison operations:
```rust
use tfhe::prelude::*;
@@ -220,16 +218,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```
### Min/Max.
### Min/Max operations
Homomorphic integers support the min/max operations.
Homomorphic integers support the min/max operations:
| name | symbol | type |
| ---- | ------ | ------ |
| Min | `min` | Binary |
| Max | `max` | Binary |
A simple example of how to use these operations:
The following example shows how to perform min/max operations:
```rust
use tfhe::prelude::*;
@@ -259,14 +257,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```
### Ternary conditional operator.
The ternary conditional operator allows computing conditional instructions of the form `if cond { choice_if } else { choice_else }`.
### Ternary conditional operations
The ternary conditional operator execute conditional instructions in the form `if cond { choice_if } else { choice_else }`.
| name | symbol | type |
|------------------|----------------|---------|
| ---------------- | -------------- | ------- |
| Ternary operator | `if_then_else` | Ternary |
The syntax is `encrypted_condition.if_then_else(encrypted_choice_if, encrypted_choice_else)`. The `encrypted_condition` should be an encryption of 0 or 1 in order to be valid.
The syntax is `encrypted_condition.if_then_else(encrypted_choice_if, encrypted_choice_else)`. The valid `encrypted_condition` must be an encryption of 0 or 1.
The following example shows how to perform ternary conditional operations:
```rust
use tfhe::prelude::*;
@@ -309,10 +310,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```
### Casting operations
### Casting.
Casting between integer types is possible via the `cast_from` associated function
or the `cast_into` method.
You can cast between integer types using either the `cast_from` associated function or the `cast_into` method.
The following example shows how to perform casting operations:
```rust
use tfhe::prelude::*;
@@ -370,12 +372,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```
### Boolean Operations
## Boolean Operations
Native homomorphic Booleans support common Boolean operations.
The list of supported operations is:
Native homomorphic Booleans support the following common Boolean operations:
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |

View File

@@ -1,15 +1,19 @@
# Quick start
The basic steps for using the high-level API of TFHE-rs are:
This document explains the basic steps of using the high-level API of **TFHE-rs.**
1. [Importing the TFHE-rs prelude](quick\_start.md#imports);
2. Client-side: [Configuring and creating keys](../fundamentals/configure-and-generate-keys.md);
3. Client-side: [Encrypting data](../fundamentals/encrypt-data.md);
4. Server-side: [Setting the server key](../fundamentals/set-the-server-key.md);
5. Server-side: [Computing over encrypted data](../fundamentals/compute.md);
6. Client-side: [Decrypting data](../fundamentals/decrypt-data.md).
## Workflow explanation
Here is a full example (combining the client and server parts):
These are the steps to use the **TFHE-rs** high-level API:
1. [Import the **TFHE-rs** prelude](quick\_start.md#imports)
2. Client-side: [configure and generate keys](../fundamentals/configure-and-generate-keys.md)
3. Client-side: [encrypt data](../fundamentals/encrypt-data.md)
4. Server-side: [set the server key](../fundamentals/set-the-server-key.md)
5. Server-side: [compute over encrypted data](../fundamentals/compute.md)
6. Client-side: [decrypt data](../fundamentals/decrypt-data.md)
This example demonstrates the basic workflow combining the client and server parts:
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
@@ -40,19 +44,19 @@ fn main() {
}
```
The default configuration for x86 Unix machines:
The default configuration for x86 Unix machines is as follows:
```toml
tfhe = { version = "0.6.0", features = ["integer", "x86_64-unix"]}
```
Configuration options for different platforms can be found [here](installation.md). Other rust and homomorphic types features can be found [here](../guides/rust\_configuration.md).
Refer to the [installation documentation](installation.md) for configuration options of different platforms.Learn more about homomorphic types features in the [configuration documentation.](../guides/rust\_configuration.md)
### Imports
## Step1: Importing
`tfhe` uses `traits` to have a consistent API for creating FHE types and enable users to write generic functions. To be able to use associated functions and methods of a trait, the trait has to be in scope.
**TFHE-rs** uses `traits` to implement consistent APIs and generic functions. To use `traits`, they must be in scope.
To make it easier, the `prelude` 'pattern' is used. All of the important `tfhe` traits are in a `prelude` module that you can **glob import**. With this, there is no need to remember or know the traits that you want to import.
The `prelude` pattern provides a convenient way to globally import all important **TFHE-rs** traits at once. This approach saves time and avoids confusion.
```rust
use tfhe::prelude::*;

View File

@@ -2,33 +2,47 @@
![](../.gitbook/assets/doc\_header\_tfhe-rs.png)
TFHE-rs is a pure Rust implementation of TFHE for Boolean and integer arithmetics over encrypted data. It includes a Rust and C API, as well as a client-side WASM API.
**TFHE-rs** is a pure Rust implementation of Fully Homomorphic Encryption over the Torus (TFHE) to perform Boolean and integer arithmetic on encrypted data.
TFHE-rs is meant for developers and researchers who want full control over what they can do with TFHE, while not worrying about the low level implementation.
**TFHE-rs** implements advanced TFHE features, empowering developers and researchers with fine-grained control over TFHE so that they can focus on high-level functionality without delving into low-level implementation.
The goal is to have a stable, simple, high-performance, and production-ready library for all the advanced features of TFHE.
**TFHE-rs** includes:
* **Rust API**: the primary API for working with **TFHE-rs** in Rust projects.
* **C API**: for developers who prefer to use C.
* **Client-side WASM API**: to integrate **TFHE-rs** functionalities into WebAssembly applications.
## Key cryptographic concepts
The TFHE-rs library implements Zamas variant of Fully Homomorphic Encryption over the Torus (TFHE). TFHE is based on Learning With Errors (LWE), a well-studied cryptographic primitive believed to be secure even against quantum computers.
TFHE is a Fully Homomorphic Encryption (FHE) scheme based on Learning With Errors (LWE), which is a secure cryptographic primitive against even quantum computers. The **TFHE-rs** library implements Zamas variant of TFHE.
In cryptography, a raw value is called a message (also sometimes called a cleartext), while an encoded message is called a plaintext and an encrypted plaintext is called a ciphertext.
#### Homomorphic Encryption Basics
The idea of homomorphic encryption is that you can compute on ciphertexts while not knowing messages encrypted within them. A scheme is said to be _fully homomorphic_, meaning any program can be evaluated with it, if at least two of the following operations are supported ($$x$$ is a plaintext and $$E[x]$$ is the corresponding ciphertext):
The basic elements of cryptography:
* homomorphic univariate function evaluation: $$f(E[x]) = E[f(x)]$$
* homomorphic addition: $$E[x] + E[y] = E[x + y]$$
* homomorphic multiplication: $$E[x] * E[y] = E[x * y]$$
* **Message (or Cleartext):** raw values before encryption.
* **Plaintext:** encoded messages.
* **Ciphertext**: encrypted messages.
Zama's variant of TFHE is fully homomorphic and deals with fixed-precision numbers as messages. It implements all needed homomorphic operations, such as addition and function evaluation via **Programmable Bootstrapping**. You can read more about Zama's TFHE variant in the [preliminary whitepaper](https://whitepaper.zama.ai/).
FHE allows to compute on ciphertexts without revealing the content of the messages. A scheme is fully homomorphic if it supports at least two of the following operations when evaluating any programs. ($$x$$ is a plaintext and $$E[x]$$ is the corresponding ciphertext):
Using FHE in a Rust program with TFHE-rs consists in:
* **Homomorphic univariate function evaluation:** $$f(E[x]) = E[f(x)]$$
* **Homomorphic addition:** $$E[x] + E[y] = E[x + y]$$
* **Homomorphic multiplication:** $$E[x] * E[y] = E[x * y]$$
* generating a client key and a server key using secure parameters:
* a client key encrypts/decrypts data and must be kept secret
* a server key is used to perform operations on encrypted data and could be public (also called an evaluation key)
* encrypting plaintexts using the client key to produce ciphertexts
* operating homomorphically on ciphertexts with the server key
* decrypting the resulting ciphertexts into plaintexts using the client key
## Zama's variant of TFHE
If you would like to know more about the problems that FHE solves, we suggest you review our [6 minute introduction to homomorphic encryption](https://6min.zama.ai/).
Zama's variant of TFHE is a fully homomorphic scheme that takes fixed-precision numbers as messages. It implements all homomorphic operations needed, such as addition and function evaluation via Programmable Bootstrapping.
Refer to the the [preliminary whitepaper](https://whitepaper.zama.ai/) for more details.
Using **TFHE-rs** in Rust includes the following steps:
1. **Key generation**: generate a pair of keys using secure parameters.
* **Client key**: used for encryption and decryption of data. This key must be kept secret.
* **Server key (or Evaluation key)**: used for performing operations on encrypted data. This key could be public.
2. **Encryption**: encrypt plaintexts using the client key to produce ciphertexts.
3. **Homomorphic operation**: perform operations on ciphertexts using the server key.
4. **Decryption**: decrypt the resulting ciphertexts back to plaintexts using the client key.
To understand more about FHE applications, see the [6-minute introduction to homomorphic encryption](https://6min.zama.ai/).

View File

@@ -1,28 +1,29 @@
# Security and Cryptography
# Security and cryptography
This document introduces the cryptographic concepts of the scheme of Fully Homomorphic Encryption over the Torus (TFHE) and the security considerations of **TFHE-rs.**
## TFHE
`TFHE-rs` is a cryptographic library dedicated to Fully Homomorphic Encryption. As its name suggests, it is based on the TFHE scheme.
**TFHE-rs** is a cryptographic library that implements Fully Homomorphic Encryption using the TFHE scheme. You should understand the basics of TFHE to consider its limitations, such as:
It is necessary to understand some basics about TFHE in order to consider the limitations. Of particular importance are the precision (number of bits used to represent plaintext values) and execution time (why TFHE operations are slower than native operations).
* **The precision**: TFHE has limitations on the number of bits used to represent plaintext values.
* **The execution time**: TFHE operations are slower than native operations due to their complexity.
## LWE ciphertexts
Although there are many kinds of ciphertexts in TFHE, all of the encrypted values in `TFHE-rs` are mainly stored as LWE ciphertexts.
TFHE-rs primarily utilizes Learning With Errors (LWE) ciphertexts. The LWE problem forms the basis of TFHE's security and is considered resistant to quantum attacks.
The security of TFHE relies on the LWE problem, which stands for Learning With Errors. The problem is believed to be secure against quantum attacks.
An LWE Ciphertext is a collection of 32-bit or 64-bit unsigned integers. Before encrypting a message in an LWE ciphertext, you first need to encode it as a plaintext by shifting the message to the most significant bits of the unsigned integer type used.
An LWE Ciphertext is a collection of 32-bit or 64-bit unsigned integers. Before encrypting a message in an LWE ciphertext, one must first encode it as a plaintext. This is done by shifting the message to the most significant bits of the unsigned integer type used.
Then, a small random value called noise is added to the least significant bits. This noise is crucial in ensuring the security of the ciphertext.
Then, you add a small random value called noise to the least significant bits. This noise is crucial in ensuring the security of the ciphertext.
$$plaintext = (\Delta * m) + e$$
$$m \in \mathbb{Z}_p$$
![](../_static/lwe.png)
![](../\_static/lwe.png)
To go from a **plaintext** to a **ciphertext,** one must encrypt the plaintext using a secret key.
To get a **ciphertext** from a **plaintext,** you must encrypt the plaintext using a secret key.
An LWE secret key is a list of `n` random integers: $$S = (s_0, ..., s_{n-1})$$. $$n$$ is called the $$LweDimension$$
@@ -31,7 +32,7 @@ An LWE ciphertext is composed of two parts:
* The mask $$(a_0, ..., a_{n-1})$$
* The body $$b$$
The mask of a _fresh_ ciphertext (one that is the result of an encryption, and not of an operation such as ciphertext addition) is a list of `n` uniformly random values.
The mask of a fresh ciphertext (the result of an encryption, and not the result of operations such as ciphertext addition) is a list of `n` uniformly random values.
The body is computed as follows:
@@ -45,58 +46,57 @@ $$
ct_0 = (a_{0}, ..., a_{n-1}, b) \\ ct_1 = (a_{0}^{\prime}, ..., a_{n-1}^{\prime}, b^{\prime}) \\ ct_{2} = ct_0 + ct_1 \\ ct_{2} = (a_{0} + a_{0}^{\prime}, ..., a_{n-1} + a_{n-1}^{\prime}, b + b^{\prime})\\ b + b^{\prime} = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext + (\sum_{i = 0}^{n-1}{a_i^{\prime} * s_i}) + plaintext^{\prime}\\ b + b^{\prime} = (\sum_{i = 0}^{n-1}{(a_i + a_i^{\prime})* s_i}) + \Delta m + \Delta m^{\prime} + e + e^{\prime}\\
$$
To add ciphertexts, it is sufficient to add their masks and bodies. Instead of just adding two integers, one needs to add $$n + 1$$ elements. This is an intuitive example to show the slowdown of FHE computation compared to plaintext computation, but other operations are far more expensive (e.g., the computation of a lookup table using Programmable Bootstrapping).
To add ciphertexts, it is necessary to add both their masks and bodies. The operation involves adding $$n + 1$$ elements, rather than just adding two integers. This is an intuitive example to show how FHE computation is slower compared to plaintext computation. However, other operations are far more expensive (for example, the computation of a lookup table using Programmable Bootstrapping).
## Programmable Bootstrapping, noise management and carry bits
## Programmable Bootstrapping, noise management, and carry bits
In FHE, there are two types of operations that can be applied to ciphertexts:
In FHE, two types of operations can be applied to ciphertexts:
* **leveled operations**, which increase the noise in the ciphertext
* **bootstrapped operations**, which reduce the noise in the ciphertext
* **Leveled operations**, which increase the noise in the ciphertext
* **Bootstrapped operations**, which reduce the noise in the ciphertext
In FHE, noise must be tracked and managed to guarantee the correctness of the computation.
Bootstrapping operations are used across the computation to decrease noise within the ciphertexts, preventing it from tampering with the message. The rest of the operations are called leveled because they do not need bootstrapping operations and are usually very fast as a result.
Noise is critical in FHE because it can tamper with the message if not tracked and managed properly. Bootstrapping operations decrease noise within the ciphertexts and guarantee the correctness of computation. The rest of the operations do not need bootstrapping operations, thus they are called leveled operations and are usually very fast as a result.
The following sections explain the concept of noise and padding in ciphertexts.
### Noise.
### Noise
For it to be secure, LWE requires random noise to be added to the message at encryption time.
To ensure security, LWE requires random noise to be added to the message during encryption.
In TFHE, this random noise is drawn from a Centered Normal Distribution, parameterized by a standard deviation. The chosen standard deviation has an impact on the security level. With everything else fixed, increasing the standard deviation will lead to an increase in the security level.
TFHE scheme draws this random noise from a Centered Normal Distribution with a standard deviation parameter. The choice of standard deviation impacts the security level: increasing the standard deviation enhances security while keeping other factors constant.
In `TFHE-rs`, noise is encoded in the least significant bits of each plaintext. Each leveled computation increases the value of the noise. If too many computations are performed, the noise will eventually overflow into the message bits and lead to an incorrect result.
**TFHE-rs** encodes the noise in the least significant bits of each plaintext. Each leveled computation increases the value of the noise. If too many computations are performed, the noise will eventually overflow into the message bits and lead to an incorrect result.
The figure below illustrates this problem in the case of an addition, where an extra bit of noise is incurred as a result.
The following figure illustrates how the extra bit of noise is incurred during an addition operation.
![Noise overtaking the plaintexts after homomorphic addition. Most significant bits are on the left.](../_static/overflow.png)
`TFHE-rs` offers the ability to automatically manage noise by performing bootstrapping operations to reset the noise.
![Noise overtaking the plaintexts after homomorphic addition. Most significant bits are on the left.](../\_static/overflow.png)
**TFHE-rs** enables automatic noise management by performing bootstrapping operations to reset the noise.
### Programmable BootStrapping (PBS)
The bootstrapping of TFHE has the particularity of being programmable: this means that any function can be homomorphically computed over an encrypted input, while also reducing the noise. These functions are represented by look-up tables. The computation of a PBS is in general either preceded or followed by a keyswitch, which is an operation used to change the encryption key. The output ciphertext is then encrypted with the same key as the input one. To do this, two (public) evaluation keys are required: a bootstrapping key and a keyswitching key. These operations are quite complex to describe, more information about these operations (or about TFHE in general) can be found here [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
The bootstrapping of TFHE is programmable. This allows any function to be homomorphically computed over an encrypted input, while also reducing the noise. These functions are represented by look-up tables.
In general, the computation of a PBS is preceded or followed by a keyswitch, an operation to change the encryption key. The output ciphertext is then encrypted with the same key as the input one. To do this, two (public) evaluation keys are required: a bootstrapping key and a keyswitching key.
### Carry.
These operations are quite complex to describe in short, you can find more details about these operations (or about TFHE in general) in the [TFHE Deep Dive](../explanations/tfhe-deep-dive.md).
Since encoded values have a fixed precision, operating on them can produce results that are outside of the original interval. To avoid losing precision or wrapping around the interval, `TFHE-rs` uses additional bits by defining bits of **padding** on the most significant bits.
### Carry
As an example, consider adding two ciphertexts. Adding two values could end up outside the range of either ciphertext, and thus necessitate a carry, which would then be carried onto the first padding bit. In the figure below, each plaintext over 32 bits has one bit of padding on its left (i.e., the most significant bit). After the addition, the padding bit is no longer available, as it has been used in order for the carry. This is referred to as **consuming** bits of padding. Since no padding is left, there is no guarantee that further additions would yield correct results.
Since encoded values have a fixed precision, operating on them can produce results that are outside of the original interval. To avoid losing precision or wrapping around the interval, **TFHE-rs** uses additional bits by defining bits of **padding** on the most significant bits.
![](../_static/carry.png)
For example, when adding two ciphertexts, the sum could exceed the range of either ciphertext, and thus necessitate a carry that would then be transferred onto the first padding bit. In the following figure, each plaintext over 32 bits has one bit of padding on its left (the most significant bit). After the addition, the padding bit gets consumed to accommodate the carry. We refer to this process as **consuming** bits of padding. Without any padding-left, further additions may not produce accurate results.
![](../\_static/carry.png)
### Security.
## Security
By default, the cryptographic parameters provided by `TFHE-rs` ensure at least 128 bits of security. The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
By default, the cryptographic parameters provided by **TFHE-rs** ensure at least 128 bits of security. The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
The default parameters for the TFHE-rs library are chosen considering the IND-CPA security model, and are selected with a bootstrapping failure probability fixed at p_error = $2^{-40}$. In particular, it is assumed that the results of decrypted computations are not shared by the secret key owner with any third parties, as such an action can lead to leakage of the secret encryption key. If you are designing an application where decryptions must be shared, you will need to craft custom encryption parameters which are chosen in consideration of the IND-CPA^D security model [1].
The default parameters for the **TFHE-rs** library are chosen considering the IND-CPA security model, and are selected with a bootstrapping failure probability fixed at p\_error = $2^{-40}$. In particular, it is assumed that the results of decrypted computations are not shared by the secret key owner with any third parties, as such an action can lead to leakage of the secret encryption key. If you are designing an application where decryptions must be shared, you will need to craft custom encryption parameters which are chosen in consideration of the IND-CPA^D security model \[1].
[1] Li, Baiyu, et al. "Securing approximate homomorphic encryption using differential privacy." Annual International Cryptology Conference. Cham: Springer Nature Switzerland, 2022. https://eprint.iacr.org/2022/816.pdf
\[1][ Li, Baiyu, et al. "Securing approximate homomorphic encryption using differential privacy." Annual International Cryptology Conference. Cham: Springer Nature Switzerland, 2022.](https://eprint.iacr.org/2022/816.pdf)
### Classical public key encryption.
## Classical public key encryption.
In classical public key encryption, the public key contains a given number of ciphertexts all encrypting the value 0. By setting the number of encryptions to 0 in the public key at $$m = \lceil (n+1) \log(q) \rceil + \lambda$$, where $$n$$ is the LWE dimension, $$q$$ is the ciphertext modulus, and $$\lambda$$ is the number of security bits. This construction is secure due to the leftover hash lemma, which relates to the impossibility of breaking the underlying multiple subset sum problem. This guarantees both a high-density subset sum and an exponentially large number of possible associated random vectors per LWE sample $$(a,b)$$.

View File

@@ -1,28 +1,33 @@
# High-Level API in C
# High-level API in C
This library exposes a C binding to the high-level TFHE-rs primitives to implement _Fully Homomorphic Encryption_ (FHE) programs.
This document describes the C bindings to the **TFHE-rs** high-level primitives for creating Fully Homomorphic Encryption (FHE) programs.
## Setting-up TFHE-rs C API for use in a C program.
## Setting up TFHE-rs C API for C programming.
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
You can build **TFHE-rs** C API on a Unix x86\_64 machine using the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo +nightly build --release --features=x86_64-unix,high-level-c-api -p tfhe && make symlink_c_libs_without_fingerprint
```
or on a Unix aarch64 machine using the following command:
For a Unix aarch64 machine, use the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo +nightly build --release --features=aarch64-unix,high-level-c-api -p tfhe && make symlink_c_libs_without_fingerprint
```
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO\_ROOT}/target/release/".
Locate files in the right path:
The `tfhe-c-api-dynamic-buffer.h` header and the static (.a) and dynamic (.so) libraries will be found in "${REPO\_ROOT}/target/release/deps/".
* In `${REPO\_ROOT}/target/release/`, you can find:
* The `tfhe.h` header
* The static (.a) and dynamic (.so) `libtfhe` binaries
* In `${REPO\_ROOT}/target/release/deps/`, you can find:
* The `tfhe-c-api-dynamic-buffer.h` header
* The static (.a) and dynamic (.so) libraries
The build system needs to be set up so that the C or C++ program links against TFHE-rs C API binaries and the dynamic buffer library.
Ensure your build system configures the C or C++ program links against **TFHE-rs** C API binaries and the dynamic buffer library.
Here is a minimal CMakeLists.txt to do just that:
The following is a minimal `CMakeLists.txt` configuration example:
```cmake
project(my-project)
@@ -57,11 +62,13 @@ target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
## Commented code of a uint128 subtraction using `TFHE-rs C API`.
The following example demonstrates uint128 subtraction using the **TFHE-rs** C API:
{% hint style="warning" %}
WARNING: The following example does not have proper memory management in the error case to make it easier to fit the code on this page.
**WARNING**: this example omits proper memory management in the error case to improve code readability.
{% endhint %}
To run the example below, the above CMakeLists.txt and main.c files need to be in the same directory. The commands to run are:
Ensure the above `CMakeLists.txt` and `main.c` files are in the same directory. Use the following commands to execute the example:
```shell
# /!\ Be sure to update CMakeLists.txt to give the absolute path to the compiled tfhe library

View File

@@ -1,20 +1,20 @@
# Tutorial
# JS on WASM API
This document outlines how to use the **TFHE-rs** WebAssembly (WASM) client API for key generation, encryption, and decryption, providing setup examples for Node.js and web browsers.
TFHE-rs supports WASM for the client api, that is, it supports key generation, encryption, decryption but not doing actual computations.
**TFHE-rs** supports WASM client API, which includes functionality for key generation, encryption, and decryption. However, it does not support FHE computations.
TFHE-rs supports 3 WASM 'targets':
- nodejs: to be used in a nodejs app/package
- web: to be used in a web browser
- web-parallel: to be used in a web browser with multi-threading support
**TFHE-rs** supports 3 WASM `targets`:
In all cases, the core of the API is same, only few initialization function
changes.
* Node.js: For use in Node.js applications or packages
* Web: For use in web browsers
* Web-parallel: For use in web browsers with multi-threading support
The core of the API remains the same, requiring only minor changes in the initialization functions.
## Example
## Node.js
### nodejs
Example:
```javascript
@@ -58,12 +58,11 @@ function fhe_uint32_example() {
}
```
### Web
## Web
- When using the Web WASM target, there is an additional `init` function to call.
- When using the Web WASM target with parallelism enabled, there is also one more initialization function to call `initThreadPool`
When using the Web WASM target, you should call an additional `init` function. With parallelism enabled, you need to call another additional `initThreadPool` function.
#### Example
Example:
```js
import init, {
@@ -87,35 +86,31 @@ async function example() {
## Compiling the WASM API
The TFHE-rs repo has a Makefile that contains targets for each of the 3 possible variants of the API:
Use the provided Makefile in the **TFHE-rs** repository to compile for the desired target:
- `make build_node_js_api` to build the nodejs API
- `make build_web_js_api` to build the browser API
- `make build_web_js_api_parallel` to build the browser API with parallelism
* `make build_node_js_api` for the Node.js API
* `make build_web_js_api` for the browser API
* `make build_web_js_api_parallel` for the browser API with parallelism
The compiled WASM package will be in tfhe/pkg.
The compiled WASM packages are located in `tfhe/pkg`.
{% hint style="info" %}
The sequential browser API and the nodejs API are published as npm packages.
You can add the browser API to your project using the command `npm i tfhe`.
You can add the nodejs API to your project using the command `npm i node-tfhe`.
The browser API and the Node.js API are available as npm packages. Using `npm i tfhe` for the browser API and `npm i node-tfhe` for the Node.js API.
{% endhint %}
## Using the JS on WASM API
TFHE-rs uses WASM to expose a JS binding to the client-side primitives, like key generation and encryption, of the Boolean and shortint modules.
**TFHE-rs** uses WASM to provide a JavaScript (JS) binding to the client-side primitives, like key generation and encryption within the Boolean and shortint modules.
There are several limitations at this time. Due to a lack of threading support in WASM, key generation can be too slow to be practical for bigger parameter sets.
Currently, there are several limitations. Due to a lack of threading support in WASM, key generation can be too slow to be practical for bigger parameter sets.
Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory space of WASM. This means that some parameter sets are virtually unusable.
Some parameter sets lead to the FHE keys exceeding the 2GB memory limit of WASM, making these parameter sets virtually unusable.
## First steps using TFHE-rs JS on WASM API
### Setting-up TFHE-rs JS on WASM API for use in nodejs programs.
### Setting up TFHE-rs JS on WASM API for Node.js programs.
To build the JS on WASM bindings for TFHE-rs, you need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible [`rust toolchain`](https://rustup.rs/).
In a shell, then run the following to clone the TFHE-rs repo (one may want to checkout a specific tag, here the default branch is used for the build):
To build the JS on WASM bindings for **TFHE-rs**, install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) and the necessary [`rust toolchain`](https://rustup.rs/). Cone the **TFHE-rs** repository and build using the following commands (this will build using the default branch, you can check out a specific tag depending on your requirements):
```shell
$ git clone https://github.com/zama-ai/tfhe-rs.git
@@ -130,11 +125,11 @@ $ rustup run wasm-pack build --release --target=nodejs --features=boolean-client
[INFO]: :-) Your wasm pkg is ready to publish at ...
```
The command above targets nodejs. A binding for a web browser can be generated as well using `--target=web`. This use case will not be discussed in this tutorial.
The command above targets Node.js. To generate a binding for a web browser, use `--target=web`. However, this tutorial does not cover that particular use case.
Both Boolean and shortint features are enabled here, but it's possible to use one without the other.
Both Boolean and shortint features are enabled here, but it's possible to use them individually.
After the build, a new directory _**pkg**_ is present in the `tfhe` directory.
After the build, a new directory **pkg** is available in the `tfhe` directory.
```shell
$ ls pkg
@@ -145,7 +140,7 @@ $
### Commented code to generate keys for shortint and encrypt a ciphertext
{% hint style="info" %}
Be sure to update the path of the required clause in the example below for the TFHE package that was just built.
Make sure to update the path of the required clause in the example below to match the location of the TFHE package that was just built.
{% endhint %}
```javascript
@@ -195,7 +190,7 @@ function shortint_example() {
shortint_example();
```
The `example.js` script can then be run using [`node`](https://nodejs.org/), like so:
Then, you can run the `example.js` script using [`node`](https://nodejs.org/) as follows:
```shell
$ node example.js

View File

@@ -1,3 +1,3 @@
# Migrating Data to TFHE-rs 0.6.0 (This Release)
# Data migration to newer versions of TFHE-rs
Forward compatibility code to migrate data from TFHE-rs 0.5 to TFHE-rs 0.6 has been added in a minor release of TFHE-rs 0.5, the documentation about the process can be found [here](https://docs.zama.ai/tfhe-rs/v/0.5-1/how-to/migrate_data).
**TFHE-rs** v0.5 includes forward compatibility code in a minor release to facilitate data migration to **TFHE-rs** v0.6.0. You can find detailed documentation on this migration process [here](https://docs.zama.ai/tfhe-rs/v/0.5-3/how-to/migrate\_data).

View File

@@ -1,16 +1,20 @@
# Overflow Detection
# Overflow detection
TFHE-rs includes a list of specific operations to detect overflows. The overall idea is to have a specific ciphertext encrypting a flag reflecting the status of the computations. When an overflow occurs, this flag is set to true. Since the server is not able to evaluate this value (since it is encrypted), the client has to check the flag value when decrypting to determine if an overflow has happened. These operations might be slower than their equivalent which do not detect overflow, so they are not enabled by default (see the table below). In order to use them, specific operators must be called.
At the moment, only additions, subtractions, multiplications are supported. Missing operations will be added soon.
This document explains how **TFHE-rs** implements specific operations to detect overflows in computations.
The list of operations along with their symbol is:
| name | symbol | type |
|----------------------------------------------------------|----------------|--------|
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `overflow_add` | Binary |
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `overflow_sub` | Binary |
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `overflow_mul` | Binary |
The mechanism of detecting overflow consists in returning an encrypted flag with a specific ciphertext that reflects the state of the computation. When an overflow occurs, this flag is set to true. Since the server is not able to evaluate this encrypted value, the client has to check the flag value when decrypting to determine if an overflow has happened.
These operations are then used exactly in the same way than the usual ones. The only difference lies into the decryption, as shown in following example:
These operations might be slower than their non-overflow-detecting equivalent, so they are not enabled by default. To use them, you must explicitly call specific operators. At the moment, only additions, subtractions, and multiplications are supported. We plan to add more operations in future releases.
Here's the list of operations supported along with their symbol:
| name | symbol | type |
| ------------------------------------------------------- | -------------- | ------ |
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `overflow_add` | Binary |
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `overflow_sub` | Binary |
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `overflow_mul` | Binary |
The usage of these operations is similar to the standard ones. The key difference is in the decryption process, as shown in following example:
```rust
/// Adds two [FheUint] and returns a boolean indicating overflow.
@@ -37,17 +41,20 @@ assert_eq!(
assert_eq!(overflowed.decrypt(&client_key), true);
```
The current benchmarks are given in the following tables (the first one for unsigned homomorphic integers and the second one for the signed integers):
The following tables show the current benchmarks result.
| Operation\Size | FheUint8 | FheUint16 | FheUint32 | FheUint64 | FheUint128 | FheUint256 |
|--------------------------|-----------|-----------|-----------|-----------|------------|------------|
| unsigned_overflowing_add | 63.67 ms | 84.11 ms | 107.95 ms | 120.8 ms | 147.38 ms | 191.28 ms |
| unsigned_overflowing_sub | 68.89 ms | 81.83 ms | 107.63 ms | 120.38 ms | 150.21 ms | 190.39 ms |
| unsigned_overflowing_mul | 140.76 ms | 191.85 ms | 272.65 ms | 510.61 ms | 1.34 s | 4.51 s |
Unsigned homomorphic integers:
| Operation\Size | FheUint8 | FheUint16 | FheUint32 | FheUint64 | FheUint128 | FheUint256 |
| -------------------------- | --------- | --------- | --------- | --------- | ---------- | ---------- |
| unsigned\_overflowing\_add | 63.67 ms | 84.11 ms | 107.95 ms | 120.8 ms | 147.38 ms | 191.28 ms |
| unsigned\_overflowing\_sub | 68.89 ms | 81.83 ms | 107.63 ms | 120.38 ms | 150.21 ms | 190.39 ms |
| unsigned\_overflowing\_mul | 140.76 ms | 191.85 ms | 272.65 ms | 510.61 ms | 1.34 s | 4.51 s |
| Operation\Size | FheInt8 | FheInt16 | FheInt32 | FheInt64 | FheInt128 | FheInt256 |
|------------------------|-----------|-----------|-----------|-----------|-----------|-----------|
| signed_overflowing_add | 76.54 ms | 84.78 ms | 104.23 ms | 134.38 ms | 162.99 ms | 202.56 ms |
| signed_overflowing_sub | 82.46 ms | 86.92 ms | 104.41 ms | 132.21 ms | 168.06 ms | 201.17 ms |
| signed_overflowing_mul | 277.91 ms | 365.67 ms | 571.22 ms | 1.21 s | 3.57 s | 12.84 s |
Signed homomorphic integers:
| Operation\Size | FheInt8 | FheInt16 | FheInt32 | FheInt64 | FheInt128 | FheInt256 |
| ------------------------ | --------- | --------- | --------- | --------- | --------- | --------- |
| signed\_overflowing\_add | 76.54 ms | 84.78 ms | 104.23 ms | 134.38 ms | 162.99 ms | 202.56 ms |
| signed\_overflowing\_sub | 82.46 ms | 86.92 ms | 104.41 ms | 132.21 ms | 168.06 ms | 201.17 ms |
| signed\_overflowing\_mul | 277.91 ms | 365.67 ms | 571.22 ms | 1.21 s | 3.57 s | 12.84 s |

View File

@@ -1,9 +1,14 @@
# Parallelized Programmable Bootstrapping
# Parallelized PBS
The [Programmable Bootstrapping](../getting_started/security_and_cryptography.md)(PBS) is a sequential operation by nature. However, some [recent results](https://marcjoye.github.io/papers/JP22ternary.pdf) showed that parallelism could be added at the cost of having larger keys. Overall, the performance of the PBS are improved. This new PBS is called a multi bit PBS.
In TFHE-rs, since integer homomorphic operations are already parallelized, activating this feature may improve performance in the case of high core count CPUs if enough cores are available, or for small input message precision.
This document describes the implementation and benefits of parallelized [Programmable Bootstrapping](../getting\_started/security\_and\_cryptography.md) (PBS) in **TFHE-rs**, including code examples for using multi-bit PBS parameters and ensuring deterministic execution.
In what follows, an example on how to use the parallelized bootstrapping by choosing multi bit PBS parameters:
## Parallelized Programmable Bootstrapping
Programmable Bootstrapping is inherently a sequential operation. However, some [recent results](https://marcjoye.github.io/papers/JP22ternary.pdf) showed that introducing parallelism is feasible at the expense of larger keys, thereby enhancing the performance of PBS. This new PBS is called a multi-bit PBS.
**TFHE-rs** can already perform parallel execution of integer homomorphic operations. Activating this feature can lead to performance improvements, particularly in the case of high core-count CPUs when enough cores are available, or when dealing with operations that require small input message precision.
The following example shows how to use parallelized bootstrapping by choosing multi-bit PBS parameters:
```rust
use tfhe::prelude::*;
@@ -33,8 +38,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```
# Deterministic Parallelized Programmable Bootstrapping
By construction, the parallelized PBS might not be deterministic: the resulting ciphertext will always decrypt to the same plaintext, but the order of the operations could differ so the output ciphertext might differ. In order to activate the deterministic version, the suffix 'with_deterministic_execution()' should be added to the parameters, as shown in the following example:
## Deterministic parallelized Programmable Bootstrapping
By nature, the parallelized PBS might not be deterministic: while the resulting ciphertext will always decrypt to the correct plaintext, the order of the operations could vary, resulting in different output ciphertext. To ensure a consistent ciphertext output regardless of execution order, add the `with_deterministic_execution()` suffix to the parameters.
Here's an example:
```rust
use tfhe::prelude::*;

View File

@@ -1,12 +1,12 @@
# PBS Statistics
# PBS statistics
The `shortint` API now keeps track of how many PBS were executed with a global counter when enabling the `pbs-stats` feature.
This document explains how to use the PBS statistics feature in **TFHE-rs'** shortint API to assess the overall computational intensity in FHE applications.
This allows knowing precisely how many PBS are executed in a circuit and estimate the overall compute intensity of FHE code using either the `shortint`, `integer` or High-Level APIs.
The `shortint` API now includes a global counter to track the number of Programmable Bootstrapping (PBS) executed with the `pbs-stats` feature. This feature enables precise tracking of PBS executions in a circuit. It helps to estimate the overall compute intensity of FHE code using either the `shortint`, `integer,` or High-Level APIs.
You can query how many PBSes were executed by calling `get_pbs_count`. You can reset the PBS count by calling `reset_pbs_count` to more easily know how many PBSes were executed by each part of your code.
To know how many PBSes were executed, call `get_pbs_count`. To reset the PBS count, call `reset_pbs_count`. You can combine two functions to understand how many PBSes were executed in each part of your code.
Combined with the [`debug mode`](`debug.md`) it can allow to have estimations very quickly while iterating on the FHE code.
When combined with the [`debug mode`](../fundamentals/debug.md), this feature allows for quick estimations during iterations on the FHE code.
Here is an example of how to use the PBS counter:

View File

@@ -1,12 +1,17 @@
# Use public key encryption
# Public key encryption
Public key encryption refers to the cryptographic paradigm where the encryption key can be publicly distributed, whereas the decryption key remains secret to the owner. This differs from usual case where the same secret key is used to encrypt and decrypt the data. In TFHE-rs, there exists two methods for public key encryptions. First, the usual one, where the public key contains many encryptions of zero. More details can be found in [Guide to Fully Homomorphic Encryption over the \[Discretized\] Torus, Appendix A.](https://eprint.iacr.org/2021/1402). The second method is based on the paper entitled [TFHE Public-Key Encryption Revisited](https://eprint.iacr.org/2023/603). The main advantage of the latter method in comparison with the former lies into the key sizes, which are drastically reduced.
This document explains public key encryption and provides instructions for 2 methods.
Note that public keys can be [compressed](../fundamentals/compress.md)
Public key encryption refers to the cryptographic paradigm where the encryption key can be publicly distributed, whereas the decryption key remains secret to the owner. This differs from the usual case where the same secret key is used to encrypt and decrypt the data. In **TFHE-rs**, there are two methods for public key encryptions:
* **Classical public key**: the first method involves the public key containing many encryptions of zero, as detailed in [Guide to Fully Homomorphic Encryption over the \[Discretized\] Torus, Appendix A.](https://eprint.iacr.org/2021/1402)
* **Compact public key**: the second method is based on the paper [TFHE Public-Key Encryption Revisited](https://eprint.iacr.org/2023/603), allowing for significantly smaller key sizes compared to the first method.
Public keys can also be [compressed](../fundamentals/compress.md) to reduce size.
## Classical public key
This example shows how to use public keys.
This example shows how to use classical public keys.
```rust
use tfhe::prelude::*;
@@ -26,9 +31,9 @@ fn main() {
## Compact public key
This example shows how to use compact public keys. The main difference is in the ConfigBuilder, where the parameter set has been changed.
This example shows how to use compact public keys. The main difference is in the `ConfigBuilder` where the parameter set has been changed.
See [the guide on ZK proofs](zk-pok.md) to see how to encrypt data using compact public keys and generate a zero knowledge proof of correct encryption at the same time.
For more information on using compact public keys to encrypt data and generate a zero-knowledge proof of correct encryption at the same time, see[ the guide on ZK proofs](zk-pok.md).
```rust
use tfhe::prelude::*;

View File

@@ -1,16 +1,14 @@
# Making Rayon And TFHE-RS Work Together
# Multi-threading with Rayon crate
[rayon](https://crates.io/crates/rayon) is a popular crate to easily write multi-threaded code in Rust.
This document describes how to use Rayon for parallel processing in **TFHE-rs**, detailing configurations for single and multi-client applications with code examples.
It is possible to use rayon to write multi-threaded TFHE-rs code. However due to internal details of `rayon` and
`TFHE-rs`, there is some special setup that needs to be done.
[Rayon](https://crates.io/crates/rayon) is a popular Rust crate that simplifies writing multi-threaded code. You can use Rayon to write multi-threaded **TFHE-rs** code. However, due to the specifications of Rayon and **TFHE-rs**, certain setups are necessary.
## Single Client Application
## Single-client application
### The Problem
### The problem
The high level api requires to call `set_server_key` on each thread where computations needs to be done.
So a first attempt at using rayon with `TFHE-rs` might look like this:
The high-level API requires to call `set_server_key` on each thread where computations need to be done. So a first attempt to use Rayon with **TFHE-rs** might look like this:
```rust
use rayon::prelude::*;
@@ -46,12 +44,11 @@ fn main() {
}
```
However, due to rayon's work stealing mechanism and TFHE-rs's internals, this may create `BorrowMutError'.
However, due to Rayon's work-stealing mechanism and **TFHE-rs'** internals, this may create `BorrowMutError`.
### Working example
### Working Example
The correct way is to call `rayon::broadcast`
The correct way is to call `rayon::broadcast` as follows:
```rust
use rayon::prelude::*;
@@ -93,11 +90,9 @@ fn main() {
}
```
## Multi-client applications
## Multi-Client Applications
If your application needs to operate on data from different clients concurrently, and that you want each client to use
multiple threads, you will need to create different rayon thread pools
For applications that need to operate concurrently on data from different clients and require each client to use multiple threads, you need to create separate Rayon thread pools:
```rust
use rayon::prelude::*;
@@ -169,7 +164,7 @@ fn main() {
}
```
This can be useful if you have some rust `#[test]`
This can be useful if you have some rust `#[test]`, see the example below:
```Rust
// Pseudo code

View File

@@ -1,45 +1,52 @@
TFHE-rs now includes a GPU backend, featuring a CUDA implementation for performing integer arithmetics on encrypted data.
In what follows, a simple tutorial is introduced: it shows how to update your existing program to use GPU acceleration, or how to start a new one using GPU.
# GPU acceleration
# Prerequisites
- Cuda version >= 10
- Compute Capability >= 3.0
- [gcc](https://gcc.gnu.org/) >= 8.0 - check this [page](https://gist.github.com/ax3l/9489132) for more details about nvcc/gcc compatible versions
- [cmake](https://cmake.org/) >= 3.24
- Rust version - check this [page](rust_configuration.md)
This guide explains how to update your existing program to leverage GPU acceleration, or to start a new program using GPU.
# Importing to your project
To use the `TFHE-rs GPU backend` in your project, you first need to add it as a dependency in your `Cargo.toml`.
**TFHE-rs** now supports a GPU backend with CUDA implementation, enabling integer arithmetics operations on encrypted data.
## Prerequisites
* Cuda version >= 10
* Compute Capability >= 3.0
* [gcc](https://gcc.gnu.org/) >= 8.0 - check this [page](https://gist.github.com/ax3l/9489132) for more details about nvcc/gcc compatible versions
* [cmake](https://cmake.org/) >= 3.24
* Rust version - check this [page](rust\_configuration.md)
## Importing to your project
To use the **TFHE-rs** GPU backend in your project, add the following dependency in your `Cargo.toml`.
If you are using an `x86` machine:
```toml
tfhe = { version = "0.6.0", features = [ "boolean", "shortint", "integer", "x86_64-unix", "gpu" ] }
```
If you are using an `ARM` machine:
```toml
tfhe = { version = "0.6.0", features = [ "boolean", "shortint", "integer", "aarch64-unix", "gpu" ] }
```
{% hint style="success" %}
When running code that uses `TFHE-rs`, it is highly recommended to run in release mode with cargo's `--release` flag to have the best possible performance
For optimal performance when using **TFHE-rs**, run your code in release mode with the `--release` flag.
{% endhint %}
## Supported platforms
### Supported platforms
TFHE-rs GPU backend is supported on Linux (x86, aarch64).
**TFHE-rs** GPU backend is supported on Linux (x86, aarch64).
| OS | x86 | aarch64 |
| ------- |---------------|------------------|
| ------- | ------------- | ---------------- |
| Linux | `x86_64-unix` | `aarch64-unix`\* |
| macOS | Unsupported | Unsupported\* |
| Windows | Unsupported | Unsupported |
## A first example
# A first example
## Configuring and creating keys.
In comparison with the [CPU example](../getting_started/quick_start), the only difference lies into the key creation, which is detailed [here](#Setting-the-keys)
### Configuring and creating keys.
Comparing to the [CPU example](../getting\_started/quick\_start.md), GPU set up differs in the key creation, as detailed [here](run\_on\_gpu.md#Setting-the-keys)
Here is a full example (combining the client and server parts):
@@ -76,17 +83,16 @@ fn main() {
}
```
### Setting the keys
The configuration of the key is different from the CPU. More precisely, if both client and server keys are still generated by the client (which is assumed to run on a CPU), the server key has then to be decompressed by the server to be converted into the right format. To do so, the server should run this function: `decompressed_to_gpu()`.
Once decompressed, the operations between CPU and GPU are identical.
## Setting the keys
The configuration of the key is different from the CPU. More precisely, if both client and server keys are still generated by the Client (which is assumed to run on a CPU), the server key has then to be decompressed by the Server to be converted into the right format.
To do so, the server should run this function: ```decompressed_to_gpu()```.
From then on, there is no difference between the CPU and the GPU.
### Encryption
On the client-side, the method to encrypt the data is exactly the same than the CPU one, as shown in the following example:
## Encrypting data
On the client-side, the method to encrypt the data is exactly the same than the CPU one, i.e.:
```Rust
let clear_a = 27u8;
let clear_b = 128u8;
@@ -95,9 +101,11 @@ On the client-side, the method to encrypt the data is exactly the same than the
let b = FheUint8::encrypt(clear_b, &client_key);
```
## Computation.
The server must first set its keys up, like in the CPU, with: ``` set_server_key(gpu_key);``` .
Then, homomorphic computations are done with the same code than the one described [here](../getting_started/operations).
### Computation
The server first need to set up its keys with `set_server_key(gpu_key)`.
Then, homomorphic computations are performed using the same approach as the [CPU operations](../getting\_started/operations.md).
```Rust
//Server-side
@@ -112,23 +120,25 @@ Then, homomorphic computations are done with the same code than the one describe
assert_eq!(decrypted_result, clear_result);
```
## Decryption.
Finally, the client gets the decrypted results by computing:
### Decryption
Finally, the client decrypts the results using:
```Rust
let decrypted_result: u8 = result.decrypt(&client_key);
```
## Improving performance.
TFHE-rs includes the possibility to leverage the high number of threads given by a GPU.
To do so, the configuration should be updated with
```
### Improving performance.
**TFHE-rs** allows to leverage the high number of threads given by a GPU. To maximize the number of GPU threads, update your configuration accordingly:
```Rust
let config = ConfigBuilder::with_custom_parameters(PARAM_GPU_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS, None).build();
```
The complete example becomes:
Here's the complete example:
```rust
```rust
use tfhe::{ConfigBuilder, set_server_key, FheUint8, ClientKey, CompressedServerKey};
use tfhe::prelude::*;
use tfhe::shortint::parameters::PARAM_GPU_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS;
@@ -161,48 +171,49 @@ fn main() {
assert_eq!(decrypted_result, clear_result);
}
```
# List of available operations
The GPU backend includes the following operations:
| name | symbol | `Enc`/`Enc` | `Enc`/ `Int` |
|-----------------------|----------------|--------------------------|--------------------------|
| Neg | `-` | :heavy_check_mark: | N/A |
| Add | `+` | :heavy_check_mark: | :heavy_check_mark: |
| Sub | `-` | :heavy_check_mark: | :heavy_check_mark: |
| Mul | `*` | :heavy_check_mark: | :heavy_check_mark: |
| Div | `/` | :heavy_multiplication_x: | :heavy_multiplication_x: |
| Rem | `%` | :heavy_multiplication_x: | :heavy_multiplication_x: |
| Not | `!` | :heavy_check_mark: | N/A |
| BitAnd | `&` | :heavy_check_mark: | :heavy_check_mark: |
| BitOr | `\|` | :heavy_check_mark: | :heavy_check_mark: |
| BitXor | `^` | :heavy_check_mark: | :heavy_check_mark: |
| Shr | `>>` | :heavy_check_mark: | :heavy_check_mark: |
| Shl | `<<` | :heavy_check_mark: | :heavy_check_mark: |
| Rotate right | `rotate_right` | :heavy_check_mark: | :heavy_check_mark: |
| Rotate left | `rotate_left` | :heavy_check_mark: | :heavy_check_mark: |
| Min | `min` | :heavy_check_mark: | :heavy_check_mark: |
| Max | `max` | :heavy_check_mark: | :heavy_check_mark: |
| Greater than | `gt` | :heavy_check_mark: | :heavy_check_mark: |
| Greater or equal than | `ge` | :heavy_check_mark: | :heavy_check_mark: |
| Lower than | `lt` | :heavy_check_mark: | :heavy_check_mark: |
| Lower or equal than | `le` | :heavy_check_mark: | :heavy_check_mark: |
| Equal | `eq` | :heavy_check_mark: | :heavy_check_mark: |
| Cast (into dest type) | `cast_into` | :heavy_multiplication_x: | N/A |
| Cast (from src type) | `cast_from` | :heavy_multiplication_x: | N/A |
| Ternary operator | `if_then_else` | :heavy_check_mark: | :heavy_multiplication_x: |
## List of available operations
The GPU backend includes the following operations:
| name | symbol | `Enc`/`Enc` | `Enc`/ `Int` |
| --------------------- | -------------- | -------------------------- | -------------------------- |
| Neg | `-` | :heavy\_check\_mark: | N/A |
| Add | `+` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Sub | `-` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Mul | `*` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Div | `/` | :heavy\_multiplication\_x: | :heavy\_multiplication\_x: |
| Rem | `%` | :heavy\_multiplication\_x: | :heavy\_multiplication\_x: |
| Not | `!` | :heavy\_check\_mark: | N/A |
| BitAnd | `&` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| BitOr | `\|` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| BitXor | `^` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Shr | `>>` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Shl | `<<` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Rotate right | `rotate_right` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Rotate left | `rotate_left` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Min | `min` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Max | `max` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Greater than | `gt` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Greater or equal than | `ge` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Lower than | `lt` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Lower or equal than | `le` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Equal | `eq` | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Cast (into dest type) | `cast_into` | :heavy\_multiplication\_x: | N/A |
| Cast (from src type) | `cast_from` | :heavy\_multiplication\_x: | N/A |
| Ternary operator | `if_then_else` | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
The equivalent signed operations are also available.
{% hint style="info" %}
All operations follow the same syntax than the one described in [here](../getting_started/operations.md).
All operations follow the same syntax than the one described in [here](../getting\_started/operations.md).
{% endhint %}
# Benchmarks
## Benchmarks
All GPU benchmarks presented here were obtained on a single H100 GPU, and rely on the multithreaded PBS algorithm.
The cryptographic parameters PARAM\_GPU\_MULTI\_BIT\_MESSAGE\_2\_CARRY\_2\_GROUP\_3\_KS\_PBS were used.
Performance is the following when the inputs of the benchmarked operation are encrypted:
All GPU benchmarks presented here were obtained on a single H100 GPU, and rely on the multithreaded PBS algorithm. The cryptographic parameters `PARAM_GPU_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS` were used.
The following table shows the performance when the inputs of the benchmarked operation are encrypted:
| Operation \ Size | `FheUint7` | `FheUint16` | `FheUint32` | `FheUint64` | `FheUint128` | `FheUint256` |
| ------------------------------------------------------ | ---------- | ----------- | ----------- | ----------- | ------------ | ------------ |
@@ -210,21 +221,21 @@ Performance is the following when the inputs of the benchmarked operation are en
| Add / Sub (`+`,`-`) | 46 ms | 60 ms | 75 ms | 94 ms | 150 ms | 247 ms |
| Mul (`x`) | 83 ms | 121 ms | 195 ms | 456 ms | 1.35 s | 4.74 s |
| Equal / Not Equal (`eq`, `ne`) | 25 ms | 26 ms | 38 ms | 41 ms | 52 ms | 78 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 46 ms | 60 ms | 74 ms | 90 ms | 109 ms | 153 ms |
| Max / Min (`max`,`min`) | 71 ms | 86 ms | 101 ms | 124 ms | 165 ms | 236 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 46 ms | 60 ms | 74 ms | 90 ms | 109 ms | 153 ms |
| Max / Min (`max`,`min`) | 71 ms | 86 ms | 101 ms | 124 ms | 165 ms | 236 ms |
| Bitwise operations (`&`, `\|`, `^`) | 11 ms | 12 ms | 13 ms | 15 ms | 23 ms | 32 ms |
| Left / Right Shifts (`<<`, `>>`) | 71 ms | 88 ms | 109 ms | 180 ms | 279 ms | 494 ms |
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 71 ms | 88 ms | 109 ms | 180 ms | 279 ms | 494 ms |
Performance is the following when the left input of the benchmarked operation is encrypted and the other is a clear scalar of the same size:
The following table shows the performance when the left input of the benchmarked operation is encrypted and the other is a clear scalar of the same size:
| Operation \ Size | `FheUint7` | `FheUint16` | `FheUint32` | `FheUint64` | `FheUint128` | `FheUint256` |
| ------------------------------------------------------ | ---------- | ----------- | ----------- | ----------- | ------------ | ------------ |
| Add / Sub (`+`,`-`) | 46 ms | 60 ms | 75 ms | 94 ms | 152 ms | 251 ms |
| Mul (`*`) | 67 ms | 101 ms | 149 ms | 282 ms | 727 ms | 2.11 s |
| Equal / Not Equal (`eq`, `ne`) | 26 ms | 27 ms | 27 ms | 41 ms | 45 ms | 57 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 29 ms | 41 ms | 54 ms | 69 ms | 87 ms | 117 ms |
| Max / Min (`max`,`min`) | 53 ms | 65 ms | 81 ms | 102 ms | 142 ms | 200 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 29 ms | 41 ms | 54 ms | 69 ms | 87 ms | 117 ms |
| Max / Min (`max`,`min`) | 53 ms | 65 ms | 81 ms | 102 ms | 142 ms | 200 ms |
| Bitwise operations (`&`, `\|`, `^`) | 11 ms | 13 ms | 13 ms | 15 ms | 23 ms | 32 ms |
| Left / Right Shifts (`<<`, `>>`) | 11 ms | 12 ms | 13 ms | 15 ms | 23 ms | 32 ms |
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 11 ms | 12 ms | 13 ms | 15 ms | 23 ms | 32 ms |

View File

@@ -1,7 +1,10 @@
# Using the right toolchain for TFHE-rs.
# Rust configuration
TFHE-rs only requires a nightly toolchain for building the C API and using advanced SIMD instructions, otherwise you can use a stable toolchain (with version >= 1.73)
Install the needed Rust toolchain:
This document provides basic instructions to configure the Rust toolchain and features for **TFHE-rs.**
**TFHE-rs** requires a nightly Rust toolchain to build the C API and utilize advanced SIMD instructions. However, for other uses, a stable toolchain (version 1.73 or later) is sufficient.
Follow the following instructions to install the necessary Rust toolchain:
```shell
# If you don't need the C API or the advanced still unstable SIMD instructions use this
@@ -10,9 +13,11 @@ rustup toolchain install stable
rustup toolchain install nightly
```
Then, you can either:
## Setting the toolchain
* Manually specify the toolchain to use in each of the cargo commands:
You can set the toolchain using either of the following methods.
Manually specify the toolchain for each cargo command:
```shell
# By default the +stable should not be needed, but we add it here for completeness
@@ -23,7 +28,7 @@ cargo +nightly build --release
cargo +nightly test --release
```
* Or override the toolchain to use for the current project:
Override the toolchain for the current project:
```shell
# This should not be necessary by default, but if you want to make sure your configuration is
@@ -37,31 +42,29 @@ rustup override set nightly
cargo build --release
```
To check the toolchain that Cargo will use by default, you can use the following command:
To verify the default toolchain used by Cargo, execute:
```shell
rustup show
```
## Choosing your features
# Choosing your features
**TFHE-rs** provides various cargo features to customize the types and features used.
`TFHE-rs` exposes different `cargo features` to customize the types and features used.
### Homomorphic types
## Homomorphic Types.
This crate provides 3 kinds of data types. Each kind is enabled by activating the corresponding feature in the TOML line and has multiple types:
This crate exposes two kinds of data types. Each kind is enabled by activating its corresponding feature in the TOML line. Each kind may have multiple types:
| Kind | Features | Type (s) |
| --------- | ---------- | ------------------------ |
| Booleans | `boolean` | Booleans |
| ShortInts | `shortint` | Short integers |
| Integers | `integer` | Arbitrary-sized integers |
| Kind | Features | Type(s) |
|-----------|------------|---------------------------|
| Booleans | `boolean` | Booleans |
| ShortInts | `shortint` | Short integers |
| Integers | `integer` | Arbitrary-sized integers |
### AVX-512
## AVX-512
In general, the library automatically chooses the best instruction sets available by the host. However, in the case of 'AVX-512', this has to be explicitly chosen as a feature. This requires to use a [nightly toolchain](#using-tfhe-rs-with-nightly-toolchain) along with the feature `nightly-avx512`.
While the library generally selects automatically the best instruction sets available by the host, in the case of 'AVX-512', you have to choose it explicitly. This requires to use a [nightly toolchain](rust\_configuration.md#using-tfhe-rs-with-nightly-toolchain) with the feature `nightly-avx512`.
```shell
cargo +nightly build --release --features=nightly-avx512

View File

@@ -1,15 +1,10 @@
# Generic Bounds
# Generic trait bounds
If you wish to write generic functions which use operators with mixed reference and non-reference,
it might get tricky at first to specify the trait [bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html).
This page should serve as a _cookbook_ to help you.
This document serves as a practical reference for implementing generic functions in Rust that use operators across mixed references and values. The following explanations help you to understand the trait [bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html) necessary to handle such operations.
Operators (+, *, >>, etc) are tied to traits in `std:::ops`, e.g. `+` is `std::ops::Add`,
so to write a generic function which uses the `+` operator, you need to use add `std::ops::Add`
as a trait bound.
Operators such as `+`, `*`, `>>,` and so on are tied to traits in `std:::ops`. For instance, the `+` operator corresponds to `std::ops::Add`. When writing a generic function that uses the `+` operator, you need to specify `std::ops::Add` as a trait bound.
Then, depending on if the left hand side / right hand side is an owned value or a reference, the trait bound
is slightly different. The table below shows the possibilities.
The trait bound varies slightly depending on whether the left-hand side / right-hand side is an owned value or a reference. The following table shows the different scenarios:
| operation | trait bound |
| ----------- | ------------------------------------- |
@@ -19,12 +14,11 @@ is slightly different. The table below shows the possibilities.
| `&T $op &T` | `for<'a> &'a T: $Op<&'a T, Output=T>` |
{% hint style="info" %}
The `for<'a>` syntax is something called [Higher-Rank Trait Bounds](https://doc.rust-lang.org/nomicon/hrtb.html), often shortened as __HRTB__
The `for<'a>` syntax refers to the [Higher-Rank Trait Bounds(HRTB)](https://doc.rust-lang.org/nomicon/hrtb.html).
{% endhint %}
{% hint style="info" %}
Writing generic functions will also allow you to call them using clear inputs,
only allowing easier debugging.
Using generic functions allows for clearer input handling, which simplifies the debugging.
{% endhint %}
## Example

View File

@@ -1,11 +1,10 @@
# Trivial Ciphertext
# Trivial ciphertexts
Sometimes, the server side needs to initialize a value.
For example, when computing the sum of a list of ciphertext,
one might want to initialize the `sum` variable to `0`.
This document describes how to use trivial encryption in **TFHE-rs** to initialize server-side values.
Instead of asking the client to send a real encryption of zero,
the server can do a *trivial encryption*
Sometimes, the server side needs to initialize a value. For example, when computing the sum of a list of ciphertexts, you typically initialize the `sum` variable to `0`.
Instead of asking the client to send an actual encrypted zero, the server can use a trivial encryption. A trivial encryption creates a ciphertext that contains the desired value but isn't securely encrypted - essentially anyone, any key can decrypt it.
```rust
use tfhe::prelude::*;
@@ -22,15 +21,7 @@ let clear: u8 = a.decrypt(&client_key);
assert_eq!(clear, 234);
```
A *trivial encryption* will create a ciphertext that contains
the desired value, however, the 'encryption' is trivial that is,
it is not really encrypted: anyone, any key can decrypt it.
Note that when you want to do an operation that involves a ciphertext
and a clear value, you should only use a trivial encryption of the clear
value if the ciphertext/clear-value operation (often called scalar operation) you want to run is not supported.
### Example
Note that when you want to do an operation that involves a ciphertext and a clear value (often called scalar operation), you should only use trivial encryption of the clear value if the scalar operations that you want to run are not supported.
```rust
use tfhe::prelude::*;

View File

@@ -1,14 +1,14 @@
# Zero Knowledge proof for Compact Public Key encryption
# Zero-knowledge proofs
TFHE-rs enables the generation of a zero-knowledge proof to verify that a compact public key encryption process has been correctly performed. In other words, the creation of a proof reveals nothing about the encrypted message, except for its already known range. This technique is derived from [Liberts work](https://eprint.iacr.org/2023/800).
This document explains how to implement the zero-knowledge proofs function for compact public key encryption to verify the encryption process without revealing the encrypted information.
**TFHE-rs** can generate zero-knowledge proofs to verify that the compact public key encryption process is correct. In other words, **TFHE-rs** generates the proof without revealing any information other than the already known range of the encrypted message. This technique is derived from [Liberts work](https://eprint.iacr.org/2023/800).
{% hint style="info" %}
You can enable this feature using the flag: `--features=zk-pok-experimental` when building TFHE-rs.
You can enable this feature using the flag: `--features=zk-pok-experimental` when building **TFHE-rs**.
{% endhint %}
Deploying this feature is straightforward: the client generates the proof at the time of encryption, while the server verifies it before proceeding with homomorphic computations. Below is an example demonstrating how a client can encrypt and prove a ciphertext, and how a server can verify the ciphertext and carry out computations on it:
Using this feature is straightforward: during encryption, the client generates the proof, and the server validates it before conducting any homomorphic computations. The following example demonstrates how a client can encrypt and prove a ciphertext, and how a server can verify the ciphertext and compute it:
```rust
use rand::prelude::*;
@@ -66,4 +66,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
```
Encrypting and proving a CompactFheUint64 takes 6.9 s on a Dell XPS 15 9500, simulating a client machine, the verification on an hpc7a.96xlarge available on AWS takes 123 ms.
In terms of performance:
* Encrypting and proving a `CompactFheUint64` takes **6.9 s** on a `Dell XPS 15 9500` (simulating a client machine).
* Verification takes **123 ms** on an `hpc7a.96xlarge` AWS instances.

View File

@@ -1,6 +1,6 @@
# Quick Start
# Quick start
The `core_crypto` module from `TFHE-rs` is dedicated to the implementation of the cryptographic tools related to TFHE. To construct an FHE application, the [shortint](../../fine\_grained\_api/shortint/tutorial.md) and/or [Boolean](../../fine\_grained\_api/Boolean/tutorial.md) modules (based on `core_crypto`) are recommended.
The `core_crypto` module from `TFHE-rs` is dedicated to the implementation of the cryptographic tools related to TFHE. To construct an FHE application, the [shortint](../fine-grained-apis/shortint/) and/or [Boolean](../fine-grained-apis/boolean/) modules (based on `core_crypto`) are recommended.
The `core_crypto` module offers an API to low-level cryptographic primitives and objects, like `lwe_encryption` or `rlwe_ciphertext`. The goal is to propose an easy-to-use API for cryptographers.

View File

@@ -1,23 +1,24 @@
# A first complete example: FheAsciiString (Integer)
# Homomorphic case changing on Ascii string
The goal of this tutorial is to build a data type that represents a ASCII string in FHE while implementing the `to_lower` and `to_upper` functions.
This tutorial demonstrates how to build a data type that represents an ASCII string in Fully Homomorphic Encryption (FHE) by implementing to\_lower and to\_upper functions.
An ASCII character is stored in 7 bits.
To store an encrypted ASCII we use the `FheUint8`.
An ASCII character is stored in 7 bits. To store an encrypted ASCII, we use the `FheUint8`:
* The uppercase letters are in the range \[65, 90]
* The lowercase letters are in the range \[97, 122]
`lower_case = upper_case + UP_LOW_DISTANCE` <=> `upper_case = lower_case - UP_LOW_DISTANCE`
The relationship between uppercase and lowercase letters is defined as follows:
* `lower_case` = `upper_case` + `UP_LOW_DISTANCE`
* `upper_case` = `lower_case` - `UP_LOW_DISTANCE`
Where `UP_LOW_DISTANCE = 32`
## Types and methods
## Types and methods.
This type stores the encrypted characters as a `Vec<FheUint8>` to implement case conversion functions.
This type will hold the encrypted characters as a `Vec<FheUint8>` to implement the functions that change the case.
To use the `FheUint8` type, the `integer` feature must be activated:
To use the `FheUint8` type, enable the `integer` feature:
```toml
# Cargo.toml
@@ -27,16 +28,11 @@ To use the `FheUint8` type, the `integer` feature must be activated:
tfhe = { version = "0.6.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
Refer to the [installation guide](../getting\_started/installation.md) for other configurations.
The `FheAsciiString::encrypt` function performs data validation to ensure the input string contains only ASCII characters.
In the `FheAsciiString::encrypt` function, some data validation is done:
* The input string can only contain ascii characters.
It is not possible to branch on an encrypted value, however it is possible to evaluate a boolean condition and use it to get the desired result.
Checking if the 'char' is an uppercase letter to modify it to a lowercase can be done without using a branch, like this:
In FHE operations, direct branching on encrypted values is not possible. However, you can evaluate a boolean condition to obtain the desired outcome. Here is an example to check and convert the 'char' to a lowercase without using a branch:
```rust
pub const UP_LOW_DISTANCE: u8 = 32;
@@ -50,7 +46,7 @@ fn to_lower(c: u8) -> u8 {
}
```
We can remove the branch this way:
You can remove the branch this way:
```rust
pub const UP_LOW_DISTANCE: u8 = 32;
@@ -60,7 +56,7 @@ fn to_lower(c: u8) -> u8 {
}
```
On an homomorphic integer, this gives
This method can adapt to operations on homomorphic integers:
```rust
use tfhe::prelude::*;
@@ -73,7 +69,7 @@ fn to_lower(c: &FheUint8) -> FheUint8 {
}
```
The whole code is:
Full example:
```rust
use tfhe::prelude::*;

View File

@@ -1,19 +1,18 @@
## A more complex example: Parity Bit (Boolean)
# Homomorphic parity bit
This example is dedicated to the building of a small function that homomorphically computes a parity bit.
This tutorial shows how to build a small function that homomorphically computes a parity bit in 2 steps:
First, a non-generic function is written. Then, generics are used to handle the case where the function inputs are both `FheBool`s and clear `bool`s.
1. Write a non-generic function
2. Use generics to handle the case where the function inputs are both `FheBool`s and clear `bool`s.
The parity bit function takes as input two parameters:
The parity bit function processes two parameters:
* A slice of Boolean
* A mode (`Odd` or `Even`)
This function returns a Boolean that will be either `true` or `false` so that the sum of Booleans (in the input and the returned one) is either an `Odd` or `Even` number, depending on the requested mode.
This function returns a Boolean (`true` or `false`) so that the total count of `true` values across the input and the result matches with the specified parity mode (`Odd` or `Even`).
***
### Non-generic version.
## Non-generic version
```toml
# Cargo.toml
@@ -22,16 +21,13 @@ This function returns a Boolean that will be either `true` or `false` so that th
tfhe = { version = "0.6.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
Refer to the [installation](../getting\_started/installation.md) for other configurations.
First, define the verification function.
#### function definition
The function initializes the parity bit to `false`, then applies the `XOR` operation across all bits, adding negation based on the requested mode.
First, the verification function is defined.
The way to find the parity bit is to initialize it to `false, then` `XOR` it with all the bits, one after the other, adding negation depending on the requested mode.
A validation function is also defined to sum together the number of the bit set within the input with the computed parity bit and check that the sum is an even or odd number, depending on the mode.
The validation function also adds the number of the bits set in the input to the computed parity bit and checks whether the sum is even or odd, depending on the mode.
```rust
use tfhe::FheBool;
@@ -78,9 +74,7 @@ fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool)
}
```
#### final code
After the mandatory configuration steps, the function is called:
After configurations, call the function:
```rust
use tfhe::{FheBool, ConfigBuilder, generate_keys, set_server_key};
@@ -156,19 +150,15 @@ fn main() {
}
```
***
## Generic version
### Generic version.
To enable the `compute_parity_bit` function to operate with both encrypted `FheBool` and plain bool, we introduce generics. This approach allows for validation using clear data and facilitates debugging.
To make the `compute_parity_bit` function compatible with both `FheBool` and `bool`, generics have to be used.
Writing generic functions that incorporate operator overloading for our Fully Homomorphic Encryption (FHE) types is more complex than usual because FHE types do not implement the `Copy` trait. Consequently, it is necessary to use references (&) with these types, unlike native types, which typically implement `Copy`.
Writing a generic function that accepts `FHE` types as well as clear types can help test the function to see if it is correct. If the function is generic, it can run with clear data, allowing the use of print-debugging or a debugger to spot errors.
This complicates generic bounds at first.
Writing generic functions that use operator overloading for our FHE types can be trickier than normal, since `FHE` types are not copy. So using the reference `&` is mandatory, even though this is not the case when using native types, which are all `Copy`.
This will make the generic bounds trickier at first.
#### writing the correct trait bounds
### Writing the correct trait bounds
The function has the following signature:
@@ -179,7 +169,7 @@ fn check_parity_bit_validity(
) -> bool
```
To make it generic, the first step is:
To make it generic, the first steps is:
```Rust
fn compute_parity_bit<BoolType>(
@@ -188,14 +178,14 @@ fn compute_parity_bit<BoolType>(
) -> BoolType
```
Next, the generic bounds have to be defined with the `where` clause.
Next, define the generic bounds with the `where` clause.
In the function, the following operators are used:
In the function, you can use the following operators:
* `!` (trait: `Not`)
* `^` (trait: `BitXor`)
By adding them to `where`, this gives:
Adding them to `where`, it gives:
```Rust
where
@@ -203,9 +193,9 @@ where
BoolType: BitXor<BoolType, Output=BoolType>,
```
However, the compiler will complain:
However, the compiler will return an error:
```text
```console
---- src/user_doc_tests.rs - user_doc_tests (line 199) stdout ----
error[E0369]: no implementation for `&BoolType ^ BoolType`
--> src/user_doc_tests.rs:218:30
@@ -222,7 +212,7 @@ help: consider extending the `where` bound, but there might be an alternative be
error: aborting due to previous error
```
`fhe_bit` is a reference to a `BoolType` (`&BoolType`) since it is borrowed from the `fhe_bits` slice when iterating over its elements. The first try is to change the `BitXor` bounds to what the Compiler suggests by requiring `&BoolType` to implement `BitXor` and not `BoolType`.
`fhe_bit` is a reference to a `BoolType` (`&BoolType`), because `BoolType` is borrowed from the `fhe_bits` slice during iteration. To fix the error, the first approach could be changing the `BitXor` bounds to what the Compiler suggests, by requiring `&BoolType` to implement `BitXor` rather than `BoolType`.
```Rust
where
@@ -230,9 +220,9 @@ where
&BoolType: BitXor<BoolType, Output=BoolType>,
```
The Compiler is still not happy:
However, this approach still leads to an error:
```text
```console
---- src/user_doc_tests.rs - user_doc_tests (line 236) stdout ----
error[E0637]: `&` without an explicit lifetime name cannot be used here
--> src/user_doc_tests.rs:251:5
@@ -252,7 +242,7 @@ help: consider adding an explicit lifetime bound...
|
```
The way to fix this is to use `Higher-Rank Trait Bounds`:
To fix this error, use `Higher-Rank Trait Bounds`:
```Rust
where
@@ -260,7 +250,7 @@ where
for<'a> &'a BoolType: BitXor<BoolType, Output = BoolType>,
```
The final code will look like this:
The final code is as follows:
```rust
use std::ops::{Not, BitXor};
@@ -290,8 +280,6 @@ where
}
```
#### final code
Here is a complete example that uses this function for both clear and FHE values:
```rust

View File

@@ -1,33 +1,34 @@
# Tutorial
# SHA256 with Boolean API
## Intro
This tutorial guides you to convert a regular SHA-256 function to its homomorphic version, with considerations of optimal performances. You will learn:
In this tutorial we will go through the steps to turn a regular sha256 implementation into its homomorphic version. We explain the basics of the sha256 function first, and then how to implement it homomorphically with performance considerations.
1. The basics of the SHA-256 function.
2. The steps to implement SHA-256 homomorphically.
## Sha256
## SHA-256 basics
The first step in this experiment is actually implementing the sha256 function. We can find the specification [here](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf), but let's summarize the three main sections of the document.
First, you need to implement the SHA-256 function. You can find the official specification for SHA-256 [here](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf). We summarize the three key aspects of SHA-256 outlined in the document:
#### Padding
### Padding
The sha256 function processes the input data in blocks or chunks of 512 bits. Before actually performing the hash computations we have to pad the input in the following way:
* Append a single "1" bit
* Append a number of "0" bits such that exactly 64 bits are left to make the message length a multiple of 512
* Append the last 64 bits as a binary encoding of the original input length
The SHA-256 function processes the input data in blocks or chunks of 512 bits. Before performing the hash computations, prepare the data as follows:
Or visually:
1. Append a single "1" bit
2. Append "0" bits until exactly 64 bits remain to make the message length a multiple of 512
3. Append the last 64 bits as a binary encoding of the original input length
![](../_static/sha256.png)
![](../\_static/sha256.png)
Where the numbers on the top represent the length of the padded input at each position, and L+1+k+64 is a multiple of 512 (the length of the padded input).
In this diagram, the numbers on the top represent the length of the padded input at each position. The formula L+1+k+64 ensures that the length reaches a multiple of 512, matching the required length of the padded input.
#### Operations and functions
### Operations and functions
Let's take a look at the operations that we will use as building blocks for functions inside the sha256 computation. These are bitwise AND, XOR, NOT, addition modulo 2^32 and the Rotate Right (ROTR) and Shift Right (SHR) operations, all working with 32-bit words and producing a new word.
We will use bitwise AND, XOR, NOT, addition modulo 2^32, the Rotate Right (ROTR) and Shift Right (SHR) operations as building blocks for functions inside the SHA-256 computation. These operations all use 32-bit words and produce new words.
We combine these operations inside the sigma (with 4 variations), Ch and Maj functions. At the end of the day, when we change the sha256 to be computed homomorphically, we will mainly change the isolated code of each operation.
We combine these operations inside the sigma (with 4 variations), `Ch,` and `Maj` functions. When changing SHA-256 to the homomorphic computation, we will mainly change the code of each operation.
Here is the definition of each function:
```
Ch(x, y, z) = (x AND y) XOR ((NOT x) AND z)
Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z)
@@ -37,12 +38,15 @@ Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z)
σ0(x) = ROTR-7(x) XOR ROTR-18(x) XOR SHR-3(x)
σ1(x) = ROTR-17(x) XOR ROTR-19(x) XOR SHR-10(x)
```
There are some things to note about the functions. Firstly we see that Maj can be simplified by applying the boolean distributive law (x AND y) XOR (x AND z) = x AND (y XOR z). So the new Maj function looks like this:
We simplify `Maj` using the Boolean distributive law: (x AND y) XOR (x AND z) = x AND (y XOR z), as shown below:
```
Maj(x, y, z) = (x AND (y XOR z)) XOR (y AND z)
```
Next we can also see that Ch can be simplified by using a single bitwise multiplexer. Let's take a look at the truth table of the Ch expression.
We simplify `Ch` using a single bitwise multiplexer. Here's the truth table of the `Ch` expression.
| x | y | z | Result |
| - | - | - | ------ |
| 0 | 0 | 0 | 0 |
@@ -54,15 +58,25 @@ Next we can also see that Ch can be simplified by using a single bitwise multipl
| 1 | 1 | 0 | 1 |
| 1 | 1 | 1 | 1 |
When ```x = 0``` the result is identical to ```z```, but when ```x = 1``` the result is identical to ```y```. This is the same as saying ```if x {y} else {z}```. Hence we can replace the 4 bitwise operations of Ch by a single bitwise multiplexer.
This table shows that the result equals to `z` when `x = 0`, and the result equals to `y` when `x = 1`, which means `if x {y} else {z}`. Hence we can replace the 4 bitwise operations of `Ch` by a single bitwise multiplexer.
Note that all these operations can be evaluated homomorphically. ROTR and SHR can be evaluated by changing the index of each individual bit of the word, even if each bit is encrypted, without using any homomorphic operation. Bitwise AND, XOR and multiplexer can be computed homomorphically and addition modulo 2^32 can be broken down into boolean homomorphic operations as well.
All these operations can be evaluated homomorphically:
#### Sha256 computation
* ROTR and SHR: They can be evaluated by changing the index of each ecrypted bit of the word without using any homomorphic operation.
* Bitwise AND, XOR and multiplexer: They can be computed homomorphically
* Addition modulo 2^32: It can be broken down into boolean homomorphic operations.
As we have mentioned, the sha256 function works with chunks of 512 bits. For each chunk, we will compute 64 32-bit words. 16 will come from the 512 bits and the rest will be computed using the previous functions. After computing the 64 words, and still within the same chunk iteration, a compression loop will compute a hash value (8 32-bit words), again using the previous functions and some constants to mix everything up. When we finish the last chunk iteration, the resulting hash values will be the output of the sha256 function.
### SHA-256 computation
Here is how this function looks like using arrays of 32 bools to represent words:
The SHA-256 function processes data in 512-bit chunks. Here is what happens during computation:
1. The 512-bit chunk is computed into 16 words, each containing 32 bits.
2. Another 48 words are computed using the previous function.
3. After computing the 64 words, within the same chunk, a compression loop will compute a hash value (8 32-bit words) using the previous functions and some constants to mix everything up.
4. This entire process iterate through each 512-bit chunk of your data.
5. When we finish the last chunk iteration, the resulting hash values will be the output of the SHA-256 function.
Here is an example of this function using arrays of 32 bools to represent words:
```rust
fn sha256(padded_input: Vec<bool>) -> [bool; 256] {
@@ -132,19 +146,27 @@ fn sha256(padded_input: Vec<bool>) -> [bool; 256] {
}
```
## Making it homomorphic
## Homomorphic SHA-256 on encrypted data
The key idea is that we can replace each bit of ```padded_input``` with a Fully Homomorphic Encryption of the same bit value, and operate over the encrypted values using homomorphic operations. To achieve this we need to change the function signatures and deal with the borrowing rules of the Ciphertext type (which represents an encrypted bit) but the structure of the sha256 function remains the same. The part of the code that requires more consideration is the implementation of the sha256 operations, since they will use homomorphic boolean operations internally.
To convert SHA-256 to a homomorphic version, you can replace each bit of `padded_input` with a fully homomorphic encryption of the same bit value and operate on the encrypted value using homomorphic operations.
Homomorphic operations are really expensive, so we have to remove their unnecessary use and maximize parallelization in order to speed up the program. To simplify our code we use the Rayon crate which provides parallel iterators and efficiently manages threads.
While the structure of the SHA-256 function remains the same, there are some important considerations in the code:
The final code is available at https://github.com/zama-ai/tfhe-rs/tree/main/tfhe/examples/sha256_bool
* The function signature and the borrowing rules should adapt to the ciphertext type (representing the encrypted bits).
* Implementing SHA-256 operations with homomorphic encryption uses homomorphic boolean operations internally.
Let's now take a look at each sha256 operation!
Homomorphic operations on encrypted data can be very expensive. Consider these options for better speed:
* Remove unnecessary use of homomorphic operations and maximize parallelization.
* Simplify the code with Rayon crate that parallelizes iterators and manages threads efficiently.
The final code is available [here](https://github.com/zama-ai/tfhe-rs/tree/main/tfhe/examples/sha256\_bool).
Now let's dive into details of each SHA256 operation.
#### Rotate Right and Shift Right
As we have highlighted, these two operations can be evaluated by changing the position of each encrypted bit in the word, thereby requiring 0 homomorphic operations. Here is our implementation:
Rotate Right and Shift Right can be evaluated by changing the position of each encrypted bit in the word, requiring no homomorphic operations. Here is the implementation:
```rust
fn rotate_right(x: &[Ciphertext; 32], n: usize) -> [Ciphertext; 32] {
@@ -163,9 +185,11 @@ fn shift_right(x: &[Ciphertext; 32], n: usize, sk: &ServerKey) -> [Ciphertext; 3
#### Bitwise XOR, AND, Multiplexer
To implement these operations we will use the ```xor```, ```and``` and ```mux``` methods provided by the tfhe library to evaluate each boolean operation homomorphically. It's important to note that, since we will operate bitwise, we can parallelize the homomorphic computations. In other words, we can homomorphically XOR the bits at index 0 of two words using a thread, while XORing the bits at index 1 using another thread, and so on. This means we could compute these bitwise operations using up to 32 concurrent threads (since we work with 32-bit words).
To implement these operations, we will use the `xor`, and `mux` methods from the **TFHE-rs** library to perform each boolean operation homomorphically.
Here is our implementation of the bitwise homomorphic XOR operation. The ```par_iter``` and ```par_iter_mut``` methods create a parallel iterator that we use to compute each individual XOR efficiently. The other two bitwise operations are implemented in the same way.
For better efficiency, we can parallelize the homomorphic computations because we operate bitwise. It means that we can homomorphically XOR the bits at index 0 of two words using one thread while XORing the bits at index 1 using another thread, and so on. This approach allows for the computation of bitwise operations using up to 32 concurrent threads, corresponding to the 32-bit words used.
Here is the implementation of the bitwise homomorphic XOR operation. The `par_iter` and `par_iter_mut` methods create a parallel iterator that we use to compute each XOR efficiently. The other two bitwise operations are implemented in the same way.
```rust
fn xor(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
@@ -179,9 +203,9 @@ fn xor(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertex
#### Addition modulo 2^32
This is perhaps the trickiest operation to efficiently implement in a homomorphic fashion. A naive implementation could use the Ripple Carry Adder algorithm, which is straightforward but cannot be parallelized because each step depends on the previous one.
This might be the trickiest operation to efficiently implement in a homomorphic manner. A naive implementation could use the Ripple Carry Adder algorithm, which is straightforward but cannot be parallelized because each step depends on the previous one.
A better choice would be the Carry Lookahead Adder, which allows us to use the parallelized AND and XOR bitwise operations. With this design, our adder is around 50% faster than the Ripple Carry Adder.
A better choice is to use Carry Lookahead Adder, which allows us to use the parallelized AND and XOR bitwise operations. With this design, our adder is around 50% faster than the Ripple Carry Adder.
```rust
pub fn add(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
@@ -206,30 +230,31 @@ fn compute_carry(propagate: &[Ciphertext; 32], generate: &[Ciphertext; 32], sk:
}
```
To even improve performance more, the function that computes the carry signals can also be parallelized using parallel prefix algorithms. These algorithms involve more boolean operations (so homomorphic operations for us) but may be faster because of their parallel nature. We have implemented the Brent-Kung and Ladner-Fischer algorithms, which entail different tradeoffs.
To further optimize performance, we use parallel prefix algorithms to parallelize the function that computes the carry signals. These algorithms involve more (homomorphic) boolean operations and their parallel nature speeds up the processing. We have implemented the Brent-Kung and Ladner-Fischer algorithms with different tradeoffs:
Brent-Kung has the least amount of boolean operations we could find (140 when using grey cells, for 32-bit numbers), which makes it suitable when we can't process many operations concurrently and fast. Our results confirm that it's indeed faster than both the sequential algorithm and Ladner-Fischer when run on regular computers.
* Brent-Kung has the least amount of boolean operations we could find (140 when using grey cells, for 32-bit numbers), which makes it suitable when we can't process many operations concurrently and fast. Our results confirm that it's indeed faster than both the sequential algorithm and Ladner-Fischer when run on regular computers.
* On the other hand, Ladner-Fischer performs more boolean operations (209 using grey cells) than Brent-Kung, but they are performed in larger batches. Hence we can compute more operations in parallel and finish earlier, but we need more fast threads available or they will slow down the carry signals computation. Ladner-Fischer can be suitable when using cloud-based computing services, which offer many high-speed threads.
On the other hand, Ladner-Fischer performs more boolean operations (209 using grey cells) than Brent-Kung, but they are performed in larger batches. Hence we can compute more operations in parallel and finish earlier, but we need more fast threads available or they will slow down the carry signals computation. Ladner-Fischer can be suitable when using cloud-based computing services, which offer many high-speed threads.
Our implementation uses Brent-Kung by default, but you can enable Ladner-Fischer by using the `--ladner-fischer` command line argument.
Our implementation uses Brent-Kung by default, but Ladner-Fischer can be enabled when needed by using the ```--ladner-fischer``` command line argument.
For more information about parallel prefix adders, you can read [this paper](https://www.iosrjournals.org/iosr-jece/papers/Vol6-Issue1/A0610106.pdf) or [this other paper](https://www.ijert.org/research/design-and-implementation-of-parallel-prefix-adder-for-improving-the-performance-of-carry-lookahead-adder-IJERTV4IS120608.pdf).
For more information about parallel prefix adders you can read [this paper](https://www.iosrjournals.org/iosr-jece/papers/Vol6-Issue1/A0610106.pdf) or [this other paper](https://www.ijert.org/research/design-and-implementation-of-parallel-prefix-adder-for-improving-the-performance-of-carry-lookahead-adder-IJERTV4IS120608.pdf).
Finally, with all these sha256 operations working homomorphically, our functions will be homomomorphic as well along with the whole sha256 function (after adapting the code to work with the Ciphertext type). Let's talk about other performance improvements we can make before we finish.
Finally, with all these SHA-256 operations working homomorphically, our functions will be homomomorphic as well along with the whole SHA-256 function (after adapting the code to work with the Ciphertext type).
### More parallel processing
If we inspect the main ```sha256_fhe``` function, we will find operations that can be performed in parallel. For instance, within the compression loop, ```temp1``` and ```temp2``` can be computed concurrently. An efficient way to parallelize computations here is using the ```rayon::join()``` function, which uses parallel processing only when there are available CPUs. Recall that the two temporary values in the compression loop are the result of several additions, so we can use nested calls to ```rayon::join()``` to potentially parallelize more operations.
Let's talk about other performance improvements we can make before we finish.
Another way to speed up consecutive additions would be using the Carry Save Adder, a very efficient adder that takes 3 numbers and returns a sum and carry sequence. If our inputs are A, B and C, we can construct a CSA with our previously implemented Maj function and the bitwise XOR operation as follows:
In the main `sha256_fhe`, you can perform some functions in parallel. For example, in the compression loop, `temp1` and `temp2` can be computed in parallel by using the `rayon::join()` function when there is a CPU available. The two temporary values in the compression loop are the result of multiple additions, so you can use nested calls to `rayon::join()` to parallelize more operations.
Another way to speed up consecutive additions would be using the Carry Save Adder, a very efficient adder that takes 3 numbers and returns a sum and a carry sequence. If our inputs are A, B, and C, we can construct a CSA with our previously implemented Maj function and the bitwise XOR operation as follows:
```
Carry = Maj(A, B, C)
Sum = A XOR B XOR C
```
By chaining CSAs, we can input the sum and carry from a preceding stage along with another number into a new CSA. Finally, to get the result of the additions we add the sum and carry sequences using a conventional adder. At the end we are performing the same number of additions, but some of them are now CSAs, speeding up the process. Let's see all this together in the ```temp1``` and ```temp2``` computations.
By chaining CSAs, we can input the sum and carry from a preceding stage along with another number into a new CSA. Finally, to get the result of the additions we add the sum and carry sequences using a conventional adder. In the end, we are performing the same number of additions, but some of them are now CSAs, speeding up the process. Below is the illustration of this process in the `temp1` and `temp2` computations.
```rust
let (temp1, temp2) = rayon::join(
@@ -254,15 +279,17 @@ let (temp1, temp2) = rayon::join(
);
```
The first closure of the outer call to join will return ```temp1``` and the second ```temp2```. Inside the first outer closure we call join recursively until we reach the addition of the value ```h```, the current word ```w[i]``` and the current constant ```K[i]``` by using the CSA, while potentially computing in parallel the ```ch``` function. Then we take the sum, carry and ch values and add them again using the CSA.
The first closure of the outer call to join will return `temp1` and the second `temp2`.
All this is done while potentially computing the ```sigma_upper_case_1``` function. Finally we input the previous sum, carry and sigma values to the CSA and perform the final addition with ```add```. Once again, this is done while potentially computing ```sigma_upper_case_0``` and ```maj``` and adding them to get ```temp2```, in the second outer closure.
Inside the first outer closure, we call join recursively until we add the value `h`, the current word `w[i],` and the current constant `K[i]` by using the CSA, while potentially computing the `ch` function in parallel. Then we take the sum, carry, and ch values and add them again using the CSA.
With some changes of this type, we finally get a homomorphic sha256 function that doesn't leave unused computational resources.
All this is done while potentially computing the `sigma_upper_case_1` function. Finally we input the previous sum, carry, and sigma values to the CSA and perform the final addition with `add`. Once again, this is done while potentially computing `sigma_upper_case_0` and `maj` and adding them to get `temp2`, in the second outer closure.
## How to use sha256_bool
With these types of changes, we finally get a homomorphic SHA256 function that doesn't leave unused computational resources.
First of all, the most important thing when running the program is using the ```--release``` flag. The use of sha256_bool would look like this, given the implementation of ```encrypt_bools``` and ```decrypt_bools```:
## How to use SHA256\_bool
First, use the `--release` flag when running the program. Considering the implementation of `encrypt_bools` and `decrypt_bools`, the use of SHA-256 will be as follows:
```rust
fn main() {
@@ -310,14 +337,14 @@ fn main() {
}
```
By using ```stdin``` we can supply the data to hash using a file instead of the command line. For example, if our file ```input.txt``` is in the same directory as the project, we can use the following shell command after building with ```cargo build --release```:
We can supply the data to hash using a file instead of the command line by using `stdin` . For example, if the file `input.txt` is in the same directory as the project, we can use the following shell command after building with `cargo build --release`:
```sh
./target/release/examples/sha256_bool < input.txt
```
Our implementation also accepts hexadecimal inputs. To be considered as such, the input must start with "0x" and contain only valid hex digits (otherwise it's interpreted as text).
The program accepts hexadecimal inputs. The input must start with "0x" and contain only valid hex digits, otherwise it will be interpreted as text.
Finally see that padding is executed on the client side. This has the advantage of hiding the exact length of the input to the server, who already doesn't know anything about the contents of it but may extract information from the length.
Finally padding is performed on the client side. This has the advantage of hiding the exact length of the input content from the server, thus avoiding the server extracting information from the length, even though the content is fully encrypted.
Another option would be to perform padding on the server side. The padding function would receive the encrypted input and pad it with trivial bit encryptions. We could then integrate the padding function inside the ```sha256_fhe``` function computed by the server.
It is also feasible to perform padding on the server side. The padding function would take the encrypted input and pad it with trivial bit encryptions. We can then integrate the padding function into the `sha256_fhe` function computed by the server.