Compare commits
17 Commits
al/debug_l
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c9b0816aa | ||
|
|
b4095f9c9f | ||
|
|
c98e7001cd | ||
|
|
4396c45885 | ||
|
|
1749d2027f | ||
|
|
c6cad2d95d | ||
|
|
eeb203c6c8 | ||
|
|
e7745deb8b | ||
|
|
5db6d3243c | ||
|
|
c58b0a3f68 | ||
|
|
1f95e2d45a | ||
|
|
9bd9180261 | ||
|
|
2d7251f88c | ||
|
|
59c5ef81e2 | ||
|
|
197afb62d0 | ||
|
|
205de1966a | ||
|
|
89def834b6 |
2
.github/workflows/check_commit.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
- name: Check first line
|
||||
uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee
|
||||
with:
|
||||
pattern: '^((feat|fix|chore|refactor|style|test|docs|doc)\(\w+\)\:) .+$'
|
||||
pattern: '^((feat|fix|chore|refactor|style|test|docs|doc)(\(\w+\))?\:) .+$'
|
||||
flags: "gs"
|
||||
error: 'Your first line has to contain a commit type and scope like "feat(my_feature): msg".'
|
||||
excludeDescription: "true" # optional: this excludes the description body of a pull request
|
||||
|
||||
148
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -34,6 +34,6 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > install-rustup.s
|
||||
chmod +x install-node.sh && \
|
||||
./install-node.sh && \
|
||||
. "$HOME/.nvm/nvm.sh" && \
|
||||
bash -i -c 'nvm install node && nvm use node'
|
||||
bash -i -c 'nvm install 20 && nvm use 20'
|
||||
|
||||
WORKDIR /tfhe-wasm-tests/tfhe-rs/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tfhe"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
|
||||
@@ -29,7 +29,7 @@ lazy_static = { version = "1.4.0" }
|
||||
criterion = "0.4.0"
|
||||
doc-comment = "0.3.3"
|
||||
serde_json = "1.0.94"
|
||||
clap = { version = "4.2.7", features = ["derive"] }
|
||||
clap = { version = "=4.2.7", features = ["derive"] }
|
||||
# Used in user documentation
|
||||
bincode = "1.3.3"
|
||||
fs2 = { version = "0.4.3" }
|
||||
@@ -65,15 +65,17 @@ fs2 = { version = "0.4.3", optional = true }
|
||||
itertools = "0.10.5"
|
||||
|
||||
# wasm deps
|
||||
wasm-bindgen = { version = "0.2.86", features = [
|
||||
wasm-bindgen = { version = "=0.2.86", features = [
|
||||
"serde-serialize",
|
||||
], optional = true }
|
||||
# MSRV was bumped in a minor update, pin to still be able to build in CI
|
||||
bumpalo = { version = "=3.14" }
|
||||
wasm-bindgen-rayon = { version = "1.0", optional = true }
|
||||
js-sys = { version = "0.3", optional = true }
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
serde-wasm-bindgen = { version = "0.4", optional = true }
|
||||
getrandom = { version = "0.2.8", optional = true }
|
||||
bytemuck = "1.13.1"
|
||||
bytemuck = "=1.14.1"
|
||||
|
||||
[features]
|
||||
boolean = []
|
||||
|
||||
@@ -4,14 +4,20 @@ use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use tfhe::boolean::client_key::ClientKey;
|
||||
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
|
||||
use tfhe::boolean::prelude::BinaryBooleanGates;
|
||||
use tfhe::boolean::parameters::{
|
||||
BooleanParameters, DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
|
||||
};
|
||||
use tfhe::boolean::prelude::{BinaryBooleanGates, DEFAULT_PARAMETERS_KS_PBS, TFHE_LIB_PARAMETERS};
|
||||
use tfhe::boolean::server_key::ServerKey;
|
||||
|
||||
criterion_group!(
|
||||
gates_benches,
|
||||
bench_default_parameters,
|
||||
bench_tfhe_lib_parameters
|
||||
bench_default_parameters_ks_pbs,
|
||||
bench_low_prob_parameters,
|
||||
bench_low_prob_parameters_ks_pbs,
|
||||
bench_tfhe_lib_parameters,
|
||||
);
|
||||
|
||||
criterion_main!(gates_benches);
|
||||
@@ -79,6 +85,26 @@ fn bench_default_parameters(c: &mut Criterion) {
|
||||
benchs(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
|
||||
}
|
||||
|
||||
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
|
||||
benchs(c, TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS");
|
||||
fn bench_default_parameters_ks_pbs(c: &mut Criterion) {
|
||||
benchs(c, DEFAULT_PARAMETERS_KS_PBS, "DEFAULT_PARAMETERS_KS_PBS");
|
||||
}
|
||||
|
||||
fn bench_low_prob_parameters(c: &mut Criterion) {
|
||||
benchs(
|
||||
c,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS",
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_low_prob_parameters_ks_pbs(c: &mut Criterion) {
|
||||
benchs(
|
||||
c,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
|
||||
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS",
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
|
||||
benchs(c, TFHE_LIB_PARAMETERS, " TFHE_LIB_PARAMETERS");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ use rayon::prelude::*;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use serde::Serialize;
|
||||
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
|
||||
use tfhe::boolean::parameters::{
|
||||
BooleanParameters, DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
};
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
use tfhe::shortint::keycache::NamedParam;
|
||||
use tfhe::shortint::parameters::*;
|
||||
@@ -31,7 +33,10 @@ const SHORTINT_BENCH_PARAMS: [ClassicPBSParameters; 15] = [
|
||||
|
||||
const BOOLEAN_BENCH_PARAMS: [(&str, BooleanParameters); 2] = [
|
||||
("BOOLEAN_DEFAULT_PARAMS", DEFAULT_PARAMETERS),
|
||||
("BOOLEAN_TFHE_LIB_PARAMS", TFHE_LIB_PARAMETERS),
|
||||
(
|
||||
"BOOLEAN_TFHE_LIB_PARAMS",
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
),
|
||||
];
|
||||
|
||||
criterion_group!(
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::env;
|
||||
|
||||
use criterion::{criterion_group, Criterion};
|
||||
use itertools::iproduct;
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::Rng;
|
||||
use std::vec::IntoIter;
|
||||
use tfhe::integer::keycache::KEY_CACHE;
|
||||
@@ -457,6 +458,92 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F>(
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
// Functions used to apply different way of selecting a scalar based on the context.
|
||||
fn default_scalar(rng: &mut ThreadRng, _clear_bit_size: usize) -> u64 {
|
||||
rng.gen::<u64>()
|
||||
}
|
||||
|
||||
fn shift_scalar(_rng: &mut ThreadRng, _clear_bit_size: usize) -> u64 {
|
||||
// Shifting by one is the worst case scenario.
|
||||
1
|
||||
}
|
||||
|
||||
fn mul_scalar(rng: &mut ThreadRng, _clear_bit_size: usize) -> u64 {
|
||||
loop {
|
||||
let scalar = rng.gen_range(3u64..=u64::MAX);
|
||||
// If scalar is power of two, it is just a shit, which is an happy path.
|
||||
if !scalar.is_power_of_two() {
|
||||
return scalar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn div_scalar(rng: &mut ThreadRng, clear_bit_size: usize) -> u64 {
|
||||
loop {
|
||||
let scalar = rng.gen_range(1..=u64::MAX);
|
||||
// Avoid overflow issues for u64 where we would take values mod 1
|
||||
if (scalar as u128 % (1u128 << clear_bit_size)) != 0 {
|
||||
return scalar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn if_then_else_parallelized(c: &mut Criterion) {
|
||||
let bench_name = "integer::if_then_else_parallelized";
|
||||
let display_name = "if_then_else";
|
||||
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_tree_values = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_0 = cks.encrypt_radix(clear_0, num_block);
|
||||
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_1 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_1 = cks.encrypt_radix(clear_1, num_block);
|
||||
|
||||
let cond = sks.create_trivial_radix(rng.gen_bool(0.5) as u64, num_block);
|
||||
|
||||
(cond, ct_0, ct_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_tree_values,
|
||||
|(condition, true_ct, false_ct)| {
|
||||
sks.if_then_else_parallelized(&condition, &true_ct, &false_ct)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus().0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
macro_rules! define_server_key_bench_unary_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
@@ -794,6 +881,7 @@ criterion_group!(
|
||||
right_shift_parallelized,
|
||||
rotate_left_parallelized,
|
||||
rotate_right_parallelized,
|
||||
if_then_else_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* [Benchmarks](getting_started/benchmarks.md)
|
||||
* [Security and Cryptography](getting_started/security_and_cryptography.md)
|
||||
|
||||
## Tutorials
|
||||
## Tutorials
|
||||
* [Homomorphic Parity Bit](tutorials/parity_bit.md)
|
||||
* [Homomorphic Case Changing on Latin String](tutorials/latin_fhe_string.md)
|
||||
|
||||
@@ -25,17 +25,17 @@
|
||||
|
||||
## Fine-grained APIs
|
||||
* [Quick Start](fine_grained_api/quick_start.md)
|
||||
* [Boolean](fine_grained_api/Boolean/tutorial.md)
|
||||
* [Boolean](fine_grained_api/Boolean/readme.md)
|
||||
* [Operations](fine_grained_api/Boolean/operations.md)
|
||||
* [Cryptographic Parameters](fine_grained_api/Boolean/parameters.md)
|
||||
* [Serialization/Deserialization](fine_grained_api/Boolean/serialization.md)
|
||||
|
||||
* [Shortint](fine_grained_api/shortint/tutorial.md)
|
||||
* [Shortint](fine_grained_api/shortint/readme.md)
|
||||
* [Operations](fine_grained_api/shortint/operations.md)
|
||||
* [Cryptographic Parameters](fine_grained_api/shortint/parameters.md)
|
||||
* [Serialization/Deserialization](fine_grained_api/shortint/serialization.md)
|
||||
|
||||
* [Integer](fine_grained_api/integer/tutorial.md)
|
||||
* [Integer](fine_grained_api/integer/readme.md)
|
||||
* [Operations](fine_grained_api/integer/operations.md)
|
||||
* [Cryptographic Parameters](fine_grained_api/integer/parameters.md)
|
||||
* [Serialization/Deserialization](fine_grained_api/integer/serialization.md)
|
||||
|
||||
BIN
tfhe/docs/_static/carry.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
tfhe/docs/_static/ciphertext-representation.png
vendored
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 23 KiB |
BIN
tfhe/docs/_static/fig6.png
vendored
|
Before Width: | Height: | Size: 15 KiB |
BIN
tfhe/docs/_static/fig7.png
vendored
|
Before Width: | Height: | Size: 12 KiB |
BIN
tfhe/docs/_static/fig8.png
vendored
|
Before Width: | Height: | Size: 12 KiB |
BIN
tfhe/docs/_static/integer-ciphertext.png
vendored
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 32 KiB |
BIN
tfhe/docs/_static/lwe.png
vendored
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 16 KiB |
BIN
tfhe/docs/_static/multisum.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tfhe/docs/_static/overflow.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
tfhe/docs/_static/sha256.png
vendored
Normal file
|
After Width: | Height: | Size: 20 KiB |
@@ -17,11 +17,8 @@ The sha256 function processes the input data in blocks or chunks of 512 bits. Be
|
||||
|
||||
Or visually:
|
||||
|
||||
```
|
||||
0 L L+1 L+1+k L+1+k+64
|
||||
|-----------------------------------|---|--------------------------------|----------------------|
|
||||
Original input (L bits) "1" bit "0" bits Encoding of the number L
|
||||
```
|
||||

|
||||
|
||||
Where the numbers on the top represent the length of the padded input at each position, and L+1+k+64 is a multiple of 512 (the length of the padded input).
|
||||
|
||||
#### Operations and functions
|
||||
@@ -63,7 +60,7 @@ Note that all these operations can be evaluated homomorphically. ROTR and SHR ca
|
||||
|
||||
#### Sha256 computation
|
||||
|
||||
As we have mentioned, the sha256 function works with chunks of 512 bits. For each chunk, we will compute 64 32-bit words. 16 will come from the 512 bits and the rest will be computed using the previous functions. After computing the 64 words, and still within the same chunk iteration, a compression loop will compute a hash value (8 32-bit words), again using the previous functions and some constants to mix everything up. When we finish the last chunk iteration, the resulting hash values will be the output of the sha256 function.
|
||||
As we have mentioned, the sha256 function works with chunks of 512 bits. For each chunk, we will compute 64 32-bit words. 16 will come from the 512 bits and the rest will be computed using the previous functions. After computing the 64 words, and still within the same chunk iteration, a compression loop will compute a hash value (8 32-bit words), again using the previous functions and some constants to mix everything up. When we finish the last chunk iteration, the resulting hash values will be the output of the sha256 function.
|
||||
|
||||
Here is how this function looks like using arrays of 32 bools to represent words:
|
||||
|
||||
@@ -317,6 +314,6 @@ By using ```stdin``` we can supply the data to hash using a file instead of the
|
||||
|
||||
Our implementation also accepts hexadecimal inputs. To be considered as such, the input must start with "0x" and contain only valid hex digits (otherwise it's interpreted as text).
|
||||
|
||||
Finally see that padding is executed on the client side. This has the advantage of hiding the exact length of the input to the server, who already doesn't know anything about the contents of it but may extract information from the length.
|
||||
Finally see that padding is executed on the client side. This has the advantage of hiding the exact length of the input to the server, who already doesn't know anything about the contents of it but may extract information from the length.
|
||||
|
||||
Another option would be to perform padding on the server side. The padding function would receive the encrypted input and pad it with trivial bit encryptions. We could then integrate the padding function inside the ```sha256_fhe``` function computed by the server.
|
||||
|
||||
@@ -9,7 +9,7 @@ Welcome to this tutorial about `TFHE-rs` `core_crypto` module.
|
||||
To use `TFHE-rs`, it first has to be added as a dependency in the `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = [ "x86_64-unix" ] }
|
||||
tfhe = { version = "0.3.2", features = [ "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available (like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available). To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
|
||||
@@ -19,19 +19,19 @@ For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `a
|
||||
In short: For `x86_64`-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
|
||||
tfhe = { version = "0.3.2", features = ["x86_64-unix"] }
|
||||
```
|
||||
|
||||
For Apple Silicon or aarch64-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = ["aarch64-unix"] }
|
||||
tfhe = { version = "0.3.2", features = ["aarch64-unix"] }
|
||||
```
|
||||
|
||||
For `x86_64`-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = ["x86_64"] }
|
||||
tfhe = { version = "0.3.2", features = ["x86_64"] }
|
||||
```
|
||||
|
||||
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.
|
||||
|
||||
@@ -38,6 +38,7 @@ fn main() {
|
||||
DecompositionLevelCount(2),
|
||||
DecompositionBaseLog(2),
|
||||
DecompositionLevelCount(5),
|
||||
EncryptionKeyChoice::Small,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
Due to their nature, homomorphic operations are naturally slower than their clear equivalent. Some timings are exposed for basic operations. For completeness, benchmarks for other libraries are also given.
|
||||
|
||||
{% hint style="info" %}
|
||||
All benchmarks were launched on an AWS m6i.metal with the following specifications: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
|
||||
{% endhint %}
|
||||
|
||||
## Boolean
|
||||
|
||||
@@ -31,7 +33,7 @@ This measures the execution time of a single binary Boolean gate.
|
||||
|
||||
## Integer
|
||||
This measures the execution time for some operation sets of tfhe-rs::integer.
|
||||
|
||||
|
||||
| Operation \ Size | `FheUint8` | `FheUint16` | `FheUint32` | ` FheUint64` | `FheUint128` | `FheUint256` |
|
||||
|--------------------------------------------------------|------------|-------------|-------------|--------------|--------------|--------------|
|
||||
| Negation (`-`) | 80.4 ms | 106 ms | 132 ms | 193 ms | 257 ms | 348 ms |
|
||||
@@ -48,7 +50,7 @@ This measures the execution time for some operation sets of tfhe-rs::integer.
|
||||
|
||||
|
||||
All timings are related to parallelized Radix-based integer operations, where each block is encrypted using the default parameters (i.e., PARAM\_MESSAGE\_2\_CARRY\_2, more information about parameters can be found [here](../fine_grained_api/shortint/parameters.md)).
|
||||
To ensure predictable timings, the operation flavor is the `default` one: the carry is propagated if needed. The operation costs could be reduced by using `unchecked`, `checked`, or `smart`.
|
||||
To ensure predictable timings, the operation flavor is the `default` one: the carry is propagated if needed. The operation costs could be reduced by using `unchecked`, `checked`, or `smart`.
|
||||
|
||||
|
||||
## Shortint
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
|
||||
tfhe = { version = "0.3.2", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
When running code that uses `TFHE-rs`, it is highly recommended to run in release mode with cargo's `--release` flag to have the best possible performance, eg: `cargo run --release`.
|
||||
{% hint style="success" %}
|
||||
When running code that uses `TFHE-rs`, it is highly recommended to run in release mode with cargo's `--release` flag to have the best possible performance
|
||||
{% endhint %}
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (
|
||||
| Windows | `x86_64` | Unsupported |
|
||||
|
||||
{% hint style="info" %}
|
||||
Users who have ARM devices can use TFHE-rs by compiling using the `nightly` toolchain (see
|
||||
Users who have ARM devices can use TFHE-rs by compiling using the `nightly` toolchain (see
|
||||
[Configuration](../how_to/rust_configuration.md) for more details).
|
||||
{% endhint %}
|
||||
{% endhint %}
|
||||
|
||||
@@ -43,7 +43,7 @@ The list of supported operations is:
|
||||
|
||||
Native small homomorphic integer types (e.g., FheUint3 or FheUint4) easily compute various operations. In general, computing over encrypted data is as easy as computing over clear data, since the same operation symbol is used. The addition between two ciphertexts is done using the symbol `+` between two FheUint values. Many operations can be computed between a clear value (i.e. a scalar) and a ciphertext.
|
||||
|
||||
In Rust, operations on native types are modular. For example, computations on `u8` are carried out modulo 2^8. A similar idea applies for FheUintX, where operations are done modulo 2^X. For FheUint3, operations are done modulo 8 = 2^3.
|
||||
In Rust, operations on native types are modular. For example, computations on `u8` are carried out modulo $$2^{8}$$. A similar idea applies for FheUintX, where operations are done modulo $$2^{X}$$. For FheUint3, operations are done modulo $$8 = 2^{3}$$.
|
||||
|
||||
### Arithmetic operations.
|
||||
|
||||
@@ -72,7 +72,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
|
||||
let clear_a = 7;
|
||||
let clear_b = 3;
|
||||
let clear_c = 2;
|
||||
@@ -85,10 +85,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
a = a * &b; // Clear equivalent computations: 7 * 3 mod 8 = 5
|
||||
b = &b + &c; // Clear equivalent computations: 3 + 2 mod 8 = 5
|
||||
b = b - 5; // Clear equivalent computations: 5 - 5 mod 8 = 0
|
||||
|
||||
|
||||
let dec_a = a.decrypt(&keys);
|
||||
let dec_b = b.decrypt(&keys);
|
||||
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_a, (clear_a * clear_b) % 8);
|
||||
assert_eq!(dec_b, ((clear_b + clear_c) - 5) % 8);
|
||||
@@ -124,20 +124,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
|
||||
let clear_a = 7;
|
||||
let clear_b = 3;
|
||||
|
||||
|
||||
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
|
||||
a = a ^ &b;
|
||||
b = b ^ &a;
|
||||
a = a ^ &b;
|
||||
|
||||
|
||||
let dec_a = a.decrypt(&keys);
|
||||
let dec_b = b.decrypt(&keys);
|
||||
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_a, clear_b);
|
||||
assert_eq!(dec_b, clear_a);
|
||||
@@ -179,13 +179,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
|
||||
let clear_a = 7;
|
||||
let clear_b = 3;
|
||||
|
||||
|
||||
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
|
||||
assert_eq!(a.gt(&b).decrypt(&keys) != 0, true);
|
||||
assert_eq!(b.le(&a).decrypt(&keys) != 0, true);
|
||||
|
||||
@@ -237,13 +237,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint2().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
|
||||
let clear_a = 1;
|
||||
let clear_b = 3;
|
||||
let a = FheUint2::try_encrypt(clear_a, &keys)?;
|
||||
let b = FheUint2::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
|
||||
|
||||
let c = a.bivariate_function(&b, std::cmp::max);
|
||||
let decrypted = c.decrypt(&keys);
|
||||
assert_eq!(decrypted, std::cmp::max(clear_a, clear_b) as u8);
|
||||
@@ -286,7 +286,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
|
||||
let clear_a = 15_u64;
|
||||
let clear_b = 27_u64;
|
||||
let clear_c = 43_u64;
|
||||
@@ -299,10 +299,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
a = a * &b; // Clear equivalent computations: 15 * 27 mod 256 = 149
|
||||
b = &b + &c; // Clear equivalent computations: 27 + 43 mod 256 = 70
|
||||
b = b - 76u8; // Clear equivalent computations: 70 - 76 mod 256 = 250
|
||||
|
||||
|
||||
let dec_a: u8 = a.decrypt(&keys);
|
||||
let dec_b: u8 = b.decrypt(&keys);
|
||||
|
||||
|
||||
assert_eq!(dec_a, ((clear_a * clear_b) % 256_u64) as u8);
|
||||
assert_eq!(dec_b, (((clear_b + clear_c).wrapping_sub(76_u64)) % 256_u64) as u8);
|
||||
|
||||
@@ -337,7 +337,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
|
||||
let clear_a = 164;
|
||||
let clear_b = 212;
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ fn main() {
|
||||
|
||||
The default configuration for x86 Unix machines:
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
|
||||
tfhe = { version = "0.3.2", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Configuration options for different platforms can be found [here](../getting_started/installation.md). Other rust and homomorphic types features can be found [here](../how_to/rust_configuration.md).
|
||||
|
||||
@@ -18,6 +18,8 @@ Then, a small random value called noise is added to the least significant bits.
|
||||
|
||||
$$plaintext = (\Delta * m) + e$$
|
||||
|
||||
$$m \in \mathbb{Z}_p$$
|
||||
|
||||

|
||||
|
||||
To go from a **plaintext** to a **ciphertext,** one must encrypt the plaintext using a secret key.
|
||||
@@ -68,7 +70,7 @@ In `TFHE-rs`, noise is encoded in the least significant bits of each plaintext.
|
||||
|
||||
The figure below illustrates this problem in the case of an addition, where an extra bit of noise is incurred as a result.
|
||||
|
||||

|
||||

|
||||
|
||||
`TFHE-rs` offers the ability to automatically manage noise by performing bootstrapping operations to reset the noise.
|
||||
|
||||
@@ -84,7 +86,7 @@ Since encoded values have a fixed precision, operating on them can produce resul
|
||||
|
||||
As an example, consider adding two ciphertexts. Adding two values could end up outside the range of either ciphertext, and thus necessitate a carry, which would then be carried onto the first padding bit. In the figure below, each plaintext over 32 bits has one bit of padding on its left (i.e., the most significant bit). After the addition, the padding bit is no longer available, as it has been used in order for the carry. This is referred to as **consuming** bits of padding. Since no padding is left, there is no guarantee that further additions would yield correct results.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### Security.
|
||||
|
||||
@@ -11,7 +11,7 @@ To serialize our data, a [data format](https://serde.rs/#data-formats) should be
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.3.0", features = ["integer","x86_64-unix"]}
|
||||
tfhe = { version = "0.3.2", features = ["integer","x86_64-unix"]}
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ To use the `FheUint8` type, the `integer` feature must be activated:
|
||||
|
||||
[dependencies]
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
|
||||
tfhe = { version = "0.3.2", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
@@ -21,7 +21,7 @@ To use Booleans, the `booleans` feature in our Cargo.toml must be enabled:
|
||||
# Cargo.toml
|
||||
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.3.0", features = ["boolean", "x86_64-unix"]}
|
||||
tfhe = { version = "0.3.2", features = ["boolean", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::utilities::{write_to_json, OperatorType};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use tfhe::boolean::parameters::{DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
|
||||
use tfhe::boolean::parameters::{DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165};
|
||||
use tfhe::boolean::{client_key, server_key};
|
||||
|
||||
fn write_result(file: &mut File, name: &str, value: usize) {
|
||||
@@ -17,7 +17,7 @@ fn write_result(file: &mut File, name: &str, value: usize) {
|
||||
fn client_server_key_sizes(results_file: &Path) {
|
||||
let boolean_params_vec = vec![
|
||||
(DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS"),
|
||||
(TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS"),
|
||||
(PARAMETERS_ERROR_PROB_2_POW_MINUS_165, "TFHE_LIB_PARAMETERS"),
|
||||
];
|
||||
File::create(results_file).expect("create results file failed");
|
||||
let mut file = OpenOptions::new()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use tfhe::boolean::client_key::ClientKey;
|
||||
use tfhe::boolean::parameters::TFHE_LIB_PARAMETERS;
|
||||
use tfhe::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165;
|
||||
use tfhe::boolean::prelude::BinaryBooleanGates;
|
||||
use tfhe::boolean::server_key::ServerKey;
|
||||
|
||||
fn main() {
|
||||
// let (cks, sks) = gen_keys();
|
||||
let cks = ClientKey::new(&TFHE_LIB_PARAMETERS);
|
||||
let cks = ClientKey::new(&PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
let sks = ServerKey::new(&cks);
|
||||
|
||||
let left = false;
|
||||
|
||||
@@ -124,11 +124,11 @@ impl ClientKey {
|
||||
/// ```rust
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::client_key::ClientKey;
|
||||
/// use tfhe::boolean::parameters::TFHE_LIB_PARAMETERS;
|
||||
/// use tfhe::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165;
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
/// // Generate the client key:
|
||||
/// let cks = ClientKey::new(&TFHE_LIB_PARAMETERS);
|
||||
/// let cks = ClientKey::new(&PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new(parameter_set: &BooleanParameters) -> ClientKey {
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
|
||||
use crate::core_crypto::commons::generators::{DeterministicSeeder, EncryptionRandomGenerator};
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
|
||||
use crate::core_crypto::commons::parameters::CiphertextModulus;
|
||||
use crate::core_crypto::commons::parameters::{CiphertextModulus, PBSOrder};
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::fft_impl::fft64::math::fft::Fft;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -19,24 +19,27 @@ struct Memory {
|
||||
buffer: Vec<u32>,
|
||||
}
|
||||
|
||||
pub struct BuffersRef<'a> {
|
||||
pub(crate) lookup_table: GlweCiphertextMutView<'a, u32>,
|
||||
// For the intermediate keyswitch result in the case of a big ciphertext
|
||||
pub(crate) buffer_lwe_after_ks: LweCiphertextMutView<'a, u32>,
|
||||
// For the intermediate PBS result in the case of a smallciphertext
|
||||
pub(crate) buffer_lwe_after_pbs: LweCiphertextMutView<'a, u32>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// Return a tuple with buffers that matches the server key.
|
||||
///
|
||||
/// - The first element is the accumulator for bootstrap step.
|
||||
/// - The second element is a lwe buffer where the result of the of the bootstrap should be
|
||||
/// written
|
||||
fn as_buffers(
|
||||
&mut self,
|
||||
server_key: &ServerKey,
|
||||
) -> (GlweCiphertextView<'_, u32>, LweCiphertextMutView<'_, u32>) {
|
||||
fn as_buffers(&mut self, server_key: &ServerKey) -> BuffersRef<'_> {
|
||||
let num_elem_in_accumulator = server_key.bootstrapping_key.glwe_size().0
|
||||
* server_key.bootstrapping_key.polynomial_size().0;
|
||||
let num_elem_in_lwe = server_key
|
||||
let num_elem_in_lwe_after_ks = server_key.key_switching_key.output_lwe_size().0;
|
||||
let num_elem_in_lwe_after_pbs = server_key
|
||||
.bootstrapping_key
|
||||
.output_lwe_dimension()
|
||||
.to_lwe_size()
|
||||
.0;
|
||||
let total_elem_needed = num_elem_in_accumulator + num_elem_in_lwe;
|
||||
|
||||
let total_elem_needed =
|
||||
num_elem_in_accumulator + num_elem_in_lwe_after_ks + num_elem_in_lwe_after_pbs;
|
||||
|
||||
let all_elements = if self.buffer.len() < total_elem_needed {
|
||||
self.buffer.resize(total_elem_needed, 0u32);
|
||||
@@ -45,30 +48,35 @@ impl Memory {
|
||||
&mut self.buffer[..total_elem_needed]
|
||||
};
|
||||
|
||||
let (accumulator_elements, lwe_elements) =
|
||||
let (accumulator_elements, other_elements) =
|
||||
all_elements.split_at_mut(num_elem_in_accumulator);
|
||||
|
||||
{
|
||||
let mut accumulator = GlweCiphertextMutView::from_container(
|
||||
accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
accumulator.get_mut_mask().as_mut().fill(0u32);
|
||||
accumulator.get_mut_body().as_mut().fill(PLAINTEXT_TRUE);
|
||||
}
|
||||
|
||||
let accumulator = GlweCiphertextView::from_container(
|
||||
let mut acc = GlweCiphertext::from_container(
|
||||
accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
let lwe =
|
||||
LweCiphertextMutView::from_container(lwe_elements, CiphertextModulus::new_native());
|
||||
acc.get_mut_mask().as_mut().fill(0u32);
|
||||
acc.get_mut_body().as_mut().fill(PLAINTEXT_TRUE);
|
||||
|
||||
(accumulator, lwe)
|
||||
let (after_ks_elements, after_pbs_elements) =
|
||||
other_elements.split_at_mut(num_elem_in_lwe_after_ks);
|
||||
|
||||
let buffer_lwe_after_ks = LweCiphertextMutView::from_container(
|
||||
after_ks_elements,
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
let buffer_lwe_after_pbs = LweCiphertextMutView::from_container(
|
||||
after_pbs_elements,
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
BuffersRef {
|
||||
lookup_table: acc,
|
||||
buffer_lwe_after_ks,
|
||||
buffer_lwe_after_pbs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +94,7 @@ impl Memory {
|
||||
pub struct ServerKey {
|
||||
pub(crate) bootstrapping_key: FourierLweBootstrapKeyOwned,
|
||||
pub(crate) key_switching_key: LweKeyswitchKeyOwned<u32>,
|
||||
pub(crate) pbs_order: PBSOrder,
|
||||
}
|
||||
|
||||
impl ServerKey {
|
||||
@@ -120,6 +129,7 @@ impl ServerKey {
|
||||
pub struct CompressedServerKey {
|
||||
pub(crate) bootstrapping_key: SeededLweBootstrapKeyOwned<u32>,
|
||||
pub(crate) key_switching_key: SeededLweKeyswitchKeyOwned<u32>,
|
||||
pub(crate) pbs_order: PBSOrder,
|
||||
}
|
||||
|
||||
/// Perform ciphertext bootstraps on the CPU
|
||||
@@ -204,6 +214,7 @@ impl Bootstrapper {
|
||||
Ok(ServerKey {
|
||||
bootstrapping_key: fourier_bsk,
|
||||
key_switching_key: ksk,
|
||||
pbs_order: cks.parameters.encryption_key_choice.into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -249,6 +260,7 @@ impl Bootstrapper {
|
||||
Ok(CompressedServerKey {
|
||||
bootstrapping_key,
|
||||
key_switching_key,
|
||||
pbs_order: cks.parameters.encryption_key_choice.into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -257,7 +269,11 @@ impl Bootstrapper {
|
||||
input: &LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<LweCiphertextOwned<u32>, Box<dyn Error>> {
|
||||
let (accumulator, mut buffer_after_pbs) = self.memory.as_buffers(server_key);
|
||||
let BuffersRef {
|
||||
lookup_table: accumulator,
|
||||
mut buffer_lwe_after_pbs,
|
||||
..
|
||||
} = self.memory.as_buffers(server_key);
|
||||
|
||||
let fourier_bsk = &server_key.bootstrapping_key;
|
||||
|
||||
@@ -277,7 +293,7 @@ impl Bootstrapper {
|
||||
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
input,
|
||||
&mut buffer_after_pbs,
|
||||
&mut buffer_lwe_after_pbs,
|
||||
&accumulator,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
@@ -285,7 +301,7 @@ impl Bootstrapper {
|
||||
);
|
||||
|
||||
Ok(LweCiphertext::from_container(
|
||||
buffer_after_pbs.as_ref().to_owned(),
|
||||
buffer_lwe_after_pbs.as_ref().to_owned(),
|
||||
input.ciphertext_modulus(),
|
||||
))
|
||||
}
|
||||
@@ -315,7 +331,11 @@ impl Bootstrapper {
|
||||
mut ciphertext: LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<Ciphertext, Box<dyn Error>> {
|
||||
let (accumulator, mut buffer_lwe_after_pbs) = self.memory.as_buffers(server_key);
|
||||
let BuffersRef {
|
||||
lookup_table,
|
||||
mut buffer_lwe_after_pbs,
|
||||
..
|
||||
} = self.memory.as_buffers(server_key);
|
||||
|
||||
let fourier_bsk = &server_key.bootstrapping_key;
|
||||
|
||||
@@ -337,7 +357,7 @@ impl Bootstrapper {
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
&ciphertext,
|
||||
&mut buffer_lwe_after_pbs,
|
||||
&accumulator,
|
||||
&lookup_table,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
@@ -352,6 +372,63 @@ impl Bootstrapper {
|
||||
|
||||
Ok(Ciphertext::Encrypted(ciphertext))
|
||||
}
|
||||
|
||||
pub(crate) fn keyswitch_bootstrap(
|
||||
&mut self,
|
||||
mut ciphertext: LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<Ciphertext, Box<dyn Error>> {
|
||||
let BuffersRef {
|
||||
lookup_table,
|
||||
mut buffer_lwe_after_ks,
|
||||
..
|
||||
} = self.memory.as_buffers(server_key);
|
||||
|
||||
let fourier_bsk = &server_key.bootstrapping_key;
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
self.computation_buffers.resize(
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<u64>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
let stack = self.computation_buffers.stack();
|
||||
|
||||
// Keyswitch from large LWE key to the small one
|
||||
keyswitch_lwe_ciphertext(
|
||||
&server_key.key_switching_key,
|
||||
&ciphertext,
|
||||
&mut buffer_lwe_after_ks,
|
||||
);
|
||||
|
||||
// Compute a bootstrap
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
&buffer_lwe_after_ks,
|
||||
&mut ciphertext,
|
||||
&lookup_table,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
|
||||
Ok(Ciphertext::Encrypted(ciphertext))
|
||||
}
|
||||
pub(crate) fn apply_bootstrapping_pattern(
|
||||
&mut self,
|
||||
ct: LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<Ciphertext, Box<dyn Error>> {
|
||||
match server_key.pbs_order {
|
||||
PBSOrder::KeyswitchBootstrap => self.keyswitch_bootstrap(ct, server_key),
|
||||
PBSOrder::BootstrapKeyswitch => self.bootstrap_keyswitch(ct, server_key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CompressedServerKey> for ServerKey {
|
||||
@@ -359,6 +436,7 @@ impl From<CompressedServerKey> for ServerKey {
|
||||
let CompressedServerKey {
|
||||
key_switching_key,
|
||||
bootstrapping_key,
|
||||
pbs_order,
|
||||
} = compressed_server_key;
|
||||
|
||||
let key_switching_key = key_switching_key.decompress_into_lwe_keyswitch_key();
|
||||
@@ -380,6 +458,7 @@ impl From<CompressedServerKey> for ServerKey {
|
||||
Self {
|
||||
key_switching_key,
|
||||
bootstrapping_key,
|
||||
pbs_order,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
//! underlying `core_crypto` module.
|
||||
|
||||
use crate::boolean::ciphertext::{Ciphertext, CompressedCiphertext};
|
||||
use crate::boolean::parameters::{BooleanKeySwitchingParameters, BooleanParameters};
|
||||
use crate::boolean::parameters::{
|
||||
BooleanKeySwitchingParameters, BooleanParameters, EncryptionKeyChoice,
|
||||
};
|
||||
use crate::boolean::{ClientKey, CompressedPublicKey, PublicKey, PLAINTEXT_FALSE, PLAINTEXT_TRUE};
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
@@ -15,7 +17,7 @@ use crate::core_crypto::commons::generators::{
|
||||
DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
};
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::parameters::{PBSOrder, *};
|
||||
use crate::core_crypto::seeders::new_seeder;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -115,25 +117,36 @@ impl BooleanEngine {
|
||||
pub fn create_public_key(&mut self, client_key: &ClientKey) -> PublicKey {
|
||||
let client_parameters = client_key.parameters;
|
||||
|
||||
let (lwe_sk, encryption_noise) = match client_parameters.encryption_key_choice {
|
||||
EncryptionKeyChoice::Big => (
|
||||
client_key.glwe_secret_key.as_lwe_secret_key(),
|
||||
client_key.parameters.glwe_modular_std_dev,
|
||||
),
|
||||
EncryptionKeyChoice::Small => {
|
||||
let view = LweSecretKey::from_container(client_key.lwe_secret_key.as_ref());
|
||||
(view, client_key.parameters.lwe_modular_std_dev)
|
||||
}
|
||||
};
|
||||
|
||||
// Formula is (n + 1) * log2(q) + 128
|
||||
let zero_encryption_count = LwePublicKeyZeroEncryptionCount(
|
||||
client_parameters.lwe_dimension.to_lwe_size().0 * LOG2_Q_32 + 128,
|
||||
lwe_sk.lwe_dimension().to_lwe_size().0 * LOG2_Q_32 + 128,
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "__wasm_api"))]
|
||||
let lwe_public_key: LwePublicKeyOwned<u32> = par_allocate_and_generate_new_lwe_public_key(
|
||||
&client_key.lwe_secret_key,
|
||||
&lwe_sk,
|
||||
zero_encryption_count,
|
||||
client_key.parameters.lwe_modular_std_dev,
|
||||
encryption_noise,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
#[cfg(feature = "__wasm_api")]
|
||||
let lwe_public_key: LwePublicKeyOwned<u32> = allocate_and_generate_new_lwe_public_key(
|
||||
&client_key.lwe_secret_key,
|
||||
&lwe_sk,
|
||||
zero_encryption_count,
|
||||
client_key.parameters.lwe_modular_std_dev,
|
||||
encryption_noise,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
@@ -147,25 +160,36 @@ impl BooleanEngine {
|
||||
pub fn create_compressed_public_key(&mut self, client_key: &ClientKey) -> CompressedPublicKey {
|
||||
let client_parameters = client_key.parameters;
|
||||
|
||||
let (lwe_sk, encryption_noise) = match client_parameters.encryption_key_choice {
|
||||
EncryptionKeyChoice::Big => (
|
||||
client_key.glwe_secret_key.as_lwe_secret_key(),
|
||||
client_key.parameters.glwe_modular_std_dev,
|
||||
),
|
||||
EncryptionKeyChoice::Small => {
|
||||
let view = LweSecretKey::from_container(client_key.lwe_secret_key.as_ref());
|
||||
(view, client_key.parameters.lwe_modular_std_dev)
|
||||
}
|
||||
};
|
||||
|
||||
// Formula is (n + 1) * log2(q) + 128
|
||||
let zero_encryption_count = LwePublicKeyZeroEncryptionCount(
|
||||
client_parameters.lwe_dimension.to_lwe_size().0 * LOG2_Q_32 + 128,
|
||||
lwe_sk.lwe_dimension().to_lwe_size().0 * LOG2_Q_32 + 128,
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "__wasm_api"))]
|
||||
let compressed_lwe_public_key = par_allocate_and_generate_new_seeded_lwe_public_key(
|
||||
&client_key.lwe_secret_key,
|
||||
&lwe_sk,
|
||||
zero_encryption_count,
|
||||
client_key.parameters.lwe_modular_std_dev,
|
||||
encryption_noise,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.bootstrapper.seeder,
|
||||
);
|
||||
|
||||
#[cfg(feature = "__wasm_api")]
|
||||
let compressed_lwe_public_key = allocate_and_generate_new_seeded_lwe_public_key(
|
||||
&client_key.lwe_secret_key,
|
||||
&lwe_sk,
|
||||
zero_encryption_count,
|
||||
client_key.parameters.lwe_modular_std_dev,
|
||||
encryption_noise,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.bootstrapper.seeder,
|
||||
);
|
||||
@@ -182,10 +206,31 @@ impl BooleanEngine {
|
||||
cks2: &ClientKey,
|
||||
params: BooleanKeySwitchingParameters,
|
||||
) -> LweKeyswitchKeyOwned<u32> {
|
||||
let (lwe_sk1, lwe_sk2) = match (
|
||||
cks1.parameters.encryption_key_choice,
|
||||
cks2.parameters.encryption_key_choice,
|
||||
) {
|
||||
(EncryptionKeyChoice::Big, EncryptionKeyChoice::Big) => (
|
||||
cks1.glwe_secret_key.as_lwe_secret_key(),
|
||||
cks2.glwe_secret_key.as_lwe_secret_key(),
|
||||
),
|
||||
(EncryptionKeyChoice::Small, EncryptionKeyChoice::Small) => {
|
||||
let view1 = LweSecretKey::from_container(cks1.lwe_secret_key.as_ref());
|
||||
let view2 = LweSecretKey::from_container(cks2.lwe_secret_key.as_ref());
|
||||
(view1, view2)
|
||||
}
|
||||
(choice1, choice2) => panic!(
|
||||
"EncryptionKeyChoice of cks1 and cks2 must be the same.\
|
||||
cks1 has {:?}, cks2 has: {:?}
|
||||
",
|
||||
choice1, choice2
|
||||
),
|
||||
};
|
||||
|
||||
// Creation of the key switching key
|
||||
allocate_and_generate_new_lwe_keyswitch_key(
|
||||
&cks1.lwe_secret_key,
|
||||
&cks2.lwe_secret_key,
|
||||
&lwe_sk1,
|
||||
&lwe_sk2,
|
||||
params.ks_base_log,
|
||||
params.ks_level,
|
||||
cks2.parameters.lwe_modular_std_dev,
|
||||
@@ -206,11 +251,22 @@ impl BooleanEngine {
|
||||
Plaintext(PLAINTEXT_FALSE)
|
||||
};
|
||||
|
||||
let (lwe_sk, encryption_noise) = match cks.parameters.encryption_key_choice {
|
||||
EncryptionKeyChoice::Big => (
|
||||
cks.glwe_secret_key.as_lwe_secret_key(),
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
),
|
||||
EncryptionKeyChoice::Small => {
|
||||
let view = LweSecretKey::from_container(cks.lwe_secret_key.as_ref());
|
||||
(view, cks.parameters.lwe_modular_std_dev)
|
||||
}
|
||||
};
|
||||
|
||||
// encryption
|
||||
let ct = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
&cks.lwe_secret_key,
|
||||
&lwe_sk,
|
||||
plain,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
encryption_noise,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
@@ -226,11 +282,22 @@ impl BooleanEngine {
|
||||
Plaintext(PLAINTEXT_FALSE)
|
||||
};
|
||||
|
||||
let (lwe_sk, encryption_noise) = match cks.parameters.encryption_key_choice {
|
||||
EncryptionKeyChoice::Big => (
|
||||
cks.glwe_secret_key.as_lwe_secret_key(),
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
),
|
||||
EncryptionKeyChoice::Small => {
|
||||
let view = LweSecretKey::from_container(cks.lwe_secret_key.as_ref());
|
||||
(view, cks.parameters.lwe_modular_std_dev)
|
||||
}
|
||||
};
|
||||
|
||||
// encryption
|
||||
let ct = allocate_and_encrypt_new_seeded_lwe_ciphertext(
|
||||
&cks.lwe_secret_key,
|
||||
&lwe_sk,
|
||||
plain,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
encryption_noise,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.bootstrapper.seeder,
|
||||
);
|
||||
@@ -248,7 +315,7 @@ impl BooleanEngine {
|
||||
|
||||
let mut output = LweCiphertext::new(
|
||||
0u32,
|
||||
pks.parameters.lwe_dimension.to_lwe_size(),
|
||||
pks.lwe_public_key.lwe_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
@@ -275,7 +342,7 @@ impl BooleanEngine {
|
||||
|
||||
let mut output = LweCiphertext::new(
|
||||
0u32,
|
||||
compressed_pk.parameters.lwe_dimension.to_lwe_size(),
|
||||
compressed_pk.compressed_lwe_public_key.lwe_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
@@ -293,8 +360,15 @@ impl BooleanEngine {
|
||||
match ct {
|
||||
Ciphertext::Trivial(b) => *b,
|
||||
Ciphertext::Encrypted(ciphertext) => {
|
||||
let lwe_sk = match cks.parameters.encryption_key_choice {
|
||||
EncryptionKeyChoice::Big => cks.glwe_secret_key.as_lwe_secret_key(),
|
||||
EncryptionKeyChoice::Small => {
|
||||
LweSecretKey::from_container(cks.lwe_secret_key.as_ref())
|
||||
}
|
||||
};
|
||||
|
||||
// decryption
|
||||
let decrypted = decrypt_lwe_ciphertext(&cks.lwe_secret_key, ciphertext);
|
||||
let decrypted = decrypt_lwe_ciphertext(&lwe_sk, ciphertext);
|
||||
|
||||
// cast as a u32
|
||||
let decrypted_u32 = decrypted.0;
|
||||
@@ -404,11 +478,20 @@ impl BooleanEngine {
|
||||
} else {
|
||||
Plaintext(PLAINTEXT_FALSE)
|
||||
};
|
||||
allocate_and_trivially_encrypt_new_lwe_ciphertext(
|
||||
server_key
|
||||
|
||||
let lwe_size = match server_key.pbs_order {
|
||||
PBSOrder::KeyswitchBootstrap => server_key
|
||||
.key_switching_key
|
||||
.input_key_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
PBSOrder::BootstrapKeyswitch => server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
};
|
||||
|
||||
allocate_and_trivially_encrypt_new_lwe_ciphertext(
|
||||
lwe_size,
|
||||
plain,
|
||||
CiphertextModulus::new_native(),
|
||||
)
|
||||
@@ -463,10 +546,7 @@ impl BooleanEngine {
|
||||
|
||||
let mut buffer_lwe_before_pbs_o = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_condition_ct.lwe_size(),
|
||||
ct_condition_ct.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
@@ -487,23 +567,47 @@ impl BooleanEngine {
|
||||
let cst = Plaintext(PLAINTEXT_FALSE);
|
||||
lwe_ciphertext_plaintext_add_assign(&mut ct_temp_2, cst); // - 1/8
|
||||
|
||||
// Compute the first programmable bootstrapping with fixed test polynomial:
|
||||
let mut ct_pbs_1 = bootstrapper
|
||||
.bootstrap(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap();
|
||||
match server_key.pbs_order {
|
||||
PBSOrder::KeyswitchBootstrap => {
|
||||
let ct_ks_1 = bootstrapper
|
||||
.keyswitch(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap();
|
||||
|
||||
let ct_pbs_2 = bootstrapper.bootstrap(&ct_temp_2, server_key).unwrap();
|
||||
// Compute the first programmable bootstrapping with fixed test polynomial:
|
||||
let mut ct_pbs_1 = bootstrapper.bootstrap(&ct_ks_1, server_key).unwrap();
|
||||
|
||||
// Compute the linear combination to add the two results:
|
||||
// buffer_lwe_pbs + ct_pbs_2 + (0,...,0, +1/8)
|
||||
lwe_ciphertext_add_assign(&mut ct_pbs_1, &ct_pbs_2); // + buffer_lwe_pbs
|
||||
let cst = Plaintext(PLAINTEXT_TRUE);
|
||||
lwe_ciphertext_plaintext_add_assign(&mut ct_pbs_1, cst); // + 1/8
|
||||
let ct_ks_2 = bootstrapper.keyswitch(&ct_temp_2, server_key).unwrap();
|
||||
let ct_pbs_2 = bootstrapper.bootstrap(&ct_ks_2, server_key).unwrap();
|
||||
|
||||
let ct_ks = bootstrapper.keyswitch(&ct_pbs_1, server_key).unwrap();
|
||||
// Compute the linear combination to add the two results:
|
||||
// buffer_lwe_pbs + ct_pbs_2 + (0,...,0, +1/8)
|
||||
lwe_ciphertext_add_assign(&mut ct_pbs_1, &ct_pbs_2); // + buffer_lwe_pbs
|
||||
let cst = Plaintext(PLAINTEXT_TRUE);
|
||||
lwe_ciphertext_plaintext_add_assign(&mut ct_pbs_1, cst); // + 1/8
|
||||
|
||||
// Output the result:
|
||||
Ciphertext::Encrypted(ct_ks)
|
||||
// Output the result:
|
||||
Ciphertext::Encrypted(ct_pbs_1)
|
||||
}
|
||||
PBSOrder::BootstrapKeyswitch => {
|
||||
// Compute the first programmable bootstrapping with fixed test polynomial:
|
||||
let mut ct_pbs_1 = bootstrapper
|
||||
.bootstrap(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap();
|
||||
|
||||
let ct_pbs_2 = bootstrapper.bootstrap(&ct_temp_2, server_key).unwrap();
|
||||
|
||||
// Compute the linear combination to add the two results:
|
||||
// buffer_lwe_pbs + ct_pbs_2 + (0,...,0, +1/8)
|
||||
lwe_ciphertext_add_assign(&mut ct_pbs_1, &ct_pbs_2); // + buffer_lwe_pbs
|
||||
let cst = Plaintext(PLAINTEXT_TRUE);
|
||||
lwe_ciphertext_plaintext_add_assign(&mut ct_pbs_1, cst); // + 1/8
|
||||
|
||||
let ct_ks = bootstrapper.keyswitch(&ct_pbs_1, server_key).unwrap();
|
||||
|
||||
// Output the result:
|
||||
Ciphertext::Encrypted(ct_ks)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,10 +633,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
@@ -547,7 +648,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
|
||||
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -572,10 +673,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
@@ -590,7 +688,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
|
||||
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -615,10 +713,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
@@ -634,7 +729,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
|
||||
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -659,10 +754,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
@@ -676,7 +768,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
|
||||
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -701,10 +793,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
@@ -721,7 +810,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
|
||||
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -746,10 +835,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
@@ -768,7 +854,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
.bootstrap_keyswitch(buffer_lwe_before_pbs, server_key)
|
||||
.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,24 @@
|
||||
|
||||
pub use crate::core_crypto::commons::dispersion::StandardDev;
|
||||
pub use crate::core_crypto::commons::parameters::{
|
||||
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
DecompositionBaseLog, DecompositionLevelCount, EncryptionKeyChoice, GlweDimension,
|
||||
LweDimension, PolynomialSize,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A set of cryptographic parameters for homomorphic Boolean circuit evaluation.
|
||||
/// The choice of encryption key for (`boolean ciphertext`)[`super::ciphertext::Ciphertext`].
|
||||
///
|
||||
/// * The `Big` choice means the big LWE key derived from the GLWE key is used to encrypt the input
|
||||
/// ciphertext. This offers better performance but the (`public
|
||||
/// key`)[`super::public_key::PublicKey`] can be extremely large and in some cases may not fit in
|
||||
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the PBS is computed
|
||||
/// first followed by a keyswitch.
|
||||
/// * The `Small` choice means the small LWE key is used to encrypt the input ciphertext.
|
||||
/// Performance is not as good as in the `Big` case but (`public
|
||||
/// key`)[`super::public_key::PublicKey`] sizes are much more manageable and shoud always fit in
|
||||
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the keyswitch is
|
||||
/// computed first followed by a PBS.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BooleanParameters {
|
||||
pub lwe_dimension: LweDimension,
|
||||
@@ -36,6 +49,7 @@ pub struct BooleanParameters {
|
||||
pub pbs_level: DecompositionLevelCount,
|
||||
pub ks_base_log: DecompositionBaseLog,
|
||||
pub ks_level: DecompositionLevelCount,
|
||||
pub encryption_key_choice: EncryptionKeyChoice,
|
||||
}
|
||||
|
||||
impl BooleanParameters {
|
||||
@@ -58,6 +72,7 @@ impl BooleanParameters {
|
||||
pbs_level: DecompositionLevelCount,
|
||||
ks_base_log: DecompositionBaseLog,
|
||||
ks_level: DecompositionLevelCount,
|
||||
encryption_key_choice: EncryptionKeyChoice,
|
||||
) -> BooleanParameters {
|
||||
BooleanParameters {
|
||||
lwe_dimension,
|
||||
@@ -69,6 +84,7 @@ impl BooleanParameters {
|
||||
pbs_level,
|
||||
ks_level,
|
||||
ks_base_log,
|
||||
encryption_key_choice,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +95,7 @@ pub struct BooleanKeySwitchingParameters {
|
||||
pub ks_base_log: DecompositionBaseLog,
|
||||
pub ks_level: DecompositionLevelCount,
|
||||
}
|
||||
|
||||
impl BooleanKeySwitchingParameters {
|
||||
/// Constructs a new set of parameters for boolean circuit evaluation.
|
||||
///
|
||||
@@ -106,30 +123,72 @@ impl BooleanKeySwitchingParameters {
|
||||
/// This parameter set allows to evaluate faster Boolean circuits than the `TFHE_LIB_PARAMETERS`
|
||||
/// one.
|
||||
pub const DEFAULT_PARAMETERS: BooleanParameters = BooleanParameters {
|
||||
lwe_dimension: LweDimension(777),
|
||||
glwe_dimension: GlweDimension(3),
|
||||
lwe_dimension: LweDimension(722),
|
||||
glwe_dimension: GlweDimension(2),
|
||||
polynomial_size: PolynomialSize(512),
|
||||
lwe_modular_std_dev: StandardDev(0.000003725679281679651),
|
||||
glwe_modular_std_dev: StandardDev(0.0000000000034525330484572114),
|
||||
pbs_base_log: DecompositionBaseLog(18),
|
||||
pbs_level: DecompositionLevelCount(1),
|
||||
ks_base_log: DecompositionBaseLog(4),
|
||||
ks_level: DecompositionLevelCount(3),
|
||||
lwe_modular_std_dev: StandardDev(0.000013071021089943935),
|
||||
glwe_modular_std_dev: StandardDev(0.00000004990272175010415),
|
||||
pbs_base_log: DecompositionBaseLog(6),
|
||||
pbs_level: DecompositionLevelCount(3),
|
||||
ks_base_log: DecompositionBaseLog(3),
|
||||
ks_level: DecompositionLevelCount(4),
|
||||
encryption_key_choice: EncryptionKeyChoice::Small,
|
||||
};
|
||||
|
||||
pub const DEFAULT_PARAMETERS_KS_PBS: BooleanParameters = BooleanParameters {
|
||||
lwe_dimension: LweDimension(664),
|
||||
glwe_dimension: GlweDimension(2),
|
||||
polynomial_size: PolynomialSize(512),
|
||||
lwe_modular_std_dev: StandardDev(0.00003808282923459771),
|
||||
glwe_modular_std_dev: StandardDev(0.00000004990272175010415),
|
||||
pbs_base_log: DecompositionBaseLog(6),
|
||||
pbs_level: DecompositionLevelCount(3),
|
||||
ks_base_log: DecompositionBaseLog(3),
|
||||
ks_level: DecompositionLevelCount(4),
|
||||
encryption_key_choice: EncryptionKeyChoice::Big,
|
||||
};
|
||||
|
||||
/// The secret keys generated with this parameter set are uniform binary.
|
||||
/// This parameter set ensures a probability of error upper-bounded by $2^{-165}$ as the ones
|
||||
/// proposed into [TFHE library](https://tfhe.github.io/tfhe/) for for 128-bits of security.
|
||||
/// They are updated to the last security standards, so they differ from the original
|
||||
/// publication.
|
||||
pub const TFHE_LIB_PARAMETERS: BooleanParameters = BooleanParameters {
|
||||
lwe_dimension: LweDimension(830),
|
||||
/// This parameter set ensures a probability of error upper-bounded by $2^{-165}$
|
||||
/// for for 128-bits of security.
|
||||
pub const PARAMETERS_ERROR_PROB_2_POW_MINUS_165: BooleanParameters = BooleanParameters {
|
||||
lwe_dimension: LweDimension(767),
|
||||
glwe_dimension: GlweDimension(2),
|
||||
polynomial_size: PolynomialSize(1024),
|
||||
lwe_modular_std_dev: StandardDev(0.000001412290588219445),
|
||||
glwe_modular_std_dev: StandardDev(0.00000000000000029403601535432533),
|
||||
pbs_base_log: DecompositionBaseLog(23),
|
||||
pbs_level: DecompositionLevelCount(1),
|
||||
ks_base_log: DecompositionBaseLog(5),
|
||||
ks_level: DecompositionLevelCount(3),
|
||||
lwe_modular_std_dev: StandardDev(0.000005104350373791501),
|
||||
glwe_modular_std_dev: StandardDev(0.0000000009313225746154785),
|
||||
pbs_base_log: DecompositionBaseLog(10),
|
||||
pbs_level: DecompositionLevelCount(2),
|
||||
ks_base_log: DecompositionBaseLog(3),
|
||||
ks_level: DecompositionLevelCount(5),
|
||||
encryption_key_choice: EncryptionKeyChoice::Small,
|
||||
};
|
||||
|
||||
pub const PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS: BooleanParameters = BooleanParameters {
|
||||
lwe_dimension: LweDimension(700),
|
||||
glwe_dimension: GlweDimension(1),
|
||||
polynomial_size: PolynomialSize(1024),
|
||||
lwe_modular_std_dev: StandardDev(0.0000196095987892077),
|
||||
glwe_modular_std_dev: StandardDev(0.00000004990272175010415),
|
||||
pbs_base_log: DecompositionBaseLog(5),
|
||||
pbs_level: DecompositionLevelCount(4),
|
||||
ks_base_log: DecompositionBaseLog(2),
|
||||
ks_level: DecompositionLevelCount(7),
|
||||
encryption_key_choice: EncryptionKeyChoice::Big,
|
||||
};
|
||||
|
||||
/// Parameter sets given in TFHE-lib:
|
||||
/// <https://github.com/tfhe/tfhe/blob/bc71bfae7ad9d5f8ce5f29bdfd691189bfe207f3/src/libtfhe/tfhe_gate_bootstrapping.cpp#L51>
|
||||
/// Original security in 2020 was 129-bits, while it is currently around 120 bits.
|
||||
pub const TFHE_LIB_PARAMETERS: BooleanParameters = BooleanParameters {
|
||||
lwe_dimension: LweDimension(630),
|
||||
glwe_dimension: GlweDimension(1),
|
||||
polynomial_size: PolynomialSize(1024),
|
||||
lwe_modular_std_dev: StandardDev(0.000005104350373791501),
|
||||
glwe_modular_std_dev: StandardDev(0.0000000009313225746154785),
|
||||
pbs_base_log: DecompositionBaseLog(7),
|
||||
pbs_level: DecompositionLevelCount(3),
|
||||
ks_base_log: DecompositionBaseLog(2),
|
||||
ks_level: DecompositionLevelCount(8),
|
||||
encryption_key_choice: EncryptionKeyChoice::Small,
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ impl CompressedPublicKey {
|
||||
mod tests {
|
||||
use crate::boolean::prelude::{
|
||||
BinaryBooleanGates, BooleanParameters, ClientKey, CompressedPublicKey, ServerKey,
|
||||
DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS,
|
||||
DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
};
|
||||
use crate::boolean::random_boolean;
|
||||
const NB_TEST: usize = 32;
|
||||
@@ -102,7 +102,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_compressed_public_key_tfhe_lib_parameters() {
|
||||
test_compressed_public_key(TFHE_LIB_PARAMETERS);
|
||||
test_compressed_public_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
|
||||
fn test_compressed_public_key(parameters: BooleanParameters) {
|
||||
|
||||
@@ -82,7 +82,7 @@ impl From<CompressedPublicKey> for PublicKey {
|
||||
mod tests {
|
||||
use crate::boolean::prelude::{
|
||||
BinaryBooleanGates, BooleanParameters, ClientKey, CompressedPublicKey, ServerKey,
|
||||
DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS,
|
||||
DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
};
|
||||
use crate::boolean::random_boolean;
|
||||
|
||||
@@ -96,7 +96,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_public_key_tfhe_lib_parameters() {
|
||||
test_public_key(TFHE_LIB_PARAMETERS);
|
||||
test_public_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
|
||||
fn test_public_key(parameters: BooleanParameters) {
|
||||
@@ -131,7 +131,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_decompressing_public_key_tfhe_lib_parameters() {
|
||||
test_decompressing_public_key(TFHE_LIB_PARAMETERS);
|
||||
test_decompressing_public_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
|
||||
fn test_decompressing_public_key(parameters: BooleanParameters) {
|
||||
|
||||
@@ -59,6 +59,144 @@ mod default_parameters_tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod low_prob_parameters_tests {
|
||||
use super::*;
|
||||
use crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165;
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_lwe_secret_key_low_prob() {
|
||||
test_encrypt_decrypt_lwe_secret_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_and_gate_low_prob() {
|
||||
test_and_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_nand_gate_low_prob() {
|
||||
test_nand_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_or_gate_low_prob() {
|
||||
test_or_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_nor_gate_low_prob() {
|
||||
test_nor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_xor_gate_low_prob() {
|
||||
test_xor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_xnor_gate_low_prob() {
|
||||
test_xnor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_not_gate_low_prob() {
|
||||
test_not_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_mux_gate_low_prob() {
|
||||
test_mux_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
#[test]
|
||||
fn test_deep_circuit_low_prob() {
|
||||
test_deep_circuit(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
}
|
||||
}
|
||||
|
||||
mod default_parameters_ks_pbs_tests {
|
||||
use super::*;
|
||||
use crate::boolean::parameters::DEFAULT_PARAMETERS_KS_PBS;
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_lwe_secret_key_default_parameters_ks_pbs() {
|
||||
test_encrypt_decrypt_lwe_secret_key(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_and_gate_default_parameters_ks_pbs() {
|
||||
test_and_gate(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_nand_gate_default_parameters_ks_pbs() {
|
||||
test_nand_gate(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_or_gate_default_parameters_ks_pbs() {
|
||||
test_or_gate(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_nor_gate_default_parameters_ks_pbs() {
|
||||
test_nor_gate(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_xor_gate_default_parameters_ks_pbs() {
|
||||
test_xor_gate(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_xnor_gate_default_parameters_ks_pbs() {
|
||||
test_xnor_gate(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_not_gate_default_parameters_ks_pbs() {
|
||||
test_not_gate(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_mux_gate_default_parameters_ks_pbs() {
|
||||
test_mux_gate(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_deep_circuit_default_parameters_ks_pbs() {
|
||||
test_deep_circuit(DEFAULT_PARAMETERS_KS_PBS);
|
||||
}
|
||||
}
|
||||
|
||||
mod low_prob_parameters_ks_pbs_tests {
|
||||
use super::*;
|
||||
use crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS;
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_lwe_secret_key_low_probability_ks_pbs() {
|
||||
test_encrypt_decrypt_lwe_secret_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_and_gate_low_probability_ks_pbs() {
|
||||
test_and_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_nand_gate_low_probability_ks_pbs() {
|
||||
test_nand_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_or_gate_low_probability_ks_pbs() {
|
||||
test_or_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_nor_gate_low_probability_ks_pbs() {
|
||||
test_nor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_xor_gate_low_probability_ks_pbs() {
|
||||
test_xor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_xnor_gate_low_probability_ks_pbs() {
|
||||
test_xnor_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_not_gate_low_probability_ks_pbs() {
|
||||
test_not_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_mux_gate_low_probability_ks_pbs() {
|
||||
test_mux_gate(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
#[test]
|
||||
fn test_deep_circuit_low_probability_ks_pbs() {
|
||||
test_deep_circuit(PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS);
|
||||
}
|
||||
}
|
||||
|
||||
mod tfhe_lib_parameters_tests {
|
||||
use super::*;
|
||||
use crate::boolean::parameters::TFHE_LIB_PARAMETERS;
|
||||
|
||||
@@ -3,6 +3,43 @@ use crate::core_crypto::commons::parameters::{
|
||||
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum BooleanEncryptionKeyChoice {
|
||||
BooleanEncryptionKeyChoiceBig,
|
||||
BooleanEncryptionKeyChoiceSmall,
|
||||
}
|
||||
|
||||
impl From<BooleanEncryptionKeyChoice>
|
||||
for crate::core_crypto::commons::parameters::EncryptionKeyChoice
|
||||
{
|
||||
fn from(value: BooleanEncryptionKeyChoice) -> Self {
|
||||
match value {
|
||||
BooleanEncryptionKeyChoice::BooleanEncryptionKeyChoiceBig => {
|
||||
crate::core_crypto::commons::parameters::EncryptionKeyChoice::Big
|
||||
}
|
||||
BooleanEncryptionKeyChoice::BooleanEncryptionKeyChoiceSmall => {
|
||||
crate::core_crypto::commons::parameters::EncryptionKeyChoice::Small
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BooleanEncryptionKeyChoice {
|
||||
// From::from cannot be marked as const, so we have to have
|
||||
// our own function
|
||||
const fn convert(rust_choice: crate::shortint::EncryptionKeyChoice) -> Self {
|
||||
match rust_choice {
|
||||
crate::core_crypto::commons::parameters::EncryptionKeyChoice::Big => {
|
||||
Self::BooleanEncryptionKeyChoiceBig
|
||||
}
|
||||
crate::core_crypto::commons::parameters::EncryptionKeyChoice::Small => {
|
||||
Self::BooleanEncryptionKeyChoiceSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct BooleanParameters {
|
||||
@@ -15,6 +52,7 @@ pub struct BooleanParameters {
|
||||
pub pbs_level: usize,
|
||||
pub ks_base_log: usize,
|
||||
pub ks_level: usize,
|
||||
pub encryption_key_choice: BooleanEncryptionKeyChoice,
|
||||
}
|
||||
|
||||
impl From<BooleanParameters> for crate::boolean::parameters::BooleanParameters {
|
||||
@@ -29,6 +67,7 @@ impl From<BooleanParameters> for crate::boolean::parameters::BooleanParameters {
|
||||
pbs_level: DecompositionLevelCount(c_params.pbs_level),
|
||||
ks_base_log: DecompositionBaseLog(c_params.ks_base_log),
|
||||
ks_level: DecompositionLevelCount(c_params.ks_level),
|
||||
encryption_key_choice: c_params.encryption_key_choice.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +90,9 @@ impl BooleanParameters {
|
||||
pbs_level: rust_params.pbs_level.0,
|
||||
ks_base_log: rust_params.ks_base_log.0,
|
||||
ks_level: rust_params.ks_level.0,
|
||||
encryption_key_choice: BooleanEncryptionKeyChoice::convert(
|
||||
rust_params.encryption_key_choice,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,4 +103,14 @@ pub static BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS: BooleanParameters =
|
||||
|
||||
#[no_mangle]
|
||||
pub static BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS: BooleanParameters =
|
||||
BooleanParameters::convert(crate::boolean::parameters::TFHE_LIB_PARAMETERS);
|
||||
BooleanParameters::convert(crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
|
||||
|
||||
#[no_mangle]
|
||||
pub static BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS_KS_PBS: BooleanParameters =
|
||||
BooleanParameters::convert(crate::boolean::parameters::DEFAULT_PARAMETERS_KS_PBS);
|
||||
|
||||
#[no_mangle]
|
||||
pub static BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS_KS_PBS: BooleanParameters =
|
||||
BooleanParameters::convert(
|
||||
crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
|
||||
);
|
||||
|
||||
@@ -104,6 +104,8 @@ macro_rules! impl_operations_for_integer_type {
|
||||
) -> c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
|
||||
let rhs = <$clear_scalar_type as $crate::c_api::high_level_api::utils::ToRustScalarType
|
||||
>::to_rust_scalar_type(rhs);
|
||||
|
||||
let (q, r) = (&lhs.0).div_rem(rhs);
|
||||
|
||||
@@ -132,6 +134,26 @@ macro_rules! impl_operations_for_integer_type {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$name:snake _if_then_else>](
|
||||
condition_ct: *const $name,
|
||||
then_ct: *const $name,
|
||||
else_ct: *const $name,
|
||||
result: *mut *mut $name,
|
||||
) -> c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let condition_ct = &$crate::c_api::utils::get_ref_checked(condition_ct).unwrap().0;
|
||||
let then_ct = &$crate::c_api::utils::get_ref_checked(then_ct).unwrap().0;
|
||||
let else_ct = &$crate::c_api::utils::get_ref_checked(else_ct).unwrap().0;
|
||||
|
||||
let r = condition_ct.if_then_else(then_ct, else_ct);
|
||||
|
||||
*result = Box::into_raw(Box::new($name(r)));
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -230,8 +252,8 @@ create_integer_wrapper_type!(name: FheUint14, clear_scalar_type: u16);
|
||||
create_integer_wrapper_type!(name: FheUint16, clear_scalar_type: u16);
|
||||
create_integer_wrapper_type!(name: FheUint32, clear_scalar_type: u32);
|
||||
create_integer_wrapper_type!(name: FheUint64, clear_scalar_type: u64);
|
||||
create_integer_wrapper_type!(name: FheUint128, clear_scalar_type: u64);
|
||||
create_integer_wrapper_type!(name: FheUint256, clear_scalar_type: u64);
|
||||
create_integer_wrapper_type!(name: FheUint128, clear_scalar_type: U128);
|
||||
create_integer_wrapper_type!(name: FheUint256, clear_scalar_type: U256);
|
||||
|
||||
impl_decrypt_on_type!(FheUint8, u8);
|
||||
impl_try_encrypt_trivial_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
|
||||
|
||||
@@ -274,6 +274,53 @@ macro_rules! impl_binary_assign_fn_on_type {
|
||||
};
|
||||
}
|
||||
|
||||
/// The C Standard only define integers from u8 to u64.
|
||||
/// So to support u128 and u256 we had to create our own C friendly
|
||||
/// data types.
|
||||
///
|
||||
/// This trait exists to be able to easily write wrapper function
|
||||
/// by allowing to generically go from a C API scalar type to a rust one
|
||||
pub(in crate::c_api::high_level_api) trait ToRustScalarType {
|
||||
type RustScalarType;
|
||||
|
||||
fn to_rust_scalar_type(self) -> Self::RustScalarType;
|
||||
}
|
||||
|
||||
/// Implements the trait for when the C API type is the same
|
||||
/// as the Rust API type (eg u64)
|
||||
macro_rules! impl_to_rust_scalar_type(
|
||||
($type:ty) => {
|
||||
impl ToRustScalarType for $type {
|
||||
type RustScalarType = $type;
|
||||
|
||||
fn to_rust_scalar_type(self) -> Self::RustScalarType {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl_to_rust_scalar_type!(u8);
|
||||
impl_to_rust_scalar_type!(u16);
|
||||
impl_to_rust_scalar_type!(u32);
|
||||
impl_to_rust_scalar_type!(u64);
|
||||
|
||||
impl ToRustScalarType for crate::c_api::high_level_api::u128::U128 {
|
||||
type RustScalarType = u128;
|
||||
|
||||
fn to_rust_scalar_type(self) -> Self::RustScalarType {
|
||||
u128::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRustScalarType for crate::c_api::high_level_api::u256::U256 {
|
||||
type RustScalarType = crate::integer::U256;
|
||||
|
||||
fn to_rust_scalar_type(self) -> Self::RustScalarType {
|
||||
crate::integer::U256::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
macro_rules! impl_scalar_binary_fn_on_type {
|
||||
($wrapper_type:ty, $scalar_type:ty => $($binary_fn_name:ident),* $(,)?) => {
|
||||
@@ -287,6 +334,7 @@ macro_rules! impl_scalar_binary_fn_on_type {
|
||||
) -> c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
|
||||
let rhs = <$scalar_type as $crate::c_api::high_level_api::utils::ToRustScalarType>::to_rust_scalar_type(rhs);
|
||||
|
||||
let inner = (&lhs.0).$binary_fn_name(rhs);
|
||||
|
||||
@@ -310,6 +358,8 @@ macro_rules! impl_scalar_binary_assign_fn_on_type {
|
||||
) -> c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
|
||||
let rhs = <$scalar_type as $crate::c_api::high_level_api::utils::ToRustScalarType
|
||||
>::to_rust_scalar_type(rhs);
|
||||
|
||||
lhs.0.$binary_assign_fn_name(rhs);
|
||||
})
|
||||
|
||||
@@ -232,3 +232,32 @@ impl LweBskGroupingFactor {
|
||||
/// The number of GGSW ciphertexts required per multi_bit BSK element
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct GgswPerLweMultiBitBskElement(pub usize);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum EncryptionKeyChoice {
|
||||
Big,
|
||||
Small,
|
||||
}
|
||||
|
||||
impl From<EncryptionKeyChoice> for PBSOrder {
|
||||
fn from(value: EncryptionKeyChoice) -> Self {
|
||||
match value {
|
||||
EncryptionKeyChoice::Big => Self::KeyswitchBootstrap,
|
||||
EncryptionKeyChoice::Small => Self::BootstrapKeyswitch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum PBSOrder {
|
||||
/// Ciphertext is encrypted using the big LWE secret key corresponding to the GLWE secret key.
|
||||
///
|
||||
/// A keyswitch is first performed to bring it to the small LWE secret key realm, then the PBS
|
||||
/// is computed bringing it back to the large LWE secret key.
|
||||
KeyswitchBootstrap = 0,
|
||||
/// Ciphertext is encrypted using the small LWE secret key.
|
||||
///
|
||||
/// The PBS is computed first and a keyswitch is applied to get back to the small LWE secret
|
||||
/// key realm.
|
||||
BootstrapKeyswitch = 1,
|
||||
}
|
||||
|
||||
@@ -110,6 +110,11 @@ impl<Scalar, C: Container<Element = Scalar>> GlweSecretKey<C> {
|
||||
LweSecretKey::from_container(self.data)
|
||||
}
|
||||
|
||||
/// Borrowes and returns the [`GlweSecretKey`] views as an [`LweSecretKey`].
|
||||
pub fn as_lwe_secret_key(&self) -> LweSecretKey<&[C::Element]> {
|
||||
LweSecretKey::from_container(self.data.as_ref())
|
||||
}
|
||||
|
||||
/// Interpret the [`GlweSecretKey`] as a [`PolynomialList`].
|
||||
pub fn as_polynomial_list(&self) -> PolynomialListView<'_, C::Element> {
|
||||
PolynomialListView::from_container(self.as_ref(), self.polynomial_size)
|
||||
|
||||
@@ -88,11 +88,13 @@ pub fn test_extract_bits() {
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let input_lwe_dimension = lwe_big_sk.lwe_dimension();
|
||||
|
||||
let req = || {
|
||||
StackReq::try_any_of([
|
||||
fill_with_forward_fourier_scratch(fft)?,
|
||||
extract_bits_scratch::<u64>(
|
||||
lwe_dimension,
|
||||
input_lwe_dimension,
|
||||
ksk_lwe_big_to_small.output_key_lwe_dimension(),
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
@@ -597,6 +599,8 @@ pub fn test_extract_bit_circuit_bootstrapping_vertical_packing() {
|
||||
PodStack::new(&mut mem),
|
||||
);
|
||||
|
||||
let input_lwe_dimension = lwe_big_sk.lwe_dimension();
|
||||
|
||||
let ksk_lwe_big_to_small: LweKeyswitchKeyOwned<u64> =
|
||||
allocate_and_generate_new_lwe_keyswitch_key(
|
||||
&lwe_big_sk,
|
||||
@@ -661,7 +665,7 @@ pub fn test_extract_bit_circuit_bootstrapping_vertical_packing() {
|
||||
|
||||
let mut mem = GlobalPodBuffer::new(
|
||||
extract_bits_scratch::<u64>(
|
||||
lwe_dimension,
|
||||
input_lwe_dimension,
|
||||
ksk_lwe_big_to_small.output_key_lwe_dimension(),
|
||||
fourier_bsk.glwe_size(),
|
||||
polynomial_size,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::boolean::parameters::{
|
||||
BooleanParameters, DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension,
|
||||
PolynomialSize, StandardDev,
|
||||
BooleanParameters, DecompositionBaseLog, DecompositionLevelCount, EncryptionKeyChoice,
|
||||
GlweDimension, LweDimension, PolynomialSize, StandardDev,
|
||||
};
|
||||
pub use crate::boolean::parameters::{DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
|
||||
pub use crate::boolean::parameters::{DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -21,11 +21,12 @@ pub struct FheBoolParameters {
|
||||
pub pbs_level: DecompositionLevelCount,
|
||||
pub ks_base_log: DecompositionBaseLog,
|
||||
pub ks_level: DecompositionLevelCount,
|
||||
pub encryption_key_choice: EncryptionKeyChoice,
|
||||
}
|
||||
|
||||
impl FheBoolParameters {
|
||||
pub fn tfhe_lib() -> Self {
|
||||
Self::from_static(&TFHE_LIB_PARAMETERS)
|
||||
Self::from_static(&PARAMETERS_ERROR_PROB_2_POW_MINUS_165)
|
||||
}
|
||||
|
||||
fn from_static(params: &'static BooleanParameters) -> Self {
|
||||
@@ -51,6 +52,7 @@ impl From<FheBoolParameters> for BooleanParameters {
|
||||
pbs_level: params.pbs_level,
|
||||
ks_base_log: params.ks_base_log,
|
||||
ks_level: params.ks_level,
|
||||
encryption_key_choice: params.encryption_key_choice,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +69,7 @@ impl From<BooleanParameters> for FheBoolParameters {
|
||||
pbs_level: params.pbs_level,
|
||||
ks_base_log: params.ks_base_log,
|
||||
ks_level: params.ks_level,
|
||||
encryption_key_choice: params.encryption_key_choice,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,3 +786,34 @@ fn test_integer_casting() {
|
||||
assert_eq!(da, clear);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_then_else() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let clear_a = 27u8;
|
||||
let clear_b = 128u8;
|
||||
|
||||
let a = FheUint8::encrypt(clear_a, &client_key);
|
||||
let b = FheUint8::encrypt(clear_b, &client_key);
|
||||
|
||||
let result = a.le(&b).if_then_else(&a, &b);
|
||||
let decrypted_result: u8 = result.decrypt(&client_key);
|
||||
assert_eq!(
|
||||
decrypted_result,
|
||||
if clear_a <= clear_b { clear_a } else { clear_b }
|
||||
);
|
||||
|
||||
let result = a.le(&b).if_then_else(&b, &a);
|
||||
let decrypted_result: u8 = result.decrypt(&client_key);
|
||||
assert_eq!(
|
||||
decrypted_result,
|
||||
if clear_a <= clear_b { clear_b } else { clear_a }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::high_level_api::traits::{
|
||||
use crate::high_level_api::{ClientKey, PublicKey};
|
||||
use crate::integer::block_decomposition::DecomposableInto;
|
||||
use crate::integer::ciphertext::RadixCiphertext;
|
||||
use crate::integer::server_key::{Reciprocable, ScalarMultiplier};
|
||||
use crate::integer::U256;
|
||||
use crate::CompactPublicKey;
|
||||
|
||||
@@ -140,6 +141,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> GenericInteger<P>
|
||||
where
|
||||
P: IntegerParameter,
|
||||
P::Id: WithGlobalKey<Key = IntegerServerKey>,
|
||||
{
|
||||
pub fn if_then_else(&self, ct_then: &Self, ct_else: &Self) -> GenericInteger<P> {
|
||||
let ct_condition = self;
|
||||
let new_ct = ct_condition.id.with_unwrapped_global(|integer_key| {
|
||||
integer_key.pbs_key().if_then_else_parallelized(
|
||||
&ct_condition.ciphertext,
|
||||
&ct_then.ciphertext,
|
||||
&ct_else.ciphertext,
|
||||
)
|
||||
});
|
||||
|
||||
GenericInteger::new(new_ct, ct_condition.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> TryFrom<RadixCiphertext> for GenericInteger<P>
|
||||
where
|
||||
P: IntegerParameter,
|
||||
@@ -342,7 +362,7 @@ where
|
||||
|
||||
impl<P, Clear> FheMax<Clear> for GenericInteger<P>
|
||||
where
|
||||
Clear: DecomposableInto<u8>,
|
||||
Clear: DecomposableInto<u64>,
|
||||
P: IntegerParameter,
|
||||
GenericInteger<P>: Clone,
|
||||
P::Id: WithGlobalKey<Key = IntegerServerKey>,
|
||||
@@ -379,7 +399,7 @@ where
|
||||
|
||||
impl<P, Clear> FheMin<Clear> for GenericInteger<P>
|
||||
where
|
||||
Clear: DecomposableInto<u8>,
|
||||
Clear: DecomposableInto<u64>,
|
||||
P: IntegerParameter,
|
||||
GenericInteger<P>: Clone,
|
||||
P::Id: WithGlobalKey<Key = IntegerServerKey>,
|
||||
@@ -452,7 +472,7 @@ where
|
||||
|
||||
impl<P, Clear> FheEq<Clear> for GenericInteger<P>
|
||||
where
|
||||
Clear: DecomposableInto<u8>,
|
||||
Clear: DecomposableInto<u64>,
|
||||
P: IntegerParameter,
|
||||
GenericInteger<P>: Clone,
|
||||
P::Id: WithGlobalKey<Key = IntegerServerKey>,
|
||||
@@ -570,7 +590,7 @@ where
|
||||
|
||||
impl<P, Clear> FheOrd<Clear> for GenericInteger<P>
|
||||
where
|
||||
Clear: DecomposableInto<u8>,
|
||||
Clear: DecomposableInto<u64>,
|
||||
P: IntegerParameter,
|
||||
GenericInteger<P>: Clone,
|
||||
P::Id: WithGlobalKey<Key = IntegerServerKey>,
|
||||
@@ -673,7 +693,7 @@ where
|
||||
impl<P, Clear> DivRem<Clear> for GenericInteger<P>
|
||||
where
|
||||
P: IntegerParameter,
|
||||
Clear: Into<u64>,
|
||||
Clear: Reciprocable + ScalarMultiplier + DecomposableInto<u8>,
|
||||
P::Id: WithGlobalKey<Key = IntegerServerKey>,
|
||||
{
|
||||
type Output = (Self, Self);
|
||||
@@ -686,7 +706,7 @@ where
|
||||
impl<P, Clear> DivRem<Clear> for &GenericInteger<P>
|
||||
where
|
||||
P: IntegerParameter,
|
||||
Clear: Into<u64>,
|
||||
Clear: Reciprocable + ScalarMultiplier + DecomposableInto<u8>,
|
||||
P::Id: WithGlobalKey<Key = IntegerServerKey>,
|
||||
{
|
||||
type Output = (GenericInteger<P>, GenericInteger<P>);
|
||||
@@ -695,7 +715,7 @@ where
|
||||
let (q, r) = self.id.with_unwrapped_global(|integer_key| {
|
||||
integer_key
|
||||
.pbs_key()
|
||||
.scalar_div_rem_parallelized(&self.ciphertext, rhs.into())
|
||||
.scalar_div_rem_parallelized(&self.ciphertext, rhs)
|
||||
});
|
||||
(
|
||||
GenericInteger::<P>::new(q, self.id),
|
||||
@@ -844,8 +864,7 @@ macro_rules! generic_integer_impl_scalar_operation {
|
||||
fn $rust_trait_method(self, rhs: $scalar_type) -> Self::Output {
|
||||
let ciphertext: RadixCiphertext =
|
||||
self.id.with_unwrapped_global(|integer_key| {
|
||||
let value: u64 = rhs.try_into().unwrap();
|
||||
integer_key.pbs_key().$key_method(&self.ciphertext, value)
|
||||
integer_key.pbs_key().$key_method(&self.ciphertext, rhs)
|
||||
});
|
||||
|
||||
GenericInteger::<P>::new(ciphertext, self.id)
|
||||
@@ -865,8 +884,7 @@ macro_rules! generic_integer_impl_scalar_operation_assign {
|
||||
{
|
||||
fn $rust_trait_method(&mut self, rhs: $scalar_type) {
|
||||
self.id.with_unwrapped_global(|integer_key| {
|
||||
let value: u64 = rhs.try_into().unwrap();
|
||||
integer_key.pbs_key().$key_method(&mut self.ciphertext, value);
|
||||
integer_key.pbs_key().$key_method(&mut self.ciphertext, rhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -900,31 +918,31 @@ generic_integer_impl_operation_assign!(RotateRightAssign(rotate_right_assign) =>
|
||||
generic_integer_impl_operation_assign!(DivAssign(div_assign) => div_assign_parallelized);
|
||||
generic_integer_impl_operation_assign!(RemAssign(rem_assign) => rem_assign_parallelized);
|
||||
|
||||
generic_integer_impl_scalar_operation!(Add(add) => scalar_add_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(Sub(sub) => scalar_sub_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(Mul(mul) => scalar_mul_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(BitAnd(bitand) => scalar_bitand_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(BitOr(bitor) => scalar_bitor_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(BitXor(bitxor) => scalar_bitxor_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(Shl(shl) => scalar_left_shift_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(Shr(shr) => scalar_right_shift_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(RotateLeft(rotate_left) => scalar_rotate_left_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(RotateRight(rotate_right) => scalar_rotate_right_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(Div(div) => scalar_div_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(Rem(rem) => scalar_rem_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation!(Add(add) => scalar_add_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(Sub(sub) => scalar_sub_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(Mul(mul) => scalar_mul_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(BitAnd(bitand) => scalar_bitand_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(BitOr(bitor) => scalar_bitor_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(BitXor(bitxor) => scalar_bitxor_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(Shl(shl) => scalar_left_shift_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(Shr(shr) => scalar_right_shift_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(RotateLeft(rotate_left) => scalar_rotate_left_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(RotateRight(rotate_right) => scalar_rotate_right_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(Div(div) => scalar_div_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation!(Rem(rem) => scalar_rem_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
|
||||
generic_integer_impl_scalar_operation_assign!(AddAssign(add_assign) => scalar_add_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(SubAssign(sub_assign) => scalar_sub_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(MulAssign(mul_assign) => scalar_mul_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(BitAndAssign(bitand_assign) => scalar_bitand_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(BitOrAssign(bitor_assign) => scalar_bitor_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(BitXorAssign(bitxor_assign) => scalar_bitxor_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(ShlAssign(shl_assign) => scalar_left_shift_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(ShrAssign(shr_assign) => scalar_right_shift_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(RotateLeftAssign(rotate_left_assign) => scalar_rotate_left_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(RotateRightAssign(rotate_right_assign) => scalar_rotate_right_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(DivAssign(div_assign) => scalar_div_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(RemAssign(rem_assign) => scalar_rem_assign_parallelized(u8, u16, u32, u64));
|
||||
generic_integer_impl_scalar_operation_assign!(AddAssign(add_assign) => scalar_add_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(SubAssign(sub_assign) => scalar_sub_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(MulAssign(mul_assign) => scalar_mul_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(BitAndAssign(bitand_assign) => scalar_bitand_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(BitOrAssign(bitor_assign) => scalar_bitor_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(BitXorAssign(bitxor_assign) => scalar_bitxor_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(ShlAssign(shl_assign) => scalar_left_shift_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(ShrAssign(shr_assign) => scalar_right_shift_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(RotateLeftAssign(rotate_left_assign) => scalar_rotate_left_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(RotateRightAssign(rotate_right_assign) => scalar_rotate_right_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(DivAssign(div_assign) => scalar_div_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
generic_integer_impl_scalar_operation_assign!(RemAssign(rem_assign) => scalar_rem_assign_parallelized(u8, u16, u32, u64, u128, U256));
|
||||
|
||||
impl<P> Neg for GenericInteger<P>
|
||||
where
|
||||
|
||||
@@ -74,6 +74,15 @@ impl RadixCiphertext {
|
||||
pub fn block_carries_are_empty(&self) -> bool {
|
||||
self.blocks.iter().all(|block| block.carry_is_empty())
|
||||
}
|
||||
|
||||
/// Returns wether the ciphertext _seems_ like it holds/encrypts
|
||||
/// a boolean (0 or 1) value.
|
||||
///
|
||||
/// Since it uses degree to do so, it will not
|
||||
/// always return the correct answer.
|
||||
pub(crate) fn holds_boolean_value(&self) -> bool {
|
||||
self.blocks[0].degree.0 <= 1 && self.blocks[1..].iter().all(|block| block.degree.0 == 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CompressedRadixCiphertext> for RadixCiphertext {
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::shortint::Ciphertext;
|
||||
pub(crate) enum ZeroComparisonType {
|
||||
// We want to compare with zeros for equality (==)
|
||||
Equality,
|
||||
// We want to compare with zeros for differeece (!=)
|
||||
// We want to compare with zeros for difference (!=)
|
||||
Difference,
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ where {
|
||||
|
||||
pub fn unchecked_scalar_sign_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> Ciphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
assert!(!lhs.blocks.is_empty());
|
||||
|
||||
@@ -406,7 +406,8 @@ where {
|
||||
|
||||
let mut scalar_blocks =
|
||||
BlockDecomposer::with_early_stop_at_zero(rhs, message_modulus.ilog2())
|
||||
.iter_as::<u8>()
|
||||
.iter_as::<u64>()
|
||||
.map(|x| x as u8)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// scalar is obviously bigger if it has non-zero
|
||||
@@ -569,43 +570,19 @@ where {
|
||||
rhs: &RadixCiphertext,
|
||||
selector: MinMaxSelector,
|
||||
) -> RadixCiphertext {
|
||||
let (lhs_lut, rhs_lut) = match selector {
|
||||
MinMaxSelector::Max => (&self.lhs_lut, &self.rhs_lut),
|
||||
MinMaxSelector::Min => (&self.rhs_lut, &self.lhs_lut),
|
||||
};
|
||||
|
||||
let mut offset = self.unchecked_sign_parallelized(lhs, rhs);
|
||||
self.server_key
|
||||
.key
|
||||
.apply_lookup_table_assign(&mut offset, &self.sign_to_offset_lut);
|
||||
|
||||
let blocks = lhs
|
||||
.blocks
|
||||
.par_iter()
|
||||
.zip(rhs.blocks.par_iter())
|
||||
.map(|(lhs_block, rhs_block)| {
|
||||
let (maybe_lhs, maybe_rhs) = rayon::join(
|
||||
|| {
|
||||
let mut lhs_block = self.server_key.key.unchecked_add(lhs_block, &offset);
|
||||
self.server_key
|
||||
.key
|
||||
.apply_lookup_table_assign(&mut lhs_block, lhs_lut);
|
||||
lhs_block
|
||||
},
|
||||
|| {
|
||||
let mut rhs_block = self.server_key.key.unchecked_add(rhs_block, &offset);
|
||||
self.server_key
|
||||
.key
|
||||
.apply_lookup_table_assign(&mut rhs_block, rhs_lut);
|
||||
rhs_block
|
||||
},
|
||||
);
|
||||
|
||||
self.server_key.key.unchecked_add(&maybe_lhs, &maybe_rhs)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
RadixCiphertext { blocks }
|
||||
let sign = self.unchecked_sign_parallelized(lhs, rhs);
|
||||
match selector {
|
||||
MinMaxSelector::Max => self
|
||||
.server_key
|
||||
.unchecked_programmable_if_then_else_parallelized(&sign, lhs, rhs, |sign| {
|
||||
sign == Self::IS_SUPERIOR
|
||||
}),
|
||||
MinMaxSelector::Min => self
|
||||
.server_key
|
||||
.unchecked_programmable_if_then_else_parallelized(&sign, lhs, rhs, |sign| {
|
||||
sign == Self::IS_INFERIOR
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn unchecked_scalar_min_or_max_parallelized<T>(
|
||||
@@ -615,59 +592,22 @@ where {
|
||||
selector: MinMaxSelector,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
let (lhs_lut, rhs_lut) = match selector {
|
||||
MinMaxSelector::Max => (&self.lhs_lut, &self.rhs_lut),
|
||||
MinMaxSelector::Min => (&self.rhs_lut, &self.lhs_lut),
|
||||
};
|
||||
|
||||
let mut offset = self.unchecked_scalar_sign_parallelized(lhs, rhs);
|
||||
self.server_key
|
||||
.key
|
||||
.apply_lookup_table_assign(&mut offset, &self.sign_to_offset_lut);
|
||||
|
||||
let message_modulus = self.server_key.key.message_modulus.0 as u64;
|
||||
let bits_in_message = message_modulus.ilog2();
|
||||
let scalar_blocks = BlockDecomposer::with_early_stop_at_zero(rhs, bits_in_message)
|
||||
.iter_as::<u8>()
|
||||
.chain(std::iter::repeat(0))
|
||||
.take(lhs.blocks.len())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let blocks = lhs
|
||||
.blocks
|
||||
.par_iter()
|
||||
.zip(scalar_blocks.into_par_iter())
|
||||
.map(|(lhs_block, scalar_block)| {
|
||||
let (maybe_lhs, maybe_rhs) = rayon::join(
|
||||
|| {
|
||||
let mut lhs_block = self.server_key.key.unchecked_add(lhs_block, &offset);
|
||||
self.server_key
|
||||
.key
|
||||
.apply_lookup_table_assign(&mut lhs_block, lhs_lut);
|
||||
lhs_block
|
||||
},
|
||||
|| {
|
||||
let mut rhs_block = if scalar_block != 0 {
|
||||
self.server_key
|
||||
.key
|
||||
.unchecked_scalar_add(&offset, scalar_block)
|
||||
} else {
|
||||
offset.clone()
|
||||
};
|
||||
self.server_key
|
||||
.key
|
||||
.apply_lookup_table_assign(&mut rhs_block, rhs_lut);
|
||||
rhs_block
|
||||
},
|
||||
);
|
||||
|
||||
self.server_key.key.unchecked_add(&maybe_lhs, &maybe_rhs)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
RadixCiphertext { blocks }
|
||||
let sign = self.unchecked_scalar_sign_parallelized(lhs, rhs);
|
||||
let rhs = self.server_key.create_trivial_radix(rhs, lhs.blocks.len());
|
||||
match selector {
|
||||
MinMaxSelector::Max => self
|
||||
.server_key
|
||||
.unchecked_programmable_if_then_else_parallelized(&sign, lhs, &rhs, |sign| {
|
||||
sign == Self::IS_SUPERIOR
|
||||
}),
|
||||
MinMaxSelector::Min => self
|
||||
.server_key
|
||||
.unchecked_programmable_if_then_else_parallelized(&sign, lhs, &rhs, |sign| {
|
||||
sign == Self::IS_INFERIOR
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn smart_min_or_max(
|
||||
@@ -1157,11 +1097,7 @@ where {
|
||||
}
|
||||
};
|
||||
|
||||
let mut res = self.unchecked_max_parallelized(lhs, rhs);
|
||||
res.blocks
|
||||
.par_iter_mut()
|
||||
.for_each(|block| self.server_key.key.message_extract_assign(block));
|
||||
res
|
||||
self.unchecked_max_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn min_parallelized(
|
||||
@@ -1195,11 +1131,7 @@ where {
|
||||
}
|
||||
};
|
||||
|
||||
let mut res = self.unchecked_min_parallelized(lhs, rhs);
|
||||
res.blocks
|
||||
.par_iter_mut()
|
||||
.for_each(|block| self.server_key.key.message_extract_assign(block));
|
||||
res
|
||||
self.unchecked_min_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
//===========================================
|
||||
@@ -1213,7 +1145,7 @@ where {
|
||||
sign_result_handler_fn: F,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
F: Fn(u64) -> bool + Sync,
|
||||
{
|
||||
let sign_block = self.unchecked_scalar_sign_parallelized(lhs, rhs);
|
||||
@@ -1226,7 +1158,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.unchecked_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_SUPERIOR)
|
||||
}
|
||||
@@ -1237,7 +1169,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.unchecked_scalar_compare_parallelized(lhs, rhs, |x| {
|
||||
x == Self::IS_SUPERIOR || x == Self::IS_EQUAL
|
||||
@@ -1250,7 +1182,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.unchecked_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_INFERIOR)
|
||||
}
|
||||
@@ -1261,7 +1193,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.unchecked_scalar_compare_parallelized(lhs, rhs, |x| {
|
||||
x == Self::IS_INFERIOR || x == Self::IS_EQUAL
|
||||
@@ -1274,7 +1206,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.unchecked_scalar_min_or_max_parallelized(lhs, rhs, MinMaxSelector::Max)
|
||||
}
|
||||
@@ -1285,7 +1217,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.unchecked_scalar_min_or_max_parallelized(lhs, rhs, MinMaxSelector::Min)
|
||||
}
|
||||
@@ -1301,7 +1233,7 @@ where {
|
||||
sign_result_handler_fn: F,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
F: Fn(u64) -> bool + Sync,
|
||||
{
|
||||
if has_non_zero_carries(lhs) {
|
||||
@@ -1316,7 +1248,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.smart_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_SUPERIOR)
|
||||
}
|
||||
@@ -1327,7 +1259,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.smart_scalar_compare_parallelized(lhs, rhs, |x| {
|
||||
x == Self::IS_SUPERIOR || x == Self::IS_EQUAL
|
||||
@@ -1340,7 +1272,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.smart_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_INFERIOR)
|
||||
}
|
||||
@@ -1351,7 +1283,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.smart_scalar_compare_parallelized(lhs, rhs, |x| {
|
||||
x == Self::IS_INFERIOR || x == Self::IS_EQUAL
|
||||
@@ -1364,7 +1296,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
if has_non_zero_carries(lhs) {
|
||||
self.server_key.full_propagate_parallelized(lhs);
|
||||
@@ -1378,7 +1310,7 @@ where {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
if has_non_zero_carries(lhs) {
|
||||
self.server_key.full_propagate_parallelized(lhs);
|
||||
@@ -1397,7 +1329,7 @@ where {
|
||||
sign_result_handler_fn: F,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
F: Fn(u64) -> bool + Sync,
|
||||
{
|
||||
let mut tmp_lhs: RadixCiphertext;
|
||||
@@ -1413,14 +1345,14 @@ where {
|
||||
|
||||
pub fn scalar_gt_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.default_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_SUPERIOR)
|
||||
}
|
||||
|
||||
pub fn scalar_ge_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.default_scalar_compare_parallelized(lhs, rhs, |x| {
|
||||
x == Self::IS_SUPERIOR || x == Self::IS_EQUAL
|
||||
@@ -1429,14 +1361,14 @@ where {
|
||||
|
||||
pub fn scalar_lt_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.default_scalar_compare_parallelized(lhs, rhs, |x| x == Self::IS_INFERIOR)
|
||||
}
|
||||
|
||||
pub fn scalar_le_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
self.default_scalar_compare_parallelized(lhs, rhs, |x| {
|
||||
x == Self::IS_INFERIOR || x == Self::IS_EQUAL
|
||||
@@ -1445,7 +1377,7 @@ where {
|
||||
|
||||
pub fn scalar_max_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
let mut tmp_lhs: RadixCiphertext;
|
||||
let lhs = if has_non_zero_carries(lhs) {
|
||||
@@ -1460,7 +1392,7 @@ where {
|
||||
|
||||
pub fn scalar_min_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
let mut tmp_lhs: RadixCiphertext;
|
||||
let lhs = if has_non_zero_carries(lhs) {
|
||||
@@ -1575,7 +1507,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl<'a> Comparator<'a> {
|
||||
pub fn unchecked_scalar_eq_parallelized<T: DecomposableInto<u8>>(
|
||||
pub fn unchecked_scalar_eq_parallelized<T: DecomposableInto<u64>>(
|
||||
&self,
|
||||
lhs: &RadixCiphertext,
|
||||
rhs: T,
|
||||
@@ -1583,7 +1515,7 @@ mod tests {
|
||||
self.server_key.unchecked_scalar_eq_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn smart_scalar_eq_parallelized<T: DecomposableInto<u8>>(
|
||||
pub fn smart_scalar_eq_parallelized<T: DecomposableInto<u64>>(
|
||||
&self,
|
||||
lhs: &mut RadixCiphertext,
|
||||
rhs: T,
|
||||
@@ -1591,7 +1523,7 @@ mod tests {
|
||||
self.server_key.smart_scalar_eq_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn scalar_eq_parallelized<T: DecomposableInto<u8>>(
|
||||
pub fn scalar_eq_parallelized<T: DecomposableInto<u64>>(
|
||||
&self,
|
||||
lhs: &RadixCiphertext,
|
||||
rhs: T,
|
||||
@@ -1599,7 +1531,7 @@ mod tests {
|
||||
self.server_key.scalar_eq_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn unchecked_scalar_ne_parallelized<T: DecomposableInto<u8>>(
|
||||
pub fn unchecked_scalar_ne_parallelized<T: DecomposableInto<u64>>(
|
||||
&self,
|
||||
lhs: &RadixCiphertext,
|
||||
rhs: T,
|
||||
@@ -1607,7 +1539,7 @@ mod tests {
|
||||
self.server_key.unchecked_scalar_ne_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn smart_scalar_ne_parallelized<T: DecomposableInto<u8>>(
|
||||
pub fn smart_scalar_ne_parallelized<T: DecomposableInto<u64>>(
|
||||
&self,
|
||||
lhs: &mut RadixCiphertext,
|
||||
rhs: T,
|
||||
@@ -1615,7 +1547,7 @@ mod tests {
|
||||
self.server_key.smart_scalar_ne_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn scalar_ne_parallelized<T: DecomposableInto<u8>>(
|
||||
pub fn scalar_ne_parallelized<T: DecomposableInto<u64>>(
|
||||
&self,
|
||||
lhs: &RadixCiphertext,
|
||||
rhs: T,
|
||||
|
||||
@@ -19,6 +19,12 @@ use crate::integer::encryption::encrypt_words_radix_impl;
|
||||
mod tests;
|
||||
|
||||
impl ServerKey {
|
||||
pub fn create_trivial_zero_assign_radix(&self, ctxt: &mut RadixCiphertext) {
|
||||
for block in &mut ctxt.blocks {
|
||||
self.key.create_trivial_assign(block, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a ciphertext filled with zeros
|
||||
///
|
||||
/// # Example
|
||||
|
||||
@@ -319,15 +319,10 @@ impl ServerKey {
|
||||
/// assert_eq!(16, clear);
|
||||
/// ```
|
||||
pub fn blockshift(&self, ctxt: &RadixCiphertext, shift: usize) -> RadixCiphertext {
|
||||
let ctxt_zero = self.key.create_trivial(0_u64);
|
||||
let mut result = ctxt.clone();
|
||||
|
||||
for res_i in result.blocks[..shift].iter_mut() {
|
||||
*res_i = ctxt_zero.clone();
|
||||
}
|
||||
|
||||
for (res_i, c_i) in result.blocks[shift..].iter_mut().zip(ctxt.blocks.iter()) {
|
||||
*res_i = c_i.clone();
|
||||
result.blocks.rotate_right(shift);
|
||||
for block in &mut result.blocks[..shift] {
|
||||
self.key.create_trivial_assign(block, 0);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
241
tfhe/src/integer/server_key/radix_parallel/cmux.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use crate::integer::ciphertext::RadixCiphertext;
|
||||
use crate::integer::ServerKey;
|
||||
use rayon::prelude::*;
|
||||
|
||||
impl ServerKey {
|
||||
pub fn unchecked_if_then_else_parallelized(
|
||||
&self,
|
||||
condition: &RadixCiphertext,
|
||||
true_ct: &RadixCiphertext,
|
||||
false_ct: &RadixCiphertext,
|
||||
) -> RadixCiphertext {
|
||||
assert!(
|
||||
condition.holds_boolean_value(),
|
||||
"The condition ciphertext does not encrypt a boolean (0 or 1) value"
|
||||
);
|
||||
|
||||
let condition_block = &condition.blocks[0];
|
||||
self.unchecked_programmable_if_then_else_parallelized(
|
||||
condition_block,
|
||||
true_ct,
|
||||
false_ct,
|
||||
|x| x == 1,
|
||||
)
|
||||
}
|
||||
|
||||
/// FHE "if then else" selection.
|
||||
///
|
||||
/// Returns a new ciphertext that encrypts the same value
|
||||
/// as either true_ct or false_ct depending on the value of condition:
|
||||
///
|
||||
/// - If condition == 1, the returned ciphertext will encrypt the same value as true_ct.
|
||||
/// - If condition == 0, the returned ciphertext will encrypt the same value as false_ct.
|
||||
///
|
||||
/// To ensure correct results, condition must encrypt either 0 or 1
|
||||
/// (e.g result from a comparison).
|
||||
///
|
||||
/// Note that while the returned ciphertext encrypts the same value as
|
||||
/// either true_ct or false_ct, it won't exactly be true_ct or false_ct.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::integer::gen_keys_radix;
|
||||
/// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
|
||||
///
|
||||
/// // We have 4 * 2 = 8 bits of message
|
||||
/// let size = 4;
|
||||
/// let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, size);
|
||||
///
|
||||
/// let a = 128u8;
|
||||
/// let b = 55u8;
|
||||
///
|
||||
/// let ct_a = cks.encrypt(a);
|
||||
/// let ct_b = cks.encrypt(b);
|
||||
///
|
||||
/// let condition = sks.scalar_ge_parallelized(&ct_a, 66);
|
||||
///
|
||||
/// let ct_res = sks.if_then_else_parallelized(&condition, &ct_a, &ct_b);
|
||||
///
|
||||
/// // Decrypt:
|
||||
/// let dec: u8 = cks.decrypt(&ct_res);
|
||||
/// assert_eq!(if a >= 66 { a } else { b }, dec);
|
||||
/// assert_ne!(ct_a, ct_res);
|
||||
/// assert_ne!(ct_b, ct_res);
|
||||
/// ```
|
||||
pub fn if_then_else_parallelized(
|
||||
&self,
|
||||
condition: &RadixCiphertext,
|
||||
true_ct: &RadixCiphertext,
|
||||
false_ct: &RadixCiphertext,
|
||||
) -> RadixCiphertext {
|
||||
let mut ct_clones = [None, None, None];
|
||||
let mut ct_refs = [condition, true_ct, false_ct];
|
||||
|
||||
ct_refs
|
||||
.par_iter_mut()
|
||||
.zip(ct_clones.par_iter_mut())
|
||||
.for_each(|(ct_ref, ct_clone)| {
|
||||
if !ct_ref.block_carries_are_empty() {
|
||||
let mut cloned = ct_ref.clone();
|
||||
self.full_propagate_parallelized(&mut cloned);
|
||||
*ct_ref = ct_clone.insert(cloned);
|
||||
}
|
||||
});
|
||||
|
||||
let [condition, true_ct, false_ct] = ct_refs;
|
||||
self.unchecked_if_then_else_parallelized(condition, true_ct, false_ct)
|
||||
}
|
||||
|
||||
/// FHE "if then else" selection.
|
||||
///
|
||||
/// Returns a new ciphertext that encrypts the same value
|
||||
/// as either true_ct or false_ct depending on the value of condition:
|
||||
///
|
||||
/// - If condition == 1, the returned ciphertext will encrypt the same value as true_ct.
|
||||
/// - If condition == 0, the returned ciphertext will encrypt the same value as false_ct.
|
||||
///
|
||||
/// To ensure correct results, condition must encrypt either 0 or 1
|
||||
/// (e.g result from a comparison).
|
||||
///
|
||||
/// Note that while the returned ciphertext encrypts the same value as
|
||||
/// either true_ct or false_ct, it won't exactly be true_ct or false_ct.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::integer::gen_keys_radix;
|
||||
/// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
|
||||
///
|
||||
/// // We have 4 * 2 = 8 bits of message
|
||||
/// let size = 4;
|
||||
/// let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, size);
|
||||
///
|
||||
/// let a = 128u8;
|
||||
/// let b = 55u8;
|
||||
///
|
||||
/// let mut ct_a = cks.encrypt(a);
|
||||
/// let mut ct_b = cks.encrypt(b);
|
||||
///
|
||||
/// let mut condition = sks.scalar_ge_parallelized(&ct_a, 66);
|
||||
///
|
||||
/// let ct_res = sks.smart_if_then_else_parallelized(&mut condition, &mut ct_a, &mut ct_b);
|
||||
///
|
||||
/// // Decrypt:
|
||||
/// let dec: u8 = cks.decrypt(&ct_res);
|
||||
/// assert_eq!(if a >= 66 { a } else { b }, dec);
|
||||
/// assert_ne!(ct_a, ct_res);
|
||||
/// assert_ne!(ct_b, ct_res);
|
||||
/// ```
|
||||
pub fn smart_if_then_else_parallelized(
|
||||
&self,
|
||||
condition: &mut RadixCiphertext,
|
||||
true_ct: &mut RadixCiphertext,
|
||||
false_ct: &mut RadixCiphertext,
|
||||
) -> RadixCiphertext {
|
||||
let mut ct_refs = [condition, true_ct, false_ct];
|
||||
|
||||
ct_refs.par_iter_mut().for_each(|ct_ref| {
|
||||
if !ct_ref.block_carries_are_empty() {
|
||||
self.full_propagate_parallelized(ct_ref);
|
||||
}
|
||||
});
|
||||
|
||||
let [condition, true_ct, false_ct] = ct_refs;
|
||||
self.unchecked_if_then_else_parallelized(condition, true_ct, false_ct)
|
||||
}
|
||||
|
||||
pub(crate) fn unchecked_programmable_if_then_else_parallelized<F>(
|
||||
&self,
|
||||
condition_block: &crate::shortint::Ciphertext,
|
||||
true_ct: &RadixCiphertext,
|
||||
false_ct: &RadixCiphertext,
|
||||
predicate: F,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
F: Fn(u64) -> bool + Send + Sync + Copy,
|
||||
{
|
||||
let inverted_predicate = |x| !predicate(x);
|
||||
|
||||
// Although our mul algorithm has special path for when rhs or lhs is a boolean value,
|
||||
// we don't call it as for the ct_false we would need an extra pbs to 'invert' the
|
||||
// ciphertext from true to false.
|
||||
let (mut true_ct, false_ct) = rayon::join(
|
||||
move || {
|
||||
let mut true_ct = true_ct.clone();
|
||||
self.zero_out_if(&mut true_ct, condition_block, inverted_predicate);
|
||||
true_ct
|
||||
},
|
||||
move || {
|
||||
let mut false_ct = false_ct.clone();
|
||||
self.zero_out_if(&mut false_ct, condition_block, predicate);
|
||||
false_ct
|
||||
},
|
||||
);
|
||||
// If the condition was true, true_ct will have kept its value and false_ct will be 0
|
||||
// If the condition was false, true_ct will be 0 and false_ct will have kept its value
|
||||
true_ct
|
||||
.blocks
|
||||
.par_iter_mut()
|
||||
.zip(false_ct.blocks.par_iter())
|
||||
.for_each(|(lhs_block, rhs_block)| {
|
||||
self.key.unchecked_add_assign(lhs_block, rhs_block);
|
||||
self.key.message_extract_assign(lhs_block)
|
||||
});
|
||||
true_ct
|
||||
}
|
||||
|
||||
/// This function takes a ciphertext encrypting any integer value
|
||||
/// and block encrypting a boolean value (0 or 1).
|
||||
///
|
||||
/// The input integer ciphertext will have all its block zeroed if condition_block
|
||||
/// encrypts 0, otherwise each block keeps its value.
|
||||
pub(crate) fn zero_out_if_condition_is_false(
|
||||
&self,
|
||||
ct: &mut RadixCiphertext,
|
||||
condition_block: &crate::shortint::Ciphertext,
|
||||
) {
|
||||
assert!(condition_block.degree.0 <= 1);
|
||||
|
||||
self.zero_out_if_condition_equals(ct, condition_block, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn zero_out_if_condition_equals(
|
||||
&self,
|
||||
ct: &mut RadixCiphertext,
|
||||
condition_block: &crate::shortint::Ciphertext,
|
||||
value: u64,
|
||||
) {
|
||||
assert!(condition_block.degree.0 < condition_block.message_modulus.0);
|
||||
assert!(value < condition_block.message_modulus.0 as u64);
|
||||
|
||||
self.zero_out_if(ct, condition_block, |x| x == value);
|
||||
}
|
||||
|
||||
pub(crate) fn zero_out_if<F>(
|
||||
&self,
|
||||
ct: &mut RadixCiphertext,
|
||||
condition_block: &crate::shortint::Ciphertext,
|
||||
predicate: F,
|
||||
) where
|
||||
F: Fn(u64) -> bool,
|
||||
{
|
||||
assert!(condition_block.degree.0 < condition_block.message_modulus.0);
|
||||
|
||||
if condition_block.degree.0 == 0 {
|
||||
return self.create_trivial_zero_assign_radix(ct);
|
||||
}
|
||||
|
||||
let lut =
|
||||
self.key.generate_lookup_table_bivariate(
|
||||
|block, condition| if predicate(condition) { 0 } else { block },
|
||||
);
|
||||
|
||||
ct.blocks
|
||||
.par_iter_mut()
|
||||
.filter(|block| block.degree.0 != 0)
|
||||
.for_each(|block| {
|
||||
self.key.unchecked_apply_lookup_table_bivariate_assign(
|
||||
block,
|
||||
condition_block,
|
||||
&lut,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::integer::ciphertext::RadixCiphertext;
|
||||
use crate::integer::server_key::comparator::ZeroComparisonType;
|
||||
use crate::integer::ServerKey;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use super::bit_extractor::BitExtractor;
|
||||
|
||||
@@ -36,15 +35,6 @@ impl ServerKey {
|
||||
let mut quotient = self.create_trivial_zero_radix(num_blocks);
|
||||
let mut remainder = self.create_trivial_zero_radix(num_blocks);
|
||||
|
||||
// This lut only works when y values are 0 or 1
|
||||
let zeroer_lut_for_merged_cmp =
|
||||
self.key
|
||||
.generate_lookup_table_bivariate(|x, y| if y != 1 { 0 } else { x });
|
||||
// This lut works when y is <= 2
|
||||
let zeroer_lut_summed_cmp =
|
||||
self.key
|
||||
.generate_lookup_table_bivariate(|x, y| if y != 2 { 0 } else { x });
|
||||
|
||||
let merge_two_cmp_lut = self
|
||||
.key
|
||||
.generate_lookup_table_bivariate(|x, y| u64::from(x == 1 && y == 1));
|
||||
@@ -125,25 +115,15 @@ impl ServerKey {
|
||||
&merge_two_cmp_lut,
|
||||
);
|
||||
|
||||
interesting_divisor.blocks.par_iter_mut().for_each(|block| {
|
||||
self.key.unchecked_apply_lookup_table_bivariate_assign(
|
||||
block,
|
||||
&merged_cmp,
|
||||
&zeroer_lut_for_merged_cmp,
|
||||
);
|
||||
});
|
||||
self.zero_out_if_condition_is_false(&mut interesting_divisor, &merged_cmp);
|
||||
is_remainder_greater_or_eq_than_divisor = Some(merged_cmp)
|
||||
} else {
|
||||
let summed_cmp = self.key.unchecked_add(
|
||||
&trivial_blocks_are_zero,
|
||||
&non_trivial_blocks_are_ge.blocks[0],
|
||||
);
|
||||
interesting_divisor.blocks.par_iter_mut().for_each(|block| {
|
||||
self.key.unchecked_apply_lookup_table_bivariate_assign(
|
||||
block,
|
||||
&summed_cmp,
|
||||
&zeroer_lut_summed_cmp,
|
||||
);
|
||||
self.zero_out_if(&mut interesting_divisor, &summed_cmp, |summed_cmp| {
|
||||
summed_cmp != 2 // summed_cmp != 2 => remainder < divisor
|
||||
});
|
||||
is_remainder_greater_or_eq_than_divisor = None;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod add;
|
||||
mod bit_extractor;
|
||||
mod bitwise_op;
|
||||
mod cmux;
|
||||
mod comparison;
|
||||
mod div_mod;
|
||||
mod mul;
|
||||
@@ -24,6 +25,8 @@ use super::ServerKey;
|
||||
use crate::integer::ciphertext::RadixCiphertext;
|
||||
pub use scalar_div_mod::{MiniUnsignedInteger, Reciprocable};
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
// parallelized versions
|
||||
impl ServerKey {
|
||||
/// Propagate the carry of the 'index' block to the next one.
|
||||
@@ -66,9 +69,40 @@ impl ServerKey {
|
||||
}
|
||||
|
||||
pub fn partial_propagate_parallelized(&self, ctxt: &mut RadixCiphertext, start_index: usize) {
|
||||
let len = ctxt.blocks.len();
|
||||
for i in start_index..len {
|
||||
self.propagate_parallelized(ctxt, i);
|
||||
// The fully parallelized way introduces more work
|
||||
// and so is slower for low number of blocks
|
||||
const MIN_NUM_BLOCKS: usize = 6;
|
||||
if self.is_eligible_for_parallel_carryless_add() && ctxt.blocks.len() >= MIN_NUM_BLOCKS {
|
||||
let num_blocks = ctxt.blocks.len();
|
||||
|
||||
let (mut message_blocks, carry_blocks) = rayon::join(
|
||||
|| {
|
||||
ctxt.blocks[start_index..]
|
||||
.par_iter()
|
||||
.map(|block| self.key.message_extract(block))
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
|| {
|
||||
let mut carry_blocks = Vec::with_capacity(num_blocks);
|
||||
// No need to compute the carry of the last block, we would just throw it away
|
||||
ctxt.blocks[start_index..num_blocks - 1]
|
||||
.par_iter()
|
||||
.map(|block| self.key.carry_extract(block))
|
||||
.collect_into_vec(&mut carry_blocks);
|
||||
carry_blocks.insert(0, self.key.create_trivial(0));
|
||||
carry_blocks
|
||||
},
|
||||
);
|
||||
|
||||
ctxt.blocks[start_index..].swap_with_slice(&mut message_blocks);
|
||||
let carries = RadixCiphertext::from(carry_blocks);
|
||||
self.unchecked_add_assign_parallelized(ctxt, &carries);
|
||||
self.propagate_single_carry_parallelized_low_latency(ctxt)
|
||||
} else {
|
||||
let len = ctxt.blocks.len();
|
||||
for i in start_index..len {
|
||||
self.propagate_parallelized(ctxt, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -425,30 +425,8 @@ impl ServerKey {
|
||||
self.unchecked_add_assign(result, term);
|
||||
}
|
||||
|
||||
let (mut message_blocks, carry_blocks) = rayon::join(
|
||||
|| {
|
||||
result
|
||||
.blocks
|
||||
.par_iter()
|
||||
.map(|block| self.key.message_extract(block))
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
|| {
|
||||
let mut carry_blocks = Vec::with_capacity(num_blocks);
|
||||
result.blocks[..num_blocks - 1] // last carry is not interesting
|
||||
.par_iter()
|
||||
.map(|block| self.key.carry_extract(block))
|
||||
.collect_into_vec(&mut carry_blocks);
|
||||
carry_blocks.insert(0, self.key.create_trivial(0));
|
||||
carry_blocks
|
||||
},
|
||||
);
|
||||
|
||||
std::mem::swap(&mut lhs.blocks, &mut message_blocks);
|
||||
|
||||
let carry = RadixCiphertext::from(carry_blocks);
|
||||
self.add_assign_parallelized(lhs, &carry);
|
||||
|
||||
std::mem::swap(&mut lhs.blocks, &mut result.blocks);
|
||||
self.full_propagate_parallelized(lhs);
|
||||
assert!(lhs.block_carries_are_empty());
|
||||
}
|
||||
|
||||
@@ -492,14 +470,31 @@ impl ServerKey {
|
||||
lhs: &mut RadixCiphertext,
|
||||
rhs: &RadixCiphertext,
|
||||
) {
|
||||
let num_blocks = lhs.blocks.len();
|
||||
let mut terms = vec![self.create_trivial_zero_radix(num_blocks); num_blocks];
|
||||
terms
|
||||
.par_iter_mut()
|
||||
.zip(rhs.blocks.par_iter().enumerate())
|
||||
.for_each(|(term, (i, rhs_i))| {
|
||||
*term = self.unchecked_block_mul_parallelized(lhs, rhs_i, i);
|
||||
});
|
||||
if rhs.holds_boolean_value() {
|
||||
self.zero_out_if_condition_is_false(lhs, &rhs.blocks[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if lhs.holds_boolean_value() {
|
||||
let mut cloned_rhs = rhs.clone();
|
||||
self.zero_out_if_condition_is_false(&mut cloned_rhs, &lhs.blocks[0]);
|
||||
*lhs = cloned_rhs;
|
||||
return;
|
||||
}
|
||||
|
||||
let terms = rhs
|
||||
.blocks
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, block)| {
|
||||
if block.degree.0 == 0 {
|
||||
// Block is a trivial 0, no need to waste time multiplying
|
||||
None
|
||||
} else {
|
||||
Some(self.unchecked_block_mul_parallelized(lhs, block, i))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.sum_multiplication_terms_into(lhs, terms);
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
debug_assert!(lhs.block_carries_are_empty());
|
||||
|
||||
@@ -303,7 +303,8 @@ impl ServerKey {
|
||||
|
||||
let mut scalar_blocks =
|
||||
BlockDecomposer::with_early_stop_at_zero(rhs, total_modulus.ilog2())
|
||||
.iter_as::<u8>()
|
||||
.iter_as::<u64>()
|
||||
.map(|x| x as u8)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// If we have more scalar blocks than lhs.blocks
|
||||
@@ -368,7 +369,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
debug_assert!(lhs.block_carries_are_empty());
|
||||
|
||||
@@ -385,7 +386,8 @@ impl ServerKey {
|
||||
|
||||
let mut scalar_blocks =
|
||||
BlockDecomposer::with_early_stop_at_zero(rhs, total_modulus.ilog2())
|
||||
.iter_as::<u8>()
|
||||
.iter_as::<u64>()
|
||||
.map(|x| x as u8)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// If we have more scalar blocks than lhs.blocks
|
||||
@@ -455,7 +457,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
if !lhs.block_carries_are_empty() {
|
||||
self.full_propagate_parallelized(lhs);
|
||||
@@ -465,7 +467,7 @@ impl ServerKey {
|
||||
|
||||
pub fn scalar_eq_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
let mut tmp_lhs: RadixCiphertext;
|
||||
let lhs = if !lhs.block_carries_are_empty() {
|
||||
@@ -484,7 +486,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
if !lhs.block_carries_are_empty() {
|
||||
self.full_propagate_parallelized(lhs);
|
||||
@@ -494,7 +496,7 @@ impl ServerKey {
|
||||
|
||||
pub fn scalar_ne_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
let mut tmp_lhs: RadixCiphertext;
|
||||
let lhs = if !lhs.block_carries_are_empty() {
|
||||
@@ -517,7 +519,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).unchecked_scalar_gt_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -528,7 +530,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).unchecked_scalar_ge_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -539,7 +541,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).unchecked_scalar_lt_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -550,7 +552,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).unchecked_scalar_le_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -561,7 +563,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).unchecked_scalar_max_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -572,7 +574,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).unchecked_scalar_min_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -587,7 +589,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).smart_scalar_gt_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -598,7 +600,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).smart_scalar_ge_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -609,7 +611,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).smart_scalar_lt_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -620,7 +622,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).smart_scalar_le_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -631,7 +633,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).smart_scalar_max_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -642,7 +644,7 @@ impl ServerKey {
|
||||
rhs: T,
|
||||
) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).smart_scalar_min_parallelized(lhs, rhs)
|
||||
}
|
||||
@@ -653,42 +655,42 @@ impl ServerKey {
|
||||
|
||||
pub fn scalar_gt_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).scalar_gt_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn scalar_ge_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).scalar_ge_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn scalar_lt_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).scalar_lt_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn scalar_le_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).scalar_le_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn scalar_max_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).scalar_max_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
pub fn scalar_min_parallelized<T>(&self, lhs: &RadixCiphertext, rhs: T) -> RadixCiphertext
|
||||
where
|
||||
T: DecomposableInto<u8>,
|
||||
T: DecomposableInto<u64>,
|
||||
{
|
||||
Comparator::new(self).scalar_min_parallelized(lhs, rhs)
|
||||
}
|
||||
|
||||
@@ -185,6 +185,8 @@ create_parametrized_test!(integer_smart_scalar_sub);
|
||||
create_parametrized_test!(integer_default_scalar_sub);
|
||||
create_parametrized_test!(integer_smart_scalar_add);
|
||||
create_parametrized_test!(integer_default_scalar_add);
|
||||
create_parametrized_test!(integer_smart_if_then_else);
|
||||
create_parametrized_test!(integer_default_if_then_else);
|
||||
|
||||
fn integer_smart_add<P>(param: P)
|
||||
where
|
||||
@@ -2581,6 +2583,25 @@ where
|
||||
// Check the correctness
|
||||
assert_eq!(clear, dec);
|
||||
}
|
||||
|
||||
{
|
||||
// test x * y and y * x
|
||||
// where y encrypts a boolean value
|
||||
let clear1 = rng.gen::<u64>() % modulus;
|
||||
let clear2 = rng.gen_range(0u64..=1);
|
||||
|
||||
let ctxt_1 = cks.encrypt(clear1);
|
||||
let ctxt_2 = sks.create_trivial_radix(clear2, ctxt_1.blocks.len());
|
||||
assert!(ctxt_2.holds_boolean_value());
|
||||
|
||||
let res = sks.mul_parallelized(&ctxt_1, &ctxt_2);
|
||||
let dec: u64 = cks.decrypt(&res);
|
||||
assert_eq!(dec, clear1 * clear2);
|
||||
|
||||
let res = sks.mul_parallelized(&ctxt_2, &ctxt_1);
|
||||
let dec: u64 = cks.decrypt(&res);
|
||||
assert_eq!(dec, clear1 * clear2);
|
||||
}
|
||||
}
|
||||
|
||||
fn integer_smart_scalar_add<P>(param: P)
|
||||
@@ -2800,3 +2821,140 @@ fn test_non_regression_clone_from() {
|
||||
let q1q2 = server_key.smart_mul_parallelized(&mut q1, &mut q2);
|
||||
assert_eq!(client_key.decrypt_radix::<u8>(&q1q2), 1);
|
||||
}
|
||||
|
||||
fn integer_smart_if_then_else<P>(param: P)
|
||||
where
|
||||
P: Into<PBSParameters>,
|
||||
{
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
let cks = RadixClientKey::from((cks, NB_CTXT));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(NB_CTXT as u32) as u64;
|
||||
|
||||
for _ in 0..NB_TEST {
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
let clear_1 = rng.gen::<u64>() % modulus;
|
||||
let clear_condition = rng.gen_range(0u64..1);
|
||||
|
||||
let mut ctxt_0 = cks.encrypt(clear_0);
|
||||
let mut ctxt_1 = cks.encrypt(clear_1);
|
||||
// cks.encrypt returns a ciphertext which does not look like
|
||||
// (when looking at the degree) it encrypts a boolean value.
|
||||
// So we 'force' having a boolean encrypting ciphertext by using eq (==)
|
||||
let mut ctxt_condition = sks.scalar_eq_parallelized(&cks.encrypt(clear_condition), 1);
|
||||
assert!(ctxt_condition.holds_boolean_value());
|
||||
|
||||
let ct_res =
|
||||
sks.smart_if_then_else_parallelized(&mut ctxt_condition, &mut ctxt_0, &mut ctxt_1);
|
||||
|
||||
let dec_res: u64 = cks.decrypt(&ct_res);
|
||||
assert_eq!(
|
||||
dec_res,
|
||||
if clear_condition == 1 {
|
||||
clear_0
|
||||
} else {
|
||||
clear_1
|
||||
}
|
||||
);
|
||||
|
||||
let clear_2 = rng.gen::<u64>() % modulus;
|
||||
let clear_3 = rng.gen::<u64>() % modulus;
|
||||
|
||||
let ctxt_2 = cks.encrypt(clear_2);
|
||||
let ctxt_3 = cks.encrypt(clear_3);
|
||||
|
||||
// Add to have non empty carries
|
||||
sks.unchecked_add_assign(&mut ctxt_0, &ctxt_2);
|
||||
sks.unchecked_add_assign(&mut ctxt_1, &ctxt_3);
|
||||
assert!(!ctxt_0.block_carries_are_empty());
|
||||
assert!(!ctxt_1.block_carries_are_empty());
|
||||
|
||||
let ct_res =
|
||||
sks.smart_if_then_else_parallelized(&mut ctxt_condition, &mut ctxt_0, &mut ctxt_1);
|
||||
assert!(ctxt_0.block_carries_are_empty());
|
||||
assert!(ctxt_1.block_carries_are_empty());
|
||||
|
||||
let dec_res: u64 = cks.decrypt(&ct_res);
|
||||
assert_eq!(
|
||||
dec_res,
|
||||
if clear_condition == 1 {
|
||||
(clear_0 + clear_2) % modulus
|
||||
} else {
|
||||
(clear_1 + clear_3) % modulus
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn integer_default_if_then_else<P>(param: P)
|
||||
where
|
||||
P: Into<PBSParameters>,
|
||||
{
|
||||
let (cks, mut sks) = KEY_CACHE.get_from_params(param);
|
||||
let cks = RadixClientKey::from((cks, NB_CTXT));
|
||||
|
||||
sks.set_deterministic_pbs_execution(true);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = cks.parameters().message_modulus().0.pow(NB_CTXT as u32) as u64;
|
||||
|
||||
for _ in 0..NB_TEST {
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
let clear_1 = rng.gen::<u64>() % modulus;
|
||||
let clear_condition = rng.gen_range(0u64..1);
|
||||
|
||||
let mut ctxt_0 = cks.encrypt(clear_0);
|
||||
let mut ctxt_1 = cks.encrypt(clear_1);
|
||||
// cks.encrypt returns a ciphertext which does not look like
|
||||
// (when looking at the degree) it encrypts a boolean value.
|
||||
// So we 'force' having a boolean encrypting ciphertext by using eq (==)
|
||||
let ctxt_condition = sks.scalar_eq_parallelized(&cks.encrypt(clear_condition), 1);
|
||||
assert!(ctxt_condition.holds_boolean_value());
|
||||
|
||||
let ct_res = sks.if_then_else_parallelized(&ctxt_condition, &ctxt_0, &ctxt_1);
|
||||
assert!(ct_res.block_carries_are_empty());
|
||||
|
||||
let dec_res: u64 = cks.decrypt(&ct_res);
|
||||
assert_eq!(
|
||||
dec_res,
|
||||
if clear_condition == 1 {
|
||||
clear_0
|
||||
} else {
|
||||
clear_1
|
||||
}
|
||||
);
|
||||
|
||||
let ct_res2 = sks.if_then_else_parallelized(&ctxt_condition, &ctxt_0, &ctxt_1);
|
||||
assert_eq!(ct_res, ct_res2, "Operation if not deterministic");
|
||||
|
||||
let clear_2 = rng.gen::<u64>() % modulus;
|
||||
let clear_3 = rng.gen::<u64>() % modulus;
|
||||
|
||||
let ctxt_2 = cks.encrypt(clear_2);
|
||||
let ctxt_3 = cks.encrypt(clear_3);
|
||||
|
||||
// Add to have non empty carries
|
||||
sks.unchecked_add_assign(&mut ctxt_0, &ctxt_2);
|
||||
sks.unchecked_add_assign(&mut ctxt_1, &ctxt_3);
|
||||
assert!(!ctxt_0.block_carries_are_empty());
|
||||
assert!(!ctxt_1.block_carries_are_empty());
|
||||
|
||||
let ct_res = sks.if_then_else_parallelized(&ctxt_condition, &ctxt_0, &ctxt_1);
|
||||
assert!(ct_res.block_carries_are_empty());
|
||||
|
||||
let dec_res: u64 = cks.decrypt(&ct_res);
|
||||
assert_eq!(
|
||||
dec_res,
|
||||
if clear_condition == 1 {
|
||||
(clear_0 + clear_2) % modulus
|
||||
} else {
|
||||
(clear_1 + clear_3) % modulus
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ pub struct BooleanParameters(pub(crate) crate::boolean::parameters::BooleanParam
|
||||
pub enum BooleanParameterSet {
|
||||
Default,
|
||||
TfheLib,
|
||||
DefaultKsPbs,
|
||||
TfheLibKsPbs,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for BooleanParameterSet {
|
||||
@@ -41,6 +43,8 @@ impl TryFrom<u32> for BooleanParameterSet {
|
||||
match value {
|
||||
0 => Ok(BooleanParameterSet::Default),
|
||||
1 => Ok(BooleanParameterSet::TfheLib),
|
||||
2 => Ok(BooleanParameterSet::DefaultKsPbs),
|
||||
3 => Ok(BooleanParameterSet::TfheLibKsPbs),
|
||||
_ => Err(format!(
|
||||
"Invalid value '{value}' for BooleansParametersSet, use \
|
||||
BooleanParameterSet constants"
|
||||
@@ -49,6 +53,27 @@ impl TryFrom<u32> for BooleanParameterSet {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub enum BooleanEncryptionKeyChoice {
|
||||
Big,
|
||||
Small,
|
||||
}
|
||||
|
||||
impl From<BooleanEncryptionKeyChoice>
|
||||
for crate::core_crypto::commons::parameters::EncryptionKeyChoice
|
||||
{
|
||||
fn from(value: BooleanEncryptionKeyChoice) -> Self {
|
||||
match value {
|
||||
BooleanEncryptionKeyChoice::Big => {
|
||||
crate::shortint::parameters::EncryptionKeyChoice::Big
|
||||
}
|
||||
BooleanEncryptionKeyChoice::Small => {
|
||||
crate::shortint::parameters::EncryptionKeyChoice::Small
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Boolean {
|
||||
#[wasm_bindgen]
|
||||
@@ -59,7 +84,15 @@ impl Boolean {
|
||||
|
||||
match parameter_choice {
|
||||
BooleanParameterSet::Default => Ok(crate::boolean::parameters::DEFAULT_PARAMETERS),
|
||||
BooleanParameterSet::TfheLib => Ok(crate::boolean::parameters::TFHE_LIB_PARAMETERS),
|
||||
BooleanParameterSet::TfheLib => {
|
||||
Ok(crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165)
|
||||
}
|
||||
BooleanParameterSet::DefaultKsPbs => {
|
||||
Ok(crate::boolean::parameters::DEFAULT_PARAMETERS_KS_PBS)
|
||||
}
|
||||
BooleanParameterSet::TfheLibKsPbs => {
|
||||
Ok(crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS)
|
||||
}
|
||||
}
|
||||
.map(BooleanParameters)
|
||||
}
|
||||
@@ -76,6 +109,7 @@ impl Boolean {
|
||||
pbs_level: usize,
|
||||
ks_base_log: usize,
|
||||
ks_level: usize,
|
||||
encryption_key_choice: BooleanEncryptionKeyChoice,
|
||||
) -> BooleanParameters {
|
||||
set_hook(Box::new(console_error_panic_hook::hook));
|
||||
use crate::core_crypto::prelude::*;
|
||||
@@ -89,6 +123,7 @@ impl Boolean {
|
||||
pbs_level: DecompositionLevelCount(pbs_level),
|
||||
ks_base_log: DecompositionBaseLog(ks_base_log),
|
||||
ks_level: DecompositionLevelCount(ks_level),
|
||||
encryption_key_choice: encryption_key_choice.into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Module with the definition of the Ciphertext.
|
||||
pub use crate::core_crypto::commons::parameters::PBSOrder;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::shortint::parameters::{CarryModulus, MessageModulus};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -9,20 +10,6 @@ use std::fmt::Debug;
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct Degree(pub usize);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum PBSOrder {
|
||||
/// Ciphertext is encrypted using the big LWE secret key corresponding to the GLWE secret key.
|
||||
///
|
||||
/// A keyswitch is first performed to bring it to the small LWE secret key realm, then the PBS
|
||||
/// is computed bringing it back to the large LWE secret key.
|
||||
KeyswitchBootstrap = 0,
|
||||
/// Ciphertext is encrypted using the small LWE secret key.
|
||||
///
|
||||
/// The PBS is computed first and a keyswitch is applied to get back to the small LWE secret
|
||||
/// key realm.
|
||||
BootstrapKeyswitch = 1,
|
||||
}
|
||||
|
||||
impl Degree {
|
||||
pub(crate) fn after_bitxor(&self, other: Degree) -> Degree {
|
||||
let max = cmp::max(self.0, other.0);
|
||||
|
||||
@@ -619,12 +619,18 @@ impl ShortintEngine {
|
||||
where
|
||||
F: Fn(u64, u64) -> u64,
|
||||
{
|
||||
// Generate the lookup table for the function
|
||||
if !server_key.is_functional_bivariate_pbs_possible(ct_left, ct_right) {
|
||||
// We don't have enough space in carries, so clear them
|
||||
self.message_extract_assign(server_key, ct_left)?;
|
||||
self.message_extract_assign(server_key, ct_right)?;
|
||||
}
|
||||
let factor = MessageModulus(ct_right.degree.0 + 1);
|
||||
|
||||
// Generate the lookup table for the function
|
||||
let lookup_table =
|
||||
self.generate_lookup_table_bivariate_with_factor(server_key, f, factor)?;
|
||||
|
||||
self.smart_apply_lookup_table_bivariate_assign(
|
||||
self.unchecked_apply_lookup_table_bivariate_assign(
|
||||
server_key,
|
||||
ct_left,
|
||||
ct_right,
|
||||
|
||||
@@ -20,6 +20,11 @@ impl ShortintEngine {
|
||||
ct_left: &mut Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
) -> EngineResult<()> {
|
||||
if ct_left.degree.0 == 0 || ct_right.degree.0 == 0 {
|
||||
// One of the ciphertext is a trivial 0
|
||||
self.create_trivial_assign(server_key, ct_left, 0)?;
|
||||
return Ok(());
|
||||
}
|
||||
let modulus = (ct_right.degree.0 + 1) as u64;
|
||||
|
||||
//message 1 is shifted to the carry bits
|
||||
@@ -58,6 +63,11 @@ impl ShortintEngine {
|
||||
ct_left: &mut Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
) -> EngineResult<()> {
|
||||
if ct_left.degree.0 == 0 || ct_right.degree.0 == 0 {
|
||||
// One of the ciphertext is a trivial 0
|
||||
self.create_trivial_assign(server_key, ct_left, 0)?;
|
||||
return Ok(());
|
||||
}
|
||||
let modulus = (ct_right.degree.0 + 1) as u64;
|
||||
let deg = (ct_left.degree.0 * ct_right.degree.0) / ct_right.message_modulus.0;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ pub use crate::core_crypto::commons::parameters::{
|
||||
CiphertextModulus as CoreCiphertextModulus, DecompositionBaseLog, DecompositionLevelCount,
|
||||
GlweDimension, LweBskGroupingFactor, LweDimension, PolynomialSize,
|
||||
};
|
||||
use crate::shortint::ciphertext::PBSOrder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod key_switching;
|
||||
@@ -20,38 +19,12 @@ pub mod parameters_wopbs;
|
||||
pub mod parameters_wopbs_message_carry;
|
||||
pub(crate) mod parameters_wopbs_prime_moduli;
|
||||
|
||||
pub use crate::core_crypto::commons::parameters::EncryptionKeyChoice;
|
||||
pub use key_switching::ShortintKeySwitchingParameters;
|
||||
pub use multi_bit::*;
|
||||
pub use parameters_compact_pk::*;
|
||||
pub use parameters_wopbs::WopbsParameters;
|
||||
|
||||
/// The choice of encryption key for (`shortint ciphertext`)[`super::ciphertext::Ciphertext`].
|
||||
///
|
||||
/// * The `Big` choice means the big LWE key derived from the GLWE key is used to encrypt the input
|
||||
/// ciphertext. This offers better performance but the (`public
|
||||
/// key`)[`super::public_key::PublicKey`] can be extremely large and in some cases may not fit in
|
||||
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the PBS is computed
|
||||
/// first followed by a keyswitch.
|
||||
/// * The `Small` choice means the small LWE key is used to encrypt the input ciphertext.
|
||||
/// Performance is not as good as in the `Big` case but (`public
|
||||
/// key`)[`super::public_key::PublicKey`] sizes are much more manageable and shoud always fit in
|
||||
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the keyswitch is
|
||||
/// computed first followed by a PBS.
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum EncryptionKeyChoice {
|
||||
Big,
|
||||
Small,
|
||||
}
|
||||
|
||||
impl From<EncryptionKeyChoice> for PBSOrder {
|
||||
fn from(value: EncryptionKeyChoice) -> Self {
|
||||
match value {
|
||||
EncryptionKeyChoice::Big => Self::KeyswitchBootstrap,
|
||||
EncryptionKeyChoice::Small => Self::BootstrapKeyswitch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of bits on which the message will be encoded.
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct MessageModulus(pub usize);
|
||||
@@ -65,6 +38,19 @@ pub type CiphertextModulus = CoreCiphertextModulus<u64>;
|
||||
|
||||
/// A structure defining the set of cryptographic parameters for homomorphic integer circuit
|
||||
/// evaluation.
|
||||
///
|
||||
/// The choice of encryption key for (`shortint ciphertext`)[`super::ciphertext::Ciphertext`].
|
||||
///
|
||||
/// * The `Big` choice means the big LWE key derived from the GLWE key is used to encrypt the input
|
||||
/// ciphertext. This offers better performance but the (`public
|
||||
/// key`)[`super::public_key::PublicKey`] can be extremely large and in some cases may not fit in
|
||||
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the PBS is computed
|
||||
/// first followed by a keyswitch.
|
||||
/// * The `Small` choice means the small LWE key is used to encrypt the input ciphertext.
|
||||
/// Performance is not as good as in the `Big` case but (`public
|
||||
/// key`)[`super::public_key::PublicKey`] sizes are much more manageable and shoud always fit in
|
||||
/// memory. When refreshing a ciphertext and/or evaluating a table lookup the keyswitch is
|
||||
/// computed first followed by a PBS.
|
||||
#[derive(Serialize, Copy, Clone, Deserialize, Debug, PartialEq)]
|
||||
pub struct ClassicPBSParameters {
|
||||
pub lwe_dimension: LweDimension,
|
||||
|
||||
@@ -141,6 +141,11 @@ impl CompactPublicKey {
|
||||
self.parameters.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let encryption_noise = match self.pbs_order {
|
||||
crate::shortint::PBSOrder::KeyswitchBootstrap => self.parameters.glwe_modular_std_dev(),
|
||||
crate::shortint::PBSOrder::BootstrapKeyswitch => self.parameters.lwe_modular_std_dev(),
|
||||
};
|
||||
|
||||
// No parallelism allowed
|
||||
#[cfg(all(feature = "__wasm_api", not(feature = "parallel-wasm-api")))]
|
||||
{
|
||||
@@ -150,8 +155,8 @@ impl CompactPublicKey {
|
||||
&self.key,
|
||||
&mut ct_list,
|
||||
&plaintext_list,
|
||||
self.parameters.glwe_modular_std_dev(),
|
||||
self.parameters.lwe_modular_std_dev(),
|
||||
encryption_noise,
|
||||
encryption_noise,
|
||||
&mut engine.secret_generator,
|
||||
&mut engine.encryption_generator,
|
||||
);
|
||||
@@ -167,8 +172,8 @@ impl CompactPublicKey {
|
||||
&self.key,
|
||||
&mut ct_list,
|
||||
&plaintext_list,
|
||||
self.parameters.glwe_modular_std_dev(),
|
||||
self.parameters.lwe_modular_std_dev(),
|
||||
encryption_noise,
|
||||
encryption_noise,
|
||||
&mut engine.secret_generator,
|
||||
&mut engine.encryption_generator,
|
||||
);
|
||||
|
||||
@@ -45,7 +45,7 @@ doctest!(
|
||||
booleans_serialization
|
||||
);
|
||||
doctest!(
|
||||
"../docs/fine_grained_api/Boolean/tutorial.md",
|
||||
"../docs/fine_grained_api/Boolean/readme.md",
|
||||
booleans_tutorial
|
||||
);
|
||||
|
||||
@@ -64,7 +64,7 @@ doctest!(
|
||||
shortint_serialization
|
||||
);
|
||||
doctest!(
|
||||
"../docs/fine_grained_api/shortint/tutorial.md",
|
||||
"../docs/fine_grained_api/shortint/readme.md",
|
||||
shortint_tutorial
|
||||
);
|
||||
|
||||
@@ -78,7 +78,7 @@ doctest!(
|
||||
integer_serialization_tuto
|
||||
);
|
||||
doctest!(
|
||||
"../docs/fine_grained_api/integer/tutorial.md",
|
||||
"../docs/fine_grained_api/integer/readme.md",
|
||||
integer_first_circuit
|
||||
);
|
||||
|
||||
|
||||
5162
tfhe/web_wasm_parallel_tests/package-lock.json
generated
@@ -17,7 +17,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.22.4",
|
||||
"jest": "^29.5.0",
|
||||
"puppeteer": "^20.3.0",
|
||||
"puppeteer": "^21.3.8",
|
||||
"serve": "^14.2.0",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.1.1"
|
||||
|
||||