docs(all): TFHE-rs v0.3.0 doc update

This commit is contained in:
J-B Orfila
2023-06-23 11:51:42 +02:00
committed by Arthur Meyre
parent a69a9c727b
commit 4e37f7e5bf
38 changed files with 1599 additions and 1308 deletions

View File

@@ -3,49 +3,50 @@
* [What is TFHE-rs?](README.md)
## Getting Started
* [Installation](getting\_started/installation.md)
* [Quick Start](getting\_started/quick\_start.md)
* [Supported Operations](getting\_started/operations.md)
* [Benchmarks](getting\_started/benchmarks.md)
* [Security and Cryptography](getting\_started/security\_and\_cryptography.md)
* [Installation](getting_started/installation.md)
* [Quick Start](getting_started/quick_start.md)
* [Operations](getting_started/operations.md)
* [Benchmarks](getting_started/benchmarks.md)
* [Security and Cryptography](getting_started/security_and_cryptography.md)
## High Level API
* [Tutorial](high_level_api/tutorial.md)
* [Operations](high_level_api/operations.md)
* [Serialization/Deserialization](high_level_api/serialization.md)
## Tutorials
* [Homomorphic Parity Bit](tutorials/parity_bit.md)
* [Homomorphic Case Changing on Latin String](tutorials/latin_fhe_string.md)
## Boolean
* [Tutorial](Boolean/tutorial.md)
* [Operations](Boolean/operations.md)
* [Cryptographic Parameters](Boolean/parameters.md)
* [Serialization/Deserialization](Boolean/serialization.md)
## How To
* [Configure Rust](how_to/rust_configuration.md)
* [Serialize/Deserialize](how_to/serialization.md)
* [Compress Ciphertexts/Keys](how_to/compress.md)
* [Use Public Key Encryption](how_to/public_key.md)
* [Use Trivial Ciphertext](how_to/trivial_ciphertext.md)
* [Use Parallelized PBS](how_to/parallelized_pbs.md)
* [Use the C API](how_to/c_api.md)
* [Use the JS on WASM API](how_to/js_on_wasm_api.md)
## Shortint
* [Tutorial](shortint/tutorial.md)
* [Operations](shortint/operations.md)
* [Cryptographic Parameters](shortint/parameters.md)
* [Serialization/Deserialization](shortint/serialization.md)
## Fine-grained APIs
* [Quick Start](fine_grained_api/quick_start.md)
* [Boolean](fine_grained_api/Boolean/tutorial.md)
* [Operations](fine_grained_api/Boolean/operations.md)
* [Cryptographic Parameters](fine_grained_api/Boolean/parameters.md)
* [Serialization/Deserialization](fine_grained_api/Boolean/serialization.md)
## Integer
* [Tutorial](integer/tutorial.md)
* [Operations](integer/operations.md)
* [Cryptographic Parameters](integer/parameters.md)
* [Serialization/Deserialization](integer/serialization.md)
* [Shortint](fine_grained_api/shortint/tutorial.md)
* [Operations](fine_grained_api/shortint/operations.md)
* [Cryptographic Parameters](fine_grained_api/shortint/parameters.md)
* [Serialization/Deserialization](fine_grained_api/shortint/serialization.md)
## Tutorials for real-life applications
* [Dark Market](tutorial/dark_market.md)
* [SHA256](tutorial/sha256_bool.md)
* [Homomorphic Regular Expressions](tutorial/regex/tutorial.md)
* [Integer](fine_grained_api/integer/tutorial.md)
* [Operations](fine_grained_api/integer/operations.md)
* [Cryptographic Parameters](fine_grained_api/integer/parameters.md)
* [Serialization/Deserialization](fine_grained_api/integer/serialization.md)
## Application Tutorials
* [SHA256 with *Boolean API*](application_tutorials/sha256_bool.md)
* [Dark Market with *Integer API*](application_tutorials/dark_market.md)
* [Homomorphic Regular Expressions *Integer API*](application_tutorials/regex.md)
## C API
* [High-Level API](c_api/high-level-api.md)
* [Shortint API](c_api/shortint-api.md)
## JS on WASM API
* [Tutorial](js_on_wasm_api/tutorial.md)
## Low-Level Core Cryptography
## Crypto Core API [Advanced users]
* [Quick Start](core_crypto/presentation.md)
* [Tutorial](core_crypto/tutorial.md)

View File

@@ -229,7 +229,7 @@ execute our RegExpr onto the encrypted content.
### Encoding and encrypting the content.
It is not possible to encrypt the entire content into a single encrypted value.
We can only encrypt numbers and preform operations on those encrypted numbers with
We can only encrypt numbers and perform operations on those encrypted numbers with
FHE. Therefore, we have to find a scheme where we encode the content into a
sequence of numbers that are then encrypted individually to form a sequence of
encrypted numbers.
@@ -509,4 +509,4 @@ Pattern | Description
`/^[a-c]b\|cd$/` | Matches with: `ab`, `bb`, `cb`, `cd`
`/^[a-c]b\|cd$/i` | Matches with: `ab`, `Ab`, `aB`, ..., `cD`, `CD`
`/^d(abc)+d$/` | For example, matches with: `dabcd`, `dabcabcd`, `dabcabcabcd`
`/^a.*d$/` | Matches with any content that starts with `a` and ends with `d`
`/^a.*d$/` | Matches with any content that starts with `a` and ends with `d`

View File

@@ -1,172 +0,0 @@
# Shortint API
## Using the shortint C API
This library exposes a C binding to the TFHE-rs shortint API to implement _Fully Homomorphic Encryption_ (FHE) programs.
## First steps using TFHE-rs C API
### Setting up TFHE-rs C API for use in a C program.
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo +nightly build --release --features=x86_64-unix,boolean-c-api,shortint-c-api -p tfhe
```
or on a Unix aarch64 machine using the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo build +nightly --release --features=aarch64-unix,boolean-c-api,shortint-c-api -p tfhe
```
All features are opt-in, but for simplicity here, the C API is enabled for Boolean and shortint.
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO\_ROOT}/target/release/"
The build system needs to be set up so that the C or C++ program links against TFHE-rs C API binaries.
Here is a minimal CMakeLists.txt to do just that:
```cmake
project(my-project)
cmake_minimum_required(VERSION 3.16)
set(TFHE_C_API "/path/to/tfhe-rs/binaries/and/header")
include_directories(${TFHE_C_API})
add_library(tfhe STATIC IMPORTED)
set_target_properties(tfhe PROPERTIES IMPORTED_LOCATION ${TFHE_C_API}/libtfhe.a)
if(APPLE)
find_library(SECURITY_FRAMEWORK Security)
if (NOT SECURITY_FRAMEWORK)
message(FATAL_ERROR "Security framework not found")
endif()
endif()
set(EXECUTABLE_NAME my-executable)
add_executable(${EXECUTABLE_NAME} main.c)
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC tfhe m pthread dl)
if(APPLE)
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC ${SECURITY_FRAMEWORK})
endif()
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
```
### Commented code of a PBS doubling a 2-bits encrypted message using `TFHE-rs C API`.
The steps required to perform the multiplication by 2 of a 2-bits ciphertext using a PBS are detailed. This is NOT the most efficient way of doing this operation, but it can help to show the management required to run a PBS manually using the C API.
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.
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:
```shell
# /!\ Be sure to update CMakeLists.txt to give the absolute path to the compiled tfhe library
$ ls
CMakeLists.txt main.c
$ mkdir build && cd build
$ cmake .. -DCMAKE_BUILD_TYPE=RELEASE
...
$ make
...
$ ./my-executable
Result: 2
$
```
```c
#include "tfhe.h"
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
uint64_t double_accumulator_2_bits_message(uint64_t in) { return (in * 2) % 4; }
uint64_t get_max_value_of_accumulator_generator(uint64_t (*accumulator_func)(uint64_t),
size_t message_bits)
{
uint64_t max_value = 0;
for (size_t idx = 0; idx < (1 << message_bits); ++idx)
{
uint64_t acc_value = accumulator_func((uint64_t)idx);
max_value = acc_value > max_value ? acc_value : max_value;
}
return max_value;
}
int main(void)
{
ShortintPBSLookupTable *accumulator = NULL;
ShortintClientKey *cks = NULL;
ShortintServerKey *sks = NULL;
ShortintParameters *params = NULL;
// Get the parameters for 2 bits messages with 2 bits of carry
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
// Generate the keys with the parameters
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
// Generate the accumulator for the PBS
int gen_acc_ok = shortint_server_key_generate_pbs_accumulator(
sks, double_accumulator_2_bits_message, &accumulator);
assert(gen_acc_ok == 0);
ShortintCiphertext *ct = NULL;
ShortintCiphertext *ct_out = NULL;
// We will compute 1 * 2 using a PBS, it's not the recommended way to perform a multiplication,
// but it shows how to manage a PBS manually in the C API
uint64_t in_val = 1;
// Encrypt the input value
int encrypt_ok = shortint_client_key_encrypt(cks, in_val, &ct);
assert(encrypt_ok == 0);
// Check the degree is set to the maximum value that can be encrypted on 2 bits, i.e. 3
// This check is not required and is just added to show, the degree information can be retrieved
// in the C APi
size_t degree = -1;
int get_degree_ok = shortint_ciphertext_get_degree(ct, &degree);
assert(get_degree_ok == 0);
assert(degree == 3);
// Apply the PBS on our encrypted input
int pbs_ok = shortint_server_key_programmable_bootstrap(sks, accumulator, ct, &ct_out);
assert(pbs_ok == 0);
// Set the degree to keep consistency for potential further computations
// Note: This is only required for the PBS
size_t degree_to_set =
(size_t)get_max_value_of_accumulator_generator(double_accumulator_2_bits_message, 2);
int set_degree_ok = shortint_ciphertext_set_degree(ct_out, degree_to_set);
assert(set_degree_ok == 0);
// Decrypt the result
uint64_t result = -1;
int decrypt_non_assign_ok = shortint_client_key_decrypt(cks, ct_out, &result);
assert(decrypt_non_assign_ok == 0);
// Check the result is what we expect i.e. 2
assert(result == double_accumulator_2_bits_message(in_val));
printf("Result: %ld\n", result);
// Destroy entities from the C API
destroy_shortint_ciphertext(ct);
destroy_shortint_ciphertext(ct_out);
destroy_shortint_pbs_accumulator(accumulator);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
destroy_shortint_parameters(params);
return EXIT_SUCCESS;
}
```

View File

