Compare commits

...

8 Commits

Author SHA1 Message Date
J-B Orfila
c58b0a3f68 chore(bench): add new parameter sets to bench 2023-08-09 09:56:15 +02:00
J-B Orfila
1f95e2d45a chore(tfhe): bump version to 0.3.1 2023-08-09 09:56:15 +02:00
tmontaigu
9bd9180261 feat(integer): make full_propagate_parallelized more parallel
Using the functions that were introduced recently,
it is possible to make the full_propagate_parallelized method
more parallel than it was, resulting in faster computations.

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

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

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

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

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

View File

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

148
README.md
View File

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

View File

@@ -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"]

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ 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.

View File

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

View File

@@ -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" %}

View File

@@ -46,7 +46,7 @@ fn main() {
The default configuration for x86 Unix machines:
```toml
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
tfhe = { version = "0.3.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).

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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