mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-13 16:47:59 -05:00
Compare commits
26 Commits
pa/paralle
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e0f86d737 | ||
|
|
81223b08d7 | ||
|
|
726a7719ae | ||
|
|
f03127281f | ||
|
|
f213bfa127 | ||
|
|
e52e45ab01 | ||
|
|
5546888007 | ||
|
|
e73aed8cbb | ||
|
|
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.3"
|
||||
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.3", 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.3", 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.3", 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.3", 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.3", 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.3", 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.3", 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.3", 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.3", 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.3" }
|
||||
```
|
||||
|
||||
### 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.3", 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.3", 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.3", 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,
|
||||
)),
|
||||
|
||||
@@ -219,6 +219,12 @@ pub fn glwe_ciphertext_size(glwe_size: GlweSize, polynomial_size: PolynomialSize
|
||||
glwe_size.0 * polynomial_size.0
|
||||
}
|
||||
|
||||
/// Return the number of elements in the **mask** of a [`GlweCiphertext`]
|
||||
/// given a [`GlweDimension`] and [`PolynomialSize`].
|
||||
pub fn glwe_mask_size(glwe_dim: GlweDimension, polynomial_size: PolynomialSize) -> usize {
|
||||
glwe_dim.0 * polynomial_size.0
|
||||
}
|
||||
|
||||
/// Return the number of elements in a [`GlweMask`] given a [`GlweDimension`] and
|
||||
/// [`PolynomialSize`].
|
||||
pub fn glwe_ciphertext_mask_size(
|
||||
|
||||
@@ -661,6 +661,18 @@ pub struct CudaGlweList<T: UnsignedInteger> {
|
||||
pub ciphertext_modulus: CiphertextModulus<T>,
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger> CudaGlweList<T> {
|
||||
pub fn duplicate(&self, streams: &CudaStreams) -> Self {
|
||||
Self {
|
||||
d_vec: self.d_vec.duplicate(streams),
|
||||
glwe_ciphertext_count: self.glwe_ciphertext_count,
|
||||
glwe_dimension: self.glwe_dimension,
|
||||
polynomial_size: self.polynomial_size,
|
||||
ciphertext_modulus: self.ciphertext_modulus,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the number of GPUs on the machine
|
||||
pub fn get_number_of_gpus() -> i32 {
|
||||
unsafe { cuda_get_number_of_gpus() }
|
||||
|
||||
@@ -424,6 +424,16 @@ impl<T: Numeric> CudaVec<T> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
pub fn duplicate(&self, streams: &CudaStreams) -> Self {
|
||||
let d_vec = unsafe {
|
||||
let mut d_vec = Self::new_async(self.len(), streams, 0);
|
||||
d_vec.copy_from_gpu_async(self, streams, 0);
|
||||
d_vec
|
||||
};
|
||||
streams.synchronize();
|
||||
d_vec
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use crate::core_crypto::entities::packed_integers::PackedIntegers;
|
||||
use crate::core_crypto::entities::GlweCiphertextList;
|
||||
use crate::core_crypto::gpu::glwe_ciphertext_list::CudaGlweCiphertextList;
|
||||
use crate::core_crypto::gpu::vec::{CudaVec, GpuIndex};
|
||||
use crate::core_crypto::gpu::CudaStreams;
|
||||
use crate::core_crypto::prelude::compressed_modulus_switched_glwe_ciphertext::CompressedModulusSwitchedGlweCiphertext;
|
||||
use crate::core_crypto::prelude::{
|
||||
glwe_ciphertext_size, CiphertextCount, ContiguousEntityContainer, LweCiphertextCount,
|
||||
};
|
||||
use crate::core_crypto::prelude::{glwe_ciphertext_size, CiphertextCount, LweCiphertextCount};
|
||||
use crate::integer::ciphertext::{CompressedCiphertextList, DataKind};
|
||||
use crate::integer::gpu::ciphertext::boolean_value::CudaBooleanBlock;
|
||||
use crate::integer::gpu::ciphertext::{
|
||||
@@ -13,7 +10,7 @@ use crate::integer::gpu::ciphertext::{
|
||||
CudaUnsignedRadixCiphertext,
|
||||
};
|
||||
use crate::integer::gpu::list_compression::server_keys::{
|
||||
CudaCompressionKey, CudaDecompressionKey, CudaPackedGlweCiphertext,
|
||||
CudaCompressionKey, CudaDecompressionKey, CudaPackedGlweCiphertextList,
|
||||
};
|
||||
use crate::shortint::ciphertext::CompressedCiphertextList as ShortintCompressedCiphertextList;
|
||||
use crate::shortint::PBSOrder;
|
||||
@@ -61,11 +58,14 @@ impl CudaExpandable for CudaBooleanBlock {
|
||||
}
|
||||
}
|
||||
pub struct CudaCompressedCiphertextList {
|
||||
pub(crate) packed_list: CudaPackedGlweCiphertext,
|
||||
pub(crate) packed_list: CudaPackedGlweCiphertextList,
|
||||
pub(crate) info: Vec<DataKind>,
|
||||
}
|
||||
|
||||
impl CudaCompressedCiphertextList {
|
||||
pub fn gpu_indexes(&self) -> &[GpuIndex] {
|
||||
&self.packed_list.data.gpu_indexes
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.info.len()
|
||||
}
|
||||
@@ -178,39 +178,45 @@ impl CudaCompressedCiphertextList {
|
||||
/// let converted_compressed = cuda_compressed.to_compressed_ciphertext_list(&streams);
|
||||
/// ```
|
||||
pub fn to_compressed_ciphertext_list(&self, streams: &CudaStreams) -> CompressedCiphertextList {
|
||||
let glwe_list = self
|
||||
.packed_list
|
||||
.glwe_ciphertext_list
|
||||
.to_glwe_ciphertext_list(streams);
|
||||
let ciphertext_modulus = self.packed_list.glwe_ciphertext_list.ciphertext_modulus();
|
||||
|
||||
let ciphertext_modulus = self.packed_list.ciphertext_modulus;
|
||||
let message_modulus = self.packed_list.message_modulus;
|
||||
let carry_modulus = self.packed_list.carry_modulus;
|
||||
let lwe_per_glwe = self.packed_list.lwe_per_glwe;
|
||||
let storage_log_modulus = self.packed_list.storage_log_modulus;
|
||||
let glwe_dimension = self.packed_list.glwe_dimension;
|
||||
let polynomial_size = self.packed_list.polynomial_size;
|
||||
let mut modulus_switched_glwe_ciphertext_list =
|
||||
Vec::with_capacity(self.packed_list.glwe_ciphertext_count().0);
|
||||
|
||||
let initial_len = self.packed_list.initial_len;
|
||||
let number_bits_to_pack = initial_len * storage_log_modulus.0;
|
||||
let len = number_bits_to_pack.div_ceil(u64::BITS as usize);
|
||||
let flat_cpu_data = unsafe {
|
||||
let mut v = vec![0u64; self.packed_list.data.len()];
|
||||
self.packed_list.data.copy_to_cpu_async(&mut v, streams, 0);
|
||||
streams.synchronize();
|
||||
v
|
||||
};
|
||||
|
||||
let modulus_switched_glwe_ciphertext_list = glwe_list
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let glwe_dimension = x.glwe_size().to_glwe_dimension();
|
||||
let polynomial_size = x.polynomial_size();
|
||||
CompressedModulusSwitchedGlweCiphertext {
|
||||
packed_integers: PackedIntegers {
|
||||
packed_coeffs: x.into_container()[0..len].to_vec(),
|
||||
log_modulus: storage_log_modulus,
|
||||
initial_len,
|
||||
},
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
bodies_count: LweCiphertextCount(self.packed_list.bodies_count),
|
||||
uncompressed_ciphertext_modulus: ciphertext_modulus,
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
let mut num_bodies_left = self.packed_list.bodies_count;
|
||||
let mut chunk_start = 0;
|
||||
while num_bodies_left != 0 {
|
||||
let bodies_count = LweCiphertextCount(num_bodies_left.min(lwe_per_glwe.0));
|
||||
let initial_len = (glwe_dimension.0 * polynomial_size.0) + bodies_count.0;
|
||||
let number_bits_to_pack = initial_len * storage_log_modulus.0;
|
||||
let len = number_bits_to_pack.div_ceil(u64::BITS as usize);
|
||||
let chunk_end = chunk_start + len;
|
||||
modulus_switched_glwe_ciphertext_list.push(CompressedModulusSwitchedGlweCiphertext {
|
||||
packed_integers: PackedIntegers {
|
||||
packed_coeffs: flat_cpu_data[chunk_start..chunk_end].to_vec(),
|
||||
log_modulus: storage_log_modulus,
|
||||
initial_len,
|
||||
},
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
bodies_count,
|
||||
uncompressed_ciphertext_modulus: ciphertext_modulus,
|
||||
});
|
||||
num_bodies_left = num_bodies_left.saturating_sub(lwe_per_glwe.0);
|
||||
chunk_start = chunk_end;
|
||||
}
|
||||
|
||||
let count = CiphertextCount(self.packed_list.bodies_count);
|
||||
let pbs_order = PBSOrder::KeyswitchBootstrap;
|
||||
@@ -312,39 +318,48 @@ impl CompressedCiphertextList {
|
||||
|
||||
let first_ct = modulus_switched_glwe_ciphertext_list.first().unwrap();
|
||||
let storage_log_modulus = first_ct.packed_integers.log_modulus;
|
||||
let initial_len = first_ct.packed_integers.initial_len;
|
||||
let bodies_count = first_ct.bodies_count.0;
|
||||
let initial_len = modulus_switched_glwe_ciphertext_list
|
||||
.iter()
|
||||
.map(|glwe| glwe.packed_integers.initial_len)
|
||||
.sum();
|
||||
|
||||
let message_modulus = self.packed_list.message_modulus;
|
||||
let carry_modulus = self.packed_list.carry_modulus;
|
||||
|
||||
let mut data = modulus_switched_glwe_ciphertext_list
|
||||
let mut flat_cpu_data = modulus_switched_glwe_ciphertext_list
|
||||
.iter()
|
||||
.flat_map(|ct| ct.packed_integers.packed_coeffs.clone())
|
||||
.collect_vec();
|
||||
let glwe_ciphertext_size = glwe_ciphertext_size(
|
||||
first_ct.glwe_dimension.to_glwe_size(),
|
||||
first_ct.polynomial_size,
|
||||
);
|
||||
data.resize(
|
||||
self.packed_list.modulus_switched_glwe_ciphertext_list.len() * glwe_ciphertext_size,
|
||||
0,
|
||||
);
|
||||
let glwe_ciphertext_list = GlweCiphertextList::from_container(
|
||||
data.as_slice(),
|
||||
first_ct.glwe_dimension.to_glwe_size(),
|
||||
first_ct.polynomial_size,
|
||||
self.packed_list.ciphertext_modulus,
|
||||
);
|
||||
|
||||
let glwe_ciphertext_count = self.packed_list.modulus_switched_glwe_ciphertext_list.len();
|
||||
let glwe_size = self.packed_list.modulus_switched_glwe_ciphertext_list[0]
|
||||
.glwe_dimension()
|
||||
.to_glwe_size();
|
||||
let polynomial_size =
|
||||
self.packed_list.modulus_switched_glwe_ciphertext_list[0].polynomial_size();
|
||||
|
||||
// FIXME: have a more precise memory handling, this is too long and should be "just" the
|
||||
// original flat_cpu_data.len()
|
||||
let unpacked_glwe_ciphertext_flat_len =
|
||||
glwe_ciphertext_count * glwe_ciphertext_size(glwe_size, polynomial_size);
|
||||
|
||||
flat_cpu_data.resize(unpacked_glwe_ciphertext_flat_len, 0u64);
|
||||
|
||||
let flat_gpu_data = unsafe {
|
||||
let v = CudaVec::from_cpu_async(flat_cpu_data.as_slice(), streams, 0);
|
||||
streams.synchronize();
|
||||
v
|
||||
};
|
||||
|
||||
CudaCompressedCiphertextList {
|
||||
packed_list: CudaPackedGlweCiphertext {
|
||||
glwe_ciphertext_list: CudaGlweCiphertextList::from_glwe_ciphertext_list(
|
||||
&glwe_ciphertext_list,
|
||||
streams,
|
||||
),
|
||||
packed_list: CudaPackedGlweCiphertextList {
|
||||
data: flat_gpu_data,
|
||||
glwe_dimension: first_ct.glwe_dimension(),
|
||||
polynomial_size: first_ct.polynomial_size(),
|
||||
message_modulus,
|
||||
carry_modulus,
|
||||
bodies_count,
|
||||
ciphertext_modulus: self.packed_list.ciphertext_modulus,
|
||||
bodies_count: self.packed_list.count.0,
|
||||
storage_log_modulus,
|
||||
lwe_per_glwe,
|
||||
initial_len,
|
||||
@@ -484,7 +499,9 @@ impl<'de> serde::Deserialize<'de> for CudaCompressedCiphertextList {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::integer::ciphertext::CompressedCiphertextListBuilder;
|
||||
use crate::integer::gpu::gen_keys_radix_gpu;
|
||||
use crate::integer::{ClientKey, RadixCiphertext, RadixClientKey};
|
||||
use crate::shortint::parameters::list_compression::COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
|
||||
use crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
|
||||
use rand::Rng;
|
||||
@@ -492,6 +509,154 @@ mod tests {
|
||||
const NB_TESTS: usize = 10;
|
||||
const NB_OPERATOR_TESTS: usize = 10;
|
||||
|
||||
#[test]
|
||||
fn test_cpu_to_gpu_compressed_ciphertext_list() {
|
||||
const NUM_BLOCKS: usize = 32;
|
||||
let streams = CudaStreams::new_multi_gpu();
|
||||
|
||||
let params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
|
||||
let comp_params = COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64;
|
||||
|
||||
let cks = ClientKey::new(params);
|
||||
|
||||
let private_compression_key = cks.new_compression_private_key(comp_params);
|
||||
let (compressed_compression_key, compressed_decompression_key) =
|
||||
cks.new_compressed_compression_decompression_keys(&private_compression_key);
|
||||
let cuda_compression_key = compressed_compression_key.decompress_to_cuda(&streams);
|
||||
let cuda_decompression_key = compressed_decompression_key.decompress_to_cuda(
|
||||
cks.parameters().glwe_dimension(),
|
||||
cks.parameters().polynomial_size(),
|
||||
cks.parameters().message_modulus(),
|
||||
cks.parameters().carry_modulus(),
|
||||
cks.parameters().ciphertext_modulus(),
|
||||
&streams,
|
||||
);
|
||||
let cpu_compression_key = compressed_compression_key.decompress();
|
||||
let cpu_decompression_key = compressed_decompression_key.decompress();
|
||||
|
||||
let radix_cks = RadixClientKey::from((cks, NUM_BLOCKS));
|
||||
|
||||
// How many uints of NUM_BLOCKS we have to push in the list to ensure it
|
||||
// internally has more than one packed GLWE
|
||||
const MAX_NB_MESSAGES: usize = 1 + 2 * COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64
|
||||
.lwe_per_glwe
|
||||
.0
|
||||
/ NUM_BLOCKS;
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let message_modulus: u128 = radix_cks.parameters().message_modulus().0 as u128;
|
||||
let modulus = message_modulus.pow(NUM_BLOCKS as u32);
|
||||
let messages = (0..MAX_NB_MESSAGES)
|
||||
.map(|_| rng.gen::<u128>() % modulus)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let cpu_cts = messages
|
||||
.iter()
|
||||
.map(|message| radix_cks.encrypt(*message))
|
||||
.collect_vec();
|
||||
|
||||
let cuda_cts = cpu_cts
|
||||
.iter()
|
||||
.map(|ct| CudaUnsignedRadixCiphertext::from_radix_ciphertext(ct, &streams))
|
||||
.collect_vec();
|
||||
|
||||
let cpu_compressed_list = {
|
||||
let mut builder = CompressedCiphertextListBuilder::new();
|
||||
for d_ct in cpu_cts {
|
||||
builder.push(d_ct);
|
||||
}
|
||||
builder.build(&cpu_compression_key)
|
||||
};
|
||||
|
||||
let cuda_compressed_list = {
|
||||
let mut builder = CudaCompressedCiphertextListBuilder::new();
|
||||
for d_ct in cuda_cts {
|
||||
builder.push(d_ct, &streams);
|
||||
}
|
||||
builder.build(&cuda_compression_key, &streams)
|
||||
};
|
||||
|
||||
// Test Decompression on Gpu
|
||||
{
|
||||
// Roundtrip Gpu->Cpu->Gpu
|
||||
let cuda_compressed_list = cuda_compressed_list
|
||||
.to_compressed_ciphertext_list(&streams)
|
||||
.to_cuda_compressed_ciphertext_list(&streams);
|
||||
|
||||
let cuda_compressed_list_2 =
|
||||
cpu_compressed_list.to_cuda_compressed_ciphertext_list(&streams);
|
||||
|
||||
for (i, message) in messages.iter().enumerate() {
|
||||
let d_decompressed: CudaUnsignedRadixCiphertext = cuda_compressed_list
|
||||
.get(i, &cuda_decompression_key, &streams)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let decompressed = d_decompressed.to_radix_ciphertext(&streams);
|
||||
let decrypted: u128 = radix_cks.decrypt(&decompressed);
|
||||
assert_eq!(
|
||||
decrypted, *message,
|
||||
"Invalid decompression for cuda list that roundtripped Cuda->Cpu->Cuda"
|
||||
);
|
||||
|
||||
let d_decompressed: CudaUnsignedRadixCiphertext = cuda_compressed_list_2
|
||||
.get(i, &cuda_decompression_key, &streams)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let decompressed = d_decompressed.to_radix_ciphertext(&streams);
|
||||
let decrypted: u128 = radix_cks.decrypt(&decompressed);
|
||||
assert_eq!(
|
||||
decrypted, *message,
|
||||
"Invalid decompression for cuda list that originated from Cpu"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Test Decompression on CPU (to test conversions)
|
||||
{
|
||||
let expected_flat_len = cpu_compressed_list.packed_list.flat_len();
|
||||
|
||||
// Roundtrip Cpu->Gpu->Cpu
|
||||
let cpu_compressed_list = cpu_compressed_list
|
||||
.to_cuda_compressed_ciphertext_list(&streams)
|
||||
.to_compressed_ciphertext_list(&streams);
|
||||
assert_eq!(
|
||||
cpu_compressed_list.packed_list.flat_len(),
|
||||
expected_flat_len,
|
||||
"Invalid flat len after Cpu->Gpu->Cpu"
|
||||
);
|
||||
|
||||
let cpu_compressed_list_2 =
|
||||
cuda_compressed_list.to_compressed_ciphertext_list(&streams);
|
||||
assert_eq!(
|
||||
cpu_compressed_list_2.packed_list.flat_len(),
|
||||
expected_flat_len,
|
||||
"Invalid flat len after Gpu->Cpu"
|
||||
);
|
||||
|
||||
for (i, message) in messages.iter().enumerate() {
|
||||
let decompressed: RadixCiphertext = cpu_compressed_list
|
||||
.get(i, &cpu_decompression_key)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let decrypted: u128 = radix_cks.decrypt(&decompressed);
|
||||
assert_eq!(
|
||||
decrypted, *message,
|
||||
"Invalid decompression for cpu list that roundtripped Cpu->Gpu->Cpu"
|
||||
);
|
||||
|
||||
let decompressed: RadixCiphertext = cpu_compressed_list_2
|
||||
.get(i, &cpu_decompression_key)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let decrypted: u128 = radix_cks.decrypt(&decompressed);
|
||||
assert_eq!(
|
||||
decrypted, *message,
|
||||
"Invalid decompression for cpu list that originated from Gpu"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gpu_ciphertext_compression() {
|
||||
const NUM_BLOCKS: usize = 32;
|
||||
@@ -550,6 +715,10 @@ mod tests {
|
||||
.get(i, &cuda_decompression_key, &streams)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
d_decompressed.block_carries_are_empty(),
|
||||
"Expected carries to be empty"
|
||||
);
|
||||
let decompressed = d_decompressed.to_radix_ciphertext(&streams);
|
||||
let decrypted: u128 = radix_cks.decrypt(&decompressed);
|
||||
assert_eq!(decrypted, *message);
|
||||
@@ -586,6 +755,10 @@ mod tests {
|
||||
.get(i, &cuda_decompression_key, &streams)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
d_decompressed.block_carries_are_empty(),
|
||||
"Expected carries to be empty"
|
||||
);
|
||||
let decompressed = d_decompressed.to_signed_radix_ciphertext(&streams);
|
||||
let decrypted: i128 = radix_cks.decrypt_signed(&decompressed);
|
||||
assert_eq!(decrypted, *message);
|
||||
@@ -624,6 +797,10 @@ mod tests {
|
||||
.get(i, &cuda_decompression_key, &streams)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
d_decompressed.0.holds_boolean_value(),
|
||||
"Expected boolean block to have the degree of a boolean value"
|
||||
);
|
||||
let decompressed = d_decompressed.to_boolean_block(&streams);
|
||||
let decrypted = radix_cks.decrypt_bool(&decompressed);
|
||||
assert_eq!(decrypted, *message);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::core_crypto::gpu::entities::lwe_packing_keyswitch_key::CudaLwePackingKeyswitchKey;
|
||||
use crate::core_crypto::gpu::glwe_ciphertext_list::CudaGlweCiphertextList;
|
||||
use crate::core_crypto::gpu::lwe_ciphertext_list::CudaLweCiphertextList;
|
||||
use crate::core_crypto::gpu::vec::CudaVec;
|
||||
use crate::core_crypto::gpu::CudaStreams;
|
||||
use crate::core_crypto::prelude::{
|
||||
CiphertextModulus, CiphertextModulusLog, GlweCiphertextCount, LweCiphertextCount,
|
||||
PolynomialSize,
|
||||
glwe_ciphertext_size, CiphertextModulus, CiphertextModulusLog, GlweCiphertextCount,
|
||||
LweCiphertextCount, PolynomialSize,
|
||||
};
|
||||
use crate::integer::ciphertext::DataKind;
|
||||
use crate::integer::compression_keys::CompressionKey;
|
||||
@@ -37,22 +36,56 @@ pub struct CudaDecompressionKey {
|
||||
pub ciphertext_modulus: CiphertextModulus<u64>,
|
||||
}
|
||||
|
||||
pub struct CudaPackedGlweCiphertext {
|
||||
pub glwe_ciphertext_list: CudaGlweCiphertextList<u64>,
|
||||
pub struct CudaPackedGlweCiphertextList {
|
||||
// The compressed GLWE list's elements
|
||||
pub data: CudaVec<u64>,
|
||||
pub glwe_dimension: GlweDimension,
|
||||
pub polynomial_size: PolynomialSize,
|
||||
pub message_modulus: MessageModulus,
|
||||
pub carry_modulus: CarryModulus,
|
||||
pub bodies_count: usize,
|
||||
pub ciphertext_modulus: CiphertextModulus<u64>,
|
||||
pub storage_log_modulus: CiphertextModulusLog,
|
||||
pub lwe_per_glwe: LweCiphertextCount,
|
||||
// Number of lwe bodies that are compressed in this list
|
||||
pub bodies_count: usize,
|
||||
// Number of elements (u64) the uncompressed GLWE list had
|
||||
// keep in mind the last GLWE may not be full
|
||||
pub initial_len: usize,
|
||||
}
|
||||
|
||||
impl Clone for CudaPackedGlweCiphertext {
|
||||
fn clone(&self) -> Self {
|
||||
impl CudaPackedGlweCiphertextList {
|
||||
pub fn glwe_ciphertext_count(&self) -> GlweCiphertextCount {
|
||||
let uncompressed_glwe_size =
|
||||
glwe_ciphertext_size(self.glwe_dimension.to_glwe_size(), self.polynomial_size);
|
||||
|
||||
GlweCiphertextCount(self.initial_len.div_ceil(uncompressed_glwe_size))
|
||||
}
|
||||
|
||||
pub fn duplicate(&self, streams: &CudaStreams) -> Self {
|
||||
Self {
|
||||
glwe_ciphertext_list: CudaGlweCiphertextList(self.glwe_ciphertext_list.0.clone()),
|
||||
data: self.data.duplicate(streams),
|
||||
glwe_dimension: self.glwe_dimension,
|
||||
polynomial_size: self.polynomial_size,
|
||||
message_modulus: self.message_modulus,
|
||||
carry_modulus: self.carry_modulus,
|
||||
ciphertext_modulus: self.ciphertext_modulus,
|
||||
bodies_count: self.bodies_count,
|
||||
storage_log_modulus: self.storage_log_modulus,
|
||||
lwe_per_glwe: self.lwe_per_glwe,
|
||||
initial_len: self.initial_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for CudaPackedGlweCiphertextList {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data: self.data.clone(),
|
||||
glwe_dimension: self.glwe_dimension,
|
||||
polynomial_size: self.polynomial_size,
|
||||
message_modulus: self.message_modulus,
|
||||
carry_modulus: self.carry_modulus,
|
||||
ciphertext_modulus: self.ciphertext_modulus,
|
||||
bodies_count: self.bodies_count,
|
||||
storage_log_modulus: self.storage_log_modulus,
|
||||
lwe_per_glwe: self.lwe_per_glwe,
|
||||
@@ -120,12 +153,12 @@ impl CudaCompressionKey {
|
||||
&self,
|
||||
ciphertexts: &[CudaRadixCiphertext],
|
||||
streams: &CudaStreams,
|
||||
) -> CudaPackedGlweCiphertext {
|
||||
) -> CudaPackedGlweCiphertextList {
|
||||
let lwe_pksk = &self.packing_key_switching_key;
|
||||
|
||||
let ciphertext_modulus = lwe_pksk.ciphertext_modulus();
|
||||
let compress_polynomial_size = lwe_pksk.output_polynomial_size();
|
||||
let compress_glwe_size = lwe_pksk.output_glwe_size();
|
||||
let compressed_polynomial_size = lwe_pksk.output_polynomial_size();
|
||||
let compressed_glwe_size = lwe_pksk.output_glwe_size();
|
||||
|
||||
let first_ct = ciphertexts.first().unwrap();
|
||||
let first_ct_info = first_ct.info.blocks.first().unwrap();
|
||||
@@ -140,27 +173,28 @@ impl CudaCompressionKey {
|
||||
.sum();
|
||||
|
||||
let num_glwes = num_lwes.div_ceil(self.lwe_per_glwe.0);
|
||||
|
||||
let mut output_glwe = CudaGlweCiphertextList::new(
|
||||
compress_glwe_size.to_glwe_dimension(),
|
||||
compress_polynomial_size,
|
||||
GlweCiphertextCount(num_glwes),
|
||||
ciphertext_modulus,
|
||||
streams,
|
||||
);
|
||||
let glwe_ciphertext_size =
|
||||
glwe_ciphertext_size(compressed_glwe_size, compressed_polynomial_size);
|
||||
// The number of u64 (both mask and bodies)
|
||||
// FIXME: have a more precise memory handling, this is too long and should be
|
||||
// num_glwes * glwe_mask_size + num_lwes
|
||||
let uncompressed_len = num_glwes * glwe_ciphertext_size;
|
||||
let number_bits_to_pack = uncompressed_len * self.storage_log_modulus.0;
|
||||
let compressed_len = number_bits_to_pack.div_ceil(u64::BITS as usize);
|
||||
let mut packed_glwe_list = CudaVec::new(compressed_len, streams, 0);
|
||||
|
||||
unsafe {
|
||||
let input_lwes = Self::flatten_async(ciphertexts, streams);
|
||||
|
||||
compress_integer_radix_async(
|
||||
streams,
|
||||
&mut output_glwe.0.d_vec,
|
||||
&mut packed_glwe_list,
|
||||
&input_lwes.0.d_vec,
|
||||
&self.packing_key_switching_key.d_vec,
|
||||
message_modulus,
|
||||
carry_modulus,
|
||||
compress_glwe_size.to_glwe_dimension(),
|
||||
compress_polynomial_size,
|
||||
compressed_glwe_size.to_glwe_dimension(),
|
||||
compressed_polynomial_size,
|
||||
lwe_dimension,
|
||||
lwe_pksk.decomposition_base_log(),
|
||||
lwe_pksk.decomposition_level_count(),
|
||||
@@ -172,17 +206,17 @@ impl CudaCompressionKey {
|
||||
streams.synchronize();
|
||||
};
|
||||
|
||||
let initial_len =
|
||||
compress_glwe_size.to_glwe_dimension().0 * compress_polynomial_size.0 + num_lwes;
|
||||
|
||||
CudaPackedGlweCiphertext {
|
||||
glwe_ciphertext_list: output_glwe,
|
||||
CudaPackedGlweCiphertextList {
|
||||
data: packed_glwe_list,
|
||||
glwe_dimension: compressed_glwe_size.to_glwe_dimension(),
|
||||
polynomial_size: compressed_polynomial_size,
|
||||
message_modulus,
|
||||
carry_modulus,
|
||||
ciphertext_modulus,
|
||||
bodies_count: num_lwes,
|
||||
storage_log_modulus: self.storage_log_modulus,
|
||||
lwe_per_glwe: LweCiphertextCount(compress_polynomial_size.0),
|
||||
initial_len,
|
||||
lwe_per_glwe: LweCiphertextCount(compressed_polynomial_size.0),
|
||||
initial_len: uncompressed_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +224,7 @@ impl CudaCompressionKey {
|
||||
impl CudaDecompressionKey {
|
||||
pub fn unpack(
|
||||
&self,
|
||||
packed_list: &CudaPackedGlweCiphertext,
|
||||
packed_list: &CudaPackedGlweCiphertextList,
|
||||
kind: DataKind,
|
||||
start_block_index: usize,
|
||||
end_block_index: usize,
|
||||
@@ -218,9 +252,8 @@ impl CudaDecompressionKey {
|
||||
|
||||
let encryption_glwe_dimension = self.glwe_dimension;
|
||||
let encryption_polynomial_size = self.polynomial_size;
|
||||
let glwe_ciphertext_list = &packed_list.glwe_ciphertext_list;
|
||||
let compression_glwe_dimension = glwe_ciphertext_list.glwe_dimension();
|
||||
let compression_polynomial_size = glwe_ciphertext_list.polynomial_size();
|
||||
let compression_glwe_dimension = packed_list.glwe_dimension;
|
||||
let compression_polynomial_size = packed_list.polynomial_size;
|
||||
let indexes_array_len = LweCiphertextCount(indexes_array.len());
|
||||
|
||||
let message_modulus = self.message_modulus;
|
||||
@@ -243,7 +276,7 @@ impl CudaDecompressionKey {
|
||||
decompress_integer_radix_async(
|
||||
streams,
|
||||
&mut output_lwe.0.d_vec,
|
||||
&glwe_ciphertext_list.0.d_vec,
|
||||
&packed_list.data,
|
||||
&bsk.d_vec,
|
||||
packed_list.bodies_count as u32,
|
||||
message_modulus,
|
||||
@@ -265,7 +298,7 @@ impl CudaDecompressionKey {
|
||||
|
||||
let degree = match kind {
|
||||
DataKind::Unsigned(_) | DataKind::Signed(_) => {
|
||||
Degree::new(message_modulus.0 * carry_modulus.0 - 1)
|
||||
Degree::new(message_modulus.0 - 1)
|
||||
}
|
||||
DataKind::Boolean => Degree::new(1),
|
||||
};
|
||||
|
||||
@@ -61,7 +61,13 @@ impl ServerKey {
|
||||
T: IntegerRadixCiphertext,
|
||||
{
|
||||
if d_range.is_empty() {
|
||||
return ct.clone();
|
||||
let mut result = ct.clone();
|
||||
result
|
||||
.blocks_mut()
|
||||
.par_iter_mut()
|
||||
.filter(|b| b.noise_level > NoiseLevel::NOMINAL)
|
||||
.for_each(|block| self.key.message_extract_assign(block));
|
||||
return result;
|
||||
}
|
||||
|
||||
assert!(
|
||||
|
||||
@@ -370,22 +370,61 @@ impl ServerKey {
|
||||
where
|
||||
T: IntegerRadixCiphertext,
|
||||
{
|
||||
if amount.blocks.is_empty() || ct.blocks().is_empty() {
|
||||
return ct.clone();
|
||||
}
|
||||
|
||||
let message_bits_per_block = self.key.message_modulus.0.ilog2() as u64;
|
||||
let carry_bits_per_block = self.key.carry_modulus.0.ilog2() as u64;
|
||||
assert!(carry_bits_per_block >= message_bits_per_block);
|
||||
|
||||
// Extracts bits and put them in the bit index 2 (=> bit number 3)
|
||||
// so that it is already aligned to the correct position of the cmux input,
|
||||
// and we reduce noise growth
|
||||
let mut shift_bit_extractor = BitExtractor::with_final_offset(
|
||||
&amount.blocks,
|
||||
self,
|
||||
message_bits_per_block as usize,
|
||||
message_bits_per_block as usize,
|
||||
);
|
||||
|
||||
assert!(message_bits_per_block.is_power_of_two());
|
||||
|
||||
if ct.blocks().len() == 1 {
|
||||
let lut = self
|
||||
.key
|
||||
.generate_lookup_table_bivariate(|input, first_shift_block| {
|
||||
let shift_within_block = first_shift_block % message_bits_per_block;
|
||||
|
||||
match operation {
|
||||
BarrelShifterOperation::LeftShift => {
|
||||
(input << shift_within_block) % self.message_modulus().0
|
||||
}
|
||||
BarrelShifterOperation::LeftRotate => {
|
||||
let shifted = (input << shift_within_block) % self.message_modulus().0;
|
||||
let wrapped = input >> (message_bits_per_block - shift_within_block);
|
||||
shifted | wrapped
|
||||
}
|
||||
BarrelShifterOperation::RightRotate => {
|
||||
let shifted = input >> shift_within_block;
|
||||
let wrapped = (input << (message_bits_per_block - shift_within_block))
|
||||
% self.message_modulus().0;
|
||||
wrapped | shifted
|
||||
}
|
||||
BarrelShifterOperation::RightShift => {
|
||||
if T::IS_SIGNED {
|
||||
let sign_bit_pos = message_bits_per_block - 1;
|
||||
let sign_bit = (input >> sign_bit_pos) & 1;
|
||||
let padding_block = (self.message_modulus().0 - 1) * sign_bit;
|
||||
|
||||
// Pad with sign bits to 'simulate' an arithmetic shift
|
||||
let input = (padding_block << message_bits_per_block) | input;
|
||||
(input >> shift_within_block) % self.message_modulus().0
|
||||
} else {
|
||||
input >> shift_within_block
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let block = self.key.unchecked_apply_lookup_table_bivariate(
|
||||
&ct.blocks()[0],
|
||||
&amount.blocks[0],
|
||||
&lut,
|
||||
);
|
||||
|
||||
return T::from_blocks(vec![block]);
|
||||
}
|
||||
|
||||
let message_for_block =
|
||||
self.key
|
||||
.generate_lookup_table_bivariate(|input, first_shift_block| {
|
||||
@@ -408,6 +447,45 @@ impl ServerKey {
|
||||
b
|
||||
}
|
||||
});
|
||||
|
||||
// When doing right shift of a signed ciphertext, we do an arithmetic shift
|
||||
// Thus, we need some special luts to be used on the last block
|
||||
// (which has the sign bit)
|
||||
let message_for_block_right_shift_signed =
|
||||
if T::IS_SIGNED && operation == BarrelShifterOperation::RightShift {
|
||||
let lut = self
|
||||
.key
|
||||
.generate_lookup_table_bivariate(|input, first_shift_block| {
|
||||
let shift_within_block = first_shift_block % message_bits_per_block;
|
||||
let shift_to_next_block = (first_shift_block / message_bits_per_block) % 2;
|
||||
|
||||
let sign_bit_pos = message_bits_per_block - 1;
|
||||
let sign_bit = (input >> sign_bit_pos) & 1;
|
||||
let padding_block = (self.message_modulus().0 - 1) * sign_bit;
|
||||
|
||||
if shift_to_next_block == 1 {
|
||||
padding_block
|
||||
} else {
|
||||
// Pad with sign bits to 'simulate' an arithmetic shift
|
||||
let input = (padding_block << message_bits_per_block) | input;
|
||||
(input >> shift_within_block) % self.message_modulus().0
|
||||
}
|
||||
});
|
||||
Some(lut)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Extracts bits and put them in the bit index 2 (=> bit number 3)
|
||||
// so that it is already aligned to the correct position of the cmux input,
|
||||
// and we reduce noise growth
|
||||
let mut shift_bit_extractor = BitExtractor::with_final_offset(
|
||||
&amount.blocks,
|
||||
self,
|
||||
message_bits_per_block as usize,
|
||||
message_bits_per_block as usize,
|
||||
);
|
||||
|
||||
let message_for_next_block =
|
||||
self.key
|
||||
.generate_lookup_table_bivariate(|previous, first_shift_block| {
|
||||
@@ -467,34 +545,6 @@ impl ServerKey {
|
||||
}
|
||||
});
|
||||
|
||||
// When doing right shift of a signed ciphertext, we do an arithmetic shift
|
||||
// Thus, we need some special luts to be used on the last block
|
||||
// (which has the sign big)
|
||||
let message_for_block_right_shift_signed =
|
||||
if T::IS_SIGNED && operation == BarrelShifterOperation::RightShift {
|
||||
let lut = self
|
||||
.key
|
||||
.generate_lookup_table_bivariate(|input, first_shift_block| {
|
||||
let shift_within_block = first_shift_block % message_bits_per_block;
|
||||
let shift_to_next_block = (first_shift_block / message_bits_per_block) % 2;
|
||||
|
||||
let sign_bit_pos = message_bits_per_block - 1;
|
||||
let sign_bit = (input >> sign_bit_pos) & 1;
|
||||
let padding_block = (self.message_modulus().0 - 1) * sign_bit;
|
||||
|
||||
if shift_to_next_block == 1 {
|
||||
padding_block
|
||||
} else {
|
||||
// Pad with sign bits to 'simulate' an arithmetic shift
|
||||
let input = (padding_block << message_bits_per_block) | input;
|
||||
(input >> shift_within_block) % self.message_modulus().0
|
||||
}
|
||||
});
|
||||
Some(lut)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let message_for_next_block_right_shift_signed = if T::IS_SIGNED
|
||||
&& operation == BarrelShifterOperation::RightShift
|
||||
{
|
||||
@@ -693,7 +743,8 @@ impl ServerKey {
|
||||
) where
|
||||
T: IntegerRadixCiphertext,
|
||||
{
|
||||
let num_blocks = shift.blocks.len();
|
||||
// What matters is the len of the ct to shift, not the `shift` len
|
||||
let num_blocks = ct.blocks().len();
|
||||
let message_bits_per_block = self.key.message_modulus.0.ilog2() as u64;
|
||||
let carry_bits_per_block = self.key.carry_modulus.0.ilog2() as u64;
|
||||
let total_nb_bits = message_bits_per_block * num_blocks as u64;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::integer::block_decomposition::BlockDecomposer;
|
||||
use crate::integer::ciphertext::boolean_value::BooleanBlock;
|
||||
use crate::integer::keycache::KEY_CACHE;
|
||||
use crate::integer::{
|
||||
IntegerKeyKind, IntegerRadixCiphertext, RadixCiphertext, RadixClientKey, ServerKey,
|
||||
ClientKey, IntegerKeyKind, IntegerRadixCiphertext, RadixCiphertext, RadixClientKey, ServerKey,
|
||||
};
|
||||
use crate::shortint::parameters::*;
|
||||
use rand::Rng;
|
||||
@@ -23,14 +23,14 @@ pub(crate) const NB_CTXT: usize = 2;
|
||||
pub(crate) trait FunctionExecutor<TestInput, TestOutput> {
|
||||
/// Setups the executor
|
||||
///
|
||||
/// Implementors are expected to be fully functional after this
|
||||
/// Implementers are expected to be fully functional after this
|
||||
/// function has been called.
|
||||
fn setup(&mut self, cks: &RadixClientKey, sks: Arc<ServerKey>);
|
||||
|
||||
/// Executes the function
|
||||
///
|
||||
/// The function receives some inputs and return some output.
|
||||
/// Implementors may have to do more than just calling the function
|
||||
/// Implementers may have to do more than just calling the function
|
||||
/// that is being tested (for example input/output may need to be converted)
|
||||
///
|
||||
/// Look at the test case function to know what are the expected inputs and outputs.
|
||||
@@ -418,55 +418,77 @@ where
|
||||
T: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
|
||||
{
|
||||
let param = param.into();
|
||||
let nb_tests = nb_tests_for_params(param);
|
||||
let nb_tests = nb_tests_smaller_for_params(param);
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
|
||||
let sks = Arc::new(sks);
|
||||
let cks = RadixClientKey::from((cks, NB_CTXT));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(NB_CTXT as u32);
|
||||
assert!(modulus.is_power_of_two());
|
||||
let nb_bits = modulus.ilog2();
|
||||
|
||||
executor.setup(&cks, sks);
|
||||
|
||||
for _ in 0..nb_tests {
|
||||
let clear = rng.gen::<u64>() % modulus;
|
||||
let clear_shift = rng.gen::<u32>();
|
||||
let cks: ClientKey = cks.into();
|
||||
|
||||
let ct = cks.encrypt(clear);
|
||||
for num_blocks in 1..MAX_NB_CTXT {
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(num_blocks as u32);
|
||||
assert!(modulus.is_power_of_two());
|
||||
let nb_bits = modulus.ilog2();
|
||||
for _ in 0..nb_tests {
|
||||
let clear = rng.gen::<u64>() % modulus;
|
||||
let clear_shift = rng.gen::<u32>();
|
||||
|
||||
// case when 0 <= shift < nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift % nb_bits;
|
||||
let shift = cks.encrypt(clear_shift as u64);
|
||||
let ct = cks.encrypt_radix(clear, num_blocks);
|
||||
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt(&encrypted_result);
|
||||
assert_eq!((clear << clear_shift) % modulus, decrypted_result);
|
||||
}
|
||||
// case when 0 <= shift < nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift % nb_bits;
|
||||
let shift = cks.encrypt_radix(clear_shift as u64, num_blocks);
|
||||
|
||||
// case when shift >= nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift.saturating_add(nb_bits);
|
||||
let shift = cks.encrypt(clear_shift as u64);
|
||||
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt(&encrypted_result);
|
||||
// When nb_bits is not a power of two
|
||||
// then the behaviour is not the same
|
||||
let mut nb_bits = modulus.ilog2();
|
||||
if !nb_bits.is_power_of_two() {
|
||||
nb_bits = nb_bits.next_power_of_two();
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
for (i, b) in encrypted_result.blocks.iter().enumerate() {
|
||||
if b.noise_level > NoiseLevel::NOMINAL {
|
||||
println!("{i}: {:?}", b.noise_level);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
encrypted_result
|
||||
.blocks
|
||||
.iter()
|
||||
.all(|b| b.noise_level <= NoiseLevel::NOMINAL),
|
||||
"Expected all blocks to have at most NOMINAL noise level"
|
||||
);
|
||||
let decrypted_result: u64 = cks.decrypt_radix(&encrypted_result);
|
||||
assert_eq!((clear << clear_shift) % modulus, decrypted_result);
|
||||
}
|
||||
|
||||
// case when shift >= nb_bits
|
||||
{
|
||||
let clear_shift = rng.gen_range(nb_bits..modulus as u32);
|
||||
let shift = cks.encrypt_radix(clear_shift as u64, num_blocks);
|
||||
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
assert!(
|
||||
encrypted_result
|
||||
.blocks
|
||||
.iter()
|
||||
.all(|b| b.noise_level <= NoiseLevel::NOMINAL),
|
||||
"Expected all blocks to have at most NOMINAL noise level"
|
||||
);
|
||||
let decrypted_result: u64 = cks.decrypt_radix(&encrypted_result);
|
||||
// When nb_bits is not a power of two
|
||||
// then the behaviour is not the same
|
||||
let mut nb_bits = modulus.ilog2();
|
||||
if !nb_bits.is_power_of_two() {
|
||||
nb_bits = nb_bits.next_power_of_two();
|
||||
}
|
||||
// We mimic wrapping_shl manually as we use a bigger type
|
||||
// than the nb_bits we actually simulate in this test
|
||||
assert_eq!(
|
||||
(clear << (clear_shift % nb_bits)) % modulus,
|
||||
decrypted_result
|
||||
);
|
||||
}
|
||||
// We mimic wrapping_shl manually as we use a bigger type
|
||||
// than the nb_bits we actually simulate in this test
|
||||
assert_eq!(
|
||||
(clear << (clear_shift % nb_bits)) % modulus,
|
||||
decrypted_result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,54 +499,71 @@ where
|
||||
T: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
|
||||
{
|
||||
let param = param.into();
|
||||
let nb_tests = nb_tests_for_params(param);
|
||||
let nb_tests = nb_tests_smaller_for_params(param);
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
|
||||
let sks = Arc::new(sks);
|
||||
let cks = RadixClientKey::from((cks, NB_CTXT));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(NB_CTXT as u32);
|
||||
assert!(modulus.is_power_of_two());
|
||||
let nb_bits = modulus.ilog2();
|
||||
|
||||
executor.setup(&cks, sks);
|
||||
|
||||
for _ in 0..nb_tests {
|
||||
let clear = rng.gen::<u64>() % modulus;
|
||||
let clear_shift = rng.gen::<u32>();
|
||||
let cks: ClientKey = cks.into();
|
||||
|
||||
let ct = cks.encrypt(clear);
|
||||
for num_blocks in 1..MAX_NB_CTXT {
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(num_blocks as u32);
|
||||
assert!(modulus.is_power_of_two());
|
||||
let nb_bits = modulus.ilog2();
|
||||
for _ in 0..nb_tests {
|
||||
let clear = rng.gen::<u64>() % modulus;
|
||||
let clear_shift = rng.gen::<u32>();
|
||||
|
||||
// case when 0 <= shift < nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift % nb_bits;
|
||||
let shift = cks.encrypt(clear_shift as u64);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt(&encrypted_result);
|
||||
assert_eq!((clear >> clear_shift) % modulus, decrypted_result);
|
||||
}
|
||||
let ct = cks.encrypt_radix(clear, num_blocks);
|
||||
|
||||
// case when shift >= nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift.saturating_add(nb_bits);
|
||||
let shift = cks.encrypt(clear_shift as u64);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt(&encrypted_result);
|
||||
|
||||
// When nb_bits is not a power of two
|
||||
// then the behaviour is not the same
|
||||
let mut nb_bits = modulus.ilog2();
|
||||
if !nb_bits.is_power_of_two() {
|
||||
nb_bits = nb_bits.next_power_of_two();
|
||||
// case when 0 <= shift < nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift % nb_bits;
|
||||
let shift = cks.encrypt_radix(clear_shift as u64, num_blocks);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
assert!(
|
||||
encrypted_result
|
||||
.blocks
|
||||
.iter()
|
||||
.all(|b| b.noise_level <= NoiseLevel::NOMINAL),
|
||||
"Expected all blocks to have at most NOMINAL noise level"
|
||||
);
|
||||
let decrypted_result: u64 = cks.decrypt_radix(&encrypted_result);
|
||||
assert_eq!((clear >> clear_shift) % modulus, decrypted_result);
|
||||
}
|
||||
|
||||
// case when shift >= nb_bits
|
||||
{
|
||||
let clear_shift = rng.gen_range(nb_bits..modulus as u32);
|
||||
let shift = cks.encrypt_radix(clear_shift as u64, num_blocks);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
assert!(
|
||||
encrypted_result
|
||||
.blocks
|
||||
.iter()
|
||||
.all(|b| b.noise_level <= NoiseLevel::NOMINAL),
|
||||
"Expected all blocks to have at most NOMINAL noise level"
|
||||
);
|
||||
let decrypted_result: u64 = cks.decrypt_radix(&encrypted_result);
|
||||
|
||||
// When nb_bits is not a power of two
|
||||
// then the behaviour is not the same
|
||||
let mut nb_bits = modulus.ilog2();
|
||||
if !nb_bits.is_power_of_two() {
|
||||
nb_bits = nb_bits.next_power_of_two();
|
||||
}
|
||||
// We mimic wrapping_shr manually as we use a bigger type
|
||||
// than the nb_bits we actually simulate in this test
|
||||
assert_eq!(
|
||||
(clear >> (clear_shift % nb_bits)) % modulus,
|
||||
decrypted_result
|
||||
);
|
||||
}
|
||||
// We mimic wrapping_shr manually as we use a bigger type
|
||||
// than the nb_bits we actually simulate in this test
|
||||
assert_eq!(
|
||||
(clear >> (clear_shift % nb_bits)) % modulus,
|
||||
decrypted_result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -535,51 +574,68 @@ where
|
||||
T: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
|
||||
{
|
||||
let param = param.into();
|
||||
let nb_tests = nb_tests_for_params(param);
|
||||
let nb_tests = nb_tests_smaller_for_params(param);
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
|
||||
let sks = Arc::new(sks);
|
||||
let cks = RadixClientKey::from((cks, NB_CTXT));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(NB_CTXT as u32);
|
||||
assert!(modulus.is_power_of_two());
|
||||
let nb_bits = modulus.ilog2();
|
||||
|
||||
executor.setup(&cks, sks);
|
||||
|
||||
for _ in 0..nb_tests {
|
||||
let clear = rng.gen::<u64>() % modulus;
|
||||
let clear_shift = rng.gen::<u32>();
|
||||
let cks: ClientKey = cks.into();
|
||||
|
||||
let ct = cks.encrypt(clear);
|
||||
for num_blocks in 1..MAX_NB_CTXT {
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(num_blocks as u32);
|
||||
assert!(modulus.is_power_of_two());
|
||||
let nb_bits = modulus.ilog2();
|
||||
for _ in 0..nb_tests {
|
||||
let clear = rng.gen::<u64>() % modulus;
|
||||
let clear_shift = rng.gen::<u32>();
|
||||
|
||||
// case when 0 <= rotate < nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift % nb_bits;
|
||||
let shift = cks.encrypt(clear_shift as u64);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt(&encrypted_result);
|
||||
let expected = rotate_left_helper(clear, clear_shift, nb_bits);
|
||||
assert_eq!(expected, decrypted_result);
|
||||
}
|
||||
let ct = cks.encrypt_radix(clear, num_blocks);
|
||||
|
||||
// case when shift >= nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift.saturating_add(nb_bits);
|
||||
let shift = cks.encrypt(clear_shift as u64);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt(&encrypted_result);
|
||||
// When nb_bits is not a power of two
|
||||
// then the behaviour is not the same
|
||||
let true_nb_bits = nb_bits;
|
||||
let mut nb_bits = nb_bits;
|
||||
if !nb_bits.is_power_of_two() {
|
||||
nb_bits = nb_bits.next_power_of_two();
|
||||
// case when 0 <= rotate < nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift % nb_bits;
|
||||
let shift = cks.encrypt_radix(clear_shift as u64, num_blocks);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt_radix(&encrypted_result);
|
||||
assert!(
|
||||
encrypted_result
|
||||
.blocks
|
||||
.iter()
|
||||
.all(|b| b.noise_level <= NoiseLevel::NOMINAL),
|
||||
"Expected all blocks to have at most NOMINAL noise level"
|
||||
);
|
||||
let expected = rotate_left_helper(clear, clear_shift, nb_bits);
|
||||
assert_eq!(expected, decrypted_result);
|
||||
}
|
||||
|
||||
// case when shift >= nb_bits
|
||||
{
|
||||
let clear_shift = rng.gen_range(nb_bits..modulus as u32);
|
||||
let shift = cks.encrypt_radix(clear_shift as u64, num_blocks);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
assert!(
|
||||
encrypted_result
|
||||
.blocks
|
||||
.iter()
|
||||
.all(|b| b.noise_level <= NoiseLevel::NOMINAL),
|
||||
"Expected all blocks to have at most NOMINAL noise level"
|
||||
);
|
||||
let decrypted_result: u64 = cks.decrypt_radix(&encrypted_result);
|
||||
// When nb_bits is not a power of two
|
||||
// then the behaviour is not the same
|
||||
let true_nb_bits = nb_bits;
|
||||
let mut nb_bits = nb_bits;
|
||||
if !nb_bits.is_power_of_two() {
|
||||
nb_bits = nb_bits.next_power_of_two();
|
||||
}
|
||||
let expected = rotate_left_helper(clear, clear_shift % nb_bits, true_nb_bits);
|
||||
assert_eq!(expected, decrypted_result);
|
||||
}
|
||||
let expected = rotate_left_helper(clear, clear_shift % nb_bits, true_nb_bits);
|
||||
assert_eq!(expected, decrypted_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,51 +646,68 @@ where
|
||||
T: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
|
||||
{
|
||||
let param = param.into();
|
||||
let nb_tests = nb_tests_for_params(param);
|
||||
let nb_tests = nb_tests_smaller_for_params(param);
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
|
||||
let sks = Arc::new(sks);
|
||||
let cks = RadixClientKey::from((cks, NB_CTXT));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(NB_CTXT as u32);
|
||||
assert!(modulus.is_power_of_two());
|
||||
let nb_bits = modulus.ilog2();
|
||||
|
||||
executor.setup(&cks, sks);
|
||||
|
||||
for _ in 0..nb_tests {
|
||||
let clear = rng.gen::<u64>() % modulus;
|
||||
let clear_shift = rng.gen::<u32>();
|
||||
let cks: ClientKey = cks.into();
|
||||
|
||||
let ct = cks.encrypt(clear);
|
||||
for num_blocks in 1..MAX_NB_CTXT {
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(num_blocks as u32);
|
||||
assert!(modulus.is_power_of_two());
|
||||
let nb_bits = modulus.ilog2();
|
||||
for _ in 0..nb_tests {
|
||||
let clear = rng.gen::<u64>() % modulus;
|
||||
let clear_shift = rng.gen::<u32>();
|
||||
|
||||
// case when 0 <= rotate < nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift % nb_bits;
|
||||
let shift = cks.encrypt(clear_shift as u64);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt(&encrypted_result);
|
||||
let expected = rotate_right_helper(clear, clear_shift, nb_bits);
|
||||
assert_eq!(expected, decrypted_result);
|
||||
}
|
||||
let ct = cks.encrypt_radix(clear, num_blocks);
|
||||
|
||||
// case when shift >= nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift.saturating_add(nb_bits);
|
||||
let shift = cks.encrypt(clear_shift as u64);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
let decrypted_result: u64 = cks.decrypt(&encrypted_result);
|
||||
// When nb_bits is not a power of two
|
||||
// then the behaviour is not the same
|
||||
let true_nb_bits = nb_bits;
|
||||
let mut nb_bits = nb_bits;
|
||||
if !nb_bits.is_power_of_two() {
|
||||
nb_bits = nb_bits.next_power_of_two();
|
||||
// case when 0 <= rotate < nb_bits
|
||||
{
|
||||
let clear_shift = clear_shift % nb_bits;
|
||||
let shift = cks.encrypt_radix(clear_shift as u64, num_blocks);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
assert!(
|
||||
encrypted_result
|
||||
.blocks
|
||||
.iter()
|
||||
.all(|b| b.noise_level <= NoiseLevel::NOMINAL),
|
||||
"Expected all blocks to have at most NOMINAL noise level"
|
||||
);
|
||||
let decrypted_result: u64 = cks.decrypt_radix(&encrypted_result);
|
||||
let expected = rotate_right_helper(clear, clear_shift, nb_bits);
|
||||
assert_eq!(expected, decrypted_result);
|
||||
}
|
||||
|
||||
// case when shift >= nb_bits
|
||||
{
|
||||
let clear_shift = rng.gen_range(nb_bits..modulus as u32);
|
||||
let shift = cks.encrypt_radix(clear_shift as u64, num_blocks);
|
||||
let encrypted_result = executor.execute((&ct, &shift));
|
||||
assert!(
|
||||
encrypted_result
|
||||
.blocks
|
||||
.iter()
|
||||
.all(|b| b.noise_level <= NoiseLevel::NOMINAL),
|
||||
"Expected all blocks to have at most NOMINAL noise level"
|
||||
);
|
||||
let decrypted_result: u64 = cks.decrypt_radix(&encrypted_result);
|
||||
// When nb_bits is not a power of two
|
||||
// then the behaviour is not the same
|
||||
let true_nb_bits = nb_bits;
|
||||
let mut nb_bits = nb_bits;
|
||||
if !nb_bits.is_power_of_two() {
|
||||
nb_bits = nb_bits.next_power_of_two();
|
||||
}
|
||||
let expected = rotate_right_helper(clear, clear_shift % nb_bits, true_nb_bits);
|
||||
assert_eq!(expected, decrypted_result);
|
||||
}
|
||||
let expected = rotate_right_helper(clear, clear_shift % nb_bits, true_nb_bits);
|
||||
assert_eq!(expected, decrypted_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -19,6 +19,17 @@ pub struct CompressedCiphertextList {
|
||||
pub count: CiphertextCount,
|
||||
}
|
||||
|
||||
impl CompressedCiphertextList {
|
||||
/// Returns how many u64 are needed to store the packed elements
|
||||
#[cfg(all(test, feature = "gpu"))]
|
||||
pub(crate) fn flat_len(&self) -> usize {
|
||||
self.modulus_switched_glwe_ciphertext_list
|
||||
.iter()
|
||||
.map(|glwe| glwe.packed_integers.packed_coeffs.len())
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterSetConformant for CompressedCiphertextList {
|
||||
type ParameterSet = CompressedCiphertextConformanceParams;
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ impl CompressionKey {
|
||||
|
||||
for ct in ct_list {
|
||||
assert!(
|
||||
ct.noise_level() == NoiseLevel::NOMINAL,
|
||||
ct.noise_level() == NoiseLevel::NOMINAL
|
||||
|| ct.noise_level() == NoiseLevel::ZERO,
|
||||
"Ciphertexts must have a nominal (post PBS) noise to be compressed"
|
||||
);
|
||||
|
||||
|
||||
@@ -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