Compare commits

...

17 Commits

Author SHA1 Message Date
tmontaigu
4c9b0816aa chore(wasm): update dependencies of wasm tests 2024-02-28 14:59:10 +01:00
Arthur Meyre
b4095f9c9f chore(ci): use npm 20 2024-02-28 14:59:10 +01:00
Arthur Meyre
c98e7001cd chore(ci): pin bumpalo so that CI can pass 2024-02-28 14:59:10 +01:00
Arthur Meyre
4396c45885 fix(core): fix test stack size 2024-02-28 14:59:10 +01:00
Arthur Meyre
1749d2027f chore(tfhe): pin clap 2024-02-28 14:59:10 +01:00
Arthur Meyre
c6cad2d95d chore(tfhe): bump version to 0.3.2 2024-02-28 14:59:10 +01:00
Arthur Meyre
eeb203c6c8 fix(shortint): use proper noise value during compact list encryption 2024-02-28 14:59:10 +01:00
Arthur Meyre
e7745deb8b chore(tfhe): pin bytemuck to avoid nightly stdsimd breakage 2024-02-28 14:59:10 +01:00
aquint-zama
5db6d3243c chore(docs): design upgrade 2023-08-11 10:12:33 +02:00
J-B Orfila
c58b0a3f68 chore(bench): add new parameter sets to bench 2023-08-09 09:56:15 +02:00
J-B Orfila
1f95e2d45a chore(tfhe): bump version to 0.3.1 2023-08-09 09:56:15 +02:00
tmontaigu
9bd9180261 feat(integer): make full_propagate_parallelized more parallel
Using the functions that were introduced recently,
it is possible to make the full_propagate_parallelized method
more parallel than it was, resulting in faster computations.

The new carry propapagation should now be the cost of
a default add + one PBS, so ~400ms in 256 instead of ~3s.

However its probably slower for smaller number of blocks (eg 4 blocks)

This is done by extracting carry and messages in parallel,
then adding the carries to the correct message, the final step
is to use the single carry propapagation function.
2023-08-09 09:56:15 +02:00
tmontaigu
2d7251f88c feat(hlapi): add if_then_else 2023-08-09 09:56:15 +02:00
tmontaigu
59c5ef81e2 feat(integer): add if_then_else
This adds if_then_else (aka cmux / select)
to the integer API.

This also makes the min/max implementation use that
cmux instead of their own version of it, and allows
to save one pbs.
2023-08-09 09:56:15 +02:00
J-B Orfila
197afb62d0 feat(boolean): add KS-PBS pattern choice to boolean
Co-authored-by: tmontaigu<thomas.montaigu@laposte.net>
2023-08-09 09:56:15 +02:00
tmontaigu
205de1966a feat(hlapi): allow scalar ops on values up to U256
This enables to use u128 and U256 as operands to
operations in the high level api.

BREAKING CHANGE: a breaking change in the C API for scalar operations
for FheUint128 and FheUint256 as they previously required
a u64 and now a U218 / U256 respectively.
2023-08-09 09:56:15 +02:00
J-B Orfila
89def834b6 docs: update the README for v0.3 2023-07-27 15:16:21 +02:00
69 changed files with 5109 additions and 2305 deletions

View File

@@ -10,7 +10,7 @@ jobs:
- name: Check first line
uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee
with:
pattern: '^((feat|fix|chore|refactor|style|test|docs|doc)\(\w+\)\:) .+$'
pattern: '^((feat|fix|chore|refactor|style|test|docs|doc)(\(\w+\))?\:) .+$'
flags: "gs"
error: 'Your first line has to contain a commit type and scope like "feat(my_feature): msg".'
excludeDescription: "true" # optional: this excludes the description body of a pull request

148
README.md
View File

@@ -31,7 +31,9 @@ implementation. The goal is to have a stable, simple, high-performance, and
production-ready library for all the advanced features of TFHE.
## Getting Started
The steps to run a first example are described below.
### Cargo.toml configuration
To use the latest version of `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
+ For x86_64-based machines running Unix-like OSes:
@@ -57,95 +59,69 @@ tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64"]
Note: aarch64-based machines are not yet supported for Windows as it's currently missing an entropy source to be able to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs
## A simple example
Here is a full example:
``` rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Basic configuration to use homomorphic integers
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
// Key generation
let (client_key, server_keys) = generate_keys(config);
let clear_a = 1344u32;
let clear_b = 5u32;
let clear_c = 7u8;
// Encrypting the input data using the (private) client_key
// FheUint32: Encrypted equivalent to u32
let mut encrypted_a = FheUint32::try_encrypt(clear_a, &client_key)?;
let encrypted_b = FheUint32::try_encrypt(clear_b, &client_key)?;
// FheUint8: Encrypted equivalent to u8
let encrypted_c = FheUint8::try_encrypt(clear_c, &client_key)?;
// On the server side:
set_server_key(server_keys);
// Clear equivalent computations: 1344 * 8 = 10752
let encrypted_res_mul = &encrypted_a * &encrypted_b;
// Clear equivalent computations: 1344 >> 8 = 42
encrypted_a = &encrypted_res_mul >> &encrypted_b;
// Clear equivalent computations: let casted_a = a as u8;
let casted_a: FheUint8 = encrypted_a.cast_into();
// Clear equivalent computations: min(42, 7) = 7
let encrypted_res_min = &casted_a.min(&encrypted_c);
// Operation between clear and encrypted data:
// Clear equivalent computations: 7 & 1 = 1
let encrypted_res = encrypted_res_min & 1_u8;
// Decrypting on the client side:
let clear_res: u8 = encrypted_res.decrypt(&client_key);
assert_eq!(clear_res, 1_u8);
Ok(())
}
```
To run this code, use the following command:
<p align="center"> <code> cargo run --release </code> </p>
Note that when running code that uses `tfhe-rs`, it is highly recommended
to run in release mode with cargo's `--release` flag to have the best performances possible,
eg: `cargo run --release`.
Here is a full example evaluating a Boolean circuit:
```rust
use tfhe::boolean::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys();
// We use the client secret key to encrypt two messages:
let ct_1 = client_key.encrypt(true);
let ct_2 = client_key.encrypt(false);
// We use the server public key to execute a boolean circuit:
// if ((NOT ct_2) NAND (ct_1 AND ct_2)) then (NOT ct_2) else (ct_1 AND ct_2)
let ct_3 = server_key.not(&ct_2);
let ct_4 = server_key.and(&ct_1, &ct_2);
let ct_5 = server_key.nand(&ct_3, &ct_4);
let ct_6 = server_key.mux(&ct_5, &ct_3, &ct_4);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_6);
assert_eq!(output, true);
}
```
Another example of how the library can be used with shortints:
```rust
use tfhe::shortint::prelude::*;
fn main() {
// Generate a set of client/server keys
// with 2 bits of message and 2 bits of carry
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let msg2 = 2;
// Encrypt two messages using the (private) client key:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
// Homomorphically compute an addition
let ct_add = server_key.unchecked_add(&ct_1, &ct_2);
// Define the Hamming weight function
// f: x -> sum of the bits of x
let f = |x:u64| x.count_ones() as u64;
// Generate the lookup table for the function
let acc = server_key.generate_lookup_table(f);
// Compute the function over the ciphertext using the PBS
let ct_res = server_key.apply_lookup_table(&ct_add, &acc);
// Decrypt the ciphertext using the (private) client key
let output = client_key.decrypt(&ct_res);
assert_eq!(output, f(msg1 + msg2));
}
```
An example using integer:
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
fn main() {
// We create keys to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, 8);
let clear_a = 2382u16;
let clear_b = 29374u16;
let mut a = cks.encrypt(clear_a as u64);
let mut b = cks.encrypt(clear_b as u64);
let encrypted_max = sks.smart_max_parallelized(&mut a, &mut b);
let decrypted_max: u64 = cks.decrypt(&encrypted_max);
assert_eq!(decrypted_max as u16, clear_a.max(clear_b))
}
```
## Contributing

View File

@@ -34,6 +34,6 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > install-rustup.s
chmod +x install-node.sh && \
./install-node.sh && \
. "$HOME/.nvm/nvm.sh" && \
bash -i -c 'nvm install node && nvm use node'
bash -i -c 'nvm install 20 && nvm use 20'
WORKDIR /tfhe-wasm-tests/tfhe-rs/