@@ -1,12 +1,12 @@
# 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](../shortint/tutorial.md) and/or [Boolean](../Boolean/tutorial.md) modules (based on this one) 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_api/shortint/tutorial.md) and/or [Boolean](../fine_grained_api/Boolean/tutorial.md) 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`. Its goal is to propose an easy-to-use API for cryptographers.
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.
The overall code architecture is split in two parts: one for entity definitions and another focused on algorithms. The entities contain the definition of useful types, like LWE ciphertext or bootstrapping keys. The algorithms are then naturally defined to work using these entities.
The API is convenient to easily add or modify existing algorithms or to have direct access to the raw data. Even if the LWE ciphertext object is defined along with functions giving access to the body, this is also possible to bypass these to get directly the $$i^{th}$$ element of LWE mask.
The API is convenient to add or modify existing algorithms, or to have direct access to the raw data. Even if the LWE ciphertext object is defined, along with functions giving access to the body, it is also possible to bypass these to get directly the $$i^{th}$$ element of LWE mask.
For instance, the code to encrypt and then decrypt a message looks like:

View File

@@ -2,21 +2,21 @@
## Using the `core_crypto` primitives
Welcome to this tutorial about TFHE-rs `core_crypto` module.
Welcome to this tutorial about `TFHE-rs` `core_crypto` module.
### Setting up TFHE-rs to use the `core_crypto` module
To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
To use `TFHE-rs`, it first has to be added as a dependency in the `Cargo.toml`:
```toml
tfhe = { version = "0.3.0", features = [ "x86_64-unix" ] }
```
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available, like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available. To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available (like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available). To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically\_secure\_pseudorandom\_number\_generator) used in TFHE-rs.
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically\_secure\_pseudorandom\_number\_generator) used in `TFHE-rs`.
In short: For x86\_64-based machines running Unix-like OSes:
In short: For `x86_64`-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
@@ -28,13 +28,13 @@ For Apple Silicon or aarch64-based machines running Unix-like OSes:
tfhe = { version = "0.3.0", features = ["aarch64-unix"] }
```
For x86\_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
For `x86_64`-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
```toml
tfhe = { version = "0.3.0", features = ["x86_64"] }
```
### Commented code to double a 2-bits message in a leveled fashion and using a PBS with the `core_crypto` module.
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.
As a complete example showing the usage of some common primitives of the `core_crypto` APIs, the following Rust code homomorphically computes 2 \* 3 using two different methods. First using a cleartext multiplication and then using a PBS.

View File

@@ -1,6 +1,6 @@
# Contributing
There are two ways to contribute to **TFHE-rs**. You can:
There are two ways to contribute to `TFHE-rs`. You can:
* open issues to report bugs and typos and to suggest ideas;
* ask to become an official contributor by emailing hello@zama.ai. Only approved contributors can send pull requests, so get in touch before you do.

View File

@@ -1,6 +1,7 @@
# Operations
# Boolean Operations
This contains the operations available in tfhe::boolean, along with code examples.
In tfhe::boolean, the available operations are mainly related to their equivalent Boolean gates (i.e., AND, OR... etc). What follows are examples of a unary gate (NOT) and a binary gate (XOR). The last one is about the ternary MUX gate, which allows homomorphic computation of conditional statements of the form `If..Then..Else`.
## The NOT unary gate

View File

@@ -8,7 +8,7 @@ Some cryptographic parameters will require tuning to ensure both the correctness
To make it simpler, **we've provided two sets of parameters**, which ensure correct computations for a certain probability with the standard security of 128 bits. There exists an error probability due to the probabilistic nature of the encryption, which requires adding randomness (noise) following a Gaussian distribution. If this noise is too large, the decryption will not give a correct result. There is a trade-off between efficiency and correctness: generally, using a less efficient parameter set (in terms of computation time) leads to a smaller risk of having an error during homomorphic evaluation.
In the two proposed sets of parameters, the only difference lies in this probability error. The default parameter set ensures a probability error of at most $$2^{-40}$$ when computing a programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error probability claimed in the original [TFHE paper](https://eprint.iacr.org/2018/421), namely $$2^{-165}$$, but it is up-to-date regarding security requirements.
In the two proposed sets of parameters, the only difference lies in this error probability. The default parameter set ensures an error probability of at most $$2^{-40}$$ when computing a programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error probability claimed in the original [TFHE paper](https://eprint.iacr.org/2018/421), namely $$2^{-165}$$, but it is up-to-date regarding security requirements.
The following array summarizes this:

View File

@@ -1,5 +1,8 @@
# Tutorial
In `tfhe::boolean`, the available operations are mainly related to their equivalent Boolean gates (i.e., AND, OR... etc). What follows are examples of a unary gate (NOT) and a binary gate (XOR). The last one is about the ternary MUX gate, which allows homomorphic computation of conditional statements of the form `If..Then..Else`.
This library is meant to be used both on the **server side** and the **client side**. The typical use case should follow the subsequent steps:
1. On the **client side**, generate the `client` and `server keys`.

View File

@@ -1,12 +1,12 @@
# Operations
The structure and operations related to the integers are described in this section.
The structure and operations related to integers are described in this section.
## How an integer is represented
In `integer`, the encrypted data is split amongst many ciphertexts encrypted with the `shortint` library. Below is a scheme representing an integer composed by k shortint ciphertexts.
![](../\_static/integer-ciphertext.png)
![](../../_static/integer-ciphertext.png)
This crate implements two ways to represent an integer:
@@ -30,11 +30,11 @@ fn main() {
}
```
In this representation, the correctness of operations requires to propagate the carries between the ciphertext. This operation is costly since it relies on the computation of many programmable bootstrapping operations over shortints.
In this representation, the correctness of operations requires the carries to be propagated throughout the ciphertext. This operation is costly, since it relies on the computation of many programmable bootstrapping operations over shortints.
### CRT-based integers.
The second approach to represent large integers is based on the Chinese Remainder Theorem. In this case, the basis $$B$$ is composed of several integers $$b_i$$, such that there are pairwise coprime, and each $$b\_i$$ has a size smaller than 4 bits. The CRT-based integer are defined modulus $$\prod b_i$$. For an integer $$m$$, its CRT decomposition is simply defined as $$m % b_0, m % b_1, ...$$. Each part is then encrypted as a shortint ciphertext. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
The second approach to represent large integers is based on the Chinese Remainder Theorem. In this case, the basis $$B$$ is composed of several integers $$b_i$$, such that there are pairwise coprime, and each $$b\_i$$ has a size smaller than 4 bits. The CRT-based integer are defined modulus $$\prod b_i$$. For an integer $$m$$, its CRT decomposition is simply defined as $$m \bmod{b_0}, m \bmod{b_1}, ...$$. Each part is then encrypted as a shortint ciphertext. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
In the following example, the chosen basis is $$B = [2, 3, 5]$$. The integer is defined modulus $$2*3*5 = 30$$. There is no need to pre-size the number of blocks since it is determined from the number of values composing the basis. Here, the integer is split over three blocks.
@@ -51,7 +51,7 @@ fn main() {
This representation has many advantages: no carry propagation is required, cleaning the carry buffer of each ciphertext block is enough. This implies that operations can easily be
parallelized. It also allows the efficient computation of PBS in the case where the function is CRT-compliant.
A variant of the CRT is proposed, where each block might be associated to a different key couple. In the end, a keychain to the computations is required, but performance might be improved.
A variant of the CRT is proposed where each block might be associated to a different key couple. Here, a keychain to the computations is required, but this may result in a performance improvement.
## List of available operations
@@ -86,10 +86,10 @@ For example, the addition has both variants:
Each operation may come in different 'flavors':
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space.
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely.
* `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation will propagate the carry buffer to make the operation possible.
* `default`: Always compute the operation and always clear the carry. Could be **slower** than smart, but ensure that the timings are consistent from one call to another.
* `unchecked`: always does the operation, without checking if the result may exceed the capacity of the plaintext space.
* `checked`: checks are done before computing the operation, returning an error if operation cannot be done safely.
* `smart`: always does the operation, if the operation cannot be computed safely, the smart operation will propagate the carry buffer to make the operation possible.
* `default`: always compute the operation and always clear the carry. Could be **slower** than smart, but ensure that the timings are consistent from one call to another.
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.

View File

@@ -2,7 +2,7 @@
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that does the computations.
The easiest way to send these data to a server is to use the serialization and deserialization features. TFHE-rs uses the serde framework, so serde's Serialize and Deserialize are implemented.
The easiest way to send these data to a server is to use the serialization and deserialization features. `TFHE-rs` uses the serde framework, so serde's Serialize and Deserialize are implemented.
To be able to serialize our data, a [data format](https://serde.rs/#data-formats) needs to be picked. Here, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.

View File

@@ -1,6 +1,6 @@
# Tutorial
The steps to homomorphically evaluate an integer circuit are described here.
`tfhe::integer` is dedicated to unsigned integers smaller than 256 bits. The steps to homomorphically evaluate an integer circuit are described here.
## Key Types
@@ -14,7 +14,7 @@ The `ClientKey` is the key that encrypts and decrypts messages, thus this key is
The `ServerKey` is the key that is used to actually do the FHE computations. It contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, so it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
To reflect that, computation/operation methods are tied to the `ServerKey` type.
To reflect this, computation/operation methods are tied to the `ServerKey` type.
The `PublicKey` is a key used to encrypt messages. It can be publicly shared to allow users to encrypt data such that only the `ClientKey` holder will be able to decrypt. Encrypting with the `PublicKey` does not alter the homomorphic capabilities associated to the `ServerKey`.

View File

@@ -0,0 +1,161 @@
# Quick Start
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans, short integers (named shortint in the rest of this documentation), or integers up to 256 bits. It allows you to execute a circuit on an **untrusted server** because both circuit inputs and outputs are kept **private**. Data are indeed encrypted on the client side, before being sent to the server. On the server side, every computation is performed on ciphertexts.
The server, however, has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. Then the user can decrypt it with the `secret key`.
## General method to write an homomorphic circuit program
The overall process to write an homomorphic program is the same for all types. The basic steps for using the TFHE-rs library are the following:
1. Choose a data type (Boolean, shortint, integer)
2. Import the library
3. Create client and server keys
4. Encrypt data with the client key
5. Compute over encrypted data using the server key
6. Decrypt data with the client key
### API levels.
This library has different modules, with different levels of abstraction.
There is the **core\_crypto** module, which is the lowest level API with the primitive functions and types of the TFHE scheme.
Above the core\_crypto module, there are the **Boolean**, **shortint**, and **integer** modules, which contain easy to use APIs enabling evaluation of Boolean, short integer, and integer circuits.
Finally, there is the high-level module built on top of the Boolean, shortint, integer modules. This module is meant to abstract cryptographic complexities: no cryptographical knowledge is required to start developing an FHE application. Another benefit of the high-level module is the drastically simplified development process compared to lower level modules.
#### high-level API
TFHE-rs exposes a high-level API by default that includes datatypes that try to match Rust's native types by having overloaded operators (+, -, ...).
Here is an example of how the high-level API is used:
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
let result = a + b;
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
}
```
#### Boolean example
Here is an example of how the library can be used to evaluate a Boolean circuit:
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
```rust
use tfhe::boolean::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys();
// We use the client secret key to encrypt two messages:
let ct_1 = client_key.encrypt(true);
let ct_2 = client_key.encrypt(false);
// We use the server public key to execute a boolean circuit:
// if ((NOT ct_2) NAND (ct_1 AND ct_2)) then (NOT ct_2) else (ct_1 AND ct_2)
let ct_3 = server_key.not(&ct_2);
let ct_4 = server_key.and(&ct_1, &ct_2);
let ct_5 = server_key.nand(&ct_3, &ct_4);
let ct_6 = server_key.mux(&ct_5, &ct_3, &ct_4);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_6);
assert_eq!(output, true);
}
```
#### shortint example
Here is a full example using shortint:
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
```rust
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
// using parameters with 2 bits of message and 2 bits of carry
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let msg1 = 1;
let msg2 = 0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
// We use the server public key to execute an integer circuit:
let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_3);
assert_eq!(output, (msg1 + msg2) % modulus as u64);
}
```
#### integer example
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We create keys for radix represention to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, 8);
let clear_a = 2382u16;
let clear_b = 29374u16;
let mut a = cks.encrypt(clear_a as u64);
let mut b = cks.encrypt(clear_b as u64);
let encrypted_max = sks.smart_max_parallelized(&mut a, &mut b);
let decrypted_max: u64 = cks.decrypt(&encrypted_max);
assert_eq!(decrypted_max as u16, clear_a.max(clear_b))
}
```
The library is simple to use and can evaluate **homomorphic circuits of arbitrary length**. The description of the algorithms can be found in the [TFHE](https://doi.org/10.1007/s00145-019-09319-x) paper (also available as [ePrint 2018/421](https://ia.cr/2018/421)).

View File

@@ -1,6 +1,6 @@
# Operations
The structure and the operations related to the short integers are described in this section.
The structure and operations related to short integers are described in this section.
## How a shortint is represented
@@ -8,11 +8,11 @@ In `shortint`, the encrypted data is stored in an LWE ciphertext.
Conceptually, the message stored in an LWE ciphertext is divided into a **carry buffer** and a **message buffer**.
![](../\_static/ciphertext-representation.png)
![](../../_static/ciphertext-representation.png)
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus. The exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus. The part of the message which exceeds the message modulus is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in incorrect outputs.
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in an incorrect output.
In order to ensure the correctness of the computation, we track the maximum value encrypted in a ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to safely resume the computations. In `shortint` the carry modulus is considered useful as a means to do more computations.
@@ -23,23 +23,23 @@ The operations available via a `ServerKey` may come in different variants:
* operations that take their inputs as encrypted values
* scalar operations that take at least one non-encrypted value as input
For example, the addition has both variants:
For example, the addition has two variants:
* `ServerKey::unchecked_add`, which takes two encrypted values and adds them.
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (the so-called scalar) and adds them.
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (a so-called scalar) and adds them.
Each operation may come in different 'flavors':
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely;
* `smart`: Always does the operation. If the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible;
* `default`: Always does the operation and always clears the carry. Could be **slower** than smart, but it ensures that the timings are consistent from one call to another.
* `unchecked`: always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
* `checked`: checks are done before computing the operation, returning an error if operation cannot be done safely;
* `smart`: always does the operation. If the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible;
* `default`: always does the operation and always clears the carry. Could be **slower** than smart, but it ensures that the timings are consistent from one call to another.
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
## How to use operation types
Let's try to do a circuit evaluation using the different flavors of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise,`checked` and `smart` are the best options.
Let's try to do a circuit evaluation using the different flavors of operations that we have already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise,`checked` and `smart` are the best options.
Let's do a scalar multiplication, a subtraction, and a multiplication.
@@ -147,7 +147,7 @@ fn main() {
}
```
The main advantage of the default flavor is to ensure predictable timings as long as only this kind of operation is used.
The main advantage of the default flavor is to ensure predictable timings as long as this is the only kind of operation which is used.
{% hint style="warning" %}
Using `default` could **slow-down** computations.

