mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-11 07:38:08 -05:00
Compare commits
8 Commits
al/remove_
...
0.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c58b0a3f68 | ||
|
|
1f95e2d45a | ||
|
|
9bd9180261 | ||
|
|
2d7251f88c | ||
|
|
59c5ef81e2 | ||
|
|
197afb62d0 | ||
|
|
205de1966a | ||
|
|
89def834b6 |
2
.github/workflows/check_commit.yml
vendored
2
.github/workflows/check_commit.yml
vendored
@@ -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
148
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tfhe"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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.1", 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.1", 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.1", 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.1", features = ["x86_64"] }
|
||||
```
|
||||
|
||||
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.
|
||||
|
||||
@@ -38,6 +38,7 @@ fn main() {
|
||||
DecompositionLevelCount(2),
|
||||
DecompositionBaseLog(2),
|
||||
DecompositionLevelCount(5),
|
||||
EncryptionKeyChoice::Small,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
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.1", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
|
||||
@@ -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.1", 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).
|
||||
|
||||
@@ -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.1", features = ["integer","x86_64-unix"]}
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
|
||||
@@ -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.1", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
@@ -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.1", features = ["boolean", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
241
tfhe/src/integer/server_key/radix_parallel/cmux.rs
Normal file
241
tfhe/src/integer/server_key/radix_parallel/cmux.rs
Normal 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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user