View File

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "0.3.0"
version = "0.3.2"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
@@ -29,7 +29,7 @@ lazy_static = { version = "1.4.0" }
criterion = "0.4.0"
doc-comment = "0.3.3"
serde_json = "1.0.94"
clap = { version = "4.2.7", features = ["derive"] }
clap = { version = "=4.2.7", features = ["derive"] }
# Used in user documentation
bincode = "1.3.3"
fs2 = { version = "0.4.3" }
@@ -65,15 +65,17 @@ fs2 = { version = "0.4.3", optional = true }
itertools = "0.10.5"
# wasm deps
wasm-bindgen = { version = "0.2.86", features = [
wasm-bindgen = { version = "=0.2.86", features = [
"serde-serialize",
], optional = true }
# MSRV was bumped in a minor update, pin to still be able to build in CI
bumpalo = { version = "=3.14" }
wasm-bindgen-rayon = { version = "1.0", optional = true }
js-sys = { version = "0.3", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
serde-wasm-bindgen = { version = "0.4", optional = true }
getrandom = { version = "0.2.8", optional = true }
bytemuck = "1.13.1"
bytemuck = "=1.14.1"
[features]
boolean = []

View File

@@ -4,14 +4,20 @@ use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tfhe::boolean::client_key::ClientKey;
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
use tfhe::boolean::prelude::BinaryBooleanGates;
use tfhe::boolean::parameters::{
BooleanParameters, DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
};
use tfhe::boolean::prelude::{BinaryBooleanGates, DEFAULT_PARAMETERS_KS_PBS, TFHE_LIB_PARAMETERS};
use tfhe::boolean::server_key::ServerKey;
criterion_group!(
gates_benches,
bench_default_parameters,
bench_tfhe_lib_parameters
bench_default_parameters_ks_pbs,
bench_low_prob_parameters,
bench_low_prob_parameters_ks_pbs,
bench_tfhe_lib_parameters,
);
criterion_main!(gates_benches);
@@ -79,6 +85,26 @@ fn bench_default_parameters(c: &mut Criterion) {
benchs(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
}
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
benchs(c, TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS");
fn bench_default_parameters_ks_pbs(c: &mut Criterion) {
benchs(c, DEFAULT_PARAMETERS_KS_PBS, "DEFAULT_PARAMETERS_KS_PBS");
}
fn bench_low_prob_parameters(c: &mut Criterion) {
benchs(
c,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS",
);
}
fn bench_low_prob_parameters_ks_pbs(c: &mut Criterion) {
benchs(
c,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS",
);
}
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
benchs(c, TFHE_LIB_PARAMETERS, " TFHE_LIB_PARAMETERS");
}

View File

@@ -5,7 +5,9 @@ use rayon::prelude::*;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use serde::Serialize;
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
use tfhe::boolean::parameters::{
BooleanParameters, DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
};
use tfhe::core_crypto::prelude::*;
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::*;
@@ -31,7 +33,10 @@ const SHORTINT_BENCH_PARAMS: [ClassicPBSParameters; 15] = [
const BOOLEAN_BENCH_PARAMS: [(&str, BooleanParameters); 2] = [
("BOOLEAN_DEFAULT_PARAMS", DEFAULT_PARAMETERS),
("BOOLEAN_TFHE_LIB_PARAMS", TFHE_LIB_PARAMETERS),
(
"BOOLEAN_TFHE_LIB_PARAMS",
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
),
];
criterion_group!(

View File

@@ -8,6 +8,7 @@ use std::env;
use criterion::{criterion_group, Criterion};
use itertools::iproduct;
use rand::rngs::ThreadRng;
use rand::Rng;
use std::vec::IntoIter;
use tfhe::integer::keycache::KEY_CACHE;
@@ -457,6 +458,92 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F>(
bench_group.finish()
}
// Functions used to apply different way of selecting a scalar based on the context.
fn default_scalar(rng: &mut ThreadRng, _clear_bit_size: usize) -> u64 {
rng.gen::<u64>()
}
fn shift_scalar(_rng: &mut ThreadRng, _clear_bit_size: usize) -> u64 {
// Shifting by one is the worst case scenario.
1
}
fn mul_scalar(rng: &mut ThreadRng, _clear_bit_size: usize) -> u64 {
loop {
let scalar = rng.gen_range(3u64..=u64::MAX);
// If scalar is power of two, it is just a shit, which is an happy path.
if !scalar.is_power_of_two() {
return scalar;
}
}
}
fn div_scalar(rng: &mut ThreadRng, clear_bit_size: usize) -> u64 {
loop {
let scalar = rng.gen_range(1..=u64::MAX);
// Avoid overflow issues for u64 where we would take values mod 1
if (scalar as u128 % (1u128 << clear_bit_size)) != 0 {
return scalar;
}
}
}
fn if_then_else_parallelized(c: &mut Criterion) {
let bench_name = "integer::if_then_else_parallelized";
let display_name = "if_then_else";
let mut bench_group = c.benchmark_group(bench_name);
bench_group
.sample_size(15)
.measurement_time(std::time::Duration::from_secs(60));
let mut rng = rand::thread_rng();
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
let param_name = param.name();
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let encrypt_tree_values = || {
let clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
let ct_0 = cks.encrypt_radix(clear_0, num_block);
let clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
let clear_1 = tfhe::integer::U256::from((clearlow, clearhigh));
let ct_1 = cks.encrypt_radix(clear_1, num_block);
let cond = sks.create_trivial_radix(rng.gen_bool(0.5) as u64, num_block);
(cond, ct_0, ct_1)
};
b.iter_batched(
encrypt_tree_values,
|(condition, true_ct, false_ct)| {
sks.if_then_else_parallelized(&condition, &true_ct, &false_ct)
},
criterion::BatchSize::SmallInput,
)
});
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus().0.ilog2(); num_block],
);
}
bench_group.finish()
}
macro_rules! define_server_key_bench_unary_fn (
(method_name: $server_key_method:ident, display_name:$name:ident) => {
fn $server_key_method(c: &mut Criterion) {
@@ -794,6 +881,7 @@ criterion_group!(
right_shift_parallelized,
rotate_left_parallelized,
rotate_right_parallelized,
if_then_else_parallelized,
);
criterion_group!(

View File

@@ -9,7 +9,7 @@
* [Benchmarks](getting_started/benchmarks.md)
* [Security and Cryptography](getting_started/security_and_cryptography.md)
## Tutorials
## Tutorials
* [Homomorphic Parity Bit](tutorials/parity_bit.md)
* [Homomorphic Case Changing on Latin String](tutorials/latin_fhe_string.md)
@@ -25,17 +25,17 @@
## Fine-grained APIs
* [Quick Start](fine_grained_api/quick_start.md)
* [Boolean](fine_grained_api/Boolean/tutorial.md)
* [Boolean](fine_grained_api/Boolean/readme.md)
* [Operations](fine_grained_api/Boolean/operations.md)
* [Cryptographic Parameters](fine_grained_api/Boolean/parameters.md)
* [Serialization/Deserialization](fine_grained_api/Boolean/serialization.md)
* [Shortint](fine_grained_api/shortint/tutorial.md)
* [Shortint](fine_grained_api/shortint/readme.md)
* [Operations](fine_grained_api/shortint/operations.md)
* [Cryptographic Parameters](fine_grained_api/shortint/parameters.md)
* [Serialization/Deserialization](fine_grained_api/shortint/serialization.md)
* [Integer](fine_grained_api/integer/tutorial.md)
* [Integer](fine_grained_api/integer/readme.md)
* [Operations](fine_grained_api/integer/operations.md)
* [Cryptographic Parameters](fine_grained_api/integer/parameters.md)
* [Serialization/Deserialization](fine_grained_api/integer/serialization.md)

BIN
tfhe/docs/_static/carry.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
tfhe/docs/_static/multisum.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
tfhe/docs/_static/overflow.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
tfhe/docs/_static/sha256.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -17,11 +17,8 @@ The sha256 function processes the input data in blocks or chunks of 512 bits. Be
Or visually:
```
0 L L+1 L+1+k L+1+k+64
|-----------------------------------|---|--------------------------------|----------------------|
Original input (L bits) "1" bit "0" bits Encoding of the number L
```
![](../_static/sha256.png)
Where the numbers on the top represent the length of the padded input at each position, and L+1+k+64 is a multiple of 512 (the length of the padded input).
#### Operations and functions
@@ -63,7 +60,7 @@ Note that all these operations can be evaluated homomorphically. ROTR and SHR ca
#### Sha256 computation
As we have mentioned, the sha256 function works with chunks of 512 bits. For each chunk, we will compute 64 32-bit words. 16 will come from the 512 bits and the rest will be computed using the previous functions. After computing the 64 words, and still within the same chunk iteration, a compression loop will compute a hash value (8 32-bit words), again using the previous functions and some constants to mix everything up. When we finish the last chunk iteration, the resulting hash values will be the output of the sha256 function.
As we have mentioned, the sha256 function works with chunks of 512 bits. For each chunk, we will compute 64 32-bit words. 16 will come from the 512 bits and the rest will be computed using the previous functions. After computing the 64 words, and still within the same chunk iteration, a compression loop will compute a hash value (8 32-bit words), again using the previous functions and some constants to mix everything up. When we finish the last chunk iteration, the resulting hash values will be the output of the sha256 function.
Here is how this function looks like using arrays of 32 bools to represent words:
@@ -317,6 +314,6 @@ By using ```stdin``` we can supply the data to hash using a file instead of the
Our implementation also accepts hexadecimal inputs. To be considered as such, the input must start with "0x" and contain only valid hex digits (otherwise it's interpreted as text).
Finally see that padding is executed on the client side. This has the advantage of hiding the exact length of the input to the server, who already doesn't know anything about the contents of it but may extract information from the length.
Finally see that padding is executed on the client side. This has the advantage of hiding the exact length of the input to the server, who already doesn't know anything about the contents of it but may extract information from the length.
Another option would be to perform padding on the server side. The padding function would receive the encrypted input and pad it with trivial bit encryptions. We could then integrate the padding function inside the ```sha256_fhe``` function computed by the server.

View File

@@ -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.3.0", features = [ "x86_64-unix" ] }
tfhe = { version = "0.3.2", features = [ "x86_64-unix" ] }
```
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available (like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available). To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
@@ -19,19 +19,19 @@ For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `a
In short: For `x86_64`-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
tfhe = { version = "0.3.2", features = ["x86_64-unix"] }
```
For Apple Silicon or aarch64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.3.0", features = ["aarch64-unix"] }
tfhe = { version = "0.3.2", features = ["aarch64-unix"] }
```
For `x86_64`-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
```toml
tfhe = { version = "0.3.0", features = ["x86_64"] }
tfhe = { version = "0.3.2", features = ["x86_64"] }
```
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.

View File

@@ -38,6 +38,7 @@ fn main() {
DecompositionLevelCount(2),
DecompositionBaseLog(2),
DecompositionLevelCount(5),
EncryptionKeyChoice::Small,
)
};
}

View File

@@ -2,7 +2,9 @@
Due to their nature, homomorphic operations are naturally slower than their clear equivalent. Some timings are exposed for basic operations. For completeness, benchmarks for other libraries are also given.
{% hint style="info" %}
All benchmarks were launched on an AWS m6i.metal with the following specifications: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
{% endhint %}
## Boolean
@@ -31,7 +33,7 @@ This measures the execution time of a single binary Boolean gate.
## Integer
This measures the execution time for some operation sets of tfhe-rs::integer.
| Operation \ Size | `FheUint8` | `FheUint16` | `FheUint32` | ` FheUint64` | `FheUint128` | `FheUint256` |
|--------------------------------------------------------|------------|-------------|-------------|--------------|--------------|--------------|
| Negation (`-`) | 80.4 ms | 106 ms | 132 ms | 193 ms | 257 ms | 348 ms |
@@ -48,7 +50,7 @@ This measures the execution time for some operation sets of tfhe-rs::integer.
All timings are related to parallelized Radix-based integer operations, where each block is encrypted using the default parameters (i.e., PARAM\_MESSAGE\_2\_CARRY\_2, more information about parameters can be found [here](../fine_grained_api/shortint/parameters.md)).
To ensure predictable timings, the operation flavor is the `default` one: the carry is propagated if needed. The operation costs could be reduced by using `unchecked`, `checked`, or `smart`.
To ensure predictable timings, the operation flavor is the `default` one: the carry is propagated if needed. The operation costs could be reduced by using `unchecked`, `checked`, or `smart`.
## Shortint

View File

@@ -7,11 +7,11 @@
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
```toml
tfhe = { version = "0.3.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
tfhe = { version = "0.3.2", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
```
{% hint style="info" %}
When running code that uses `TFHE-rs`, it is highly recommended to run in release mode with cargo's `--release` flag to have the best possible performance, eg: `cargo run --release`.
{% hint style="success" %}
When running code that uses `TFHE-rs`, it is highly recommended to run in release mode with cargo's `--release` flag to have the best possible performance
{% endhint %}
@@ -27,6 +27,6 @@ TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (
| Windows | `x86_64` | Unsupported |
{% hint style="info" %}
Users who have ARM devices can use TFHE-rs by compiling using the `nightly` toolchain (see
Users who have ARM devices can use TFHE-rs by compiling using the `nightly` toolchain (see
[Configuration](../how_to/rust_configuration.md) for more details).
{% endhint %}
{% endhint %}

View File

@@ -43,7 +43,7 @@ The list of supported operations is:
Native small homomorphic integer types (e.g., FheUint3 or FheUint4) easily compute various operations. In general, computing over encrypted data is as easy as computing over clear data, since the same operation symbol is used. The addition between two ciphertexts is done using the symbol `+` between two FheUint values. Many operations can be computed between a clear value (i.e. a scalar) and a ciphertext.
In Rust, operations on native types are modular. For example, computations on `u8` are carried out modulo 2^8. A similar idea applies for FheUintX, where operations are done modulo 2^X. For FheUint3, operations are done modulo 8 = 2^3.
In Rust, operations on native types are modular. For example, computations on `u8` are carried out modulo $$2^{8}$$. A similar idea applies for FheUintX, where operations are done modulo $$2^{X}$$. For FheUint3, operations are done modulo $$8 = 2^{3}$$.
### Arithmetic operations.
@@ -72,7 +72,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let clear_c = 2;
@@ -85,10 +85,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
a = a * &b; // Clear equivalent computations: 7 * 3 mod 8 = 5
b = &b + &c; // Clear equivalent computations: 3 + 2 mod 8 = 5
b = b - 5; // Clear equivalent computations: 5 - 5 mod 8 = 0
let dec_a = a.decrypt(&keys);
let dec_b = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, (clear_a * clear_b) % 8);
assert_eq!(dec_b, ((clear_b + clear_c) - 5) % 8);
@@ -124,20 +124,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
a = a ^ &b;
b = b ^ &a;
a = a ^ &b;
let dec_a = a.decrypt(&keys);
let dec_b = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, clear_b);
assert_eq!(dec_b, clear_a);
@@ -179,13 +179,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
assert_eq!(a.gt(&b).decrypt(&keys) != 0, true);
assert_eq!(b.le(&a).decrypt(&keys) != 0, true);
@@ -237,13 +237,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint2().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 1;
let clear_b = 3;
let a = FheUint2::try_encrypt(clear_a, &keys)?;
let b = FheUint2::try_encrypt(clear_b, &keys)?;
let c = a.bivariate_function(&b, std::cmp::max);
let decrypted = c.decrypt(&keys);
assert_eq!(decrypted, std::cmp::max(clear_a, clear_b) as u8);
@@ -286,7 +286,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 15_u64;
let clear_b = 27_u64;
let clear_c = 43_u64;
@@ -299,10 +299,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
a = a * &b; // Clear equivalent computations: 15 * 27 mod 256 = 149
b = &b + &c; // Clear equivalent computations: 27 + 43 mod 256 = 70
b = b - 76u8; // Clear equivalent computations: 70 - 76 mod 256 = 250
let dec_a: u8 = a.decrypt(&keys);
let dec_b: u8 = b.decrypt(&keys);
assert_eq!(dec_a, ((clear_a * clear_b) % 256_u64) as u8);
assert_eq!(dec_b, (((clear_b + clear_c).wrapping_sub(76_u64)) % 256_u64) as u8);
@@ -337,7 +337,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 164;
let clear_b = 212;

View File

@@ -46,7 +46,7 @@ fn main() {
The default configuration for x86 Unix machines:
```toml
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
tfhe = { version = "0.3.2", features = ["integer", "x86_64-unix"]}
```
Configuration options for different platforms can be found [here](../getting_started/installation.md). Other rust and homomorphic types features can be found [here](../how_to/rust_configuration.md).

View File

@@ -18,6 +18,8 @@ Then, a small random value called noise is added to the least significant bits.
$$plaintext = (\Delta * m) + e$$
$$m \in \mathbb{Z}_p$$
![](../_static/lwe.png)
To go from a **plaintext** to a **ciphertext,** one must encrypt the plaintext using a secret key.
@@ -68,7 +70,7 @@ In `TFHE-rs`, noise is encoded in the least significant bits of each plaintext.
The figure below illustrates this problem in the case of an addition, where an extra bit of noise is incurred as a result.
![Noise overtaking the plaintexts after homomorphic addition. Most significant bits are on the left.](../_static/fig7.png)
![Noise overtaking the plaintexts after homomorphic addition. Most significant bits are on the left.](../_static/overflow.png)
`TFHE-rs` offers the ability to automatically manage noise by performing bootstrapping operations to reset the noise.
@@ -84,7 +86,7 @@ Since encoded values have a fixed precision, operating on them can produce resul
As an example, consider adding two ciphertexts. Adding two values could end up outside the range of either ciphertext, and thus necessitate a carry, which would then be carried onto the first padding bit. In the figure below, each plaintext over 32 bits has one bit of padding on its left (i.e., the most significant bit). After the addition, the padding bit is no longer available, as it has been used in order for the carry. This is referred to as **consuming** bits of padding. Since no padding is left, there is no guarantee that further additions would yield correct results.
![](../_static/fig6.png)
![](../_static/carry.png)
### Security.

View File

@@ -11,7 +11,7 @@ To serialize our data, a [data format](https://serde.rs/#data-formats) should be
[dependencies]
# ...
tfhe = { version = "0.3.0", features = ["integer","x86_64-unix"]}
tfhe = { version = "0.3.2", features = ["integer","x86_64-unix"]}
bincode = "1.3.3"
```

View File

@@ -48,7 +48,7 @@ To use the `FheUint8` type, the `integer` feature must be activated:
[dependencies]
# Default configuration for x86 Unix machines:
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
tfhe = { version = "0.3.2", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).

View File

@@ -21,7 +21,7 @@ To use Booleans, the `booleans` feature in our Cargo.toml must be enabled:
# Cargo.toml
# Default configuration for x86 Unix machines:
tfhe = { version = "0.3.0", features = ["boolean", "x86_64-unix"]}
tfhe = { version = "0.3.2", features = ["boolean", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).

View File

@@ -5,7 +5,7 @@ use crate::utilities::{write_to_json, OperatorType};
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use tfhe::boolean::parameters::{DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
use tfhe::boolean::parameters::{DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165};
use tfhe::boolean::{client_key, server_key};
fn write_result(file: &mut File, name: &str, value: usize) {
@@ -17,7 +17,7 @@ fn write_result(file: &mut File, name: &str, value: usize) {
fn client_server_key_sizes(results_file: &Path) {
let boolean_params_vec = vec![
(DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS"),
(TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS"),
(PARAMETERS_ERROR_PROB_2_POW_MINUS_165, "TFHE_LIB_PARAMETERS"),
];
File::create(results_file).expect("create results file failed");
let mut file = OpenOptions::new()

View File

@@ -1,11 +1,11 @@
use tfhe::boolean::client_key::ClientKey;
use tfhe::boolean::parameters::TFHE_LIB_PARAMETERS;
use tfhe::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165;
use tfhe::boolean::prelude::BinaryBooleanGates;
use tfhe::boolean::server_key::ServerKey;
fn main() {
// let (cks, sks) = gen_keys();
let cks = ClientKey::new(&TFHE_LIB_PARAMETERS);
let cks = ClientKey::new(&PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
let sks = ServerKey::new(&cks);
let left = false;

View File

@@ -124,11 +124,11 @@ impl ClientKey {
/// ```rust
/// # fn main() {
/// use tfhe::boolean::client_key::ClientKey;
/// use tfhe::boolean::parameters::TFHE_LIB_PARAMETERS;
/// use tfhe::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165;
/// use tfhe::boolean::prelude::*;
///
/// // Generate the client key:
/// let cks = ClientKey::new(&TFHE_LIB_PARAMETERS);
/// let cks = ClientKey::new(&PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
/// # }
/// ```
pub fn new(parameter_set: &BooleanParameters) -> ClientKey {

View File

@@ -4,7 +4,7 @@ use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
use crate::core_crypto::commons::generators::{DeterministicSeeder, EncryptionRandomGenerator};
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
use crate::core_crypto::commons::parameters::CiphertextModulus;
use crate::core_crypto::commons::parameters::{CiphertextModulus, PBSOrder};
use crate::core_crypto::entities::*;
use crate::core_crypto::fft_impl::fft64::math::fft::Fft;
use serde::{Deserialize, Serialize};
@@ -19,24 +19,27 @@ struct Memory {
buffer: Vec<u32>,
}
pub struct BuffersRef<'a> {
pub(crate) lookup_table: GlweCiphertextMutView<'a, u32>,
// For the intermediate keyswitch result in the case of a big ciphertext
pub(crate) buffer_lwe_after_ks: LweCiphertextMutView<'a, u32>,
// For the intermediate PBS result in the case of a smallciphertext
pub(crate) buffer_lwe_after_pbs: LweCiphertextMutView<'a, u32>,
}
impl Memory {
/// Return a tuple with buffers that matches the server key.
///
/// - The first element is the accumulator for bootstrap step.
/// - The second element is a lwe buffer where the result of the of the bootstrap should be
/// written
fn as_buffers(
&mut self,
server_key: &ServerKey,
) -> (GlweCiphertextView<'_, u32>, LweCiphertextMutView<'_, u32>) {
fn as_buffers(&mut self, server_key: &ServerKey) -> BuffersRef<'_> {
let num_elem_in_accumulator = server_key.bootstrapping_key.glwe_size().0
* server_key.bootstrapping_key.polynomial_size().0;
let num_elem_in_lwe = server_key
let num_elem_in_lwe_after_ks = server_key.key_switching_key.output_lwe_size().0;
let num_elem_in_lwe_after_pbs = server_key
.bootstrapping_key
.output_lwe_dimension()
.to_lwe_size()
.0;
let total_elem_needed = num_elem_in_accumulator + num_elem_in_lwe;
let total_elem_needed =
num_elem_in_accumulator + num_elem_in_lwe_after_ks + num_elem_in_lwe_after_pbs;
let all_elements = if self.buffer.len() < total_elem_needed {
self.buffer.resize(total_elem_needed, 0u32);
@@ -45,30 +48,35 @@ impl Memory {
&mut self.buffer[..total_elem_needed]
};
let (accumulator_elements, lwe_elements) =
let (accumulator_elements, other_elements) =
all_elements.split_at_mut(num_elem_in_accumulator);
{
let mut accumulator = GlweCiphertextMutView::from_container(
accumulator_elements,
server_key.bootstrapping_key.polynomial_size(),
CiphertextModulus::new_native(),
);
accumulator.get_mut_mask().as_mut().fill(0u32);
accumulator.get_mut_body().as_mut().fill(PLAINTEXT_TRUE);
}
let accumulator = GlweCiphertextView::from_container(
let mut acc = GlweCiphertext::from_container(
accumulator_elements,
server_key.bootstrapping_key.polynomial_size(),
CiphertextModulus::new_native(),
);
let lwe =
LweCiphertextMutView::from_container(lwe_elements, CiphertextModulus::new_native());
acc.get_mut_mask().as_mut().fill(0u32);
acc.get_mut_body().as_mut().fill(PLAINTEXT_TRUE);
(accumulator, lwe)
let (after_ks_elements, after_pbs_elements) =
other_elements.split_at_mut(num_elem_in_lwe_after_ks);
let buffer_lwe_after_ks = LweCiphertextMutView::from_container(
after_ks_elements,
CiphertextModulus::new_native(),
);
let buffer_lwe_after_pbs = LweCiphertextMutView::from_container(
after_pbs_elements,
CiphertextModulus::new_native(),
);
BuffersRef {
lookup_table: acc,
buffer_lwe_after_ks,
buffer_lwe_after_pbs,
}
}
}
@@ -86,6 +94,7 @@ impl Memory {
pub struct ServerKey {
pub(crate) bootstrapping_key: FourierLweBootstrapKeyOwned,
pub(crate) key_switching_key: LweKeyswitchKeyOwned<u32>,
pub(crate) pbs_order: PBSOrder,
}
impl ServerKey {
@@ -120,6 +129,7 @@ impl ServerKey {
pub struct CompressedServerKey {
pub(crate) bootstrapping_key: SeededLweBootstrapKeyOwned<u32>,
pub(crate) key_switching_key: SeededLweKeyswitchKeyOwned<u32>,
pub(crate) pbs_order: PBSOrder,
}
/// Perform ciphertext bootstraps on the CPU
@@ -204,6 +214,7 @@ impl Bootstrapper {
Ok(ServerKey {
bootstrapping_key: fourier_bsk,
key_switching_key: ksk,
pbs_order: cks.parameters.encryption_key_choice.into(),
})
}
@@ -249,6 +260,7 @@ impl Bootstrapper {
Ok(CompressedServerKey {
bootstrapping_key,
key_switching_key,
pbs_order: cks.parameters.encryption_key_choice.into(),
})
}
@@ -257,7 +269,11 @@ impl Bootstrapper {
input: &LweCiphertextOwned<u32>,
server_key: &ServerKey,
) -> Result<LweCiphertextOwned<u32>, Box<dyn Error>> {
let (accumulator, mut buffer_after_pbs) = self.memory.as_buffers(server_key);
let BuffersRef {
lookup_table: accumulator,
mut buffer_lwe_after_pbs,
..
} = self.memory.as_buffers(server_key);
let fourier_bsk = &server_key.bootstrapping_key;
@@ -277,7 +293,7 @@ impl Bootstrapper {
programmable_bootstrap_lwe_ciphertext_mem_optimized(
input,
&mut buffer_after_pbs,
&mut buffer_lwe_after_pbs,
&accumulator,
fourier_bsk,
fft,
@@ -285,7 +301,7 @@ impl Bootstrapper {
);
Ok(LweCiphertext::from_container(
buffer_after_pbs.as_ref().to_owned(),
buffer_lwe_after_pbs.as_ref().to_owned(),
input.ciphertext_modulus(),
))
}
@@ -315,7 +331,11 @@ impl Bootstrapper {
mut ciphertext: LweCiphertextOwned<u32>,
server_key: &ServerKey,
) -> Result<Ciphertext, Box<dyn Error>> {
let (accumulator, mut buffer_lwe_after_pbs) = self.memory.as_buffers(server_key);
let BuffersRef {
lookup_table,
mut buffer_lwe_after_pbs,
..
} = self.memory.as_buffers(server_key);
let fourier_bsk = &server_key.bootstrapping_key;
@@ -337,7 +357,7 @@ impl Bootstrapper {
programmable_bootstrap_lwe_ciphertext_mem_optimized(
&ciphertext,
&mut buffer_lwe_after_pbs,
&accumulator,
&lookup_table,
fourier_bsk,
fft,
stack,
@@ -352,6 +372,63 @@ impl Bootstrapper {
Ok(Ciphertext::Encrypted(ciphertext))
}
pub(crate) fn keyswitch_bootstrap(
&mut self,
mut ciphertext: LweCiphertextOwned<u32>,
server_key: &ServerKey,
) -> Result<Ciphertext, Box<dyn Error>> {
let BuffersRef {
lookup_table,
mut buffer_lwe_after_ks,
..
} = self.memory.as_buffers(server_key);
let fourier_bsk = &server_key.bootstrapping_key;
let fft = Fft::new(fourier_bsk.polynomial_size());
let fft = fft.as_view();
self.computation_buffers.resize(
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<u64>(
fourier_bsk.glwe_size(),
fourier_bsk.polynomial_size(),
fft,
)
.unwrap()
.unaligned_bytes_required(),
);
let stack = self.computation_buffers.stack();
// Keyswitch from large LWE key to the small one
keyswitch_lwe_ciphertext(
&server_key.key_switching_key,
&ciphertext,
&mut buffer_lwe_after_ks,
);
// Compute a bootstrap
programmable_bootstrap_lwe_ciphertext_mem_optimized(
&buffer_lwe_after_ks,
&mut ciphertext,
&lookup_table,
fourier_bsk,
fft,
stack,
);
Ok(Ciphertext::Encrypted(ciphertext))
}
pub(crate) fn apply_bootstrapping_pattern(
&mut self,
ct: LweCiphertextOwned<u32>,
server_key: &ServerKey,
) -> Result<Ciphertext, Box<dyn Error>> {
match server_key.pbs_order {
PBSOrder::KeyswitchBootstrap => self.keyswitch_bootstrap(ct, server_key),
PBSOrder::BootstrapKeyswitch => self.bootstrap_keyswitch(ct, server_key),
}
}
}
impl From<CompressedServerKey> for ServerKey {
@@ -359,6 +436,7 @@ impl From<CompressedServerKey> for ServerKey {
let CompressedServerKey {
key_switching_key,
bootstrapping_key,
pbs_order,
} = compressed_server_key;
let key_switching_key = key_switching_key.decompress_into_lwe_keyswitch_key();
@@ -380,6 +458,7 @@ impl From<CompressedServerKey> for ServerKey {
Self {
key_switching_key,
bootstrapping_key,
pbs_order,
}
}
}

View File

@@ -4,7 +4,9 @@
//! underlying `core_crypto` module.
use crate::boolean::ciphertext::{Ciphertext, CompressedCiphertext};
use crate::boolean::parameters::{BooleanKeySwitchingParameters, BooleanParameters};
use crate::boolean::parameters::{
BooleanKeySwitchingParameters, BooleanParameters, EncryptionKeyChoice,
};
use crate::boolean::{ClientKey, CompressedPublicKey, PublicKey, PLAINTEXT_FALSE, PLAINTEXT_TRUE};
use crate::core_crypto::algorithms::*;
use crate::core_crypto::entities::*;
@@ -15,7 +17,7 @@ use crate::core_crypto::commons::generators::{
DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator,
};
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::parameters::{PBSOrder, *};
use crate::core_crypto::seeders::new_seeder;
#[cfg(test)]
@@ -115,25 +117,36 @@ impl BooleanEngine {
pub fn create_public_key(&mut self, client_key: &ClientKey) -> PublicKey {
let client_parameters = client_key.parameters;
let (lwe_sk, encryption_noise) = match client_parameters.encryption_key_choice {
EncryptionKeyChoice::Big => (
client_key.glwe_secret_key.as_lwe_secret_key(),
client_key.parameters.glwe_modular_std_dev,
),
EncryptionKeyChoice::Small => {
let view = LweSecretKey::from_container(client_key.lwe_secret_key.as_ref());
(view, client_key.parameters.lwe_modular_std_dev)
}
};
// Formula is (n + 1) * log2(q) + 128
let zero_encryption_count = LwePublicKeyZeroEncryptionCount(
client_parameters.lwe_dimension.to_lwe_size().0 * LOG2_Q_32 + 128,
lwe_sk.lwe_dimension().to_lwe_size().0 * LOG2_Q_32 + 128,
);
#[cfg(not(feature = "__wasm_api"))]
let lwe_public_key: LwePublicKeyOwned<u32> = par_allocate_and_generate_new_lwe_public_key(
&client_key.lwe_secret_key,
&lwe_sk,
zero_encryption_count,
client_key.parameters.lwe_modular_std_dev,
encryption_noise,
CiphertextModulus::new_native(),
&mut self.encryption_generator,
);
#[cfg(feature = "__wasm_api")]
let lwe_public_key: LwePublicKeyOwned<u32> = allocate_and_generate_new_lwe_public_key(
&client_key.lwe_secret_key,
&lwe_sk,
zero_encryption_count,
client_key.parameters.lwe_modular_std_dev,
encryption_noise,
CiphertextModulus::new_native(),
&mut self.encryption_generator,
);
@@ -147,25 +160,36 @@ impl BooleanEngine {
pub fn create_compressed_public_key(&mut self, client_key: &ClientKey) -> CompressedPublicKey {
let client_parameters = client_key.parameters;
let (lwe_sk, encryption_noise) = match client_parameters.encryption_key_choice {
EncryptionKeyChoice::Big => (
client_key.glwe_secret_key.as_lwe_secret_key(),
client_key.parameters.glwe_modular_std_dev,
),
EncryptionKeyChoice::Small => {
let view = LweSecretKey::from_container(client_key.lwe_secret_key.as_ref());
(view, client_key.parameters.lwe_modular_std_dev)
}
};
// Formula is (n + 1) * log2(q) + 128
let zero_encryption_count = LwePublicKeyZeroEncryptionCount(
client_parameters.lwe_dimension.to_lwe_size().0 * LOG2_Q_32 + 128,
lwe_sk.lwe_dimension().to_lwe_size().0 * LOG2_Q_32 + 128,
);
#[cfg(not(feature = "__wasm_api"))]
let compressed_lwe_public_key = par_allocate_and_generate_new_seeded_lwe_public_key(
&client_key.lwe_secret_key,
&lwe_sk,
zero_encryption_count,
client_key.parameters.lwe_modular_std_dev,
encryption_noise,
CiphertextModulus::new_native(),
&mut self.bootstrapper.seeder,
);
#[cfg(feature = "__wasm_api")]
let compressed_lwe_public_key = allocate_and_generate_new_seeded_lwe_public_key(
&client_key.lwe_secret_key,
&lwe_sk,
zero_encryption_count,
client_key.parameters.lwe_modular_std_dev,
encryption_noise,
CiphertextModulus::new_native(),
&mut self.bootstrapper.seeder,
);
@@ -182,10 +206,31 @@ impl BooleanEngine {
cks2: &ClientKey,
params: BooleanKeySwitchingParameters,
) -> LweKeyswitchKeyOwned<u32> {
let (lwe_sk1, lwe_sk2) = match (
cks1.parameters.encryption_key_choice,
cks2.parameters.encryption_key_choice,
) {
(EncryptionKeyChoice::Big, EncryptionKeyChoice::Big) => (
cks1.glwe_secret_key.as_lwe_secret_key(),
cks2.glwe_secret_key.as_lwe_secret_key(),
),
(EncryptionKeyChoice::Small, EncryptionKeyChoice::Small) => {
let view1 = LweSecretKey::from_container(cks1.lwe_secret_key.as_ref());
let view2 = LweSecretKey::from_container(cks2.lwe_secret_key.as_ref());
(view1, view2)
}
(choice1, choice2) => panic!(
"EncryptionKeyChoice of cks1 and cks2 must be the same.\
cks1 has {:?}, cks2 has: {:?}
",
choice1, choice2
),
};
// Creation of the key switching key
allocate_and_generate_new_lwe_keyswitch_key(
&cks1.lwe_secret_key,
&cks2.lwe_secret_key,
&lwe_sk1,
&lwe_sk2,
params.ks_base_log,
params.ks_level,
cks2.parameters.lwe_modular_std_dev,
@@ -206,11 +251,22 @@ impl BooleanEngine {
Plaintext(PLAINTEXT_FALSE)
};
let (lwe_sk, encryption_noise) = match cks.parameters.encryption_key_choice {
EncryptionKeyChoice::Big => (
cks.glwe_secret_key.as_lwe_secret_key(),
cks.parameters.glwe_modular_std_dev,
),
EncryptionKeyChoice::Small => {
let view = LweSecretKey::from_container(cks.lwe_secret_key.as_ref());
(view, cks.parameters.lwe_modular_std_dev)
}
};
// encryption
let ct = allocate_and_encrypt_new_lwe_ciphertext(
&cks.lwe_secret_key,
&lwe_sk,
plain,
cks.parameters.lwe_modular_std_dev,
encryption_noise,
CiphertextModulus::new_native(),
&mut self.encryption_generator,
);
@@ -226,11 +282,22 @@ impl BooleanEngine {
Plaintext(PLAINTEXT_FALSE)
};
let (lwe_sk, encryption_noise) = match cks.parameters.encryption_key_choice {
EncryptionKeyChoice::Big => (
cks.glwe_secret_key.as_lwe_secret_key(),
cks.parameters.glwe_modular_std_dev,
),
EncryptionKeyChoice::Small => {
let view = LweSecretKey::from_container(cks.lwe_secret_key.as_ref());
(view, cks.parameters.lwe_modular_std_dev)
}
};
// encryption
let ct = allocate_and_encrypt_new_seeded_lwe_ciphertext(
&cks.lwe_secret_key,
&lwe_sk,
plain,
cks.parameters.lwe_modular_std_dev,
encryption_noise,
CiphertextModulus::new_native(),
&mut self.bootstrapper.seeder,
);
@@ -248,7 +315,7 @@ impl BooleanEngine {
let mut output = LweCiphertext::new(
0u32,
pks.parameters.lwe_dimension.to_lwe_size(),
pks.lwe_public_key.lwe_size(),
CiphertextModulus::new_native(),
);
@@ -275,7 +342,7 @@ impl BooleanEngine {
let mut output = LweCiphertext::new(
0u32,
compressed_pk.parameters.lwe_dimension.to_lwe_size(),
compressed_pk.compressed_lwe_public_key.lwe_size(),
CiphertextModulus::new_native(),
);
@@ -293,8 +360,15 @@ impl BooleanEngine {
match ct {
Ciphertext::Trivial(b) => *b,
Ciphertext::Encrypted(ciphertext) => {
let lwe_sk = match cks.parameters.encryption_key_choice {
EncryptionKeyChoice::Big => cks.glwe_secret_key.as_lwe_secret_key(),
EncryptionKeyChoice::Small => {
LweSecretKey::from_container(cks.lwe_secret_key.as_ref())
}
};
// decryption
let decrypted = decrypt_lwe_ciphertext(&cks.lwe_secret_key, ciphertext);
let decrypted = decrypt_lwe_ciphertext(&lwe_sk, ciphertext);
// cast as a u32
let decrypted_u32 = decrypted.0;
@@ -404,11 +478,20 @@ impl BooleanEngine {
} else {
Plaintext(PLAINTEXT_FALSE)
};
allocate_and_trivially_encrypt_new_lwe_ciphertext(
server_key
let lwe_size = match server_key.pbs_order {
PBSOrder::KeyswitchBootstrap => server_key
.key_switching_key
.input_key_lwe_dimension()
.to_lwe_size(),
PBSOrder::BootstrapKeyswitch => server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
};
allocate_and_trivially_encrypt_new_lwe_ciphertext(
lwe_size,
plain,
CiphertextModulus::new_native(),
)
@@ -463,10 +546,7 @@ impl BooleanEngine {
let mut buffer_lwe_before_pbs_o = LweCiphertext::new(
0u32,
server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
ct_condition_ct.lwe_size(),
ct_condition_ct.ciphertext_modulus(),
);
@@ -487,23 +567,47 @@ impl BooleanEngine {
let cst = Plaintext(PLAINTEXT_FALSE);
lwe_ciphertext_plaintext_add_assign(&mut ct_temp_2, cst); // - 1/8
// Compute the first programmable bootstrapping with fixed test polynomial:
let mut ct_pbs_1 = bootstrapper
.bootstrap(buffer_lwe_before_pbs, server_key)
.unwrap();
match server_key.pbs_order {
PBSOrder::KeyswitchBootstrap => {
let ct_ks_1 = bootstrapper
.keyswitch(buffer_lwe_before_pbs, server_key)
.unwrap();
let ct_pbs_2 = bootstrapper.bootstrap(&ct_temp_2, server_key).unwrap();
// Compute the first programmable bootstrapping with fixed test polynomial:
let mut ct_pbs_1 = bootstrapper.bootstrap(&ct_ks_1, server_key).unwrap();
// Compute the linear combination to add the two results:
// buffer_lwe_pbs + ct_pbs_2 + (0,...,0, +1/8)
lwe_ciphertext_add_assign(&mut ct_pbs_1, &ct_pbs_2); // + buffer_lwe_pbs
let cst = Plaintext(PLAINTEXT_TRUE);
lwe_ciphertext_plaintext_add_assign(&mut ct_pbs_1, cst); // + 1/8
let ct_ks_2 = bootstrapper.keyswitch(&ct_temp_2, server_key).unwrap();
let ct_pbs_2 = bootstrapper.bootstrap(&ct_ks_2, server_key).unwrap();
let ct_ks = bootstrapper.keyswitch(&ct_pbs_1, server_key).unwrap();
// Compute the linear combination to add the two results:
// buffer_lwe_pbs + ct_pbs_2 + (0,...,0, +1/8)
lwe_ciphertext_add_assign(&mut ct_pbs_1, &ct_pbs_2); // + buffer_lwe_pbs
let cst = Plaintext(PLAINTEXT_TRUE);
lwe_ciphertext_plaintext_add_assign(&mut ct_pbs_1, cst); // + 1/8
// Output the result:
Ciphertext::Encrypted(ct_ks)
// Output the result:
Ciphertext::Encrypted(ct_pbs_1)
}
PBSOrder::BootstrapKeyswitch => {
// Compute the first programmable bootstrapping with fixed test polynomial:
let mut ct_pbs_1 = bootstrapper
.bootstrap(buffer_lwe_before_pbs, server_key)
.unwrap();
let ct_pbs_2 = bootstrapper.bootstrap(&ct_temp_2, server_key).unwrap();
// Compute the linear combination to add the two results:
// buffer_lwe_pbs + ct_pbs_2 + (0,...,0, +1/8)
lwe_ciphertext_add_assign(&mut ct_pbs_1, &ct_pbs_2); // + buffer_lwe_pbs
let cst = Plaintext(PLAINTEXT_TRUE);
lwe_ciphertext_plaintext_add_assign(&mut ct_pbs_1, cst); // + 1/8
let ct_ks = bootstrapper.keyswitch(&ct_pbs_1, server_key).unwrap();
// Output the result:
Ciphertext::Encrypted(ct_ks)
}
}
}
}
}
@@ -529,10 +633,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
let mut buffer_lwe_before_pbs = LweCiphertext::new(
0u32,
server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
ct_left_ct.lwe_size(),
ct_left_ct.ciphertext_modulus(),
);
@@ -547,7 +648,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
// compute the bootstrap and the key switch
bootstrapper
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
.unwrap()
}
}
@@ -572,10 +673,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
let mut buffer_lwe_before_pbs = LweCiphertext::new(
0u32,
server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
ct_left_ct.lwe_size(),
ct_left_ct.ciphertext_modulus(),
);
let bootstrapper = &mut self.bootstrapper;
@@ -590,7 +688,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
// compute the bootstrap and the key switch
bootstrapper
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
.unwrap()
}
}
@@ -615,10 +713,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
let mut buffer_lwe_before_pbs = LweCiphertext::new(
0u32,
server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
ct_left_ct.lwe_size(),
ct_left_ct.ciphertext_modulus(),
);
let bootstrapper = &mut self.bootstrapper;
@@ -634,7 +729,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
// compute the bootstrap and the key switch
bootstrapper
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
.unwrap()
}
}
@@ -659,10 +754,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
let mut buffer_lwe_before_pbs = LweCiphertext::new(
0u32,
server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
ct_left_ct.lwe_size(),
ct_left_ct.ciphertext_modulus(),
);
let bootstrapper = &mut self.bootstrapper;
@@ -676,7 +768,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
// compute the bootstrap and the key switch
bootstrapper
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
.unwrap()
}
}
@@ -701,10 +793,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
let mut buffer_lwe_before_pbs = LweCiphertext::new(
0u32,
server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
ct_left_ct.lwe_size(),
ct_left_ct.ciphertext_modulus(),
);
let bootstrapper = &mut self.bootstrapper;
@@ -721,7 +810,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
// compute the bootstrap and the key switch
bootstrapper
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
.unwrap()
}
}
@@ -746,10 +835,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
let mut buffer_lwe_before_pbs = LweCiphertext::new(
0u32,
server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
ct_left_ct.lwe_size(),
ct_left_ct.ciphertext_modulus(),
);
let bootstrapper = &mut self.bootstrapper;
@@ -768,7 +854,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
// compute the bootstrap and the key switch
bootstrapper
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
.unwrap()
}
}

View File

@@ -20,11 +20,24 @@
pub use crate::core_crypto::commons::dispersion::StandardDev;
pub use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
DecompositionBaseLog, DecompositionLevelCount, EncryptionKeyChoice, GlweDimension,
LweDimension, PolynomialSize,
};
use serde::{Deserialize, Serialize};
/// A set of cryptographic parameters for homomorphic Boolean circuit evaluation.
/// The choice of encryption key for (`boolean ciphertext`)[`super::ciphertext::Ciphertext`].
///
/// * The `Big` choice means the big LWE key derived from the GLWE key is used to encrypt the input
/// ciphertext. This offers better performance but the (`public
/// key`)[`super::public_key::PublicKey`] can be extremely large and in some cases may not fit in
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the PBS is computed
/// first followed by a keyswitch.
/// * The `Small` choice means the small LWE key is used to encrypt the input ciphertext.
/// Performance is not as good as in the `Big` case but (`public
/// key`)[`super::public_key::PublicKey`] sizes are much more manageable and shoud always fit in
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the keyswitch is
/// computed first followed by a PBS.
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BooleanParameters {
pub lwe_dimension: LweDimension,
@@ -36,6 +49,7 @@ pub struct BooleanParameters {
pub pbs_level: DecompositionLevelCount,
pub ks_base_log: DecompositionBaseLog,
pub ks_level: DecompositionLevelCount,
pub encryption_key_choice: EncryptionKeyChoice,
}
impl BooleanParameters {
@@ -58,6 +72,7 @@ impl BooleanParameters {
pbs_level: DecompositionLevelCount,
ks_base_log: DecompositionBaseLog,
ks_level: DecompositionLevelCount,
encryption_key_choice: EncryptionKeyChoice,
) -> BooleanParameters {
BooleanParameters {
lwe_dimension,
@@ -69,6 +84,7 @@ impl BooleanParameters {
pbs_level,
ks_level,
ks_base_log,
encryption_key_choice,
}
}
}
@@ -79,6 +95,7 @@ pub struct BooleanKeySwitchingParameters {
pub ks_base_log: DecompositionBaseLog,
pub ks_level: DecompositionLevelCount,
}
impl BooleanKeySwitchingParameters {
/// Constructs a new set of parameters for boolean circuit evaluation.
///
@@ -106,30 +123,72 @@ impl BooleanKeySwitchingParameters {
/// This parameter set allows to evaluate faster Boolean circuits than the `TFHE_LIB_PARAMETERS`
/// one.
pub const DEFAULT_PARAMETERS: BooleanParameters = BooleanParameters {
lwe_dimension: LweDimension(777),
glwe_dimension: GlweDimension(3),
lwe_dimension: LweDimension(722),
glwe_dimension: GlweDimension(2),
polynomial_size: PolynomialSize(512),
lwe_modular_std_dev: StandardDev(0.000003725679281679651),
glwe_modular_std_dev: StandardDev(0.0000000000034525330484572114),
pbs_base_log: DecompositionBaseLog(18),
pbs_level: DecompositionLevelCount(1),
ks_base_log: DecompositionBaseLog(4),
ks_level: DecompositionLevelCount(3),
lwe_modular_std_dev: StandardDev(0.000013071021089943935),
glwe_modular_std_dev: StandardDev(0.00000004990272175010415),
pbs_base_log: DecompositionBaseLog(6),
pbs_level: DecompositionLevelCount(3),
ks_base_log: DecompositionBaseLog(3),
ks_level: DecompositionLevelCount(4),
encryption_key_choice: EncryptionKeyChoice::Small,
};
pub const DEFAULT_PARAMETERS_KS_PBS: BooleanParameters = BooleanParameters {
lwe_dimension: LweDimension(664),
glwe_dimension: GlweDimension(2),
polynomial_size: PolynomialSize(512),
lwe_modular_std_dev: StandardDev(0.00003808282923459771),
glwe_modular_std_dev: StandardDev(0.00000004990272175010415),
pbs_base_log: DecompositionBaseLog(6),
pbs_level: DecompositionLevelCount(3),
ks_base_log: DecompositionBaseLog(3),
ks_level: DecompositionLevelCount(4),
encryption_key_choice: EncryptionKeyChoice::Big,
};
/// The secret keys generated with this parameter set are uniform binary.
/// This parameter set ensures a probability of error upper-bounded by $2^{-165}$ as the ones
/// proposed into [TFHE library](https://tfhe.github.io/tfhe/) for for 128-bits of security.
/// They are updated to the last security standards, so they differ from the original
/// publication.
pub const TFHE_LIB_PARAMETERS: BooleanParameters = BooleanParameters {
lwe_dimension: LweDimension(830),
/// This parameter set ensures a probability of error upper-bounded by $2^{-165}$
/// for for 128-bits of security.
pub const PARAMETERS_ERROR_PROB_2_POW_MINUS_165: BooleanParameters = BooleanParameters {
lwe_dimension: LweDimension(767),
glwe_dimension: GlweDimension(2),
polynomial_size: PolynomialSize(1024),
lwe_modular_std_dev: StandardDev(0.000001412290588219445),
glwe_modular_std_dev: StandardDev(0.00000000000000029403601535432533),
pbs_base_log: DecompositionBaseLog(23),
pbs_level: DecompositionLevelCount(1),
ks_base_log: DecompositionBaseLog(5),
ks_level: DecompositionLevelCount(3),
lwe_modular_std_dev: StandardDev(0.000005104350373791501),
glwe_modular_std_dev: StandardDev(0.0000000009313225746154785),
pbs_base_log: DecompositionBaseLog(10),
pbs_level: DecompositionLevelCount(2),
ks_base_log: DecompositionBaseLog(3),
ks_level: DecompositionLevelCount(5),
encryption_key_choice: EncryptionKeyChoice::Small,
};
pub const PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS: BooleanParameters = BooleanParameters {
lwe_dimension: LweDimension(700),
glwe_dimension: GlweDimension(1),
polynomial_size: PolynomialSize(1024),
lwe_modular_std_dev: StandardDev(0.0000196095987892077),
glwe_modular_std_dev: StandardDev(0.00000004990272175010415),
pbs_base_log: DecompositionBaseLog(5),
pbs_level: DecompositionLevelCount(4),
ks_base_log: DecompositionBaseLog(2),
ks_level: DecompositionLevelCount(7),
encryption_key_choice: EncryptionKeyChoice::Big,
};
/// Parameter sets given in TFHE-lib:
/// <https://github.com/tfhe/tfhe/blob/bc71bfae7ad9d5f8ce5f29bdfd691189bfe207f3/src/libtfhe/tfhe_gate_bootstrapping.cpp#L51>
/// Original security in 2020 was 129-bits, while it is currently around 120 bits.
pub const TFHE_LIB_PARAMETERS: BooleanParameters = BooleanParameters {
lwe_dimension: LweDimension(630),
glwe_dimension: GlweDimension(1),
polynomial_size: PolynomialSize(1024),
lwe_modular_std_dev: StandardDev(0.000005104350373791501),
glwe_modular_std_dev: StandardDev(0.0000000009313225746154785),
pbs_base_log: DecompositionBaseLog(7),
pbs_level: DecompositionLevelCount(3),
ks_base_log: DecompositionBaseLog(2),
ks_level: DecompositionLevelCount(8),
encryption_key_choice: EncryptionKeyChoice::Small,
};

View File

@@ -90,7 +90,7 @@ impl CompressedPublicKey {
mod tests {
use crate::boolean::prelude::{
BinaryBooleanGates, BooleanParameters, ClientKey, CompressedPublicKey, ServerKey,
DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS,
DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
};
use crate::boolean::random_boolean;
const NB_TEST: usize = 32;
@@ -102,7 +102,7 @@ mod tests {
#[test]
fn test_compressed_public_key_tfhe_lib_parameters() {
test_compressed_public_key(TFHE_LIB_PARAMETERS);
test_compressed_public_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
fn test_compressed_public_key(parameters: BooleanParameters) {

View File

@@ -82,7 +82,7 @@ impl From<CompressedPublicKey> for PublicKey {
mod tests {
use crate::boolean::prelude::{
BinaryBooleanGates, BooleanParameters, ClientKey, CompressedPublicKey, ServerKey,
DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS,
DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
};
use crate::boolean::random_boolean;
@@ -96,7 +96,7 @@ mod tests {
#[test]
fn test_public_key_tfhe_lib_parameters() {
test_public_key(TFHE_LIB_PARAMETERS);
test_public_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
fn test_public_key(parameters: BooleanParameters) {
@@ -131,7 +131,7 @@ mod tests {
#[test]
fn test_decompressing_public_key_tfhe_lib_parameters() {
test_decompressing_public_key(TFHE_LIB_PARAMETERS);
test_decompressing_public_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
fn test_decompressing_public_key(parameters: BooleanParameters) {

View File

@@ -59,6 +59,144 @@ mod default_parameters_tests {
}
}
mod low_prob_parameters_tests {
use super::*;
use crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165;
#[test]
fn test_encrypt_decrypt_lwe_secret_key_low_prob() {
test_encrypt_decrypt_lwe_secret_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_and_gate_low_prob() {
test_and_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_nand_gate_low_prob() {
test_nand_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_or_gate_low_prob() {
test_or_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_nor_gate_low_prob() {
test_nor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_xor_gate_low_prob() {
test_xor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_xnor_gate_low_prob() {
test_xnor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_not_gate_low_prob() {
test_not_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_mux_gate_low_prob() {
test_mux_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
#[test]
fn test_deep_circuit_low_prob() {
test_deep_circuit(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
}
mod default_parameters_ks_pbs_tests {
use super::*;
use crate::boolean::parameters::DEFAULT_PARAMETERS_KS_PBS;
#[test]
fn test_encrypt_decrypt_lwe_secret_key_default_parameters_ks_pbs() {
test_encrypt_decrypt_lwe_secret_key(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_and_gate_default_parameters_ks_pbs() {
test_and_gate(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_nand_gate_default_parameters_ks_pbs() {
test_nand_gate(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_or_gate_default_parameters_ks_pbs() {
test_or_gate(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_nor_gate_default_parameters_ks_pbs() {
test_nor_gate(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_xor_gate_default_parameters_ks_pbs() {
test_xor_gate(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_xnor_gate_default_parameters_ks_pbs() {
test_xnor_gate(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_not_gate_default_parameters_ks_pbs() {
test_not_gate(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_mux_gate_default_parameters_ks_pbs() {
test_mux_gate(DEFAULT_PARAMETERS_KS_PBS);
}
#[test]
fn test_deep_circuit_default_parameters_ks_pbs() {
test_deep_circuit(DEFAULT_PARAMETERS_KS_PBS);
}
}
mod low_prob_parameters_ks_pbs_tests {
use super::*;
use crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS;
#[test]
fn test_encrypt_decrypt_lwe_secret_key_low_probability_ks_pbs() {
test_encrypt_decrypt_lwe_secret_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_and_gate_low_probability_ks_pbs() {
test_and_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_nand_gate_low_probability_ks_pbs() {
test_nand_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_or_gate_low_probability_ks_pbs() {
test_or_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_nor_gate_low_probability_ks_pbs() {
test_nor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_xor_gate_low_probability_ks_pbs() {
test_xor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_xnor_gate_low_probability_ks_pbs() {
test_xnor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_not_gate_low_probability_ks_pbs() {
test_not_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_mux_gate_low_probability_ks_pbs() {
test_mux_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
#[test]
fn test_deep_circuit_low_probability_ks_pbs() {
test_deep_circuit(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
}
}
mod tfhe_lib_parameters_tests {
use super::*;
use crate::boolean::parameters::TFHE_LIB_PARAMETERS;

View File

@@ -3,6 +3,43 @@ use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
};
#[repr(C)]
#[derive(Copy, Clone)]
pub enum BooleanEncryptionKeyChoice {
BooleanEncryptionKeyChoiceBig,
BooleanEncryptionKeyChoiceSmall,
}
impl From<BooleanEncryptionKeyChoice>
for crate::core_crypto::commons::parameters::EncryptionKeyChoice
{
fn from(value: BooleanEncryptionKeyChoice) -> Self {
match value {
BooleanEncryptionKeyChoice::BooleanEncryptionKeyChoiceBig => {
crate::core_crypto::commons::parameters::EncryptionKeyChoice::Big
}
BooleanEncryptionKeyChoice::BooleanEncryptionKeyChoiceSmall => {
crate::core_crypto::commons::parameters::EncryptionKeyChoice::Small
}
}
}
}
impl BooleanEncryptionKeyChoice {
// From::from cannot be marked as const, so we have to have
// our own function
const fn convert(rust_choice: crate::shortint::EncryptionKeyChoice) -> Self {
match rust_choice {
crate::core_crypto::commons::parameters::EncryptionKeyChoice::Big => {
Self::BooleanEncryptionKeyChoiceBig
}
crate::core_crypto::commons::parameters::EncryptionKeyChoice::Small => {
Self::BooleanEncryptionKeyChoiceSmall
}
}
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct BooleanParameters {
@@ -15,6 +52,7 @@ pub struct BooleanParameters {
pub pbs_level: usize,
pub ks_base_log: usize,
pub ks_level: usize,
pub encryption_key_choice: BooleanEncryptionKeyChoice,
}
impl From<BooleanParameters> for crate::boolean::parameters::BooleanParameters {
@@ -29,6 +67,7 @@ impl From<BooleanParameters> for crate::boolean::parameters::BooleanParameters {
pbs_level: DecompositionLevelCount(c_params.pbs_level),
ks_base_log: DecompositionBaseLog(c_params.ks_base_log),
ks_level: DecompositionLevelCount(c_params.ks_level),
encryption_key_choice: c_params.encryption_key_choice.into(),
}
}
}
@@ -51,6 +90,9 @@ impl BooleanParameters {
pbs_level: rust_params.pbs_level.0,
ks_base_log: rust_params.ks_base_log.0,
ks_level: rust_params.ks_level.0,
encryption_key_choice: BooleanEncryptionKeyChoice::convert(
rust_params.encryption_key_choice,
),
}
}
}
@@ -61,4 +103,14 @@ pub static BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS: BooleanParameters =
#[no_mangle]
pub static BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS: BooleanParameters =
BooleanParameters::convert(crate::boolean::parameters::TFHE_LIB_PARAMETERS);
BooleanParameters::convert(crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
#[no_mangle]
pub static BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS_KS_PBS: BooleanParameters =
BooleanParameters::convert(crate::boolean::parameters::DEFAULT_PARAMETERS_KS_PBS);
#[no_mangle]
pub static BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS_KS_PBS: BooleanParameters =
BooleanParameters::convert(
crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
);

View File

@@ -104,6 +104,8 @@ macro_rules! impl_operations_for_integer_type {
) -> c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
let rhs = <$clear_scalar_type as $crate::c_api::high_level_api::utils::ToRustScalarType
>::to_rust_scalar_type(rhs);
let (q, r) = (&lhs.0).div_rem(rhs);
@@ -132,6 +134,26 @@ macro_rules! impl_operations_for_integer_type {
})
}
}
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$name:snake _if_then_else>](
condition_ct: *const $name,
then_ct: *const $name,
else_ct: *const $name,
result: *mut *mut $name,
) -> c_int {
$crate::c_api::utils::catch_panic(|| {
let condition_ct = &$crate::c_api::utils::get_ref_checked(condition_ct).unwrap().0;
let then_ct = &$crate::c_api::utils::get_ref_checked(then_ct).unwrap().0;
let else_ct = &$crate::c_api::utils::get_ref_checked(else_ct).unwrap().0;
let r = condition_ct.if_then_else(then_ct, else_ct);
*result = Box::into_raw(Box::new($name(r)));
})
}
}
};
}
@@ -230,8 +252,8 @@ create_integer_wrapper_type!(name: FheUint14, clear_scalar_type: u16);
create_integer_wrapper_type!(name: FheUint16, clear_scalar_type: u16);
create_integer_wrapper_type!(name: FheUint32, clear_scalar_type: u32);
create_integer_wrapper_type!(name: FheUint64, clear_scalar_type: u64);
create_integer_wrapper_type!(name: FheUint128, clear_scalar_type: u64);
create_integer_wrapper_type!(name: FheUint256, clear_scalar_type: u64);
create_integer_wrapper_type!(name: FheUint128, clear_scalar_type: U128);
create_integer_wrapper_type!(name: FheUint256, clear_scalar_type: U256);
impl_decrypt_on_type!(FheUint8, u8);
impl_try_encrypt_trivial_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);

View File

@@ -274,6 +274,53 @@ macro_rules! impl_binary_assign_fn_on_type {
};
}
/// The C Standard only define integers from u8 to u64.
/// So to support u128 and u256 we had to create our own C friendly
/// data types.
///
/// This trait exists to be able to easily write wrapper function
/// by allowing to generically go from a C API scalar type to a rust one
pub(in crate::c_api::high_level_api) trait ToRustScalarType {
type RustScalarType;
fn to_rust_scalar_type(self) -> Self::RustScalarType;
}
/// Implements the trait for when the C API type is the same
/// as the Rust API type (eg u64)
macro_rules! impl_to_rust_scalar_type(
($type:ty) => {
impl ToRustScalarType for $type {
type RustScalarType = $type;
fn to_rust_scalar_type(self) -> Self::RustScalarType {
self
}
}
}
);
impl_to_rust_scalar_type!(u8);
impl_to_rust_scalar_type!(u16);
impl_to_rust_scalar_type!(u32);
impl_to_rust_scalar_type!(u64);
impl ToRustScalarType for crate::c_api::high_level_api::u128::U128 {
type RustScalarType = u128;
fn to_rust_scalar_type(self) -> Self::RustScalarType {
u128::from(self)
}
}
impl ToRustScalarType for crate::c_api::high_level_api::u256::U256 {
type RustScalarType = crate::integer::U256;
fn to_rust_scalar_type(self) -> Self::RustScalarType {
crate::integer::U256::from(self)
}
}
#[cfg(feature = "integer")]
macro_rules! impl_scalar_binary_fn_on_type {
($wrapper_type:ty, $scalar_type:ty => $($binary_fn_name:ident),* $(,)?) => {
@@ -287,6 +334,7 @@ macro_rules! impl_scalar_binary_fn_on_type {
) -> c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
let rhs = <$scalar_type as $crate::c_api::high_level_api::utils::ToRustScalarType>::to_rust_scalar_type(rhs);
let inner = (&lhs.0).$binary_fn_name(rhs);
@@ -310,6 +358,8 @@ macro_rules! impl_scalar_binary_assign_fn_on_type {
) -> c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
let rhs = <$scalar_type as $crate::c_api::high_level_api::utils::ToRustScalarType
>::to_rust_scalar_type(rhs);
lhs.0.$binary_assign_fn_name(rhs);
})

View File

@@ -232,3 +232,32 @@ impl LweBskGroupingFactor {
/// The number of GGSW ciphertexts required per multi_bit BSK element
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub struct GgswPerLweMultiBitBskElement(pub usize);
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum EncryptionKeyChoice {
Big,
Small,
}
impl From<EncryptionKeyChoice> for PBSOrder {
fn from(value: EncryptionKeyChoice) -> Self {
match value {
EncryptionKeyChoice::Big => Self::KeyswitchBootstrap,
EncryptionKeyChoice::Small => Self::BootstrapKeyswitch,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum PBSOrder {
/// Ciphertext is encrypted using the big LWE secret key corresponding to the GLWE secret key.
///
/// A keyswitch is first performed to bring it to the small LWE secret key realm, then the PBS
/// is computed bringing it back to the large LWE secret key.
KeyswitchBootstrap = 0,
/// Ciphertext is encrypted using the small LWE secret key.
///
/// The PBS is computed first and a keyswitch is applied to get back to the small LWE secret
/// key realm.
BootstrapKeyswitch = 1,
}

View File

@@ -110,6 +110,11 @@ impl<Scalar, C: Container<Element = Scalar>> GlweSecretKey<C> {
LweSecretKey::from_container(self.data)
}
/// Borrowes and returns the [`GlweSecretKey`] views as an [`LweSecretKey`].
pub fn as_lwe_secret_key(&self) -> LweSecretKey<&[C::Element]> {
LweSecretKey::from_container(self.data.as_ref())
}
/// Interpret the [`GlweSecretKey`] as a [`PolynomialList`].
pub fn as_polynomial_list(&self) -> PolynomialListView<'_, C::Element> {
PolynomialListView::from_container(self.as_ref(), self.polynomial_size)

View File

@@ -88,11 +88,13 @@ pub fn test_extract_bits() {
&mut encryption_generator,
);
let input_lwe_dimension = lwe_big_sk.lwe_dimension();
let req = || {
StackReq::try_any_of([
fill_with_forward_fourier_scratch(fft)?,
extract_bits_scratch::<u64>(
lwe_dimension,
input_lwe_dimension,
ksk_lwe_big_to_small.output_key_lwe_dimension(),
glwe_dimension.to_glwe_size(),
polynomial_size,
@@ -597,6 +599,8 @@ pub fn test_extract_bit_circuit_bootstrapping_vertical_packing() {
PodStack::new(&mut mem),
);
let input_lwe_dimension = lwe_big_sk.lwe_dimension();
let ksk_lwe_big_to_small: LweKeyswitchKeyOwned<u64> =
allocate_and_generate_new_lwe_keyswitch_key(
&lwe_big_sk,
@@ -661,7 +665,7 @@ pub fn test_extract_bit_circuit_bootstrapping_vertical_packing() {
let mut mem = GlobalPodBuffer::new(
extract_bits_scratch::<u64>(
lwe_dimension,
input_lwe_dimension,
ksk_lwe_big_to_small.output_key_lwe_dimension(),
fourier_bsk.glwe_size(),
polynomial_size,

View File

@@ -1,8 +1,8 @@
use crate::boolean::parameters::{
BooleanParameters, DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension,
PolynomialSize, StandardDev,
BooleanParameters, DecompositionBaseLog, DecompositionLevelCount, EncryptionKeyChoice,
GlweDimension, LweDimension, PolynomialSize, StandardDev,
};
pub use crate::boolean::parameters::{DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
pub use crate::boolean::parameters::{DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165};
use serde::{Deserialize, Serialize};
@@ -21,11 +21,12 @@ pub struct FheBoolParameters {
pub pbs_level: DecompositionLevelCount,
pub ks_base_log: DecompositionBaseLog,
pub ks_level: DecompositionLevelCount,
pub encryption_key_choice: EncryptionKeyChoice,
}
impl FheBoolParameters {
pub fn tfhe_lib() -> Self {
Self::from_static(&TFHE_LIB_PARAMETERS)
Self::from_static(&PARAMETERS_ERROR_PROB_2_POW_MINUS_165)
}
fn from_static(params: &'static BooleanParameters) -> Self {
@@ -51,6 +52,7 @@ impl From<FheBoolParameters> for BooleanParameters {
pbs_level: params.pbs_level,
ks_base_log: params.ks_base_log,
ks_level: params.ks_level,
encryption_key_choice: params.encryption_key_choice,
}
}
}
@@ -67,6 +69,7 @@ impl From<BooleanParameters> for FheBoolParameters {
pbs_level: params.pbs_level,
ks_base_log: params.ks_base_log,
ks_level: params.ks_level,
encryption_key_choice: params.encryption_key_choice,
}
}
}

View File

@@ -786,3 +786,34 @@ fn test_integer_casting() {
assert_eq!(da, clear);
}
}
#[test]
fn test_if_then_else() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
let result = a.le(&b).if_then_else(&a, &b);
let decrypted_result: u8 = result.decrypt(&client_key);
assert_eq!(
decrypted_result,
if clear_a <= clear_b { clear_a } else { clear_b }
);
let result = a.le(&b).if_then_else(&b, &a);
let decrypted_result: u8 = result.decrypt(&client_key);
assert_eq!(
decrypted_result,
if clear_a <= clear_b { clear_b } else { clear_a }
);
}

View File

@@ -21,6 +21,7 @@ use crate::high_level_api::traits::{
use crate::high_level_api::{ClientKey, PublicKey};
use crate::integer::block_decomposition::DecomposableInto;
use crate::integer::ciphertext::RadixCiphertext;
use crate::integer::server_key::{Reciprocable, ScalarMultiplier};
use crate::integer::U256;
use crate::CompactPublicKey;
@@ -140,6 +141,25 @@ where
}
}
impl<P> GenericInteger<P>
where
P: IntegerParameter,
P::Id: WithGlobalKey<Key = IntegerServerKey>,
{
pub fn if_then_else(&self, ct_then: &Self, ct_else: &Self) -> GenericInteger<P> {
let ct_condition = self;
let new_ct = ct_condition.id.with_unwrapped_global(|integer_key| {
integer_key.pbs_key().if_then_else_parallelized(
&ct_condition.ciphertext,
&ct_then.ciphertext,
&ct_else.ciphertext,
)
});
GenericInteger::new(new_ct, ct_condition.id)
}
}
impl<P> TryFrom<RadixCiphertext> for GenericInteger<P>
where
P: IntegerParameter,
@@ -342,7 +362,7 @@ where
impl<P, Clear> FheMax<Clear> for GenericInteger<P>
where
Clear: DecomposableInto<u8>,
Clear: DecomposableInto<u64>,
P: IntegerParameter,
GenericInteger<P>: Clone,
P::Id: WithGlobalKey<Key = IntegerServerKey>,
@@ -379,7 +399,7 @@ where
impl<P, Clear> FheMin<Clear> for GenericInteger<P>
where
Clear: DecomposableInto<u8>,
Clear: DecomposableInto<u64>,
P: IntegerParameter,
GenericInteger<P>: Clone,
P::Id: WithGlobalKey<Key = IntegerServerKey>,
@@ -452,7 +472,7 @@ where
impl<P, Clear> FheEq<Clear> for GenericInteger<P>
where
Clear: DecomposableInto<u8>,
Clear: DecomposableInto<u64>,
P: IntegerParameter,
GenericInteger<P>: Clone,
P::Id: WithGlobalKey<Key = IntegerServerKey>,
@@ -570,7 +590,7 @@ where
impl<P, Clear> FheOrd<Clear> for GenericInteger<P>
where
Clear: DecomposableInto<u8>,
Clear: DecomposableInto<u64>,
P: IntegerParameter,
GenericInteger<P>: Clone,
P::Id: WithGlobalKey<Key = IntegerServerKey>,
@@ -673,7 +693,7 @@ where
impl<P, Clear> DivRem<Clear> for GenericInteger<P>
where
P: IntegerParameter,
Clear: Into<u64>,
Clear: Reciprocable + ScalarMultiplier + DecomposableInto<u8>,
P::Id: WithGlobalKey<Key = IntegerServerKey>,
{
type Output = (Self, Self);
@@ -686,7 +706,7 @@ where
impl<P, Clear> DivRem<Clear> for &GenericInteger<P>
where
P: IntegerParameter,
Clear: Into<u64>,
Clear: Reciprocable + ScalarMultiplier + DecomposableInto<u8>,
P::Id: WithGlobalKey<Key = IntegerServerKey>,
{
type Output = (GenericInteger<P>, GenericInteger<P>);
@@ -695,7 +715,7 @@ where
let (q, r) = self.id.with_unwrapped_global(|integer_key| {
integer_key
.pbs_key()
.scalar_div_rem_parallelized(&self.ciphertext, rhs.into())
.scalar_div_rem_parallelized(&self.ciphertext, rhs)
});
(
GenericInteger::<P>::new(q, self.id),
@@ -844,8 +864,7 @@ macro_rules! generic_integer_impl_scalar_operation {
fn $rust_trait_method(self, rhs: $scalar_type) -> Self::Output {
let ciphertext: RadixCiphertext =
self.id.with_unwrapped_global(|integer_key| {
let value: u64 = rhs.try_into().unwrap();
integer_key.pbs_key().$key_method(&self.ciphertext, value)
integer_key.pbs_key().$key_method(&self.ciphertext, rhs)
});
GenericInteger::<P>::new(ciphertext, self.id)
@@ -865,8 +884,7 @@ macro_rules! generic_integer_impl_scalar_operation_assign {
{
fn $rust_trait_method(&mut self, rhs: $scalar_type) {
self.id.with_unwrapped_global(|integer_key| {
let value: u64 = rhs.try_into().unwrap();
integer_key.pbs_key().$key_method(&mut self.ciphertext, value);
integer_key.pbs_key().$key_method(&mut self.ciphertext, rhs);
})
}
}
@@ -900,31 +918,31 @@ generic_integer_impl_operation_assign!(RotateRightAssign(rotate_right_assign) =>
generic_integer_impl_operation_assign!(DivAssign(div_assign) => div_assign_parallelized);
generic_integer_impl_operation_assign!(RemAssign(rem_assign) => rem_assign_parallelized);
generic_integer_impl_scalar_operation!(Add(add) => scalar_add_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(Sub(sub) => scalar_sub_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(Mul(mul) => scalar_mul_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(BitAnd(bitand) => scalar_bitand_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(BitOr(bitor) => scalar_bitor_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(BitXor(bitxor) => scalar_bitxor_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(Shl(shl) => scalar_left_shift_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(Shr(shr) => scalar_right_shift_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(RotateLeft(rotate_left) => scalar_rotate_left_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(RotateRight(rotate_right) => scalar_rotate_right_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(Div(div) => scalar_div_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(Rem(rem) => scalar_rem_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation!(Add(add) => scalar_add_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(Sub(sub) => scalar_sub_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(Mul(mul) => scalar_mul_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(BitAnd(bitand) => scalar_bitand_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(BitOr(bitor) => scalar_bitor_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(BitXor(bitxor) => scalar_bitxor_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(Shl(shl) => scalar_left_shift_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(Shr(shr) => scalar_right_shift_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(RotateLeft(rotate_left) => scalar_rotate_left_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(RotateRight(rotate_right) => scalar_rotate_right_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(Div(div) => scalar_div_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation!(Rem(rem) => scalar_rem_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(AddAssign(add_assign) => scalar_add_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(SubAssign(sub_assign) => scalar_sub_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(MulAssign(mul_assign) => scalar_mul_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(BitAndAssign(bitand_assign) => scalar_bitand_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(BitOrAssign(bitor_assign) => scalar_bitor_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(BitXorAssign(bitxor_assign) => scalar_bitxor_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(ShlAssign(shl_assign) => scalar_left_shift_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(ShrAssign(shr_assign) => scalar_right_shift_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(RotateLeftAssign(rotate_left_assign) => scalar_rotate_left_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(RotateRightAssign(rotate_right_assign) => scalar_rotate_right_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(DivAssign(div_assign) => scalar_div_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(RemAssign(rem_assign) => scalar_rem_assign_parallelized(u8, u16, u32, u64));
generic_integer_impl_scalar_operation_assign!(AddAssign(add_assign) => scalar_add_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(SubAssign(sub_assign) => scalar_sub_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(MulAssign(mul_assign) => scalar_mul_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(BitAndAssign(bitand_assign) => scalar_bitand_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(BitOrAssign(bitor_assign) => scalar_bitor_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(BitXorAssign(bitxor_assign) => scalar_bitxor_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(ShlAssign(shl_assign) => scalar_left_shift_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(ShrAssign(shr_assign) => scalar_right_shift_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(RotateLeftAssign(rotate_left_assign) => scalar_rotate_left_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(RotateRightAssign(rotate_right_assign) => scalar_rotate_right_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(DivAssign(div_assign) => scalar_div_assign_parallelized(u8, u16, u32, u64, u128, U256));
generic_integer_impl_scalar_operation_assign!(RemAssign(rem_assign) => scalar_rem_assign_parallelized(u8, u16, u32, u64, u128, U256));
impl<P> Neg for GenericInteger<P>
where

View File

@@ -74,6 +74,15 @@ impl RadixCiphertext {
pub fn block_carries_are_empty(&self) -> bool {
self.blocks.iter().all(|block| block.carry_is_empty())
}
/// Returns wether the ciphertext _seems_ like it holds/encrypts
/// a boolean (0 or 1) value.
///
/// Since it uses degree to do so, it will not
/// always return the correct answer.
pub(crate) fn holds_boolean_value(&self) -> bool {
self.blocks[0].degree.0 <= 1 && self.blocks[1..].iter().all(|block| block.degree.0 == 0)
}
}
impl From<CompressedRadixCiphertext> for RadixCiphertext {

View File

@@ -11,7 +11,7 @@ use crate::shortint::Ciphertext;
pub(crate) enum ZeroComparisonType {
// We want to compare with zeros for equality (==)
Equality,
// We want to compare with zeros for differeece (!=)
// We want to compare with zeros for difference (!=)
Difference,
}
@@ -398,7 +398,7 @@ where {
pub fn unchecked_scalar_sign_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> Ciphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
assert!(!lhs.blocks.is_empty());
@@ -406,7 +406,8 @@ where {
let mut scalar_blocks =
BlockDecomposer::with_early_stop_at_zero(rhs, message_modulus.ilog2())
.iter_as::<u8>()
.iter_as::<u64>()
.map(|x| x as u8)
.collect::<Vec<_>>();
// scalar is obviously bigger if it has non-zero
@@ -569,43 +570,19 @@ where {
rhs: &RadixCiphertext,
selector: MinMaxSelector,
) -> RadixCiphertext {
let (lhs_lut, rhs_lut) = match selector {
MinMaxSelector::Max => (&self.lhs_lut, &self.rhs_lut),
MinMaxSelector::Min => (&self.rhs_lut, &self.lhs_lut),
};
let mut offset = self.unchecked_sign_parallelized(lhs, rhs);
self.server_key
.key
.apply_lookup_table_assign(&mut offset, &self.sign_to_offset_lut);
let blocks = lhs
.blocks
.par_iter()
.zip(rhs.blocks.par_iter())
.map(|(lhs_block, rhs_block)| {
let (maybe_lhs, maybe_rhs) = rayon::join(
|| {
let mut lhs_block = self.server_key.key.unchecked_add(lhs_block, &offset);
self.server_key
.key
.apply_lookup_table_assign(&mut lhs_block, lhs_lut);
lhs_block
},
|| {
let mut rhs_block = self.server_key.key.unchecked_add(rhs_block, &offset);
self.server_key
.key
.apply_lookup_table_assign(&mut rhs_block, rhs_lut);
rhs_block
},
);
self.server_key.key.unchecked_add(&maybe_lhs, &maybe_rhs)
})
.collect::<Vec<_>>();
RadixCiphertext { blocks }
let sign = self.unchecked_sign_parallelized(lhs, rhs);
match selector {
MinMaxSelector::Max => self
.server_key
.unchecked_programmable_if_then_else_parallelized(&sign, lhs, rhs, |sign| {
sign == Self::IS_SUPERIOR
}),
MinMaxSelector::Min => self
.server_key
.unchecked_programmable_if_then_else_parallelized(&sign, lhs, rhs, |sign| {
sign == Self::IS_INFERIOR
}),
}
}
fn unchecked_scalar_min_or_max_parallelized<T>(
@@ -615,59 +592,22 @@ where {
selector: MinMaxSelector,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
let (lhs_lut, rhs_lut) = match selector {
MinMaxSelector::Max => (&self.lhs_lut, &self.rhs_lut),
MinMaxSelector::Min => (&self.rhs_lut, &self.lhs_lut),
};
let mut offset = self.unchecked_scalar_sign_parallelized(lhs, rhs);
self.server_key
.key
.apply_lookup_table_assign(&mut offset, &self.sign_to_offset_lut);
let message_modulus = self.server_key.key.message_modulus.0 as u64;
let bits_in_message = message_modulus.ilog2();
let scalar_blocks = BlockDecomposer::with_early_stop_at_zero(rhs, bits_in_message)
.iter_as::<u8>()
.chain(std::iter::repeat(0))
.take(lhs.blocks.len())
.collect::<Vec<_>>();
let blocks = lhs
.blocks
.par_iter()
.zip(scalar_blocks.into_par_iter())
.map(|(lhs_block, scalar_block)| {
let (maybe_lhs, maybe_rhs) = rayon::join(
|| {
let mut lhs_block = self.server_key.key.unchecked_add(lhs_block, &offset);
self.server_key
.key
.apply_lookup_table_assign(&mut lhs_block, lhs_lut);
lhs_block
},
|| {
let mut rhs_block = if scalar_block != 0 {
self.server_key
.key
.unchecked_scalar_add(&offset, scalar_block)
} else {
offset.clone()
};
self.server_key
.key
.apply_lookup_table_assign(&mut rhs_block, rhs_lut);
rhs_block
},
);
self.server_key.key.unchecked_add(&maybe_lhs, &maybe_rhs)
})
.collect::<Vec<_>>();
RadixCiphertext { blocks }
let sign = self.unchecked_scalar_sign_parallelized(lhs, rhs);
let rhs = self.server_key.create_trivial_radix(rhs, lhs.blocks.len());
match selector {
MinMaxSelector::Max => self
.server_key
.unchecked_programmable_if_then_else_parallelized(&sign, lhs, &rhs, |sign| {
sign == Self::IS_SUPERIOR
}),
MinMaxSelector::Min => self
.server_key
.unchecked_programmable_if_then_else_parallelized(&sign, lhs, &rhs, |sign| {
sign == Self::IS_INFERIOR
}),
}
}
fn smart_min_or_max(
@@ -1157,11 +1097,7 @@ where {
}
};
let mut res = self.unchecked_max_parallelized(lhs, rhs);
res.blocks
.par_iter_mut()
.for_each(|block| self.server_key.key.message_extract_assign(block));
res
self.unchecked_max_parallelized(lhs, rhs)
}
pub fn min_parallelized(
@@ -1195,11 +1131,7 @@ where {
}
};
let mut res = self.unchecked_min_parallelized(lhs, rhs);
res.blocks
.par_iter_mut()
.for_each(|block| self.server_key.key.message_extract_assign(block));
res
self.unchecked_min_parallelized(lhs, rhs)
}
//===========================================
@@ -1213,7 +1145,7 @@ where {
sign_result_handler_fn: F,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
F: Fn(u64) -> bool + Sync,
{
let sign_block = self.unchecked_scalar_sign_parallelized(lhs, rhs);
@@ -1226,7 +1158,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.unchecked_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_SUPERIOR)
}
@@ -1237,7 +1169,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.unchecked_scalar_compare_parallelized(lhs, rhs, |x| {
x == Self::IS_SUPERIOR || x == Self::IS_EQUAL
@@ -1250,7 +1182,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.unchecked_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_INFERIOR)
}
@@ -1261,7 +1193,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.unchecked_scalar_compare_parallelized(lhs, rhs, |x| {
x == Self::IS_INFERIOR || x == Self::IS_EQUAL
@@ -1274,7 +1206,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.unchecked_scalar_min_or_max_parallelized(lhs, rhs, MinMaxSelector::Max)
}
@@ -1285,7 +1217,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.unchecked_scalar_min_or_max_parallelized(lhs, rhs, MinMaxSelector::Min)
}
@@ -1301,7 +1233,7 @@ where {
sign_result_handler_fn: F,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
F: Fn(u64) -> bool + Sync,
{
if has_non_zero_carries(lhs) {
@@ -1316,7 +1248,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.smart_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_SUPERIOR)
}
@@ -1327,7 +1259,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.smart_scalar_compare_parallelized(lhs, rhs, |x| {
x == Self::IS_SUPERIOR || x == Self::IS_EQUAL
@@ -1340,7 +1272,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.smart_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_INFERIOR)
}
@@ -1351,7 +1283,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.smart_scalar_compare_parallelized(lhs, rhs, |x| {
x == Self::IS_INFERIOR || x == Self::IS_EQUAL
@@ -1364,7 +1296,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
if has_non_zero_carries(lhs) {
self.server_key.full_propagate_parallelized(lhs);
@@ -1378,7 +1310,7 @@ where {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
if has_non_zero_carries(lhs) {
self.server_key.full_propagate_parallelized(lhs);
@@ -1397,7 +1329,7 @@ where {
sign_result_handler_fn: F,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
F: Fn(u64) -> bool + Sync,
{
let mut tmp_lhs: RadixCiphertext;
@@ -1413,14 +1345,14 @@ where {
pub fn scalar_gt_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.default_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_SUPERIOR)
}
pub fn scalar_ge_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.default_scalar_compare_parallelized(lhs, rhs, |x| {
x == Self::IS_SUPERIOR || x == Self::IS_EQUAL
@@ -1429,14 +1361,14 @@ where {
pub fn scalar_lt_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.default_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_INFERIOR)
}
pub fn scalar_le_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
self.default_scalar_compare_parallelized(lhs, rhs, |x| {
x == Self::IS_INFERIOR || x == Self::IS_EQUAL
@@ -1445,7 +1377,7 @@ where {
pub fn scalar_max_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
let mut tmp_lhs: RadixCiphertext;
let lhs = if has_non_zero_carries(lhs) {
@@ -1460,7 +1392,7 @@ where {
pub fn scalar_min_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
let mut tmp_lhs: RadixCiphertext;
let lhs = if has_non_zero_carries(lhs) {
@@ -1575,7 +1507,7 @@ mod tests {
}
impl<'a> Comparator<'a> {
pub fn unchecked_scalar_eq_parallelized<T: DecomposableInto<u8>>(
pub fn unchecked_scalar_eq_parallelized<T: DecomposableInto<u64>>(
&self,
lhs: &RadixCiphertext,
rhs: T,
@@ -1583,7 +1515,7 @@ mod tests {
self.server_key.unchecked_scalar_eq_parallelized(lhs, rhs)
}
pub fn smart_scalar_eq_parallelized<T: DecomposableInto<u8>>(
pub fn smart_scalar_eq_parallelized<T: DecomposableInto<u64>>(
&self,
lhs: &mut RadixCiphertext,
rhs: T,
@@ -1591,7 +1523,7 @@ mod tests {
self.server_key.smart_scalar_eq_parallelized(lhs, rhs)
}
pub fn scalar_eq_parallelized<T: DecomposableInto<u8>>(
pub fn scalar_eq_parallelized<T: DecomposableInto<u64>>(
&self,
lhs: &RadixCiphertext,
rhs: T,
@@ -1599,7 +1531,7 @@ mod tests {
self.server_key.scalar_eq_parallelized(lhs, rhs)
}
pub fn unchecked_scalar_ne_parallelized<T: DecomposableInto<u8>>(
pub fn unchecked_scalar_ne_parallelized<T: DecomposableInto<u64>>(
&self,
lhs: &RadixCiphertext,
rhs: T,
@@ -1607,7 +1539,7 @@ mod tests {
self.server_key.unchecked_scalar_ne_parallelized(lhs, rhs)
}
pub fn smart_scalar_ne_parallelized<T: DecomposableInto<u8>>(
pub fn smart_scalar_ne_parallelized<T: DecomposableInto<u64>>(
&self,
lhs: &mut RadixCiphertext,
rhs: T,
@@ -1615,7 +1547,7 @@ mod tests {
self.server_key.smart_scalar_ne_parallelized(lhs, rhs)
}
pub fn scalar_ne_parallelized<T: DecomposableInto<u8>>(
pub fn scalar_ne_parallelized<T: DecomposableInto<u64>>(
&self,
lhs: &RadixCiphertext,
rhs: T,

View File

@@ -19,6 +19,12 @@ use crate::integer::encryption::encrypt_words_radix_impl;
mod tests;
impl ServerKey {
pub fn create_trivial_zero_assign_radix(&self, ctxt: &mut RadixCiphertext) {
for block in &mut ctxt.blocks {
self.key.create_trivial_assign(block, 0)
}
}
/// Create a ciphertext filled with zeros
///
/// # Example

View File

@@ -319,15 +319,10 @@ impl ServerKey {
/// assert_eq!(16, clear);
/// ```
pub fn blockshift(&self, ctxt: &RadixCiphertext, shift: usize) -> RadixCiphertext {
let ctxt_zero = self.key.create_trivial(0_u64);
let mut result = ctxt.clone();
for res_i in result.blocks[..shift].iter_mut() {
*res_i = ctxt_zero.clone();
}
for (res_i, c_i) in result.blocks[shift..].iter_mut().zip(ctxt.blocks.iter()) {
*res_i = c_i.clone();
result.blocks.rotate_right(shift);
for block in &mut result.blocks[..shift] {
self.key.create_trivial_assign(block, 0);
}
result
}

View File

@@ -0,0 +1,241 @@
use crate::integer::ciphertext::RadixCiphertext;
use crate::integer::ServerKey;
use rayon::prelude::*;
impl ServerKey {
pub fn unchecked_if_then_else_parallelized(
&self,
condition: &RadixCiphertext,
true_ct: &RadixCiphertext,
false_ct: &RadixCiphertext,
) -> RadixCiphertext {
assert!(
condition.holds_boolean_value(),
"The condition ciphertext does not encrypt a boolean (0 or 1) value"
);
let condition_block = &condition.blocks[0];
self.unchecked_programmable_if_then_else_parallelized(
condition_block,
true_ct,
false_ct,
|x| x == 1,
)
}
/// FHE "if then else" selection.
///
/// Returns a new ciphertext that encrypts the same value
/// as either true_ct or false_ct depending on the value of condition:
///
/// - If condition == 1, the returned ciphertext will encrypt the same value as true_ct.
/// - If condition == 0, the returned ciphertext will encrypt the same value as false_ct.
///
/// To ensure correct results, condition must encrypt either 0 or 1
/// (e.g result from a comparison).
///
/// Note that while the returned ciphertext encrypts the same value as
/// either true_ct or false_ct, it won't exactly be true_ct or false_ct.
///
/// ```
/// use tfhe::integer::gen_keys_radix;
/// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
///
/// // We have 4 * 2 = 8 bits of message
/// let size = 4;
/// let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, size);
///
/// let a = 128u8;
/// let b = 55u8;
///
/// let ct_a = cks.encrypt(a);
/// let ct_b = cks.encrypt(b);
///
/// let condition = sks.scalar_ge_parallelized(&ct_a, 66);
///
/// let ct_res = sks.if_then_else_parallelized(&condition, &ct_a, &ct_b);
///
/// // Decrypt:
/// let dec: u8 = cks.decrypt(&ct_res);
/// assert_eq!(if a >= 66 { a } else { b }, dec);
/// assert_ne!(ct_a, ct_res);
/// assert_ne!(ct_b, ct_res);
/// ```
pub fn if_then_else_parallelized(
&self,
condition: &RadixCiphertext,
true_ct: &RadixCiphertext,
false_ct: &RadixCiphertext,
) -> RadixCiphertext {
let mut ct_clones = [None, None, None];
let mut ct_refs = [condition, true_ct, false_ct];
ct_refs
.par_iter_mut()
.zip(ct_clones.par_iter_mut())
.for_each(|(ct_ref, ct_clone)| {
if !ct_ref.block_carries_are_empty() {
let mut cloned = ct_ref.clone();
self.full_propagate_parallelized(&mut cloned);
*ct_ref = ct_clone.insert(cloned);
}
});
let [condition, true_ct, false_ct] = ct_refs;
self.unchecked_if_then_else_parallelized(condition, true_ct, false_ct)
}
/// FHE "if then else" selection.
///
/// Returns a new ciphertext that encrypts the same value
/// as either true_ct or false_ct depending on the value of condition:
///
/// - If condition == 1, the returned ciphertext will encrypt the same value as true_ct.
/// - If condition == 0, the returned ciphertext will encrypt the same value as false_ct.
///
/// To ensure correct results, condition must encrypt either 0 or 1
/// (e.g result from a comparison).
///
/// Note that while the returned ciphertext encrypts the same value as
/// either true_ct or false_ct, it won't exactly be true_ct or false_ct.
///
/// ```
/// use tfhe::integer::gen_keys_radix;
/// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
///
/// // We have 4 * 2 = 8 bits of message
/// let size = 4;
/// let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, size);
///
/// let a = 128u8;
/// let b = 55u8;
///
/// let mut ct_a = cks.encrypt(a);
/// let mut ct_b = cks.encrypt(b);
///
/// let mut condition = sks.scalar_ge_parallelized(&ct_a, 66);
///
/// let ct_res = sks.smart_if_then_else_parallelized(&mut condition, &mut ct_a, &mut ct_b);
///
/// // Decrypt:
/// let dec: u8 = cks.decrypt(&ct_res);
/// assert_eq!(if a >= 66 { a } else { b }, dec);
/// assert_ne!(ct_a, ct_res);
/// assert_ne!(ct_b, ct_res);
/// ```
pub fn smart_if_then_else_parallelized(
&self,
condition: &mut RadixCiphertext,
true_ct: &mut RadixCiphertext,
false_ct: &mut RadixCiphertext,
) -> RadixCiphertext {
let mut ct_refs = [condition, true_ct, false_ct];
ct_refs.par_iter_mut().for_each(|ct_ref| {
if !ct_ref.block_carries_are_empty() {
self.full_propagate_parallelized(ct_ref);
}
});
let [condition, true_ct, false_ct] = ct_refs;
self.unchecked_if_then_else_parallelized(condition, true_ct, false_ct)
}
pub(crate) fn unchecked_programmable_if_then_else_parallelized<F>(
&self,
condition_block: &crate::shortint::Ciphertext,
true_ct: &RadixCiphertext,
false_ct: &RadixCiphertext,
predicate: F,
) -> RadixCiphertext
where
F: Fn(u64) -> bool + Send + Sync + Copy,
{
let inverted_predicate = |x| !predicate(x);
// Although our mul algorithm has special path for when rhs or lhs is a boolean value,
// we don't call it as for the ct_false we would need an extra pbs to 'invert' the
// ciphertext from true to false.
let (mut true_ct, false_ct) = rayon::join(
move || {
let mut true_ct = true_ct.clone();
self.zero_out_if(&mut true_ct, condition_block, inverted_predicate);
true_ct
},
move || {
let mut false_ct = false_ct.clone();
self.zero_out_if(&mut false_ct, condition_block, predicate);
false_ct
},
);
// If the condition was true, true_ct will have kept its value and false_ct will be 0
// If the condition was false, true_ct will be 0 and false_ct will have kept its value
true_ct
.blocks
.par_iter_mut()
.zip(false_ct.blocks.par_iter())
.for_each(|(lhs_block, rhs_block)| {
self.key.unchecked_add_assign(lhs_block, rhs_block);
self.key.message_extract_assign(lhs_block)
});
true_ct
}
/// This function takes a ciphertext encrypting any integer value
/// and block encrypting a boolean value (0 or 1).
///
/// The input integer ciphertext will have all its block zeroed if condition_block
/// encrypts 0, otherwise each block keeps its value.
pub(crate) fn zero_out_if_condition_is_false(
&self,
ct: &mut RadixCiphertext,
condition_block: &crate::shortint::Ciphertext,
) {
assert!(condition_block.degree.0 <= 1);
self.zero_out_if_condition_equals(ct, condition_block, 0)
}
pub(crate) fn zero_out_if_condition_equals(
&self,
ct: &mut RadixCiphertext,
condition_block: &crate::shortint::Ciphertext,
value: u64,
) {
assert!(condition_block.degree.0 < condition_block.message_modulus.0);
assert!(value < condition_block.message_modulus.0 as u64);
self.zero_out_if(ct, condition_block, |x| x == value);
}
pub(crate) fn zero_out_if<F>(
&self,
ct: &mut RadixCiphertext,
condition_block: &crate::shortint::Ciphertext,
predicate: F,
) where
F: Fn(u64) -> bool,
{
assert!(condition_block.degree.0 < condition_block.message_modulus.0);
if condition_block.degree.0 == 0 {
return self.create_trivial_zero_assign_radix(ct);
}
let lut =
self.key.generate_lookup_table_bivariate(
|block, condition| if predicate(condition) { 0 } else { block },
);
ct.blocks
.par_iter_mut()
.filter(|block| block.degree.0 != 0)
.for_each(|block| {
self.key.unchecked_apply_lookup_table_bivariate_assign(
block,
condition_block,
&lut,
);
});
}
}

View File

@@ -1,7 +1,6 @@
use crate::integer::ciphertext::RadixCiphertext;
use crate::integer::server_key::comparator::ZeroComparisonType;
use crate::integer::ServerKey;
use rayon::prelude::*;
use super::bit_extractor::BitExtractor;
@@ -36,15 +35,6 @@ impl ServerKey {
let mut quotient = self.create_trivial_zero_radix(num_blocks);
let mut remainder = self.create_trivial_zero_radix(num_blocks);
// This lut only works when y values are 0 or 1
let zeroer_lut_for_merged_cmp =
self.key
.generate_lookup_table_bivariate(|x, y| if y != 1 { 0 } else { x });
// This lut works when y is <= 2
let zeroer_lut_summed_cmp =
self.key
.generate_lookup_table_bivariate(|x, y| if y != 2 { 0 } else { x });
let merge_two_cmp_lut = self
.key
.generate_lookup_table_bivariate(|x, y| u64::from(x == 1 && y == 1));
@@ -125,25 +115,15 @@ impl ServerKey {
&merge_two_cmp_lut,
);
interesting_divisor.blocks.par_iter_mut().for_each(|block| {
self.key.unchecked_apply_lookup_table_bivariate_assign(
block,
&merged_cmp,
&zeroer_lut_for_merged_cmp,
);
});
self.zero_out_if_condition_is_false(&mut interesting_divisor, &merged_cmp);
is_remainder_greater_or_eq_than_divisor = Some(merged_cmp)
} else {
let summed_cmp = self.key.unchecked_add(
&trivial_blocks_are_zero,
&non_trivial_blocks_are_ge.blocks[0],
);
interesting_divisor.blocks.par_iter_mut().for_each(|block| {
self.key.unchecked_apply_lookup_table_bivariate_assign(
block,
&summed_cmp,
&zeroer_lut_summed_cmp,
);
self.zero_out_if(&mut interesting_divisor, &summed_cmp, |summed_cmp| {
summed_cmp != 2 // summed_cmp != 2 => remainder < divisor
});
is_remainder_greater_or_eq_than_divisor = None;
}

View File

@@ -1,6 +1,7 @@
mod add;
mod bit_extractor;
mod bitwise_op;
mod cmux;
mod comparison;
mod div_mod;
mod mul;
@@ -24,6 +25,8 @@ use super::ServerKey;
use crate::integer::ciphertext::RadixCiphertext;
pub use scalar_div_mod::{MiniUnsignedInteger, Reciprocable};
use rayon::prelude::*;
// parallelized versions
impl ServerKey {
/// Propagate the carry of the 'index' block to the next one.
@@ -66,9 +69,40 @@ impl ServerKey {
}
pub fn partial_propagate_parallelized(&self, ctxt: &mut RadixCiphertext, start_index: usize) {
let len = ctxt.blocks.len();
for i in start_index..len {
self.propagate_parallelized(ctxt, i);
// The fully parallelized way introduces more work
// and so is slower for low number of blocks
const MIN_NUM_BLOCKS: usize = 6;
if self.is_eligible_for_parallel_carryless_add() && ctxt.blocks.len() >= MIN_NUM_BLOCKS {
let num_blocks = ctxt.blocks.len();
let (mut message_blocks, carry_blocks) = rayon::join(
|| {
ctxt.blocks[start_index..]
.par_iter()
.map(|block| self.key.message_extract(block))
.collect::<Vec<_>>()
},
|| {
let mut carry_blocks = Vec::with_capacity(num_blocks);
// No need to compute the carry of the last block, we would just throw it away
ctxt.blocks[start_index..num_blocks - 1]
.par_iter()
.map(|block| self.key.carry_extract(block))
.collect_into_vec(&mut carry_blocks);
carry_blocks.insert(0, self.key.create_trivial(0));
carry_blocks
},
);
ctxt.blocks[start_index..].swap_with_slice(&mut message_blocks);
let carries = RadixCiphertext::from(carry_blocks);
self.unchecked_add_assign_parallelized(ctxt, &carries);
self.propagate_single_carry_parallelized_low_latency(ctxt)
} else {
let len = ctxt.blocks.len();
for i in start_index..len {
self.propagate_parallelized(ctxt, i);
}
}
}

View File

@@ -425,30 +425,8 @@ impl ServerKey {
self.unchecked_add_assign(result, term);
}
let (mut message_blocks, carry_blocks) = rayon::join(
|| {
result
.blocks
.par_iter()
.map(|block| self.key.message_extract(block))
.collect::<Vec<_>>()
},
|| {
let mut carry_blocks = Vec::with_capacity(num_blocks);
result.blocks[..num_blocks - 1] // last carry is not interesting
.par_iter()
.map(|block| self.key.carry_extract(block))
.collect_into_vec(&mut carry_blocks);
carry_blocks.insert(0, self.key.create_trivial(0));
carry_blocks
},
);
std::mem::swap(&mut lhs.blocks, &mut message_blocks);
let carry = RadixCiphertext::from(carry_blocks);
self.add_assign_parallelized(lhs, &carry);
std::mem::swap(&mut lhs.blocks, &mut result.blocks);
self.full_propagate_parallelized(lhs);
assert!(lhs.block_carries_are_empty());
}
@@ -492,14 +470,31 @@ impl ServerKey {
lhs: &mut RadixCiphertext,
rhs: &RadixCiphertext,
) {
let num_blocks = lhs.blocks.len();
let mut terms = vec![self.create_trivial_zero_radix(num_blocks); num_blocks];
terms
.par_iter_mut()
.zip(rhs.blocks.par_iter().enumerate())
.for_each(|(term, (i, rhs_i))| {
*term = self.unchecked_block_mul_parallelized(lhs, rhs_i, i);
});
if rhs.holds_boolean_value() {
self.zero_out_if_condition_is_false(lhs, &rhs.blocks[0]);
return;
}
if lhs.holds_boolean_value() {
let mut cloned_rhs = rhs.clone();
self.zero_out_if_condition_is_false(&mut cloned_rhs, &lhs.blocks[0]);
*lhs = cloned_rhs;
return;
}
let terms = rhs
.blocks
.par_iter()
.enumerate()
.filter_map(|(i, block)| {
if block.degree.0 == 0 {
// Block is a trivial 0, no need to waste time multiplying
None
} else {
Some(self.unchecked_block_mul_parallelized(lhs, block, i))
}
})
.collect::<Vec<_>>();
self.sum_multiplication_terms_into(lhs, terms);
}

View File

@@ -286,7 +286,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
debug_assert!(lhs.block_carries_are_empty());
@@ -303,7 +303,8 @@ impl ServerKey {
let mut scalar_blocks =
BlockDecomposer::with_early_stop_at_zero(rhs, total_modulus.ilog2())
.iter_as::<u8>()
.iter_as::<u64>()
.map(|x| x as u8)
.collect::<Vec<_>>();
// If we have more scalar blocks than lhs.blocks
@@ -368,7 +369,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
debug_assert!(lhs.block_carries_are_empty());
@@ -385,7 +386,8 @@ impl ServerKey {
let mut scalar_blocks =
BlockDecomposer::with_early_stop_at_zero(rhs, total_modulus.ilog2())
.iter_as::<u8>()
.iter_as::<u64>()
.map(|x| x as u8)
.collect::<Vec<_>>();
// If we have more scalar blocks than lhs.blocks
@@ -455,7 +457,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
if !lhs.block_carries_are_empty() {
self.full_propagate_parallelized(lhs);
@@ -465,7 +467,7 @@ impl ServerKey {
pub fn scalar_eq_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
let mut tmp_lhs: RadixCiphertext;
let lhs = if !lhs.block_carries_are_empty() {
@@ -484,7 +486,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
if !lhs.block_carries_are_empty() {
self.full_propagate_parallelized(lhs);
@@ -494,7 +496,7 @@ impl ServerKey {
pub fn scalar_ne_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
let mut tmp_lhs: RadixCiphertext;
let lhs = if !lhs.block_carries_are_empty() {
@@ -517,7 +519,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).unchecked_scalar_gt_parallelized(lhs, rhs)
}
@@ -528,7 +530,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).unchecked_scalar_ge_parallelized(lhs, rhs)
}
@@ -539,7 +541,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).unchecked_scalar_lt_parallelized(lhs, rhs)
}
@@ -550,7 +552,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).unchecked_scalar_le_parallelized(lhs, rhs)
}
@@ -561,7 +563,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).unchecked_scalar_max_parallelized(lhs, rhs)
}
@@ -572,7 +574,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).unchecked_scalar_min_parallelized(lhs, rhs)
}
@@ -587,7 +589,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).smart_scalar_gt_parallelized(lhs, rhs)
}
@@ -598,7 +600,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).smart_scalar_ge_parallelized(lhs, rhs)
}
@@ -609,7 +611,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).smart_scalar_lt_parallelized(lhs, rhs)
}
@@ -620,7 +622,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).smart_scalar_le_parallelized(lhs, rhs)
}
@@ -631,7 +633,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).smart_scalar_max_parallelized(lhs, rhs)
}
@@ -642,7 +644,7 @@ impl ServerKey {
rhs: T,
) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).smart_scalar_min_parallelized(lhs, rhs)
}
@@ -653,42 +655,42 @@ impl ServerKey {
pub fn scalar_gt_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).scalar_gt_parallelized(lhs, rhs)
}
pub fn scalar_ge_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).scalar_ge_parallelized(lhs, rhs)
}
pub fn scalar_lt_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).scalar_lt_parallelized(lhs, rhs)
}
pub fn scalar_le_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).scalar_le_parallelized(lhs, rhs)
}
pub fn scalar_max_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).scalar_max_parallelized(lhs, rhs)
}
pub fn scalar_min_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
where
T: DecomposableInto<u8>,
T: DecomposableInto<u64>,
{
Comparator::new(self).scalar_min_parallelized(lhs, rhs)
}

View File

@@ -185,6 +185,8 @@ create_parametrized_test!(integer_smart_scalar_sub);
create_parametrized_test!(integer_default_scalar_sub);
create_parametrized_test!(integer_smart_scalar_add);
create_parametrized_test!(integer_default_scalar_add);
create_parametrized_test!(integer_smart_if_then_else);
create_parametrized_test!(integer_default_if_then_else);
fn integer_smart_add<P>(param: P)
where
@@ -2581,6 +2583,25 @@ where
// Check the correctness
assert_eq!(clear, dec);
}
{
// test x * y and y * x
// where y encrypts a boolean value
let clear1 = rng.gen::<u64>() % modulus;
let clear2 = rng.gen_range(0u64..=1);
let ctxt_1 = cks.encrypt(clear1);
let ctxt_2 = sks.create_trivial_radix(clear2, ctxt_1.blocks.len());
assert!(ctxt_2.holds_boolean_value());
let res = sks.mul_parallelized(&ctxt_1, &ctxt_2);
let dec: u64 = cks.decrypt(&res);
assert_eq!(dec, clear1 * clear2);
let res = sks.mul_parallelized(&ctxt_2, &ctxt_1);
let dec: u64 = cks.decrypt(&res);
assert_eq!(dec, clear1 * clear2);
}
}
fn integer_smart_scalar_add<P>(param: P)
@@ -2800,3 +2821,140 @@ fn test_non_regression_clone_from() {
let q1q2 = server_key.smart_mul_parallelized(&mut q1, &mut q2);
assert_eq!(client_key.decrypt_radix::<u8>(&q1q2), 1);
}
fn integer_smart_if_then_else<P>(param: P)
where
P: Into<PBSParameters>,
{
let (cks, sks) = KEY_CACHE.get_from_params(param);
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) as u64;
for _ in 0..NB_TEST {
let clear_0 = rng.gen::<u64>() % modulus;
let clear_1 = rng.gen::<u64>() % modulus;
let clear_condition = rng.gen_range(0u64..1);
let mut ctxt_0 = cks.encrypt(clear_0);
let mut ctxt_1 = cks.encrypt(clear_1);
// cks.encrypt returns a ciphertext which does not look like
// (when looking at the degree) it encrypts a boolean value.
// So we 'force' having a boolean encrypting ciphertext by using eq (==)
let mut ctxt_condition = sks.scalar_eq_parallelized(&cks.encrypt(clear_condition), 1);
assert!(ctxt_condition.holds_boolean_value());
let ct_res =
sks.smart_if_then_else_parallelized(&mut ctxt_condition, &mut ctxt_0, &mut ctxt_1);
let dec_res: u64 = cks.decrypt(&ct_res);
assert_eq!(
dec_res,
if clear_condition == 1 {
clear_0
} else {
clear_1
}
);
let clear_2 = rng.gen::<u64>() % modulus;
let clear_3 = rng.gen::<u64>() % modulus;
let ctxt_2 = cks.encrypt(clear_2);
let ctxt_3 = cks.encrypt(clear_3);
// Add to have non empty carries
sks.unchecked_add_assign(&mut ctxt_0, &ctxt_2);
sks.unchecked_add_assign(&mut ctxt_1, &ctxt_3);
assert!(!ctxt_0.block_carries_are_empty());
assert!(!ctxt_1.block_carries_are_empty());
let ct_res =
sks.smart_if_then_else_parallelized(&mut ctxt_condition, &mut ctxt_0, &mut ctxt_1);
assert!(ctxt_0.block_carries_are_empty());
assert!(ctxt_1.block_carries_are_empty());
let dec_res: u64 = cks.decrypt(&ct_res);
assert_eq!(
dec_res,
if clear_condition == 1 {
(clear_0 + clear_2) % modulus
} else {
(clear_1 + clear_3) % modulus
}
);
}
}
fn integer_default_if_then_else<P>(param: P)
where
P: Into<PBSParameters>,
{
let (cks, mut sks) = KEY_CACHE.get_from_params(param);
let cks = RadixClientKey::from((cks, NB_CTXT));
sks.set_deterministic_pbs_execution(true);
let mut rng = rand::thread_rng();
// message_modulus^vec_length
let modulus = cks.parameters().message_modulus().0.pow(NB_CTXT as u32) as u64;
for _ in 0..NB_TEST {
let clear_0 = rng.gen::<u64>() % modulus;
let clear_1 = rng.gen::<u64>() % modulus;
let clear_condition = rng.gen_range(0u64..1);
let mut ctxt_0 = cks.encrypt(clear_0);
let mut ctxt_1 = cks.encrypt(clear_1);
// cks.encrypt returns a ciphertext which does not look like
// (when looking at the degree) it encrypts a boolean value.
// So we 'force' having a boolean encrypting ciphertext by using eq (==)
let ctxt_condition = sks.scalar_eq_parallelized(&cks.encrypt(clear_condition), 1);
assert!(ctxt_condition.holds_boolean_value());
let ct_res = sks.if_then_else_parallelized(&ctxt_condition, &ctxt_0, &ctxt_1);
assert!(ct_res.block_carries_are_empty());
let dec_res: u64 = cks.decrypt(&ct_res);
assert_eq!(
dec_res,
if clear_condition == 1 {
clear_0
} else {
clear_1
}
);
let ct_res2 = sks.if_then_else_parallelized(&ctxt_condition, &ctxt_0, &ctxt_1);
assert_eq!(ct_res, ct_res2, "Operation if not deterministic");
let clear_2 = rng.gen::<u64>() % modulus;
let clear_3 = rng.gen::<u64>() % modulus;
let ctxt_2 = cks.encrypt(clear_2);
let ctxt_3 = cks.encrypt(clear_3);
// Add to have non empty carries
sks.unchecked_add_assign(&mut ctxt_0, &ctxt_2);
sks.unchecked_add_assign(&mut ctxt_1, &ctxt_3);
assert!(!ctxt_0.block_carries_are_empty());
assert!(!ctxt_1.block_carries_are_empty());
let ct_res = sks.if_then_else_parallelized(&ctxt_condition, &ctxt_0, &ctxt_1);
assert!(ct_res.block_carries_are_empty());
let dec_res: u64 = cks.decrypt(&ct_res);
assert_eq!(
dec_res,
if clear_condition == 1 {
(clear_0 + clear_2) % modulus
} else {
(clear_1 + clear_3) % modulus
}
);
}
}

View File

@@ -32,6 +32,8 @@ pub struct BooleanParameters(pub(crate) crate::boolean::parameters::BooleanParam
pub enum BooleanParameterSet {
Default,
TfheLib,
DefaultKsPbs,
TfheLibKsPbs,
}
impl TryFrom<u32> for BooleanParameterSet {
@@ -41,6 +43,8 @@ impl TryFrom<u32> for BooleanParameterSet {
match value {
0 => Ok(BooleanParameterSet::Default),
1 => Ok(BooleanParameterSet::TfheLib),
2 => Ok(BooleanParameterSet::DefaultKsPbs),
3 => Ok(BooleanParameterSet::TfheLibKsPbs),
_ => Err(format!(
"Invalid value '{value}' for BooleansParametersSet, use \
BooleanParameterSet constants"
@@ -49,6 +53,27 @@ impl TryFrom<u32> for BooleanParameterSet {
}
}
#[wasm_bindgen]
pub enum BooleanEncryptionKeyChoice {
Big,
Small,
}
impl From<BooleanEncryptionKeyChoice>
for crate::core_crypto::commons::parameters::EncryptionKeyChoice
{
fn from(value: BooleanEncryptionKeyChoice) -> Self {
match value {
BooleanEncryptionKeyChoice::Big => {
crate::shortint::parameters::EncryptionKeyChoice::Big
}
BooleanEncryptionKeyChoice::Small => {
crate::shortint::parameters::EncryptionKeyChoice::Small
}
}
}
}
#[wasm_bindgen]
impl Boolean {
#[wasm_bindgen]
@@ -59,7 +84,15 @@ impl Boolean {
match parameter_choice {
BooleanParameterSet::Default => Ok(crate::boolean::parameters::DEFAULT_PARAMETERS),
BooleanParameterSet::TfheLib => Ok(crate::boolean::parameters::TFHE_LIB_PARAMETERS),
BooleanParameterSet::TfheLib => {
Ok(crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165)
}
BooleanParameterSet::DefaultKsPbs => {
Ok(crate::boolean::parameters::DEFAULT_PARAMETERS_KS_PBS)
}
BooleanParameterSet::TfheLibKsPbs => {
Ok(crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS)
}
}
.map(BooleanParameters)
}
@@ -76,6 +109,7 @@ impl Boolean {
pbs_level: usize,
ks_base_log: usize,
ks_level: usize,
encryption_key_choice: BooleanEncryptionKeyChoice,
) -> BooleanParameters {
set_hook(Box::new(console_error_panic_hook::hook));
use crate::core_crypto::prelude::*;
@@ -89,6 +123,7 @@ impl Boolean {
pbs_level: DecompositionLevelCount(pbs_level),
ks_base_log: DecompositionBaseLog(ks_base_log),
ks_level: DecompositionLevelCount(ks_level),
encryption_key_choice: encryption_key_choice.into(),
})
}

View File

@@ -1,4 +1,5 @@
//! Module with the definition of the Ciphertext.
pub use crate::core_crypto::commons::parameters::PBSOrder;
use crate::core_crypto::entities::*;
use crate::shortint::parameters::{CarryModulus, MessageModulus};
use serde::{Deserialize, Serialize};
@@ -9,20 +10,6 @@ use std::fmt::Debug;
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub struct Degree(pub usize);
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum PBSOrder {
/// Ciphertext is encrypted using the big LWE secret key corresponding to the GLWE secret key.
///
/// A keyswitch is first performed to bring it to the small LWE secret key realm, then the PBS
/// is computed bringing it back to the large LWE secret key.
KeyswitchBootstrap = 0,
/// Ciphertext is encrypted using the small LWE secret key.
///
/// The PBS is computed first and a keyswitch is applied to get back to the small LWE secret
/// key realm.
BootstrapKeyswitch = 1,
}
impl Degree {
pub(crate) fn after_bitxor(&self, other: Degree) -> Degree {
let max = cmp::max(self.0, other.0);

View File

@@ -619,12 +619,18 @@ impl ShortintEngine {
where
F: Fn(u64, u64) -> u64,
{
// Generate the lookup table for the function
if !server_key.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
// We don't have enough space in carries, so clear them
self.message_extract_assign(server_key, ct_left)?;
self.message_extract_assign(server_key, ct_right)?;
}
let factor = MessageModulus(ct_right.degree.0 + 1);
// Generate the lookup table for the function
let lookup_table =
self.generate_lookup_table_bivariate_with_factor(server_key, f, factor)?;
self.smart_apply_lookup_table_bivariate_assign(
self.unchecked_apply_lookup_table_bivariate_assign(
server_key,
ct_left,
ct_right,

View File

@@ -20,6 +20,11 @@ impl ShortintEngine {
ct_left: &mut Ciphertext,
ct_right: &Ciphertext,
) -> EngineResult<()> {
if ct_left.degree.0 == 0 || ct_right.degree.0 == 0 {
// One of the ciphertext is a trivial 0
self.create_trivial_assign(server_key, ct_left, 0)?;
return Ok(());
}
let modulus = (ct_right.degree.0 + 1) as u64;
//message 1 is shifted to the carry bits
@@ -58,6 +63,11 @@ impl ShortintEngine {
ct_left: &mut Ciphertext,
ct_right: &Ciphertext,
) -> EngineResult<()> {
if ct_left.degree.0 == 0 || ct_right.degree.0 == 0 {
// One of the ciphertext is a trivial 0
self.create_trivial_assign(server_key, ct_left, 0)?;
return Ok(());
}
let modulus = (ct_right.degree.0 + 1) as u64;
let deg = (ct_left.degree.0 * ct_right.degree.0) / ct_right.message_modulus.0;

View File

@@ -10,7 +10,6 @@ pub use crate::core_crypto::commons::parameters::{
CiphertextModulus as CoreCiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
GlweDimension, LweBskGroupingFactor, LweDimension, PolynomialSize,
};
use crate::shortint::ciphertext::PBSOrder;
use serde::{Deserialize, Serialize};
pub mod key_switching;
@@ -20,38 +19,12 @@ pub mod parameters_wopbs;
pub mod parameters_wopbs_message_carry;
pub(crate) mod parameters_wopbs_prime_moduli;
pub use crate::core_crypto::commons::parameters::EncryptionKeyChoice;
pub use key_switching::ShortintKeySwitchingParameters;
pub use multi_bit::*;
pub use parameters_compact_pk::*;
pub use parameters_wopbs::WopbsParameters;
/// The choice of encryption key for (`shortint ciphertext`)[`super::ciphertext::Ciphertext`].
///
/// * The `Big` choice means the big LWE key derived from the GLWE key is used to encrypt the input
/// ciphertext. This offers better performance but the (`public
/// key`)[`super::public_key::PublicKey`] can be extremely large and in some cases may not fit in
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the PBS is computed
/// first followed by a keyswitch.
/// * The `Small` choice means the small LWE key is used to encrypt the input ciphertext.
/// Performance is not as good as in the `Big` case but (`public
/// key`)[`super::public_key::PublicKey`] sizes are much more manageable and shoud always fit in
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the keyswitch is
/// computed first followed by a PBS.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum EncryptionKeyChoice {
Big,
Small,
}
impl From<EncryptionKeyChoice> for PBSOrder {
fn from(value: EncryptionKeyChoice) -> Self {
match value {
EncryptionKeyChoice::Big => Self::KeyswitchBootstrap,
EncryptionKeyChoice::Small => Self::BootstrapKeyswitch,
}
}
}
/// The number of bits on which the message will be encoded.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub struct MessageModulus(pub usize);
@@ -65,6 +38,19 @@ pub type CiphertextModulus = CoreCiphertextModulus<u64>;
/// A structure defining the set of cryptographic parameters for homomorphic integer circuit
/// evaluation.
///
/// The choice of encryption key for (`shortint ciphertext`)[`super::ciphertext::Ciphertext`].
///
/// * The `Big` choice means the big LWE key derived from the GLWE key is used to encrypt the input
/// ciphertext. This offers better performance but the (`public
/// key`)[`super::public_key::PublicKey`] can be extremely large and in some cases may not fit in
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the PBS is computed
/// first followed by a keyswitch.
/// * The `Small` choice means the small LWE key is used to encrypt the input ciphertext.
/// Performance is not as good as in the `Big` case but (`public
/// key`)[`super::public_key::PublicKey`] sizes are much more manageable and shoud always fit in
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the keyswitch is
/// computed first followed by a PBS.
#[derive(Serialize, Copy, Clone, Deserialize, Debug, PartialEq)]
pub struct ClassicPBSParameters {
pub lwe_dimension: LweDimension,

View File

@@ -141,6 +141,11 @@ impl CompactPublicKey {
self.parameters.ciphertext_modulus(),
);
let encryption_noise = match self.pbs_order {
crate::shortint::PBSOrder::KeyswitchBootstrap => self.parameters.glwe_modular_std_dev(),
crate::shortint::PBSOrder::BootstrapKeyswitch => self.parameters.lwe_modular_std_dev(),
};
// No parallelism allowed
#[cfg(all(feature = "__wasm_api", not(feature = "parallel-wasm-api")))]
{
@@ -150,8 +155,8 @@ impl CompactPublicKey {
&self.key,
&mut ct_list,
&plaintext_list,
self.parameters.glwe_modular_std_dev(),
self.parameters.lwe_modular_std_dev(),
encryption_noise,
encryption_noise,
&mut engine.secret_generator,
&mut engine.encryption_generator,
);
@@ -167,8 +172,8 @@ impl CompactPublicKey {
&self.key,
&mut ct_list,
&plaintext_list,
self.parameters.glwe_modular_std_dev(),
self.parameters.lwe_modular_std_dev(),
encryption_noise,
encryption_noise,
&mut engine.secret_generator,
&mut engine.encryption_generator,
);

View File

@@ -45,7 +45,7 @@ doctest!(
booleans_serialization
);
doctest!(
"../docs/fine_grained_api/Boolean/tutorial.md",
"../docs/fine_grained_api/Boolean/readme.md",
booleans_tutorial
);
@@ -64,7 +64,7 @@ doctest!(
shortint_serialization
);
doctest!(
"../docs/fine_grained_api/shortint/tutorial.md",
"../docs/fine_grained_api/shortint/readme.md",
shortint_tutorial
);
@@ -78,7 +78,7 @@ doctest!(
integer_serialization_tuto
);
doctest!(
"../docs/fine_grained_api/integer/tutorial.md",
"../docs/fine_grained_api/integer/readme.md",
integer_first_circuit
);

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@
"devDependencies": {
"@babel/preset-env": "^7.22.4",
"jest": "^29.5.0",
"puppeteer": "^20.3.0",
"puppeteer": "^21.3.8",
"serve": "^14.2.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.1.1"