View File

@@ -1,14 +1,14 @@
# Cryptographic Parameters
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing using programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting\_started/security\_and\_cryptography.md) for more details about the encryption process).
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when using programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../../getting_started/security_and_cryptography.md) for more details about the encryption process).
## Parameters and message precision
`shortint` comes with sets of parameters that permit the use of the library functionalities securely and efficiently. Each parameter set is associated to the message and carry precisions. Thus, each key pair is entangled to precision.
`shortint` comes with sets of parameters that permit the use of the library functionalities securely and efficiently. Each parameter set is associated to the message and carry precisions. Therefore, each key pair is entangled to precision.
The user is allowed to choose which set of parameters to use when creating the pair of keys.
The difference between the parameter sets is the total amount of space dedicated to the plaintext, how it is split between the message buffer and the carry buffer and the order in which the keyswitch (KS) and bootstrap (PBS) are computed. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}_{KS_PBS | PBS_KS}`. For example, the set of parameters for a message buffer of 5 bits, a carry buffer of 2 bits and where the keyswitch is computed before the bootstrap is `PARAM_MESSAGE_5_CARRY_2_KS_PBS`.
The difference between the parameter sets is the total amount of space dedicated to the plaintext, how it is split between the message buffer and the carry buffer, and the order in which the keyswitch (KS) and bootstrap (PBS) are computed. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}_{KS_PBS | PBS_KS}`. For example, the set of parameters for a message buffer of 5 bits, a carry buffer of 2 bits and where the keyswitch is computed before the bootstrap is `PARAM_MESSAGE_5_CARRY_2_KS_PBS`.
Note that the `KS_PBS` order should have better performance at the expense of ciphertext size, `PBS_KS` is the opposite.
@@ -34,11 +34,11 @@ fn main() {
## Impact of parameters on the operations
As shown [here](../getting\_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
As shown [here](../../getting_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
### Generic bi-variate functions.
The computations of bi-variate functions is based on a trick: _concatenating_ two ciphertexts into one. Where the carry buffer is not at least as large as the message one, this trick no longer works. Many bi-variate operations, such as comparisons, then cannot be correctly computed. The only exception concerns multiplication.
The computations of bi-variate functions is based on a trick: _concatenating_ two ciphertexts into one. Where the carry buffer is not at least as large as the message buffer, this trick no longer works. In this case, many bi-variate operations, such as comparisons, cannot be correctly computed. The only exception concerns multiplication.
### Multiplication.

View File

@@ -2,9 +2,9 @@
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that performs the computations.
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework. Serde's Serialize and Deserialize are then implemented on tfhe::shortint's types.
The easiest way to send these data to a server is to use the serialization and deserialization features. `tfhe::shortint` uses the [serde](https://crates.io/crates/serde) framework. Serde's Serialize and Deserialize are then implemented on the `tfhe::shortint` types.
To serialize the data, we need to pick a [data format](https://serde.rs/#data-formats). For our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
To serialize the data, we need to pick a [data format](https://serde.rs/#data-formats). For our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is a binary format.
```toml
# Cargo.toml

View File

@@ -1,6 +1,6 @@
# Tutorial
The steps to homomorphically evaluate a circuit are described below.
`tfhe::shortint` is dedicated to small unsigned integers smaller than 8 bits. The steps to homomorphically evaluate a circuit are described below.
## Key generation
@@ -12,7 +12,7 @@ The steps to homomorphically evaluate a circuit are described below.
The `ClientKey` is the key that encrypts and decrypts messages (integer values up to 8 bits here). It is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
The `ServerKey` is the key that is used to actually do the FHE computations. Most importantly, it contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, therefore it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
The `ServerKey` is the key that is used to evaluate the FHE computations. Most importantly, it contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server (it is not meant to be kept private). A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
Computation/operation methods are tied to the `ServerKey` type.
@@ -70,7 +70,7 @@ fn main() {
## Computing and decrypting
With the `server_key`, addition is now possible over encrypted values. The resulting plaintext is recovered after the decryption with the secret client key.
Using the `server_key`, addition is possible over encrypted values. The resulting plaintext is recovered after the decryption via the secret client key.
```rust
use tfhe::shortint::prelude::*;

View File

@@ -10,10 +10,10 @@ This measures the execution time of a single binary Boolean gate.
### tfhe-rs::boolean.
| Parameter set | Concrete FFT | Concrete FFT + avx512 |
| --------------------- | ------------ | --------------------- |
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
| Parameter set | Concrete FFT | Concrete FFT + AVX-512 |
| --------------------- | ------------ | ---------------------- |
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
### tfhe-lib.
@@ -29,19 +29,41 @@ This measures the execution time of a single binary Boolean gate.
| MEDIUM | 113ms | 50.2ms |
## Shortint
This measures the execution time for some operations and some parameter sets of tfhe-rs::shortint.
## Integer
This measures the execution time for some operation sets of tfhe-rs::integer.
| Operation \ Size | `FheUint8` | `FheUint16` | `FheUint32` | ` FheUint64` | `FheUint128` | `FheUint256` |
|--------------------------------------------------------|------------|-------------|-------------|--------------|--------------|--------------|
| Negation (`-`) | 80.4 ms | 106 ms | 132 ms | 193 ms | 257 ms | 348 ms |
| Add / Sub (`+`,`-`) | 81.5 ms | 110 ms | 139 ms | 200 ms | 262 ms | 355 ms |
| Mul (`x`) | 150 ms | 221 ms | 361 ms | 928 ms | 2.90 s | 10.97 s |
| Equal / Not Equal (`eq`, `ne`) | 39.4 ms | 40.2 ms | 61.1 ms | 66.4 ms | 74.5 ms | 85.7 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 57.5 ms | 79.6 ms | 105 ms | 136 ms | 174 ms | 219 ms |
| Max / Min (`max`,`min`) | 100 ms | 130 ms | 163 ms | 204 ms | 245 ms | 338 ms |
| Bitwise operations (`&`, `|`, `^`) | 20.7 ms | 21.1 ms | 22.6 ms | 30.2 ms | 34.1 ms | 42.1 ms |
| Div / Rem (`/`, `%`) | 1.37 s | 3.50 s | 9.12 s | 23.9 s | 59.9 s | 149.2 s |
| Left / Right Shifts (`<<`, `>>`) | 106 ms | 140 ms | 202 ms | 262 ms | 403 ms | 827 ms |
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 105 ms | 140 ms | 199 ms | 263 ms | 403 ms | 829 ms |
This uses the Concrete FFT + avx512 configuration.
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, more information about parameters can be found [here](../fine_grained_api/shortint/parameters.md)).
To ensure predictable timings, the operation flavor is the `default` one: the carry is propagated if needed. The operation costs could be reduced by using `unchecked`, `checked`, or `smart`.
## Shortint
This measures the execution time for some operations using various parameter sets of tfhe-rs::shortint.
This uses the Concrete FFT + AVX-512 configuration.
| Parameter set | unchecked\_add | unchecked\_mul\_lsb | keyswitch\_programmable\_bootstrap |
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
|-----------------------------|----------------|---------------------|------------------------------------|
| PARAM\_MESSAGE\_1\_CARRY\_1 | 338 ns | 8.3 ms | 8.1 ms |
| PARAM\_MESSAGE\_2\_CARRY\_2 | 406 ns | 18.4 ms | 18.4 ms |
| PARAM\_MESSAGE\_3\_CARRY\_3 | 3.06 µs | 134 ms | 129.4 ms |
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 828.1 ms |
Next, the timings for the operation flavor `default` are given. This flavor ensures predictable timings of an operation all along the circuit by clearing the carry space after each operation.
Next, the timings for the operation flavor `default` are given. This flavor ensures predictable timings of an operation along the entire circuit by clearing the carry space after each operation.
| Parameter set | add | mul\_lsb | keyswitch\_programmable\_bootstrap |
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
@@ -50,19 +72,28 @@ Next, the timings for the operation flavor `default` are given. This flavor ensu
| PARAM\_MESSAGE\_3\_CARRY\_3 | 131.5 ms | 129.5 ms | 129.4 ms |
| PARAM\_MESSAGE\_4\_CARRY\_4 | 852.5 ms | 839.7 ms | 828.1 ms |
## How to reproduce benchmarks
TFHE-rs benchmarks can easily be reproduced from the [sources](https://github.com/zama-ai/tfhe-rs).
```shell
#Boolean benchmarks:
make bench_boolean
#Integer benchmarks:
make bench_integer
#Shortint benchmarks:
make bench_shortint
```
If the host machine supports AVX-512, then the argument `AVX512_SUPPORT=ON' should be added, e.g.:
```shell
#Integer benchmarks:
make AVX512_SUPPORT=ON bench_integer
```
## Integer
This measures the execution time for some operation sets of tfhe-rs::integer.
All timings are related to parallelized Radix-based integer operations, where each block is encrypted using PARAM\_MESSAGE\_2\_CARRY\_2.
To ensure predictable timings, the operation flavor is the `default` one: a carry propagation is computed after each operation. Operation cost could be reduced by using `unchecked`, `checked`, or `smart`.
| Plaintext size | add | mul | greater\_than (gt) | min |
| -------------------| ---------------| --------------------| ---------------------| -------------|
| 8 bits | 129.0 ms | 227 ms | 111.9 ms | 186.8 ms |
| 16 bits | 195 ms | 369 ms | 145.3 ms | 233.1 ms |
| 32 bits | 238 ms | 519 ms | 192.0 ms | 282.9 ms |
| 40 bits | 283 ms | 754 ms | 228.4 ms | 318.6 ms |
| 64 bits | 297 ms | 1.18 s | 249.0 ms | 336.5 ms |
| 128 bits | 424 ms | 3.13 s | 294.7 ms | 398.6 ms |
| 256 bits | 500 ms | 11 s | 361.8 ms | 509.1 ms |

View File

@@ -1,5 +1,7 @@
# Installation
## Importing into your project
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
@@ -9,28 +11,10 @@ tfhe = { version = "0.3.0", features = [ "boolean", "shortint", "integer", "x86_
```
{% hint style="info" %}
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 performances possible, eg: `cargo run --release`.
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, eg: `cargo run --release`.
{% endhint %}
## Choosing your features
`TFHE-rs` exposes different `cargo features` to customize the types and features used.
### Kinds.
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 unsigned integers |
| Integers | `integer` | Arbitrary-sized unsigned integers |
### Serialization.
The different data types and keys exposed by the crate can be serialized / deserialized.
More information can be found [here](../Boolean/serialization.md) for Boolean and [here](../shortint/serialization.md) for shortint.
## Supported platforms
@@ -43,36 +27,6 @@ TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (
| Windows | `x86_64` | Unsupported |
{% hint style="info" %}
Users who have ARM devices can use `TFHE-rs` by compiling using the `nightly` toolchain.
{% endhint %}
### Using TFHE-rs with nightly toolchain.
Install the needed Rust toolchain:
```shell
rustup toolchain install nightly
```
Then, you can either:
* Manually specify the toolchain to use in each of the cargo commands:
```shell
cargo +nightly build
cargo +nightly test
```
* Or override the toolchain to use for the current project:
```shell
rustup override set nightly
# cargo will use the `nightly` toolchain.
cargo build
```
To check the toolchain that Cargo will use by default, you can use the following command:
```shell
rustup show
```
Users who have ARM devices can use TFHE-rs by compiling using the `nightly` toolchain (see
[Configuration](../how_to/rust_configuration.md) for more details).
{% endhint %}

View File

@@ -1,64 +1,508 @@
# Supported Operations
# Operations
## Boolean
The table below contains an overview of the available operations in `TFHE-rs`. More details, and further examples, are given in the following sections.
The list of supported operations by the homomorphic Booleans is:
| name | symbol | FheUint/FheUint | FheUint/Uint | Uint/FheUint |
|-----------------------|-------------|--------------------|--------------------------|--------------------------|
| Neg | `-` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Add | `+` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Sub | `-` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Mul | `*` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Div | `/` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Rem | `%` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Not | `!` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| BitAnd | `&` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| BitOr | `\|` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| BitXor | `^` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Shr | `>>` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Shl | `<<` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Min | `min` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Max | `max` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Greater than | `gt` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Greater or equal than | `ge` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Lower than | `lt` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Lower or equal than | `le` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Equal | `eq` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Cast (into dest type) | `cast_into` | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |
| Cast (from src type) | `cast_from` | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |
| Operation Name | type |
| -------------- | ------- |
| `not` | Unary |
| `and` | Binary |
| `or` | Binary |
| `xor` | Binary |
| `nor` | Binary |
| `xnor` | Binary |
| `cmux` | Ternary |
## Boolean Operations
A walk-through using homomorphic Booleans can be found [here](../Boolean/tutorial.md).
## Shortint
In TFHE-rs, shortint represents short unsigned integers encoded over a maximum of 8 bits. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
Native homomorphic Booleans support common Boolean operations.
The list of supported operations is:
| Operation name | Type |
| ------------------------- | ------------ |
| Negation | Unary |
| Addition | Binary |
| Subtraction | Binary |
| Multiplication | Binary |
| Division\* | Binary |
| Modular reduction | Binary |
| Comparisons | Binary |
| Left/Right Shift | Binary |
| And | Binary |
| Or | Binary |
| Xor | Binary |
| Exact Function Evaluation | Unary/Binary |
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |
| [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 |
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
{% hint style="info" %}
The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. The division is tweaked so that dividing by 0 returns 0.
{% endhint %}
## ShortInt Operations
A walk-through example can be found [here](../shortint/tutorial.md), and more examples and explanations can be found [here](../shortint/operations.md).
Native small homomorphic integer types (e.g., FheUint3 or FheUint4) easily compute various operations. In general, computing over encrypted data is as easy as computing over clear data, since the same operation symbol is used. The addition between two ciphertexts is done using the symbol `+` between two FheUint values. Many operations can be computed between a clear value (i.e. a scalar) and a ciphertext.
In Rust, operations on native types are modular. For example, computations on `u8` are carried out modulo 2^8. A similar idea applies for FheUintX, where operations are done modulo 2^X. For FheUint3, operations are done modulo 8 = 2^3.
### Arithmetic operations.
Small homomorphic integer types support all common arithmetic operations, meaning `+`, `-`, `x`, `/`, `mod`.
The division operation implements a subtlety: since data is encrypted, it is possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns the max possible value for the message.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------- | ------ | ------ |
| [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 |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `-` | Unary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let clear_c = 2;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
let mut c = FheUint3::try_encrypt(clear_c, &keys)?;
a = a * &b; // Clear equivalent computations: 7 * 3 mod 8 = 5
b = &b + &c; // Clear equivalent computations: 3 + 2 mod 8 = 5
b = b - 5; // Clear equivalent computations: 5 - 5 mod 8 = 0
let dec_a = a.decrypt(&keys);
let dec_b = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, (clear_a * clear_b) % 8);
assert_eq!(dec_b, ((clear_b + clear_c) - 5) % 8);
Ok(())
}
```
### Bitwise operations.
Small homomorphic integer types support some 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 |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
a = a ^ &b;
b = b ^ &a;
a = a ^ &b;
let dec_a = a.decrypt(&keys);
let dec_b = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, clear_b);
assert_eq!(dec_b, clear_a);
Ok(())
}
```
### Comparisons.
Small homomorphic integer types 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. 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:
* [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:
| 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 |
| [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 |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
assert_eq!(a.gt(&b).decrypt(&keys) != 0, true);
assert_eq!(b.le(&a).decrypt(&keys) != 0, true);
Ok(())
}
```
### Univariate function evaluation.
The shortint type also supports the computation of univariate functions, which make use of TFHE's _programmable bootstrapping_.
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint4};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint4().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let pow_5 = |value: u64| {
value.pow(5) % FheUint4::MODULUS as u64
};
let clear_a = 12;
let a = FheUint4::try_encrypt(12, &keys)?;
let c = a.map(pow_5);
let decrypted = c.decrypt(&keys);
assert_eq!(decrypted, pow_5(clear_a) as u8);
Ok(())
}
```
### Bivariate function evaluations.
Using the shortint type allows you to evaluate bivariate functions (i.e., functions that take two ciphertexts as input).
A simple code example:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint2};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint2().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 1;
let clear_b = 3;
let a = FheUint2::try_encrypt(clear_a, &keys)?;
let b = FheUint2::try_encrypt(clear_b, &keys)?;
let c = a.bivariate_function(&b, std::cmp::max);
let decrypted = c.decrypt(&keys);
assert_eq!(decrypted, std::cmp::max(clear_a, clear_b) as u8);
Ok(())
}
```
## Integer
In TFHE-rs, integers represent unsigned integers up to 256 bits. They are encoded using Radix representations by default (more details [here](../integer/operations.md)).
In TFHE-rs, integers are used to encrypt any messages larger than 4 bits. All supported operations are listed below.
### Arithmetic operations.
Homomorphic integer types support arithmetic operations.
The list of supported operations is:
| Operation name | Type |
| ------------------------------ | ------ |
| Negation | Unary |
| Addition | Binary |
| Subtraction | Binary |
| Multiplication | Binary |
| Bitwise OR, AND, XOR | Binary |
| Equality | Binary |
| Left/Right Shift | Binary |
| Comparisons `<`,`<=`,`>`, `>=` | Binary |
| Min, Max | Binary |
| 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 |
A walk-through example can be found [here](../integer/tutorial.md).
For the division operator, the convention is to return the 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, for ct1 = FheUint8(63) and ct2 = FheUint8(0), then ct1 % ct2 will return FheUint8(63).
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 15_u64;
let clear_b = 27_u64;
let clear_c = 43_u64;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let mut c = FheUint8::try_encrypt(clear_c, &keys)?;
a = a * &b; // Clear equivalent computations: 15 * 27 mod 256 = 149
b = &b + &c; // Clear equivalent computations: 27 + 43 mod 256 = 70
b = b - 76u8; // Clear equivalent computations: 70 - 76 mod 256 = 250
let dec_a: u8 = a.decrypt(&keys);
let dec_b: u8 = b.decrypt(&keys);
assert_eq!(dec_a, ((clear_a * clear_b) % 256_u64) as u8);
assert_eq!(dec_b, (((clear_b + clear_c).wrapping_sub(76_u64)) % 256_u64) as u8);
Ok(())
}
```
### Bitwise operations.
Homomorphic integer types support some 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 |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 164;
let clear_b = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
a = a ^ &b;
b = b ^ &a;
a = a ^ &b;
let dec_a: u8 = a.decrypt(&keys);
let dec_b: u8 = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, clear_b);
assert_eq!(dec_b, clear_a);
Ok(())
}
```
### Comparisons.
Homomorphic integers support comparison operations. Since Rust does not allow the overloading of these operations, a simple function has been associated to each one.
The list of supported operations is:
| 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 |
| [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 |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a:u8 = 164;
let clear_b:u8 = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let greater = a.gt(&b);
let greater_or_equal = a.ge(&b);
let lower = a.lt(&b);
let lower_or_equal = a.le(&b);
let equal = a.eq(&b);
let dec_gt : u8 = greater.decrypt(&keys);
let dec_ge : u8 = greater_or_equal.decrypt(&keys);
let dec_lt : u8 = lower.decrypt(&keys);
let dec_le : u8 = lower_or_equal.decrypt(&keys);
let dec_eq : u8 = equal.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_gt, (clear_a > clear_b ) as u8);
assert_eq!(dec_ge, (clear_a >= clear_b) as u8);
assert_eq!(dec_lt, (clear_a < clear_b ) as u8);
assert_eq!(dec_le, (clear_a <= clear_b) as u8);
assert_eq!(dec_eq, (clear_a == clear_b) as u8);
Ok(())
}
```
### Min/Max.
Homomorphic integers support the min/max operations.
| name | symbol | type |
| ---- | ------ | ------ |
| Min | `min` | Binary |
| Max | `max` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a:u8 = 164;
let clear_b:u8 = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let min = a.min(&b);
let max = a.max(&b);
let dec_min : u8 = min.decrypt(&keys);
let dec_max : u8 = max.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_min, u8::min(clear_a, clear_b));
assert_eq!(dec_max, u8::max(clear_a, clear_b));
Ok(())
}
```
### Casting.
Casting between integer types is possible via the `cast_from` associated function
or the `cast_into` method.
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheUint32, FheUint16};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
// Casting requires server_key to set
// (encryptions/decryptions do not need server_key to be set)
set_server_key(server_key);
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Downcasting
let a: FheUint8 = a.cast_into();
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, clear as u8);
// Upcasting
let a: FheUint32 = a.cast_into();
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, (clear as u8) as u32);
}
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Upcasting
let a = FheUint32::cast_from(a);
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, clear as u32);
// Downcasting
let a = FheUint8::cast_from(a);
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, (clear as u32) as u8);
}
Ok(())
}
```

View File

@@ -1,39 +1,17 @@
# Quick Start
# Tutorial
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans, short integers (named shortint in the rest of this documentation), or integers up to 256 bits. It allows you to execute a circuit on an **untrusted server** because both circuit inputs and outputs are kept **private**. Data are indeed encrypted on the client side, before being sent to the server. On the server side, every computation is performed on ciphertexts.
## Quick Start
The server, however, has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. Then the user can decrypt it with the `secret key`.
The basic steps for using the high-level API of TFHE-rs are:
## General method to write an homomorphic circuit program
1. Importing the TFHE-rs prelude;
2. Client-side: Configuring and creating keys;
3. Client-side: Encrypting data;
4. Server-side: Setting the server key;
5. Server-side: Computing over encrypted data;
6. Client-side: Decrypting data.
The overall process to write an homomorphic program is the same for all types. The basic steps for using the TFHE-rs library are the following:
1. Choose a data type (Boolean, shortint, integer)
2. Import the library
3. Create client and server keys
4. Encrypt data with the client key
5. Compute over encrypted data using the server key
6. Decrypt data with the client key
### API levels.
This library has different modules, with different levels of abstraction.
There is the **core\_crypto** module, which is the lowest level API with the primitive functions and types of the TFHE scheme.
Above the core\_crypto module, there are the B**oolean**, **shortint**, and **integer** modules, which simply allow evaluation of Boolean, short integer, and integer circuits.
Finally, there is the high-level module built on top of the Boolean, shortint, integer modules. This module is meant to abstract cryptographic complexities: no cryptographical knowledge is required to start developing an FHE application. Another benefit of the high-level module is the drastically simplified development process compared to lower level modules.
#### high-level API
TFHE-rs exposes a high-level API by default that includes datatypes that try to match Rust's native types by having overloaded operators (+, -, ...).
Here is an example of how the high-level API is used:
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
Here is a full example (combining the client and server parts):
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
@@ -44,18 +22,20 @@ fn main() {
.enable_default_integers()
.build();
// Client-side
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
//Server-side
set_server_key(server_key);
let result = a + b;
//Client-side
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
@@ -64,98 +44,101 @@ fn main() {
}
```
#### Boolean example
The default configuration for x86 Unix machines:
```toml
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
```
Here is an example of how the library can be used to evaluate a Boolean circuit:
Configuration options for different platforms can be found [here](../getting_started/installation.md). Other rust and homomorphic types features can be found [here](../how_to/rust_configuration.md).
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
### Imports.
`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.
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.
```rust
use tfhe::boolean::prelude::*;
use tfhe::prelude::*;
```
### 1. Configuring and creating keys.
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.
A configuration can be created by using the ConfigBuilder type.
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](../how_to/rust_configuration.md#choosing-your-features).
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.
```rust
use tfhe::{ConfigBuilder, generate_keys};
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys();
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
// We use the client secret key to encrypt two messages:
let ct_1 = client_key.encrypt(true);
let ct_2 = client_key.encrypt(false);
// We use the server public key to execute a boolean circuit:
// if ((NOT ct_2) NAND (ct_1 AND ct_2)) then (NOT ct_2) else (ct_1 AND ct_2)
let ct_3 = server_key.not(&ct_2);
let ct_4 = server_key.and(&ct_1, &ct_2);
let ct_5 = server_key.nand(&ct_3, &ct_4);
let ct_6 = server_key.mux(&ct_5, &ct_3, &ct_4);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_6);
assert_eq!(output, true);
let (client_key, server_key) = generate_keys(config);
}
```
#### shortint example
The `generate_keys` command returns a client key and a server key.
Here is a full example using shortint:
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.
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
### 2. Setting the server key.
The next step is to call `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.
```rust
use tfhe::shortint::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key};
fn main() {
// We generate a set of client/server keys
// using parameters with 2 bits of message and 2 bits of carry
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let msg1 = 1;
let msg2 = 0;
let (client_key, server_key) = generate_keys(config);
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
// We use the server public key to execute an integer circuit:
let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_3);
assert_eq!(output, (msg1 + msg2) % modulus as u64);
set_server_key(server_key);
}
```
#### integer example
### 3. Encrypting data.
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
Encrypting data is achieved via the `encrypt` associated function of the FheEncrypt trait.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
Types exposed by this crate implement at least one of FheEncrypt or FheTryEncrypt to allow encryption.
fn main() {
// We create keys for radix represention to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, 8);
```Rust
let clear_a = 27u8;
let clear_b = 128u8;
let clear_a = 2382u16;
let clear_b = 29374u16;
let mut a = cks.encrypt(clear_a as u64);
let mut b = cks.encrypt(clear_b as u64);
let encrypted_max = sks.smart_max_parallelized(&mut a, &mut b);
let decrypted_max: u64 = cks.decrypt(&encrypted_max);
assert_eq!(decrypted_max as u16, clear_a.max(clear_b))
}
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
```
The library is simple to use and can evaluate **homomorphic circuits of arbitrary length**. The description of the algorithms can be found in the [TFHE](https://doi.org/10.1007/s00145-019-09319-x) paper (also available as [ePrint 2018/421](https://ia.cr/2018/421)).
### 4. Computation and decryption.
Computations should be as easy as normal Rust to write, thanks to the usage of operator overloading.
```Rust
let result = a + b;
```
The decryption is achieved by using the `decrypt` method, which comes from the FheDecrypt trait.
```Rust
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
```

View File

@@ -2,34 +2,34 @@
## 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 dedicated to Fully Homomorphic Encryption. As its name suggests, it is based on the TFHE scheme.
It is necessary to understand some basics about TFHE to comprehend where the limitations are coming from, both in terms of precision (number of bits used to represent plaintext values) and execution time (why TFHE operations are slower than native operations).
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).
## LWE ciphertexts
Although there are many kinds of ciphertexts in TFHE, all the encrypted values in TFHE-rs are mainly stored as 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.
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, 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 little random value called noise is added to the least significant bits. This noise (also called error for Learning With Errors) is crucial to the security of the ciphertext.
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.
$$plaintext = (\Delta * m) + e$$
![](../\_static/lwe.png)
![](../_static/lwe.png)
To go from a **plaintext** to a **ciphertext,** one must encrypt the plaintext using a secret key.
An LWE secret key is a list of `n` random integers: $$S = (s_0, ..., s_n)$$. $$n$$ is called the $$LweDimension$$
An LWE secret key is a list of `n` random integers: $$S = (s_0, ..., s_{n-1})$$. $$n$$ is called the $$LweDimension$$
A LWE ciphertext is composed of two parts:
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 an operation, such as ciphertext addition) is a list of `n` uniformly random values.
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 body is computed as follows:
@@ -40,12 +40,12 @@ Now that the encryption scheme is defined, let's review the example of the addit
To add two ciphertexts, we must add their $mask$ and $body$:
$$
ct_0 = (a_{0}, ..., a_{n}, b) \\ ct_1 = (a_{1}^{'}, ..., a_{n}^{'}, b^{'}) \\ ct_{2} = ct_0 + ct_1 \\ ct_{2} = (a_{0} + a_{0}^{'}, ..., a_{n} + a_{n}^{'}, b + b^{'})\\ b + b^{'} = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext + (\sum_{i = 0}^{n-1}{a_i^{'} * s_i}) + plaintext^{'}\\ b + b^{'} = (\sum_{i = 0}^{n-1}{(a_i + a_i^{'})* s_i}) + \Delta m + \Delta m^{'} + e + e^{'}\\
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. The addition 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 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).
## Understanding noise and padding
## Programmable Bootstrapping, noise management and carry bits
In FHE, there are two types of operations that can be applied to ciphertexts:
@@ -54,7 +54,7 @@ In FHE, there are two types of operations that can be applied to ciphertexts:
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 the message. The rest of the operations are called leveled because they do not need bootstrapping operations and are usually really fast as a result.
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.
The following sections explain the concept of noise and padding in ciphertexts.
@@ -62,25 +62,30 @@ The following sections explain the concept of noise and padding in ciphertexts.
For it to be secure, LWE requires random noise to be added to the message at encryption time.
In TFHE, this random noise is drawn from a Centered Normal Distribution, parameterized by a standard deviation. This standard deviation is a security parameter. With all other security parameters set, the more secure the encryption, the larger the standard deviation.
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.
In `TFHE-rs`, noise is encoded in the least significant bits of the plaintexts. Each leveled computation increases the noise. If too many computations are performed, the noise will eventually overflow onto the significant data bits of the message and lead to an incorrect result.
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.
The figure below illustrates this problem in case of an addition, where an extra bit of noise is incurred as a result.
The figure below illustrates this problem in the case of an addition, where an extra bit of noise is incurred as a result.
![Noise overtaking the plaintexts after homomorphic addition. Most significant bits are on the left.](../\_static/fig7.png)
![Noise overtaking the plaintexts after homomorphic addition. Most significant bits are on the left.](../_static/fig7.png)
TFHE-rs offers the ability to automatically manage noise by performing bootstrapping operations to reset the noise.
`TFHE-rs` offers the ability to automatically manage noise by performing bootstrapping operations to reset the noise.
### Padding.
Since encoded values have a fixed precision, operating on them can produce results that are outside 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.
### 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 boostrapping 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).
### Carry.
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.
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.
![](../\_static/fig6.png)
![](../_static/fig6.png)
If you would like to know more about TFHE, you can find more information in our [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
### Security.
@@ -88,6 +93,6 @@ By default, the cryptographic parameters provided by `TFHE-rs` ensure at least 1
For all sets of parameters, the error probability when computing a univariate function over one ciphertext is $$2^{-40}$$. Note that univariate functions might be performed when arithmetic functions are computed (i.e., the multiplication of two ciphertexts).
### Public key encryption.
### Classical public key encryption.
In 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).
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,458 +0,0 @@
# Operations
The structure and operations related to all types (ì.e., Booleans, shortint and integer) are described in this section.
## Booleans
Native homomorphic Booleans support common Boolean operations.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |
| [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 |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
## ShortInt
Native small homomorphic integer types (e.g., FheUint3 or FheUint4) easily compute various operations. In general, computing over encrypted data is as easy as computing over clear data, since the same operation symbol is used. The addition between two ciphertexts is done using the symbol `+` between two FheUint. Many operations can be computed between a clear value (i.e. a scalar) and a ciphertext.
In Rust native types, any operation is modular. In Rust, `u8`, computations are done modulus 2^8. The similar idea is applied for FheUintX, where operations are done modulus 2^X. In the type FheUint3, operations are done modulo 8.
### Arithmetic operations.
Small homomorphic integer types support all common arithmetic operations, meaning `+`, `-`, `x`, `/`, `mod`.
The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns 0.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------- | ------ | ------ |
| [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 |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let clear_c = 2;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
let mut c = FheUint3::try_encrypt(clear_c, &keys)?;
a = a * &b; // Clear equivalent computations: 7 * 3 mod 8 = 5
b = &b + &c; // Clear equivalent computations: 3 + 2 mod 8 = 5
b = b - 5; // Clear equivalent computations: 5 - 5 mod 8 = 0
let dec_a = a.decrypt(&keys);
let dec_b = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, (clear_a * clear_b) % 8);
assert_eq!(dec_b, ((clear_b + clear_c) - 5) % 8);
Ok(())
}
```
### Bitwise operations.
Small homomorphic integer types support some bitwise operations.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |
| [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 |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
a = a ^ &b;
b = b ^ &a;
a = a ^ &b;
let dec_a = a.decrypt(&keys);
let dec_b = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, clear_b);
assert_eq!(dec_b, clear_a);
Ok(())
}
```
### Comparisons.
Small homomorphic integer types 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. Rust expects to have a Boolean as an output, whereas a ciphertext encrypted result is returned when using homomorphic types.
You will need to use the different methods instead of using symbols for the comparisons. These methods follow the same naming conventions as the two standard Rust traits:
* [PartialOrd](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html)
* [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
assert_eq!(a.gt(&b).decrypt(&keys) != 0, true);
assert_eq!(b.le(&a).decrypt(&keys) != 0, true);
Ok(())
}
```
### Univariate function evaluations.
The shortint type also supports the computation of univariate functions, which deep down uses TFHE's _programmable bootstrapping_.
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint4};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint4().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let pow_5 = |value: u64| {
value.pow(5) % FheUint4::MODULUS as u64
};
let clear_a = 12;
let a = FheUint4::try_encrypt(12, &keys)?;
let c = a.map(pow_5);
let decrypted = c.decrypt(&keys);
assert_eq!(decrypted, pow_5(clear_a) as u8);
Ok(())
}
```
### Bivariate function evaluations.
Using the shortint type allows you to evaluate bivariate functions (i.e., functions that takes two ciphertexts as input).
A simple code example:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint2};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint2().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 1;
let clear_b = 3;
let a = FheUint2::try_encrypt(clear_a, &keys)?;
let b = FheUint2::try_encrypt(clear_b, &keys)?;
let c = a.bivariate_function(&b, std::cmp::max);
let decrypted = c.decrypt(&keys);
assert_eq!(decrypted, std::cmp::max(clear_a, clear_b) as u8);
Ok(())
}
```
## Integer
In TFHE-rs, integers are used to encrypt any messages larger than 4 bits. All supported operations are listed below.
### Arithmetic operations.
Homomorphic integer types support arithmetic operations.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------- | ------ | ------ |
| [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 |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 15_u64;
let clear_b = 27_u64;
let clear_c = 43_u64;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let mut c = FheUint8::try_encrypt(clear_c, &keys)?;
a = a * &b; // Clear equivalent computations: 15 * 27 mod 256 = 149
b = &b + &c; // Clear equivalent computations: 27 + 43 mod 256 = 70
b = b - 76u8; // Clear equivalent computations: 70 - 76 mod 256 = 250
let dec_a: u8 = a.decrypt(&keys);
let dec_b: u8 = b.decrypt(&keys);
assert_eq!(dec_a, ((clear_a * clear_b) % 256_u64) as u8);
assert_eq!(dec_b, (((clear_b + clear_c).wrapping_sub(76_u64)) % 256_u64) as u8);
Ok(())
}
```
### Bitwise operations.
Homomorphic integer types support some bitwise operations.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |
| [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 |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 164;
let clear_b = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
a = a ^ &b;
b = b ^ &a;
a = a ^ &b;
let dec_a: u8 = a.decrypt(&keys);
let dec_b: u8 = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, clear_b);
assert_eq!(dec_b, clear_a);
Ok(())
}
```
### Comparisons.
Homomorphic integers support comparison operations. Since Rust does not allow the overloading of these operations, a simple function has been associated to each one.
The list of supported operations is:
| name | symbol | type |
| --------------------- | ------ | ------ |
| Greater than | `gt` | Binary |
| Greater or equal than | `ge` | Binary |
| Lower than | `lt` | Binary |
| Lower or equal than | `le` | Binary |
| Equal | `eq` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a:u8 = 164;
let clear_b:u8 = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let greater = a.gt(&b);
let greater_or_equal = a.ge(&b);
let lower = a.lt(&b);
let lower_or_equal = a.le(&b);
let equal = a.eq(&b);
let dec_gt : u8 = greater.decrypt(&keys);
let dec_ge : u8 = greater_or_equal.decrypt(&keys);
let dec_lt : u8 = lower.decrypt(&keys);
let dec_le : u8 = lower_or_equal.decrypt(&keys);
let dec_eq : u8 = equal.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_gt, (clear_a > clear_b ) as u8);
assert_eq!(dec_ge, (clear_a >= clear_b) as u8);
assert_eq!(dec_lt, (clear_a < clear_b ) as u8);
assert_eq!(dec_le, (clear_a <= clear_b) as u8);
assert_eq!(dec_eq, (clear_a == clear_b) as u8);
Ok(())
}
```
### Min/Max.
Homomorphic integers support the min/max operations.
| name | symbol | type |
| ---- | ------ | ------ |
| Min | `min` | Binary |
| Max | `max` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a:u8 = 164;
let clear_b:u8 = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let min = a.min(&b);
let max = a.max(&b);
let dec_min : u8 = min.decrypt(&keys);
let dec_max : u8 = max.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_min, u8::min(clear_a, clear_b));
assert_eq!(dec_max, u8::max(clear_a, clear_b));
Ok(())
}
```
### Casting.
Casting between integer types is possible via the `cast_from` associated function
of `cast_into` method.
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheUint32, FheUint16};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
// Casting requires server_key to set
// (encryptions/decryptions do not need server_key to be set)
set_server_key(server_key);
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Downcasting
let a: FheUint8 = a.cast_into();
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, clear as u8);
// Upcasting
let a: FheUint32 = a.cast_into();
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, (clear as u8) as u32);
}
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Upcasting
let a = FheUint32::cast_from(a);
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, clear as u32);
// Downcasting
let a = FheUint8::cast_from(a);
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, (clear as u32) as u8);
}
Ok(())
}
```

View File

@@ -1,12 +1,8 @@
# High-Level API
\#Using the High-level C API
# 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.
## First steps using TFHE-rs C API
### Setting-up TFHE-rs C API for use in a C program.
## Setting-up TFHE-rs C API for use in a C program.
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
@@ -54,7 +50,7 @@ endif()
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
```
### Commented code of a uint128 subtraction using `TFHE-rs C API`.
## Commented code of a uint128 subtraction using `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.

