mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-11 07:38:08 -05:00
Compare commits
18 Commits
main
...
tfhe-rs-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98d31d8f17 | ||
|
|
721ca2fed6 | ||
|
|
e95399306e | ||
|
|
f3e3c2d499 | ||
|
|
626074a16b | ||
|
|
b4fa072d2c | ||
|
|
0377a74103 | ||
|
|
8690c625fc | ||
|
|
ce81c4983e | ||
|
|
c271265749 | ||
|
|
f0e5e9fce9 | ||
|
|
65dd420ed8 | ||
|
|
478716db7e | ||
|
|
5392070c2e | ||
|
|
1bb7526157 | ||
|
|
e99676f294 | ||
|
|
ebc186e7f5 | ||
|
|
5652d417c8 |
35
.github/workflows/aws_tfhe_fast_tests.yml
vendored
35
.github/workflows/aws_tfhe_fast_tests.yml
vendored
@@ -11,26 +11,17 @@ env:
|
||||
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
|
||||
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
IS_PULL_REQUEST: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
IS_PULL_REQUEST: ${{ github.event_name == 'pull_request_target' }}
|
||||
REF: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab as an alternative.
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
check-user-permission:
|
||||
if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target'
|
||||
uses: ./.github/workflows/check_triggering_actor.yml
|
||||
secrets:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
should-run:
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-user-permission
|
||||
if: github.event_name != 'pull_request_target' ||
|
||||
needs.check-user-permission.result == 'success'
|
||||
permissions:
|
||||
pull-requests: write
|
||||
outputs:
|
||||
@@ -64,8 +55,8 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.FHE_ACTIONS_TOKEN }}
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
token: ${{ secrets.REPO_CHECKOUT_TOKEN }}
|
||||
ref: ${{ env.REF }}
|
||||
|
||||
- name: Check for file changes
|
||||
id: changed-files
|
||||
@@ -133,11 +124,17 @@ jobs:
|
||||
run: |
|
||||
echo "any_changed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
check-user-permission:
|
||||
needs: should-run
|
||||
uses: ./.github/workflows/check_triggering_actor.yml
|
||||
secrets:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
setup-instance:
|
||||
name: Setup instance (fast-tests)
|
||||
if: github.event_name != 'pull_request' ||
|
||||
if: github.event_name != 'pull_request_target' ||
|
||||
needs.should-run.outputs.any_file_changed == 'true'
|
||||
needs: should-run
|
||||
needs: [ should-run, check-user-permission ]
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
runner-name: ${{ steps.start-instance.outputs.label }}
|
||||
@@ -155,8 +152,8 @@ jobs:
|
||||
|
||||
fast-tests:
|
||||
name: Fast CPU tests
|
||||
if: github.event_name != 'pull_request' ||
|
||||
(github.event_name == 'pull_request' && needs.setup-instance.result != 'skipped')
|
||||
if: github.event_name != 'pull_request_target' ||
|
||||
(github.event_name == 'pull_request_target' && needs.setup-instance.result != 'skipped')
|
||||
needs: [ should-run, setup-instance ]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}_${{ github.head_ref || github.ref }}
|
||||
@@ -167,8 +164,8 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
persist-credentials: 'false'
|
||||
token: ${{ secrets.FHE_ACTIONS_TOKEN }}
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
token: ${{ secrets.REPO_CHECKOUT_TOKEN }}
|
||||
ref: ${{ env.REF }}
|
||||
|
||||
- name: Install latest stable
|
||||
uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203
|
||||
|
||||
2
Makefile
2
Makefile
@@ -824,7 +824,7 @@ test_strings: install_rs_build_toolchain
|
||||
.PHONY: test_user_doc # Run tests from the .md documentation
|
||||
test_user_doc: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) --doc \
|
||||
--features=boolean,shortint,integer,internal-keycache,pbs-stats,zk-pok \
|
||||
--features=boolean,shortint,integer,internal-keycache,pbs-stats,zk-pok,strings \
|
||||
-p $(TFHE_SPEC) \
|
||||
-- test_user_docs::
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tfhe"
|
||||
version = "0.11.0"
|
||||
version = "0.11.2"
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
|
||||
@@ -53,7 +53,9 @@ strum = { version = "0.26", features = ["derive"] }
|
||||
cbindgen = { version = "0.26.0", optional = true }
|
||||
|
||||
[dependencies]
|
||||
tfhe-csprng = { version = "0.5.0", path = "../tfhe-csprng", features = ["parallel"] }
|
||||
tfhe-csprng = { version = "0.5.0", path = "../tfhe-csprng", features = [
|
||||
"parallel",
|
||||
] }
|
||||
serde = { workspace = true, features = ["default", "derive"] }
|
||||
rayon = { workspace = true }
|
||||
bincode = "1.3.3"
|
||||
@@ -140,7 +142,15 @@ split-linked-modules = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
# TODO: manage builds for docs.rs based on their documentation https://docs.rs/about
|
||||
features = ["boolean", "shortint", "integer", "gpu", "zk-pok", "software-prng"]
|
||||
features = [
|
||||
"boolean",
|
||||
"shortint",
|
||||
"integer",
|
||||
"gpu",
|
||||
"zk-pok",
|
||||
"software-prng",
|
||||
"strings",
|
||||
]
|
||||
rustdoc-args = ["--html-in-header", "katex-header.html"]
|
||||
|
||||
###########
|
||||
|
||||
@@ -48,7 +48,7 @@ Take a deep dive into TFHE-rs, exploring APIs from the highest to the lowest lev
|
||||
Ask technical questions and discuss with the community. Our team of experts usually answers within 24 hours during working days.
|
||||
|
||||
* [Community forum](https://community.zama.ai/)
|
||||
* [Discord channel](https://discord.com/invite/fhe-org)
|
||||
* [Discord channel](https://discord.com/invite/zama)
|
||||
|
||||
### Developers
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
* [Trivial ciphertexts](guides/trivial\_ciphertext.md)
|
||||
* [PBS statistics](guides/pbs-stats.md)
|
||||
* [Array](guides/array.md)
|
||||
* [Strings](guides/strings.md)
|
||||
|
||||
## Tutorials
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ fn main() {
|
||||
|
||||
let mut buffer = vec![];
|
||||
|
||||
// The last argument is the max allowed size for the serialized buffer
|
||||
// The last argument is the max allowed size for the serialized buffer
|
||||
safe_serialize(&server_key, &mut buffer, 1 << 30).unwrap();
|
||||
|
||||
let _server_key_deser: ServerKey =
|
||||
@@ -158,7 +158,7 @@ In the following example, we use [bincode](https://crates.io/crates/bincode) for
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.11.0", features = ["integer"] }
|
||||
tfhe = { version = "0.11.2", features = ["integer"] }
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ The following tables benchmark the execution time of some operation sets using `
|
||||
|
||||
The next table shows the operation timings on CPU when all inputs are encrypted
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1Z2NZvWEkDnbHPYE4Su0Oh2Zz1VBnT9dWbo3E29-LcDg/edit?usp=sharing" %}
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1b_-72ArnSdaqfr-gJOnMmVdcBokYZohnylO4LUj2PMw/edit?usp=sharing" %}
|
||||
|
||||
The next table shows the operation timings on CPU when the left input is encrypted and the right is a clear scalar of the same size:
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1NGPnuBhRasES9Ghaij4ixJJTpXVMqDzbqMniX-qIMGc/edit?usp=sharing" %}
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1m3tjCi_2GSIHop2zZLAtVbhdDn5wqTGd2lOA3CcJe-U/edit?usp=sharing" %}
|
||||
|
||||
All timings are based on parallelized Radix-based integer operations where each block is encrypted using the default parameters `PARAM_MESSAGE_2_CARRY_2_KS_PBS`. To ensure predictable timings, we perform operations in the `default` mode, which ensures that the input and output encoding are similar (i.e., the carries are always emptied).
|
||||
|
||||
@@ -28,7 +28,9 @@ You can minimize operational costs by selecting from 'unchecked', 'checked', or
|
||||
|
||||
The next table shows the execution time of a keyswitch followed by a programmable bootstrapping depending on the precision of the input message. The associated parameter set is given. The configuration is Concrete FFT + AVX-512.
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1OdZrsk0dHTWSLLvstkpiv0u5G5tE0mCqItTb7WixGdg/edit?usp=sharing" %}
|
||||
Note that these benchmarks use Gaussian parameters.
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1o6MWpbzbYhDs3Pnoq-2hlNEgO9G8wGR5niW-OOZ6c_4/edit?usp=sharing" %}
|
||||
|
||||
## Reproducing TFHE-rs benchmarks
|
||||
|
||||
|
||||
@@ -8,26 +8,28 @@ All GPU benchmarks presented here were obtained on H100 GPUs, and rely on the mu
|
||||
Below come the results for the execution on a single H100.
|
||||
The following table shows the performance when the inputs of the benchmarked operation are encrypted:
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1dhNYXm7oY0l2qjX3dNpSZKjIBJElkEZtPDIWHZ4FA_A/edit?usp=sharing" %}
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1xGWykMa8fZ7RWUjkCl-52FJ-BNge8cB-5CSHrVZ6XRo/edit?usp=sharing" %}
|
||||
|
||||
The following table shows the performance when the left input of the benchmarked operation is encrypted and the other is a clear scalar of the same size:
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1wtnFnOwHrSOvfTWluUEaDoTULyveseVl1ZsYo3AOFKk/edit?usp=sharing" %}
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1MZfE9c-cQw3yAP55tu0i8uLl4lTAiH9zW3gRFp0ve7s/edit?usp=sharing" %}
|
||||
|
||||
## 2xH100
|
||||
|
||||
Below come the results for the execution on two H100's.
|
||||
The following table shows the performance when the inputs of the benchmarked operation are encrypted:
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1_2AUeu3ua8_PXxMfeJCh-pp6b9e529PGVEYUuZRAThg/edit?usp=sharing" %}
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1bcL0wgFk-cfR4asGSCWDFt7JaqDYJT-l4pH58A-yBkc/edit?usp=sharing" %}
|
||||
|
||||
|
||||
The following table shows the performance when the left input of the benchmarked operation is encrypted and the other is a clear scalar of the same size:
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1nLPt_m1MbkSdhMop0iKDnSN_c605l_JdMpK5JC90N_Q/edit?usp=sharing" %}
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1_8VIoStixns22lQq_RBSjVm-0iFHjJpntQTrvEHZpSg/edit?usp=sharing" %}
|
||||
|
||||
## Programmable bootstrapping
|
||||
|
||||
The next table shows the execution time of a keyswitch followed by a programmable bootstrapping depending on the precision of the input message. The associated parameter set is given.
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/11JfbPxJ8XMMfob4AZIWhDSglTO9X_YX8R7dNQK73uuk/edit?usp=sharing" %}
|
||||
Note that these benchmarks use Gaussian parameters.
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1KhElQ7sIsShUSVQw5bKFoP-x5BgMaWh1pZtrVAdC3T4/edit?usp=sharing" %}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This document summarizes the timings of some homomorphic operations over 64-bit encrypted integers, depending on the hardware. More details are given for [the CPU](cpu\_benchmarks.md), [the GPU](gpu\_benchmarks.md), or [zeros-knowledge proofs](zk\_proof\_benchmarks.md).
|
||||
|
||||
The cryptographic parameters used for benchmarking follow a tweaked uniform (TUniform) noise distribution instead of a Gaussian. The main advantage of this distribution is to be bounded, whereas the usual Gaussian one is not. In some practical cases, this can simplify the use of homomorphic computation. See the [noise section](../security\_and\_cryptography.md#noise) of the Security and cryptography documentation page for more information on the noise distributions.
|
||||
|
||||
You can get the parameters used for benchmarks by cloning the repository and checking out the commit you want to use (starting with the v0.11.0 release) and run the following make command:
|
||||
|
||||
```console
|
||||
@@ -10,4 +12,4 @@ make print_doc_bench_parameters
|
||||
|
||||
### Operation time (ms) over FheUint 64
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1ZbgsKnFH8eKrFjy9khFeaLYnUhbSV8Xu4H6rwulo0o8/edit?usp=sharing" %}
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1OMdGSakEUbIFSEQKhAinTolJjvmPBbafi3DEe3UfzsQ/edit?usp=sharing" %}
|
||||
|
||||
@@ -4,4 +4,4 @@ This document details the performance benchmarks of [zero-knowledge proofs](../.
|
||||
|
||||
Benchmarks for the zero-knowledge proofs have been run on a `m6i.4xlarge` with 16 cores to simulate an usual client configuration. The verification are done on a `hpc7a.96xlarge` AWS instances to mimic a powerful server.
|
||||
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1llCYHCz2CyLdTwXkiqhjVzJLzxW_RqdjHxmk72m1jm4/edit?usp=sharing" %}
|
||||
{% embed url="https://docs.google.com/spreadsheets/d/1x12I7Tkdx63Q6sNllygg6urSd5KC1sj1wj4L9jWiET4/edit?usp=sharing" %}
|
||||
|
||||
@@ -7,7 +7,7 @@ This document provides instructions to set up **TFHE-rs** in your project.
|
||||
First, add **TFHE-rs** as a dependency in your `Cargo.toml`.
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.11.0", features = ["boolean", "shortint", "integer"] }
|
||||
tfhe = { version = "0.11.2", features = ["boolean", "shortint", "integer"] }
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
@@ -35,5 +35,5 @@ By default, **TFHE-rs** makes the assumption that hardware AES features are enab
|
||||
To add support for older CPU, import **TFHE-rs** with the `software-prng` feature in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.11.0", features = ["boolean", "shortint", "integer", "software-prng"] }
|
||||
tfhe = { version = "0.11.2", features = ["boolean", "shortint", "integer", "software-prng"] }
|
||||
```
|
||||
|
||||
@@ -274,39 +274,39 @@ use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt32};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Basic configuration to use homomorphic integers
|
||||
// Basic configuration to use homomorphic integers
|
||||
let config = ConfigBuilder::default().build();
|
||||
|
||||
// Key generation
|
||||
let (client_key, server_keys) = generate_keys(config);
|
||||
|
||||
let clear_a = 32i32;
|
||||
let clear_b = -45i32;
|
||||
|
||||
// Encrypting the input data using the (private) client_key
|
||||
// FheInt32: Encrypted equivalent to i32
|
||||
let encrypted_a = FheInt32::try_encrypt(clear_a, &client_key)?;
|
||||
let encrypted_b = FheInt32::try_encrypt(clear_b, &client_key)?;
|
||||
|
||||
// On the server side:
|
||||
set_server_key(server_keys);
|
||||
|
||||
// Clear equivalent computations: 32 > -45
|
||||
let encrypted_comp = &encrypted_a.gt(&encrypted_b);
|
||||
let clear_res = encrypted_comp.decrypt(&client_key);
|
||||
assert_eq!(clear_res, clear_a > clear_b);
|
||||
|
||||
// `encrypted_comp` is a FheBool, thus it encrypts a boolean value.
|
||||
// Key generation
|
||||
let (client_key, server_keys) = generate_keys(config);
|
||||
|
||||
let clear_a = 32i32;
|
||||
let clear_b = -45i32;
|
||||
|
||||
// Encrypting the input data using the (private) client_key
|
||||
// FheInt32: Encrypted equivalent to i32
|
||||
let encrypted_a = FheInt32::try_encrypt(clear_a, &client_key)?;
|
||||
let encrypted_b = FheInt32::try_encrypt(clear_b, &client_key)?;
|
||||
|
||||
// On the server side:
|
||||
set_server_key(server_keys);
|
||||
|
||||
// Clear equivalent computations: 32 > -45
|
||||
let encrypted_comp = &encrypted_a.gt(&encrypted_b);
|
||||
let clear_res = encrypted_comp.decrypt(&client_key);
|
||||
assert_eq!(clear_res, clear_a > clear_b);
|
||||
|
||||
// `encrypted_comp` is a FheBool, thus it encrypts a boolean value.
|
||||
// This acts as a condition on which the
|
||||
// `select` function can be applied on.
|
||||
// Clear equivalent computations:
|
||||
// if 32 > -45 {result = 32} else {result = -45}
|
||||
let encrypted_res = &encrypted_comp.select(&encrypted_a, &encrypted_b);
|
||||
|
||||
let clear_res: i32 = encrypted_res.decrypt(&client_key);
|
||||
assert_eq!(clear_res, clear_a);
|
||||
|
||||
Ok(())
|
||||
// `select` function can be applied on.
|
||||
// Clear equivalent computations:
|
||||
// if 32 > -45 {result = 32} else {result = -45}
|
||||
let encrypted_res = &encrypted_comp.select(&encrypted_a, &encrypted_b);
|
||||
|
||||
let clear_res: i32 = encrypted_res.decrypt(&client_key);
|
||||
assert_eq!(clear_res, clear_a);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ edition = "2021"
|
||||
Then add the following configuration to include **TFHE-rs**:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.11.0", features = ["integer"] }
|
||||
tfhe = { version = "0.11.2", features = ["integer"] }
|
||||
```
|
||||
|
||||
Your updated `Cargo.toml` file should look like this:
|
||||
@@ -71,7 +71,7 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tfhe = { version = "0.11.0", features = ["integer"] }
|
||||
tfhe = { version = "0.11.2", features = ["integer"] }
|
||||
```
|
||||
|
||||
If you are on a different platform please refer to the [installation documentation](installation.md) for configuration options of other supported platforms.
|
||||
|
||||
@@ -40,7 +40,7 @@ $$b = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext$$
|
||||
|
||||
Now that the encryption scheme is defined, let's review the example of the addition between ciphertexts to illustrate why it is slower to compute over encrypted data.
|
||||
|
||||
To add two ciphertexts, we must add their $mask$ and $body$:
|
||||
To add two ciphertexts, we must add their $$mask$$ and $$body$$:
|
||||
|
||||
$$
|
||||
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}\\
|
||||
@@ -63,7 +63,9 @@ The following sections explain the concept of noise and padding in ciphertexts.
|
||||
|
||||
To ensure security, LWE requires random noise to be added to the message during encryption.
|
||||
|
||||
TFHE scheme draws this random noise from a Centered Normal Distribution with a standard deviation parameter. The choice of standard deviation impacts the security level: increasing the standard deviation enhances security while keeping other factors constant.
|
||||
TFHE scheme draws this random noise either from:
|
||||
- A Centered Normal Distribution with a standard deviation parameter. The choice of standard deviation impacts the security level: increasing the standard deviation enhances security while keeping other factors constant.
|
||||
- A Tweaked Uniform (TUniform) Distribution with a bound parameter $$2^b$$ defined as follows: any value in the interval $$(−2^b, ... , 2^b)$$ is selected with probability $$1/2^{b+1}$$, with the two end points $$−2^b$$ and $$2^b$$ being selected with probability $$1/2^{b+2}$$. The main advantage of this distribution is to be bounded, whereas the usual Central Normal Distribution one is not. In some practical cases, this can simplify the use of homomorphic computation. The choice of the bound impacts the security level: increasing the bound enhances security while keeping other factors constant.
|
||||
|
||||
**TFHE-rs** encodes the noise in the least significant bits of each plaintext. Each leveled computation increases the value of the noise. If too many computations are performed, the noise will eventually overflow into the message bits and lead to an incorrect result.
|
||||
|
||||
@@ -93,7 +95,7 @@ For example, when adding two ciphertexts, the sum could exceed the range of eith
|
||||
|
||||
By default, the cryptographic parameters provided by **TFHE-rs** ensure at least 128 bits of security. The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
|
||||
|
||||
The default parameters for the **TFHE-rs** library are chosen considering the IND-CPA security model, and are selected with a bootstrapping failure probability fixed at p\_error = $2^{-40}$. In particular, it is assumed that the results of decrypted computations are not shared by the secret key owner with any third parties, as such an action can lead to leakage of the secret encryption key. If you are designing an application where decryptions must be shared, you will need to craft custom encryption parameters which are chosen in consideration of the IND-CPA^D security model \[1].
|
||||
The default parameters for the **TFHE-rs** library are chosen considering the IND-CPA security model, and are selected with a bootstrapping failure probability fixed at p\_error = $$2^{-64}$$. In particular, it is assumed that the results of decrypted computations are not shared by the secret key owner with any third parties, as such an action can lead to leakage of the secret encryption key. If you are designing an application where decryptions must be shared, you will need to craft custom encryption parameters which are chosen in consideration of the IND-CPA^D security model \[1].
|
||||
|
||||
\[1][ Li, Baiyu, et al. "Securing approximate homomorphic encryption using differential privacy." Annual International Cryptology Conference. Cham: Springer Nature Switzerland, 2022.](https://eprint.iacr.org/2022/816.pdf)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ The following example shows a complete workflow of working with encrypted arrays
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
tfhe = { version = "0.11.0", features = ["integer"] }
|
||||
tfhe = { version = "0.11.2", features = ["integer"] }
|
||||
```
|
||||
|
||||
```rust
|
||||
|
||||
@@ -16,7 +16,7 @@ You can load serialized data with the `unversionize` function, even in newer ver
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.11.0", features = ["integer"] }
|
||||
tfhe = { version = "0.11.2", features = ["integer"] }
|
||||
tfhe-versionable = "0.4.0"
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
@@ -35,8 +35,8 @@ let (result, overflowed) = (&a).overflowing_add(&b);
|
||||
let result: u16 = result.decrypt(&client_key);
|
||||
assert_eq!(result, u16::MAX.wrapping_add(1u16));
|
||||
assert_eq!(
|
||||
overflowed.decrypt(&client_key),
|
||||
u16::MAX.overflowing_add(1u16).1
|
||||
overflowed.decrypt(&client_key),
|
||||
u16::MAX.overflowing_add(1u16).1
|
||||
);
|
||||
assert!(overflowed.decrypt(&client_key));
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@ To use the **TFHE-rs** GPU backend in your project, add the following dependency
|
||||
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.11.0", features = ["boolean", "shortint", "integer", "gpu"] }
|
||||
tfhe = { version = "0.11.2", features = ["boolean", "shortint", "integer", "gpu"] }
|
||||
```
|
||||
|
||||
{% hint style="success" %}
|
||||
@@ -164,13 +164,7 @@ All operations follow the same syntax than the one described in [here](../gettin
|
||||
|
||||
## Multi-GPU support
|
||||
|
||||
TFHE-rs supports platforms with multiple GPUs with some restrictions at the moment:
|
||||
the platform should have NVLink support, and only GPUs that have peer access to GPU 0 via NVLink
|
||||
will be used for the computation.
|
||||
Depending on the platform, this can restrict the number of GPUs used to perform the computation.
|
||||
|
||||
There is **nothing to change in the code to execute on multiple GPUs**, when
|
||||
they are available and have peer access to GPU 0 via NVLink. To keep the API as user-friendly as possible, the configuration is automatically set, i.e., the user has no fine-grained control over the number of GPUs to be used.
|
||||
TFHE-rs supports platforms with multiple GPUs. There is **nothing to change in the code to execute on such platforms**. To keep the API as user-friendly as possible, the configuration is automatically set, i.e., the user has no fine-grained control over the number of GPUs to be used.
|
||||
|
||||
## Benchmark
|
||||
Please refer to the [GPU benchmarks](../getting_started/benchmarks/gpu_benchmarks.md) for detailed performance benchmark results.
|
||||
|
||||
97
tfhe/docs/guides/strings.md
Normal file
97
tfhe/docs/guides/strings.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Strings
|
||||
|
||||
This document explains the FheAsciiString type for handling encrypted strings in TFHE-rs.
|
||||
|
||||
TFHE-rs has supports for ASCII strings with the type FheAsciiString.
|
||||
You can enable this feature using the flag: --features=strings
|
||||
|
||||
{% hint style="warning" %}
|
||||
Strings are not yet compatible with `CompactCiphertextList` and `CompressedCiphertextList`
|
||||
{% endhint %}
|
||||
|
||||
## Supported Operations
|
||||
|
||||
A variety of common operations are supported for `FheAsciiString`. These include:
|
||||
|
||||
- **Comparisons**: `eq`, `ne`, `lt`, `le`, `gt`, `ge`, `eq_ignore_case`
|
||||
- **Case conversion**: `to_lowercase` / `to_uppercase`
|
||||
- **String checks**: `starts_with` / `ends_with` / `contains`
|
||||
- **Trimming**: `trim_start` / `trim_end` / `trim`
|
||||
- **Prefix/suffix operations**: `strip_prefix` / `strip_suffix`
|
||||
- **Search**: `find` / `rfind`
|
||||
|
||||
|
||||
When encrypting strings, you can add padding to hide the actual length of strings.
|
||||
The null character (b'\0') is used as the padding.
|
||||
Here is an example:
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
tfhe = { version = "0.11.2", features = ["integer", "strings"] }
|
||||
```
|
||||
|
||||
```rust
|
||||
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheAsciiString, FheStringLen, ClearString};
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::safe_serialization::safe_serialize;
|
||||
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::default().build();
|
||||
let (cks, sks) = generate_keys(config);
|
||||
|
||||
set_server_key(sks);
|
||||
|
||||
let r = FheAsciiString::try_encrypt("café is french for coffee", &cks);
|
||||
// As the input string is not strictly ASCII, it is not compatible
|
||||
assert!(r.is_err());
|
||||
|
||||
let string = FheAsciiString::try_encrypt("tfhe-rs", &cks).unwrap();
|
||||
// This adds 3 chars of padding to the chars of the input string
|
||||
let padded_string = FheAsciiString::try_encrypt_with_padding("tfhe-rs", 3, &cks).unwrap();
|
||||
// This makes it so the string has 10 chars (adds padding or truncates input as necessary)
|
||||
let other_string = FheAsciiString::try_encrypt_with_fixed_sized("tfhe", 10, &cks).unwrap();
|
||||
|
||||
let mut buffer1 = vec![];
|
||||
safe_serialize(&padded_string, &mut buffer1, 1 << 30).unwrap();
|
||||
let mut buffer2 = vec![];
|
||||
safe_serialize(&other_string, &mut buffer2, 1 << 30).unwrap();
|
||||
// The two strings created with padding, have the same
|
||||
// memory/disk footprint, even though the lengths are not the same
|
||||
assert_eq!(buffer1.len(), buffer2.len());
|
||||
|
||||
// When a string has no padding, its length is known in clear
|
||||
let len = string.len();
|
||||
assert!(matches!(len, FheStringLen::NoPadding(7)));
|
||||
// When a string has padding, its length is only known as an encrypted value
|
||||
let FheStringLen::Padding(encrypted_len) = padded_string.len() else {
|
||||
panic!("Expected len to be encrypted");
|
||||
};
|
||||
let padded_string_len: u16 = encrypted_len.decrypt(&cks);
|
||||
assert_eq!(padded_string_len, 7); // Note padding chars are not counted
|
||||
// The enum resulting of a len() / is_empty() call can be transformed
|
||||
// to a FheUint16 using `into_ciphertext`
|
||||
assert!(string.len().into_ciphertext().is_trivial());
|
||||
assert!(!padded_string.len().into_ciphertext().is_trivial());
|
||||
let other_string_len: u16 = other_string.len().into_ciphertext().decrypt(&cks);
|
||||
assert_eq!(other_string_len, 4);
|
||||
|
||||
// Padded and un-padded strings are equal if the content is
|
||||
assert!(padded_string.eq(&string).decrypt(&cks));
|
||||
|
||||
let prefix = ClearString::new("tfhe".to_string());
|
||||
let (stripped_string, has_been_stripped) = string.strip_prefix(&prefix);
|
||||
// Notice that stripping, makes the string as being considered as padded
|
||||
// as it is not possible to homomorphically remove chars
|
||||
let FheStringLen::Padding(encrypted_len) = stripped_string.len() else {
|
||||
panic!("Expected len to be encrypted");
|
||||
};
|
||||
let stripped_string_len: u16 = encrypted_len.decrypt(&cks);
|
||||
assert_eq!(stripped_string_len, 3);
|
||||
let decrypted = stripped_string.decrypt(&cks);
|
||||
assert_eq!(decrypted, "-rs");
|
||||
assert!(has_been_stripped.decrypt(&cks));
|
||||
}
|
||||
```
|
||||
@@ -8,10 +8,14 @@ This document explains how to implement the zero-knowledge proofs function for c
|
||||
You can enable this feature using the flag: `--features=zk-pok` when building **TFHE-rs**.
|
||||
{% endhint %}
|
||||
|
||||
Using this feature is straightforward: during encryption, the client generates the proof, and the server validates it before conducting any homomorphic computations. The following example demonstrates how a client can encrypt and prove a ciphertext, and how a server can verify the ciphertext and compute it:
|
||||
To use this feature, you must first generate a **CRS** (Common Reference String). The CRS is a piece of cryptographic data that is necessary to ensure the security of zero-knowledge proofs. The CRS should be generated in advance and shared between all the clients and the server. A CRS can be reused for multiple encryptions with the same parameters.
|
||||
|
||||
Once the CRS is generated, using zero-knowledge proofs is straightforward: during encryption, the client generates the proof, and the server validates it before performing any homomorphic computations.
|
||||
|
||||
Note that you need to use dedicated parameters for the compact public key encryption. This helps to reduce the size of encrypted data and speed up the zero-knowledge proof computation.
|
||||
|
||||
The following example shows how a client can encrypt and prove a ciphertext, and how a server can verify and compute the ciphertext:
|
||||
|
||||
```rust
|
||||
use rand::prelude::*;
|
||||
use tfhe::prelude::*;
|
||||
@@ -28,12 +32,13 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let casting_params = tfhe::shortint::parameters::key_switching::p_fail_2_minus_64::ks_pbs::V0_11_PARAM_KEYSWITCH_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
|
||||
// Enable the dedicated parameters on the config
|
||||
let config = tfhe::ConfigBuilder::with_custom_parameters(params)
|
||||
.use_dedicated_compact_public_key_parameters((cpk_params, casting_params));
|
||||
.use_dedicated_compact_public_key_parameters((cpk_params, casting_params)).build();
|
||||
|
||||
// The CRS should be generated in an offline phase then shared to all clients and the server
|
||||
let crs = CompactPkeCrs::from_config(config, 64).unwrap();
|
||||
|
||||
// Then use TFHE-rs as usual
|
||||
let client_key = tfhe::ClientKey::generate(config.clone());
|
||||
// This is done in an offline phase and the CRS is shared to all clients and the server
|
||||
let crs = CompactPkeCrs::from_config(config.into(), 64).unwrap();
|
||||
let client_key = tfhe::ClientKey::generate(config);
|
||||
let server_key = tfhe::ServerKey::new(&client_key);
|
||||
let public_key = tfhe::CompactPublicKey::try_new(&client_key).unwrap();
|
||||
// This can be left empty, but if provided allows to tie the proof to arbitrary data
|
||||
@@ -79,5 +84,71 @@ and by building the code for the native CPU architecture and in release mode, e.
|
||||
You can choose a more costly proof with `ZkComputeLoad::Proof`, which has a faster verification time. Alternatively, you can select `ZkComputeLoad::Verify` for a faster proof and slower verification.
|
||||
{% endhint %}
|
||||
|
||||
## Scheme version
|
||||
The ZK scheme used to generate and verify proofs is available in two versions:
|
||||
|
||||
- ZKV1: This version is close to the original paper from [Libert](https://eprint.iacr.org/2023/800).
|
||||
- ZKV2: Differing from the paper, this version provides better performance for provers and verifiers.
|
||||
|
||||
**TFHE-rs** selects automatically the scheme to use based on the encryption parameters during the CRS generation. With default parameters, ZKV2 is selected.
|
||||
|
||||
The following example shows how to generate a CRS and proofs for ZKV1. Compared to the previous example, only the parameters are changed:
|
||||
```rust
|
||||
use rand::prelude::*;
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::set_server_key;
|
||||
use tfhe::zk::{CompactPkeCrs, ZkComputeLoad};
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let params = tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
|
||||
// Indicate which parameters to use for the Compact Public Key encryption
|
||||
let cpk_params = tfhe::shortint::parameters::compact_public_key_only::p_fail_2_minus_64::ks_pbs::V0_11_PARAM_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1;
|
||||
// And parameters allowing to keyswitch/cast to the computation parameters.
|
||||
let casting_params = tfhe::shortint::parameters::key_switching::p_fail_2_minus_64::ks_pbs::V0_11_PARAM_KEYSWITCH_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64_ZKV1;
|
||||
// Enable the dedicated parameters on the config
|
||||
let config = tfhe::ConfigBuilder::with_custom_parameters(params)
|
||||
.use_dedicated_compact_public_key_parameters((cpk_params, casting_params)).build();
|
||||
|
||||
// The CRS should be generated in an offline phase then shared to all clients and the server
|
||||
let crs = CompactPkeCrs::from_config(config, 64).unwrap();
|
||||
|
||||
// Then use TFHE-rs as usual
|
||||
let client_key = tfhe::ClientKey::generate(config);
|
||||
let server_key = tfhe::ServerKey::new(&client_key);
|
||||
let public_key = tfhe::CompactPublicKey::try_new(&client_key).unwrap();
|
||||
// This can be left empty, but if provided allows to tie the proof to arbitrary data
|
||||
let metadata = [b'T', b'F', b'H', b'E', b'-', b'r', b's'];
|
||||
|
||||
let clear_a = rng.gen::<u64>();
|
||||
let clear_b = rng.gen::<u64>();
|
||||
|
||||
let proven_compact_list = tfhe::ProvenCompactCiphertextList::builder(&public_key)
|
||||
.push(clear_a)
|
||||
.push(clear_b)
|
||||
.build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Verify)?;
|
||||
|
||||
// Server side
|
||||
let result = {
|
||||
set_server_key(server_key);
|
||||
|
||||
// Verify the ciphertexts
|
||||
let expander =
|
||||
proven_compact_list.verify_and_expand(&crs, &public_key, &metadata)?;
|
||||
let a: tfhe::FheUint64 = expander.get(0)?.unwrap();
|
||||
let b: tfhe::FheUint64 = expander.get(1)?.unwrap();
|
||||
|
||||
a + b
|
||||
};
|
||||
|
||||
// Back on the client side
|
||||
let a_plus_b: u64 = result.decrypt(&client_key);
|
||||
assert_eq!(a_plus_b, clear_a.wrapping_add(clear_b));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Benchmark
|
||||
Please refer to the [Zero-knowledge proof benchmarks](../getting_started/benchmarks/zk_proof_benchmarks.md) for detailed performance benchmark results.
|
||||
|
||||
@@ -9,7 +9,7 @@ Welcome to this tutorial about `TFHE-rs` `core_crypto` module.
|
||||
To use `TFHE-rs`, it first has to be added as a dependency in the `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.11.0" }
|
||||
tfhe = { version = "0.11.2" }
|
||||
```
|
||||
|
||||
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.
|
||||
|
||||
@@ -8,13 +8,13 @@ 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 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.
|
||||
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^{-64}$$ 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:
|
||||
|
||||
| Parameter set | Error probability |
|
||||
| :-------------------: | :---------------: |
|
||||
| DEFAULT\_PARAMETERS | $$2^{-40}$$ |
|
||||
| DEFAULT\_PARAMETERS | $$2^{-64}$$ |
|
||||
| TFHE\_LIB\_PARAMETERS | $$2^{-165}$$ |
|
||||
|
||||
## User-defined parameters
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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 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^{-64}$$ 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
|
||||
|
||||
@@ -57,26 +57,22 @@ use tfhe::shortint::parameters::DynamicDistribution;
|
||||
fn main() {
|
||||
// WARNING: might be insecure and/or incorrect
|
||||
// You can create your own set of parameters
|
||||
let param = ClassicPBSParameters::new(
|
||||
LweDimension(656),
|
||||
GlweDimension(2),
|
||||
PolynomialSize(512),
|
||||
DynamicDistribution::new_gaussian_from_std_dev(
|
||||
StandardDev(0.000034119201269311964),
|
||||
),
|
||||
DynamicDistribution::new_gaussian_from_std_dev(
|
||||
StandardDev(0.00000004053919869756513),
|
||||
),
|
||||
DecompositionBaseLog(8),
|
||||
DecompositionLevelCount(2),
|
||||
DecompositionBaseLog(3),
|
||||
DecompositionLevelCount(4),
|
||||
MessageModulus(4),
|
||||
CarryModulus(1),
|
||||
MaxNoiseLevel::new(2),
|
||||
2.0f64.powi(-40),
|
||||
CiphertextModulus::new_native(),
|
||||
EncryptionKeyChoice::Big,
|
||||
);
|
||||
let param = ClassicPBSParameters {
|
||||
lwe_dimension: LweDimension(879),
|
||||
glwe_dimension: GlweDimension(1),
|
||||
polynomial_size: PolynomialSize(2048),
|
||||
lwe_noise_distribution: DynamicDistribution::new_t_uniform(46),
|
||||
glwe_noise_distribution: DynamicDistribution::new_t_uniform(17),
|
||||
pbs_base_log: DecompositionBaseLog(23),
|
||||
pbs_level: DecompositionLevelCount(1),
|
||||
ks_base_log: DecompositionBaseLog(3),
|
||||
ks_level: DecompositionLevelCount(5),
|
||||
message_modulus: MessageModulus(4),
|
||||
carry_modulus: CarryModulus(4),
|
||||
max_noise_level: MaxNoiseLevel::new(5),
|
||||
log2_p_fail: -71.625,
|
||||
ciphertext_modulus: CiphertextModulus::new_native(),
|
||||
encryption_key_choice: EncryptionKeyChoice::Big,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
# Homomorphic case changing on Ascii string
|
||||
|
||||
This tutorial demonstrates how to build a data type that represents an ASCII string in Fully Homomorphic Encryption (FHE) by implementing to\_lower and to\_upper functions.
|
||||
This tutorial demonstrates how to build your own data type that represents an ASCII string in Fully Homomorphic Encryption (FHE) by implementing to\_lower and to\_upper functions.
|
||||
|
||||
An ASCII character is stored in 7 bits. To store an encrypted ASCII, we use the `FheUint8`:
|
||||
{% hint style="info" %}
|
||||
Since version 0.11, **TFHE-rs** has introduced the `strings` feature, which provides an easy to use FHE strings API. See the [fhe strings guide](../guides/strings.md) for more information.
|
||||
{% endhint %}
|
||||
|
||||
|
||||
An ASCII character is stored in 7 bits. In this tutorial, we use the `FheUint8` to store an encrypted ASCII:
|
||||
|
||||
* The uppercase letters are in the range \[65, 90]
|
||||
* The lowercase letters are in the range \[97, 122]
|
||||
@@ -24,13 +29,10 @@ To use the `FheUint8` type, enable the `integer` feature:
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.11.0", features = ["integer"] }
|
||||
tfhe = { version = "0.11.2", features = ["integer"] }
|
||||
```
|
||||
|
||||
Refer to the [installation guide](../getting\_started/installation.md) for other configurations.
|
||||
|
||||
The `FheAsciiString::encrypt` function performs data validation to ensure the input string contains only ASCII characters.
|
||||
The `MyFheString::encrypt` function performs data validation to ensure the input string contains only ASCII characters.
|
||||
|
||||
In FHE operations, direct branching on encrypted values is not possible. However, you can evaluate a boolean condition to obtain the desired outcome. Here is an example to check and convert the 'char' to a lowercase without using a branch:
|
||||
|
||||
@@ -83,7 +85,7 @@ use tfhe::{generate_keys, set_server_key, ClientKey, ConfigBuilder, FheUint8};
|
||||
|
||||
const UP_LOW_DISTANCE: u8 = 32;
|
||||
|
||||
struct FheAsciiString {
|
||||
struct MyFheString {
|
||||
bytes: Vec<FheUint8>,
|
||||
}
|
||||
|
||||
@@ -95,7 +97,7 @@ fn to_lower(c: &FheUint8) -> FheUint8 {
|
||||
c + FheUint8::cast_from(c.gt(64) & c.lt(91)) * UP_LOW_DISTANCE
|
||||
}
|
||||
|
||||
impl FheAsciiString {
|
||||
impl MyFheString {
|
||||
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
|
||||
assert!(
|
||||
string.is_ascii(),
|
||||
@@ -140,7 +142,7 @@ fn main() {
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let my_string = FheAsciiString::encrypt("Hello Zama, how is it going?", &client_key);
|
||||
let my_string = MyFheString::encrypt("Hello Zama, how is it going?", &client_key);
|
||||
let verif_string = my_string.decrypt(&client_key);
|
||||
println!("Start string: {verif_string}");
|
||||
|
||||
@@ -155,3 +157,45 @@ fn main() {
|
||||
assert_eq!(verif_string, "hello zama, how is it going?");
|
||||
}
|
||||
```
|
||||
|
||||
## Using **TFHE-rs** strings feature
|
||||
This code can be greatly simplified by using the `strings` feature from **TFHE-rs**.
|
||||
|
||||
First, add the feature in your `Cargo.toml`
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
tfhe = { version = "0.11.2", features = ["strings"] }
|
||||
```
|
||||
|
||||
The `FheAsciiString` type allows to simply do homomorphic case changing of encrypted strings (and much more!):
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheAsciiString};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::default().build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let my_string =
|
||||
FheAsciiString::try_encrypt("Hello Zama, how is it going?", &client_key).unwrap();
|
||||
let verif_string = my_string.decrypt(&client_key);
|
||||
println!("Start string: {verif_string}");
|
||||
|
||||
let my_string_upper = my_string.to_uppercase();
|
||||
let verif_string = my_string_upper.decrypt(&client_key);
|
||||
println!("Upper string: {verif_string}");
|
||||
assert_eq!(verif_string, "HELLO ZAMA, HOW IS IT GOING?");
|
||||
|
||||
let my_string_lower = my_string_upper.to_lowercase();
|
||||
let verif_string = my_string_lower.decrypt(&client_key);
|
||||
println!("Lower string: {verif_string}");
|
||||
assert_eq!(verif_string, "hello zama, how is it going?");
|
||||
}
|
||||
```
|
||||
|
||||
You can read more about this in the [FHE strings documentation](../guides/strings.md)
|
||||
|
||||
@@ -17,7 +17,7 @@ This function returns a Boolean (`true` or `false`) so that the total count of `
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
tfhe = { version = "0.11.0", features = ["integer"] }
|
||||
tfhe = { version = "0.11.2", features = ["integer"] }
|
||||
```
|
||||
|
||||
First, define the verification function.
|
||||
|
||||
@@ -36,16 +36,16 @@ pub const NOISE_TEST_PARAMS_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN: ClassicTestPara
|
||||
#[allow(dead_code)]
|
||||
pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_2_BITS_NATIVE_U64_132_BITS_GAUSSIAN:
|
||||
MultiBitTestParams<u64> = MultiBitTestParams {
|
||||
input_lwe_dimension: LweDimension(256 * 3),
|
||||
input_lwe_dimension: LweDimension(759),
|
||||
lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
|
||||
1.1098369627275701e-05,
|
||||
1.296274149494132e-05,
|
||||
)),
|
||||
decomp_base_log: DecompositionBaseLog(17),
|
||||
decomp_base_log: DecompositionBaseLog(22),
|
||||
decomp_level_count: DecompositionLevelCount(1),
|
||||
glwe_dimension: GlweDimension(3),
|
||||
polynomial_size: PolynomialSize(512),
|
||||
glwe_dimension: GlweDimension(2),
|
||||
polynomial_size: PolynomialSize(1024),
|
||||
glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
|
||||
1.9524392655548086e-11,
|
||||
2.845267479601915e-15,
|
||||
)),
|
||||
message_modulus_log: MessageModulusLog(2),
|
||||
ciphertext_modulus: CiphertextModulus::new_native(),
|
||||
@@ -56,9 +56,9 @@ pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_2_BITS_NATIVE_U64_132_BITS_GAUSSIA
|
||||
#[allow(clippy::excessive_precision)]
|
||||
pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIAN:
|
||||
MultiBitTestParams<u64> = MultiBitTestParams {
|
||||
input_lwe_dimension: LweDimension(279 * 3),
|
||||
input_lwe_dimension: LweDimension(912),
|
||||
lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
|
||||
3.3747142481837397e-06,
|
||||
9.252442079345288e-07,
|
||||
)),
|
||||
decomp_base_log: DecompositionBaseLog(22),
|
||||
decomp_level_count: DecompositionLevelCount(1),
|
||||
@@ -77,7 +77,7 @@ pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_4_BITS_NATIVE_U64_132_BITS_GAUSSIA
|
||||
#[allow(dead_code)]
|
||||
pub const NOISE_TEST_PARAMS_MULTI_BIT_GROUP_3_6_BITS_NATIVE_U64_132_BITS_GAUSSIAN:
|
||||
MultiBitTestParams<u64> = MultiBitTestParams {
|
||||
input_lwe_dimension: LweDimension(326 * 3),
|
||||
input_lwe_dimension: LweDimension(978),
|
||||
lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
|
||||
2.962875621642539e-07,
|
||||
)),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::ClientKey;
|
||||
use crate::conformance::ParameterSetConformant;
|
||||
use crate::integer::backward_compatibility::list_compression::*;
|
||||
use crate::named::Named;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tfhe_versionable::Versionize;
|
||||
|
||||
@@ -34,6 +35,26 @@ pub struct CompressedDecompressionKey {
|
||||
pub(crate) key: crate::shortint::list_compression::CompressedDecompressionKey,
|
||||
}
|
||||
|
||||
impl Named for CompressionPrivateKeys {
|
||||
const NAME: &'static str = "high_level_api::CompressionPrivateKeys";
|
||||
}
|
||||
|
||||
impl Named for CompressionKey {
|
||||
const NAME: &'static str = "high_level_api::CompressionKey";
|
||||
}
|
||||
|
||||
impl Named for DecompressionKey {
|
||||
const NAME: &'static str = "high_level_api::DecompressionKey";
|
||||
}
|
||||
|
||||
impl Named for CompressedCompressionKey {
|
||||
const NAME: &'static str = "high_level_api::CompressedCompressionKey";
|
||||
}
|
||||
|
||||
impl Named for CompressedDecompressionKey {
|
||||
const NAME: &'static str = "high_level_api::CompressedDecompressionKey";
|
||||
}
|
||||
|
||||
impl CompressedCompressionKey {
|
||||
pub fn decompress(&self) -> CompressionKey {
|
||||
CompressionKey {
|
||||
|
||||
@@ -120,7 +120,8 @@ mod js_on_wasm_api;
|
||||
feature = "shortint",
|
||||
feature = "boolean",
|
||||
feature = "integer",
|
||||
feature = "zk-pok"
|
||||
feature = "zk-pok",
|
||||
feature = "strings"
|
||||
))]
|
||||
mod test_user_docs;
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
pub trait Named {
|
||||
/// Default name for the type
|
||||
const NAME: &'static str;
|
||||
/// Aliases that should also be accepted for backward compatibility when checking the name of
|
||||
/// values of this type
|
||||
const BACKWARD_COMPATIBILITY_ALIASES: &'static [&'static str] = &[];
|
||||
}
|
||||
|
||||
@@ -122,7 +122,11 @@ Please use the versioned serialization mode for backward compatibility.",
|
||||
}
|
||||
}
|
||||
|
||||
if self.name != T::NAME {
|
||||
if self.name != T::NAME
|
||||
&& T::BACKWARD_COMPATIBILITY_ALIASES
|
||||
.iter()
|
||||
.all(|alias| self.name != *alias)
|
||||
{
|
||||
return Err(format!(
|
||||
"On deserialization, expected type {}, got type {}",
|
||||
T::NAME,
|
||||
@@ -494,13 +498,17 @@ pub fn safe_deserialize_conformant<
|
||||
|
||||
#[cfg(all(test, feature = "shortint"))]
|
||||
mod test_shortint {
|
||||
use crate::safe_serialization::{DeserializationConfig, SerializationConfig};
|
||||
use tfhe_versionable::Versionize;
|
||||
|
||||
use crate::named::Named;
|
||||
use crate::shortint::parameters::{
|
||||
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64,
|
||||
V0_11_PARAM_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M64,
|
||||
};
|
||||
use crate::shortint::{gen_keys, Ciphertext};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn safe_deserialization_ct_unversioned() {
|
||||
let (ck, _sk) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64);
|
||||
@@ -626,6 +634,46 @@ mod test_shortint {
|
||||
let dec = ck.decrypt(&ct2);
|
||||
assert_eq!(msg, dec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn safe_deserialization_named() {
|
||||
#[derive(Serialize, Deserialize, Versionize)]
|
||||
#[repr(transparent)]
|
||||
struct Foo(u64);
|
||||
|
||||
impl Named for Foo {
|
||||
const NAME: &'static str = "Foo";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Versionize)]
|
||||
#[repr(transparent)]
|
||||
struct Bar(u64);
|
||||
|
||||
impl Named for Bar {
|
||||
const NAME: &'static str = "Bar";
|
||||
|
||||
const BACKWARD_COMPATIBILITY_ALIASES: &'static [&'static str] = &["Foo"];
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Versionize)]
|
||||
#[repr(transparent)]
|
||||
struct Baz(u64);
|
||||
|
||||
impl Named for Baz {
|
||||
const NAME: &'static str = "Baz";
|
||||
}
|
||||
|
||||
let foo = Foo(3);
|
||||
let mut foo_ser = Vec::new();
|
||||
safe_serialize(&foo, &mut foo_ser, 0x1000).unwrap();
|
||||
|
||||
let foo_deser: Foo = safe_deserialize(foo_ser.as_slice(), 0x1000).unwrap();
|
||||
let bar_deser: Bar = safe_deserialize(foo_ser.as_slice(), 0x1000).unwrap();
|
||||
|
||||
assert_eq!(foo_deser.0, bar_deser.0);
|
||||
|
||||
assert!(safe_deserialize::<Baz>(foo_ser.as_slice(), 0x1000).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "integer"))]
|
||||
|
||||
@@ -57,6 +57,7 @@ mod test_cpu_doc {
|
||||
doctest!("../docs/guides/pbs-stats.md", guides_pbs_stats);
|
||||
doctest!("../docs/guides/public_key.md", guides_public_key);
|
||||
doctest!("../docs/guides/rayon_crate.md", guides_rayon_crate);
|
||||
doctest!("../docs/guides/strings.md", guides_strings);
|
||||
doctest!("../docs/guides/trait_bounds.md", guides_trait_bounds);
|
||||
doctest!(
|
||||
"../docs/guides/trivial_ciphertext.md",
|
||||
|
||||
@@ -196,6 +196,8 @@ pub enum CompactPkeCrs {
|
||||
|
||||
impl Named for CompactPkeCrs {
|
||||
const NAME: &'static str = "zk::CompactPkeCrs";
|
||||
|
||||
const BACKWARD_COMPATIBILITY_ALIASES: &'static [&'static str] = &["zk::CompactPkePublicParams"];
|
||||
}
|
||||
|
||||
impl From<ZkCompactPkeV1PublicParams> for CompactPkeCrs {
|
||||
|
||||
Reference in New Issue
Block a user