View File

@@ -0,0 +1,107 @@
# 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.
In the library, entities that can be compressed are prefixed by `Compressed`. For instance, the type of a compressed `FheUint256` is `CompressedFheUint256`.
## Compressed ciphertexts
This example shows how to compress a ciphertext encypting messages over 16 bits.
```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key, CompressedFheUint16};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, _) = generate_keys(config);
let clear = 12_837u16;
let compressed = CompressedFheUint16::try_encrypt(clear, &client_key).unwrap();
let decompressed = compressed.decompress();
let clear_decompressed: u16 = decompressed.decrypt(&client_key);
assert_eq!(clear_decompressed, clear);
}
```
## Compressed server keys
This example shows how to compress the server keys.
```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, CompressedServerKey, ClientKey};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let cks = ClientKey::generate(config);
let compressed_sks = CompressedServerKey::new(&cks);
let sks = compressed_sks.decompress();
set_server_key(sks);
let clear_a = 12u8;
let a = FheUint8::try_encrypt(clear_a, &cks).unwrap();
let c = a + 234u8;
let decrypted: u8 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.wrapping_add(234));
}
```
## Compressed 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 adressed in future releases.
{% endhint %}
```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, CompressedPublicKey};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, _) = generate_keys(config);
let public_key = CompressedPublicKey::new(&client_key);
let a = FheUint8::try_encrypt(213u8, &public_key).unwrap();
let clear: u8 = a.decrypt(&client_key);
assert_eq!(clear, 213u8);
}
```
## Compressed compact public key
This example shows how to use compressed compact public keys.
```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, CompressedCompactPublicKey};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS,
None,
)
.build();
let (client_key, _) = generate_keys(config);
let public_key_compressed = CompressedCompactPublicKey::new(&client_key);
let public_key = public_key_compressed.decompress();
let a = FheUint8::try_encrypt(255u8, &public_key).unwrap();
let clear: u8 = a.decrypt(&client_key);
assert_eq!(clear, 255u8);
}
```

View File

@@ -1,8 +1,108 @@
# Tutorial
## Using the JS on WASM API
Welcome to this TFHE-rs JS on WASM API tutorial.
TFHE-rs supports WASM for the client api, that is, it supports key generation, encryption, decryption but not doing actual 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
In all cases, the core of the API is same, only few initialization function
changes.
## Example
### nodejs
```javascript
const {
init_panic_hook,
ShortintParametersName,
ShortintParameters,
TfheClientKey,
TfheCompactPublicKey,
TfheCompressedServerKey,
TfheConfigBuilder,
CompactFheUint32List
} = require("./pkg/tfhe.js");
function fhe_uint32_example() {
// Makes it so that if a rust thread panics,
// the error message will be displayed in the console
init_panic_hook();
const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK);
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers()
.build();
let clientKey = TfheClientKey.generate(config);
let compressedServerKey = TfheCompressedServerKey.new(clientKey);
let publicKey = TfheCompactPublicKey.new(clientKey);
let values = [0, 1, 2394, U32_MAX];
let compact_list = CompactFheUint32List.encrypt_with_compact_public_key(values, publicKey);
let serialized_list = compact_list.serialize();
let deserialized_list = CompactFheUint32List.deserialize(serialized_list);
let encrypted_list = deserialized_list.expand();
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}
}
```
### 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`
#### Example
```js
import init, {
initThreadPool, // only available with parallelism
init_panic_hook,
ShortintParametersName,
ShortintParameters,
TfheClientKey,
TfhePublicKey,
} from "./pkg/tfhe.js";
async function example() {
await init()
await initThreadPool(navigator.hardwareConcurrency);
await init_panic_hook();
const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK);
// ....
}
```
## Compiling the WASM API
The TFHE-rs repo has a Makefile that contains targets for each of the 3 possible variants of the API:
- `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
The compiled WASM package will be 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`.
{% 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.

View File

@@ -0,0 +1,68 @@
# Parallelized Programmable Bootstrapping
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.
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.
In what follows, an example on how to use the parallelized bootstrapping:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
tfhe::shortint::parameters::PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
None,
)
.build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 673;
let clear_b = 6;
let a = FheUint32::try_encrypt(clear_a, &keys)?;
let b = FheUint32::try_encrypt(clear_b, &keys)?;
let c = &a >> &b;
let decrypted: u32 = c.decrypt(&keys);
assert_eq!(decrypted, clear_a >> clear_b);
Ok(())
}
```
# 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:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
tfhe::shortint::parameters::PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS.with_deterministic_execution(),
None,
)
.build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 673;
let clear_b = 6;
let a = FheUint32::try_encrypt(clear_a, &keys)?;
let b = FheUint32::try_encrypt(clear_b, &keys)?;
let c = &a >> &b;
let decrypted: u32 = c.decrypt(&keys);
assert_eq!(decrypted, clear_a >> clear_b);
Ok(())
}
```

View File

@@ -0,0 +1,52 @@
# 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 ma y encryption of zeroes. 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.
Note that public keys can be [compressed](./compress.md)
## classical public key
This example shows how to use public keys.
```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, PublicKey};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, _) = generate_keys(config);
let public_key = PublicKey::new(&client_key);
let a = FheUint8::try_encrypt(255u8, &public_key).unwrap();
let clear: u8 = a.decrypt(&client_key);
assert_eq!(clear, 255u8);
}
```
## 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.
```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, CompactPublicKey};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS,
None,
)
.build();
let (client_key, _) = generate_keys(config);
let public_key = CompactPublicKey::new(&client_key);
let a = FheUint8::try_encrypt(255u8, &public_key).unwrap();
let clear: u8 = a.decrypt(&client_key);
assert_eq!(clear, 255u8);
}
```

View File

@@ -0,0 +1,53 @@
# Using TFHE-rs with nightly toolchain.
Install the needed Rust toolchain:
```shell
rustup toolchain install nightly
```
Then, you can either:
* Manually specify the toolchain to use in each of the cargo commands:
```shell
cargo +nightly build
cargo +nightly test
```
* Or override the toolchain to use for the current project:
```shell
rustup override set nightly
# cargo will use the `nightly` toolchain.
cargo build
```
To check the toolchain that Cargo will use by default, you can use the following command:
```shell
rustup show
```
# Choosing your features
`TFHE-rs` exposes different `cargo features` to customize the types and features used.
## Homomorphic 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 unsigned integers |
| Integers | `integer` | Arbitrary-sized unsigned integers |
## 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 explicitly chosen as a feature. This requires to use the [nightly toolchain](#using-tfhe-rs-with-nightly-toolchain) along with the feature `nightly-avx512`.
```shell
cargo +nightly build --features=nightly-avx512
```

View File

@@ -0,0 +1,61 @@
# Trival Ciphertext
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`.
Instead of asking the client to send a real encryption of zero,
the server can do a *trivial encryption*
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, sks) = generate_keys(config);
set_server_key(sks);
let a = FheUint8::try_encrypt_trivial(234u8).unwrap();
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
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32};
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, sks) = generate_keys(config);
set_server_key(sks);
// This is going to be faster
let a = FheUint32::try_encrypt(2097152u32, &client_key).unwrap();
let shift = 1u32;
let shifted = a << shift;
let clear: u32 = shifted.decrypt(&client_key);
assert_eq!(clear, 2097152 << 1);
// This is going to be slower
let a = FheUint32::try_encrypt(2097152u32, &client_key).unwrap();
let shift = FheUint32::try_encrypt_trivial(1).unwrap();
let shifted = a << shift;
let clear: u32 = shifted.decrypt(&client_key);
assert_eq!(clear, 2097152 << 1);
```

View File

@@ -0,0 +1,155 @@
# A first complete example: FheLatinString (Integer)
The goal of this tutorial is to build a data type that represents a Latin string in FHE while implementing the `to_lower` and `to_upper` functions.
The allowed characters in a Latin string are:
* Uppercase letters: `A B C D E F G H I J K L M N O P Q R S T U V W X Y Z`
* Lowercase letters: `a b c d e f g h i j k l m n o p q r s t u v w x y z`
For the code point of the letters,`ascii` codes are used:
* The uppercase letters are in the range \[65, 90]
* The lowercase letters are in the range \[97, 122]
`lower_case` = `upper_case` + 32 <=> `upper_case` = `lower_case` - 32
For this type, the `FheUint8` type is used.
## Types and methods.
This type will hold the encrypted characters as a `Vec<FheUint8>`, as well as the encrypted constant `32` to implement the functions that change the case.
In the `FheLatinString::encrypt` function, some data validation is done:
* The input string can only contain ascii letters (no digit, no special characters).
* The input string cannot mix lower and upper case letters.
These two points are to work around a limitation of FHE. It is not possible to create branches, meaning the function cannot use conditional statements. Checking if the 'char' is an uppercase letter to modify it to a lowercase one cannot be done, like in the example below.
```rust
fn to_lower(string: &String) -> String {
let mut result = String::with_capacity(string.len());
for char in string.chars() {
if char.is_uppercase() {
result.extend(char.to_lowercase().to_string().chars())
}
}
result
}
```
With these preconditions checked, implementing `to_lower` and `to_upper` is rather simple.
To use the `FheUint8` type, the `integer` feature must be activated:
```toml
# Cargo.toml
[dependencies]
# Default configuration for x86 Unix machines:
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
```rust
use tfhe::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
use tfhe::prelude::*;
struct FheLatinString{
bytes: Vec<FheUint8>,
// Constant used to switch lower case <=> upper case
cst: FheUint8,
}
impl FheLatinString {
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
assert!(
string.chars().all(|char| char.is_ascii_alphabetic()),
"The input string must only contain ascii letters"
);
let has_mixed_case = string.as_bytes().windows(2).any(|window| {
let first = char::from(*window.first().unwrap());
let second = char::from(*window.last().unwrap());
(first.is_ascii_lowercase() && second.is_ascii_uppercase())
|| (first.is_ascii_uppercase() && second.is_ascii_lowercase())
});
assert!(
!has_mixed_case,
"The input string cannot mix lower case and upper case letters"
);
let fhe_bytes = string
.bytes()
.map(|b| FheUint8::encrypt(b, client_key))
.collect::<Vec<FheUint8>>();
let cst = FheUint8::encrypt(32u8, client_key);
Self {
bytes: fhe_bytes,
cst,
}
}
fn decrypt(&self, client_key: &ClientKey) -> String {
let ascii_bytes = self
.bytes
.iter()
.map(|fhe_b| fhe_b.decrypt(client_key))
.collect::<Vec<u8>>();
String::from_utf8(ascii_bytes).unwrap()
}
fn to_upper(&self) -> Self {
Self {
bytes: self
.bytes
.iter()
.map(|b| b - &self.cst)
.collect::<Vec<FheUint8>>(),
cst: self.cst.clone(),
}
}
fn to_lower(&self) -> Self {
Self {
bytes: self
.bytes
.iter()
.map(|b| b + &self.cst)
.collect::<Vec<FheUint8>>(),
cst: self.cst.clone(),
}
}
}
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let my_string = FheLatinString::encrypt("zama", &client_key);
let verif_string = my_string.decrypt(&client_key);
println!("{}", verif_string);
let my_string_upper = my_string.to_upper();
let verif_string = my_string_upper.decrypt(&client_key);
println!("{}", verif_string);
assert_eq!(verif_string, "ZAMA");
let my_string_lower = my_string_upper.to_lower();
let verif_string = my_string_lower.decrypt(&client_key);
println!("{}", verif_string);
assert_eq!(verif_string, "zama");
}
```

View File

@@ -1,301 +1,3 @@
# Tutorial
## Quick Start
The basic steps for using the high-level API of TFHE-rs are:
1. Importing TFHE-rs prelude;
2. Client-side: Configuring and creating keys;
3. Client-side: Encrypting data;
4. Server-side: Setting the server key;
5. Server-side: Computing over encrypted data;
6. Client-side: Decrypting data.
Here is the full example (mixing client and server parts):
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
// Client-side
let (client_key, server_key) = generate_keys(config);
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
//Server-side
set_server_key(server_key);
let result = a + b;
//Client-side
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
}
```
Default configuration for x86 Unix machines:
```toml
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
### Imports.
`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.
To make it easier, the `prelude` 'pattern' is used. All `tfhe` important traits are in a `prelude` module that you **glob import**. With this, there is no need to remember or know the traits to import.
```rust
use tfhe::prelude::*;
```
### 1. Configuring and creating keys.
The first step is the creation of the configuration. The configuration is used to declare which type you will use or not use, as well as enabling you to use custom crypto-parameters for these types for more advanced usage / testing.
Creating a configuration is done using the ConfigBuilder type.
In this example, 8-bit unsigned integers with default parameters are used. The `integers`
feature must also be enabled, as per the table on the [Getting Started page](../getting_started/installation.md).
The config is done by first creating a builder with all types deactivated. Then, the `uint8` type with default parameters is activated.
```rust
use tfhe::{ConfigBuilder, generate_keys};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
}
```
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.
### 2. Setting the server key.
The next step is to call `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.
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
}
```
### 3. Encrypting data.
Encrypting data is done via the `encrypt` associated function of the \[FheEncrypt] trait.
Types exposed by this crate implement at least one of \[FheEncrypt] or \[FheTryEncrypt] to allow enryption.
```Rust
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
```
### 4. Computation and decryption.
Computations should be as easy as normal Rust to write, thanks to operator overloading.
```Rust
let result = a + b;
```
The decryption is done by using the `decrypt` method, which comes from the \[FheDecrypt] trait.
```Rust
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
```
## A first complete example: FheLatinString (Integer)
The goal of this tutorial is to build a data type that represents a Latin string in FHE while implementing the `to_lower` and `to_upper` functions.
The allowed characters in a Latin string are:
* Uppercase letters: `A B C D E F G H I J K L M N O P Q R S T U V W X Y Z`
* Lowercase letters: `a b c d e f g h i j k l m n o p q r s t u v w x y z`
For the code point of the letters,`ascii` codes are used:
* The uppercase letters are in the range \[65, 90]
* The lowercase letters are in the range \[97, 122]
`lower_case` = `upper_case` + 32 <=> `upper_case` = `lower_case` - 32
For this type, the `FheUint8` type is used.
### Types and methods.
This type will hold the encrypted characters as a `Vec<FheUint8>`, as well as the encrypted constant `32` to implement the functions that change the case.
In the `FheLatinString::encrypt` function, some data validation is done:
* The input string can only contain ascii letters (no digit, no special characters).
* The input string cannot mix lower and upper case letters.
These two points are to work around a limitation of FHE. It is not possible to create branches, meaning the function cannot use conditional statements. Checking if the 'char' is an uppercase letter to modify it to a lowercase one cannot be done, like in the example below.
```rust
fn to_lower(string: &String) -> String {
let mut result = String::with_capacity(string.len());
for char in string.chars() {
if char.is_uppercase() {
result.extend(char.to_lowercase().to_string().chars())
}
}
result
}
```
With these preconditions checked, implementing `to_lower` and `to_upper` is rather simple.
To use the `FheUint8` type, the `integer` feature must be activated:
```toml
# Cargo.toml
[dependencies]
# Default configuration for x86 Unix machines:
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
```rust
use tfhe::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
use tfhe::prelude::*;
struct FheLatinString{
bytes: Vec<FheUint8>,
// Constant used to switch lower case <=> upper case
cst: FheUint8,
}
impl FheLatinString {
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
assert!(
string.chars().all(|char| char.is_ascii_alphabetic()),
"The input string must only contain ascii letters"
);
let has_mixed_case = string.as_bytes().windows(2).any(|window| {
let first = char::from(*window.first().unwrap());
let second = char::from(*window.last().unwrap());
(first.is_ascii_lowercase() && second.is_ascii_uppercase())
|| (first.is_ascii_uppercase() && second.is_ascii_lowercase())
});
assert!(
!has_mixed_case,
"The input string cannot mix lower case and upper case letters"
);
let fhe_bytes = string
.bytes()
.map(|b| FheUint8::encrypt(b, client_key))
.collect::<Vec<FheUint8>>();
let cst = FheUint8::encrypt(32u8, client_key);
Self {
bytes: fhe_bytes,
cst,
}
}
fn decrypt(&self, client_key: &ClientKey) -> String {
let ascii_bytes = self
.bytes
.iter()
.map(|fhe_b| fhe_b.decrypt(client_key))
.collect::<Vec<u8>>();
String::from_utf8(ascii_bytes).unwrap()
}
fn to_upper(&self) -> Self {
Self {
bytes: self
.bytes
.iter()
.map(|b| b - &self.cst)
.collect::<Vec<FheUint8>>(),
cst: self.cst.clone(),
}
}
fn to_lower(&self) -> Self {
Self {
bytes: self
.bytes
.iter()
.map(|b| b + &self.cst)
.collect::<Vec<FheUint8>>(),
cst: self.cst.clone(),
}
}
}
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let my_string = FheLatinString::encrypt("zama", &client_key);
let verif_string = my_string.decrypt(&client_key);
println!("{}", verif_string);
let my_string_upper = my_string.to_upper();
let verif_string = my_string_upper.decrypt(&client_key);
println!("{}", verif_string);
assert_eq!(verif_string, "ZAMA");
let my_string_lower = my_string_upper.to_lower();
let verif_string = my_string_lower.decrypt(&client_key);
println!("{}", verif_string);
assert_eq!(verif_string, "zama");
}
```
## A more complex example: Parity Bit (Boolean)
This example is dedicated to the building of a small function that homomorphically computes a parity bit.

View File

@@ -1,53 +1,97 @@
use doc_comment::doctest;
// readme
// README
doctest!("../../README.md", readme);
// Getting started
doctest!("../docs/getting_started/quick_start.md", quick_start);
doctest!("../docs/getting_started/operations.md", operations);
// GETTING STARTED
doctest!(
"../docs/getting_started/operations.md",
getting_started_operations
);
doctest!(
"../docs/getting_started/quick_start.md",
getting_started_quick_start
);
// Booleans
doctest!("../docs/Boolean/parameters.md", booleans_parameters);
doctest!("../docs/Boolean/operations.md", booleans_operations);
doctest!("../docs/Boolean/serialization.md", booleans_serialization);
doctest!("../docs/Boolean/tutorial.md", booleans_tutorial);
// HOW TO
doctest!("../docs/how_to/compress.md", how_to_compress);
doctest!(
"../docs/how_to/parallelized_pbs.md",
how_to_parallelized_pbs
);
doctest!("../docs/how_to/public_key.md", how_to_public_key);
doctest!("../docs/how_to/serialization.md", how_to_serialize);
doctest!(
"../docs/how_to/trivial_ciphertext.md",
how_to_trivial_ciphertext
);
// Shortint
doctest!("../docs/shortint/parameters.md", shortint_parameters);
doctest!("../docs/shortint/serialization.md", shortint_serialization);
doctest!("../docs/shortint/tutorial.md", shortint_tutorial);
doctest!("../docs/shortint/operations.md", shortint_operations);
//FINE GRAINED API
doctest!(
"../docs/fine_grained_api/quick_start.md",
fine_grained_api_quick_start
);
// fine_grained_api/Boolean
doctest!(
"../docs/fine_grained_api/Boolean/operations.md",
booleans_operations
);
doctest!(
"../docs/fine_grained_api/Boolean/parameters.md",
booleans_parameters
);
doctest!(
"../docs/fine_grained_api/Boolean/serialization.md",
booleans_serialization
);
doctest!(
"../docs/fine_grained_api/Boolean/tutorial.md",
booleans_tutorial
);
// fine_grained_api/shortint
doctest!(
"../docs/fine_grained_api/shortint/operations.md",
shortint_operations
);
doctest!(
"../docs/fine_grained_api/shortint/parameters.md",
shortint_parameters
);
doctest!(
"../docs/fine_grained_api/shortint/serialization.md",
shortint_serialization
);
doctest!(
"../docs/fine_grained_api/shortint/tutorial.md",
shortint_tutorial
);
// fine_grained_api/integer
doctest!(
"../docs/fine_grained_api/integer/operations.md",
integer_operations
);
doctest!(
"../docs/fine_grained_api/integer/serialization.md",
integer_serialization_tuto
);
doctest!(
"../docs/fine_grained_api/integer/tutorial.md",
integer_first_circuit
);
// core_crypto
doctest!(
"../docs/core_crypto/presentation.md",
core_crypto_presentation
);
doctest!("../docs/core_crypto/tutorial.md", core_crypto_turorial);
doctest!("../docs/core_crypto/tutorial.md", core_crypto_tutorial);
// Integer
doctest!("../docs/integer/tutorial.md", integer_first_circuit);
doctest!("../docs/integer/operations.md", integer_operations);
// Tutorials
doctest!(
"../docs/integer/serialization.md",
integer_serialization_tuto
);
// high_level_api
doctest!(
"../docs/high_level_api/tutorial.md",
high_level_api_first_circuit
);
doctest!(
"../docs/high_level_api/operations.md",
high_level_api_operations
);
doctest!(
"../docs/high_level_api/serialization.md",
high_level_api_serialization_tuto
);
doctest!(
"../docs/high_level_api/tutorial.md",
high_level_api_tutorial
"../docs/tutorials/latin_fhe_string.md",
latin_fhe_string_tutorial
);
doctest!("../docs/tutorials/parity_bit.md", parity_bit_tutorial);