Compare commits

...

13 Commits

Author SHA1 Message Date
J-B Orfila
b69222df1c doc: updated examples 2023-10-03 19:51:59 +02:00
J-B Orfila
934a78496a doc: updating doc for v0.4 2023-10-03 15:26:12 +02:00
tmontaigu
6d49993629 WIP add FheInt{8,16,32,64,128,256} 2023-09-28 10:48:34 +02:00
tmontaigu
66a193eaca WIP lol 2023-09-28 10:44:16 +02:00
tmontaigu
a5fa497096 WIP 2023-09-28 09:56:24 +02:00
tmontaigu
7e3bf8e08a WIP 2023-09-27 19:14:24 +02:00
tmontaigu
9cb98cca2e feat(integer): add default signed_div_rem 2023-09-27 19:09:52 +02:00
tmontaigu
23d25a8281 feat(integer): add encryption of compressed signed radix 2023-09-27 19:09:25 +02:00
tmontaigu
ef26dc0e9e feat(integer): make compact ciphertext compatible with signed 2023-09-27 19:08:39 +02:00
tmontaigu
2e2f1200dc feat(integer): add sign extend fn for SignedRadixCiphertext 2023-09-27 09:00:19 +02:00
tmontaigu
0a27ed44f7 chore(integer): impl RecomposableSignedInteger for StaticSignedBigInt 2023-09-26 18:52:14 +02:00
tmontaigu
37a47e63a4 fix(integer): StaticSignedBigInt right shift 2023-09-26 18:52:14 +02:00
tmontaigu
417feb6a20 chore(hlapi): remove unused keychain_member from macro 2023-09-26 18:52:13 +02:00
29 changed files with 2433 additions and 585 deletions

View File

@@ -5,7 +5,7 @@
## Getting Started
* [Installation](getting_started/installation.md)
* [Quick Start](getting_started/quick_start.md)
* [Operations](getting_started/operations.md)
* [Types & Operations](getting_started/operations.md)
* [Benchmarks](getting_started/benchmarks.md)
* [Security and Cryptography](getting_started/security_and_cryptography.md)

View File

@@ -17,7 +17,7 @@ This crate implements two ways to represent an integer:
The first possibility to represent a large integer is to use a Radix-based decomposition on the plaintexts. Let $$B \in \mathbb{N}$$ be a basis such that the size of $$B$$ is smaller than (or equal to) 4 bits. Then, an integer $$m \in \mathbb{N}$$ can be written as $$m = m_0 + m_1*B + m_2*B^2 + ...$$, where each $$m_i$$ is strictly smaller than $$B$$. Each $$m_i$$ is then independently encrypted. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
The definition of an integer requires a basis and a number of blocks. This is done at key generation. Below, the keys are dedicated to unsigned integers encrypting messages over 8 bits, using a basis over 2 bits (i.e., $$B=2^2$$) and 4 blocks.
The definition of an integer requires a basis and a number of blocks. This is done at key generation. Below, the keys are dedicated to integers encrypting messages over 8 bits, using a basis over 2 bits (i.e., $$B=2^2$$) and 4 blocks.
```rust
use tfhe::integer::gen_keys_radix;

View File

@@ -1,6 +1,6 @@
# Tutorial
`tfhe::integer` is dedicated to unsigned integers smaller than 256 bits. The steps to homomorphically evaluate an integer circuit are described here.
`tfhe::integer` is dedicated to integers smaller than 256 bits. The steps to homomorphically evaluate an integer circuit are described here.
## Key Types
@@ -25,7 +25,7 @@ To generate the keys, a user needs two parameters:
* A set of `shortint` cryptographic parameters.
* The number of ciphertexts used to encrypt an integer (we call them "shortint blocks").
We are now going to build a pair of keys that can encrypt an **8-bit** integer by using **4** shortint blocks that store **2** bits of message each.
We are now going to build a pair of keys that can encrypt **8-bit** integers (signed or unsigned) by using **4** shortint blocks that store **2** bits of message each.
```rust
use tfhe::integer::gen_keys_radix;

View File

@@ -1,6 +1,6 @@
# Tutorial
`tfhe::shortint` is dedicated to small unsigned integers smaller than 8 bits. The steps to homomorphically evaluate a circuit are described below.
`tfhe::shortint` is dedicated to small integers smaller than 8 bits. The steps to homomorphically evaluate a circuit are described below.
## Key generation

View File

@@ -6,47 +6,22 @@ Due to their nature, homomorphic operations are naturally slower than their clea
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
This measures the execution time of a single binary Boolean gate.
### tfhe-rs::boolean.
| Parameter set | Concrete FFT | Concrete FFT + AVX-512 |
| --------------------- | ------------ | ---------------------- |
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
### tfhe-lib.
| Parameter set | fftw | spqlios-fma |
| ------------------------------------------------ | ------ | ----------- |
| default\_128bit\_gate\_bootstrapping\_parameters | 28.9ms | 15.7ms |
### OpenFHE.
| Parameter set | GINX | GINX (Intel HEXL) |
| ------------- | ----- | ----------------- |
| STD\_128 | 172ms | 78ms |
| MEDIUM | 113ms | 50.2ms |
## Integer
This measures the execution time for some operation sets of tfhe-rs::integer.
This measures the execution time for some operation sets of tfhe-rs::integer. Note that the timings with `FheInt` are similar.
| Operation \ Size | `FheUint8` | `FheUint16` | `FheUint32` | ` FheUint64` | `FheUint128` | `FheUint256` |
|--------------------------------------------------------|------------|-------------|-------------|--------------|--------------|--------------|
| Negation (`-`) | 80.4 ms | 106 ms | 132 ms | 193 ms | 257 ms | 348 ms |
| Add / Sub (`+`,`-`) | 81.5 ms | 110 ms | 139 ms | 200 ms | 262 ms | 355 ms |
| Mul (`x`) | 150 ms | 221 ms | 361 ms | 928 ms | 2.90 s | 10.97 s |
| Equal / Not Equal (`eq`, `ne`) | 39.4 ms | 40.2 ms | 61.1 ms | 66.4 ms | 74.5 ms | 85.7 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 57.5 ms | 79.6 ms | 105 ms | 136 ms | 174 ms | 219 ms |
| Max / Min (`max`,`min`) | 100 ms | 130 ms | 163 ms | 204 ms | 245 ms | 338 ms |
| Bitwise operations (`&`, `|`, `^`) | 20.7 ms | 21.1 ms | 22.6 ms | 30.2 ms | 34.1 ms | 42.1 ms |
| Div / Rem (`/`, `%`) | 1.37 s | 3.50 s | 9.12 s | 23.9 s | 59.9 s | 149.2 s |
| Left / Right Shifts (`<<`, `>>`) | 106 ms | 140 ms | 202 ms | 262 ms | 403 ms | 827 ms |
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 105 ms | 140 ms | 199 ms | 263 ms | 403 ms | 829 ms |
| Negation (`-`) | 70.9 ms | 99.3 ms | 129 ms | 180 ms | 239 ms | 333 ms |
| Add / Sub (`+`,`-`) | 70.5 ms | 100 ms | 132 ms | 186 ms | 249 ms | 334 ms |
| Mul (`x`) | 144 ms | 216 ms | 333 ms | 832 ms | 2.50 s | 8.85 s |
| Equal / Not Equal (`eq`, `ne`) | 36.1 ms | 36.5 ms | 57.4 ms | 64.2 ms | 67.3 ms | 78.1 ms |
| Comparisons (`ge`, `gt`, `le`, `lt`) | 52.6 ms | 73.1 ms | 98.8 ms | 124 ms | 165 ms | 201 ms |
| Max / Min (`max`,`min`) | 76.2 ms | 102 ms | 135 ms | 171 ms | 212 ms | 301 ms |
| Bitwise operations (`&`, `\|`, `^`) | 19.4 ms | 20.3 ms | 21.0 ms | 27.2 ms | 31.6 ms | 40.2 ms |
| Div / Rem (`/`, `%`) | 1.10 s | 2.97 s | 7.17 s | 19.7 s | 50.2 s | 131 s |
| Left / Right Shifts (`<<`, `>>`) | 99.4 ms | 129 ms | 180 ms | 243 ms | 372 ms | 762 ms |
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 103 ms | 128 ms | 182 ms | 241 ms | 374 ms | 763 ms |
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)).
@@ -54,29 +29,65 @@ To ensure predictable timings, the operation flavor is the `default` one: the ca
## Shortint
This measures the execution time for some operations using various parameter sets of tfhe-rs::shortint.
This measures the execution time for some operations using various parameter sets of tfhe-rs::shortint. Except from the `unchecked_add`, all timings are related to the `default` operations. This flavor ensures predictable timings of an operation along the entire circuit by clearing the carry space after each operation.
This uses the Concrete FFT + AVX-512 configuration.
| Parameter set | unchecked\_add | unchecked\_mul\_lsb | keyswitch\_programmable\_bootstrap |
|-----------------------------|----------------|---------------------|------------------------------------|
| PARAM\_MESSAGE\_1\_CARRY\_1 | 338 ns | 8.3 ms | 8.1 ms |
| PARAM\_MESSAGE\_2\_CARRY\_2 | 406 ns | 18.4 ms | 18.4 ms |
| PARAM\_MESSAGE\_3\_CARRY\_3 | 3.06 µs | 134 ms | 129.4 ms |
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 828.1 ms |
| Parameter set | PARAM\_MESSAGE\_1\_CARRY\_1 | PARAM\_MESSAGE\_2\_CARRY\_2 | PARAM\_MESSAGE\_3\_CARRY\_3 | PARAM\_MESSAGE\_4\_CARRY\_4 |
|------------------------------------|-----------------------------|-----------------------------|-----------------------------|-----------------------------|
| unchecked\_add | 348 ns | 413 ns | 2.95 µs | 12.1 µs |
| add | 7.59 ms | 17.0 ms | 121 ms | 835 ms |
| mul\_lsb | 8.13 ms | 16.8 ms | 121 ms | 827 ms |
| keyswitch\_programmable\_bootstrap | 7.28 ms | 16.6 ms | 121 ms | 811 ms |
Next, the timings for the operation flavor `default` are given. This flavor ensures predictable timings of an operation along the entire circuit by clearing the carry space after each operation.
| Parameter set | add | mul\_lsb | keyswitch\_programmable\_bootstrap |
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
| PARAM\_MESSAGE\_1\_CARRY\_1 | 7.90 ms | 8.00 ms | 8.10 ms |
| PARAM\_MESSAGE\_2\_CARRY\_2 | 18.4 ms | 18.1 ms | 18.4 ms |
| PARAM\_MESSAGE\_3\_CARRY\_3 | 131.5 ms | 129.5 ms | 129.4 ms |
| PARAM\_MESSAGE\_4\_CARRY\_4 | 852.5 ms | 839.7 ms | 828.1 ms |
## Boolean
## How to reproduce benchmarks
This measures the execution time of a single binary Boolean gate.
TFHE-rs benchmarks can easily be reproduced from the [sources](https://github.com/zama-ai/tfhe-rs).
### tfhe-rs::boolean.
| Parameter set | Concrete FFT + AVX-512 |
|------------------------------------------------------|------------------------|
| DEFAULT\_PARAMETERS\_KS\_PBS | 9.19 ms |
| PARAMETERS\_ERROR\_PROB\_2\_POW\_MINUS\_165\_KS\_PBS | 14.1 ms |
| TFHE\_LIB\_PARAMETERS | 10.0 ms |
### tfhe-lib.
| Parameter set | spqlios-fma |
|--------------------------------------------------|-------------|
| default\_128bit\_gate\_bootstrapping\_parameters | 15.4 ms |
### OpenFHE (v1.1.1).
Following the instructions from OpenFHE maintainers, `clang14` is used and this command to setup the project:
`cmake -DNATIVE_SIZE=32 -DWITH_NATIVEOPT=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DWITH_OPENMP=OFF ..`
To use the HEXL library, the configuration file is:
```bash
export CXX=clang++
export CC=clang
scripts/configure.sh
Release -> y
hexl -> y
scripts/build-openfhe-development-hexl.sh
```
Using the same m6i machine as previous benchmarks, the timings are:
| Parameter set | GINX | GINX w/ Intel HEXL |
|----------------------------------|---------|--------------------|
| FHEW\_BINGATE/STD128\_OR | 40.2 ms | 31.0 ms |
| FHEW\_BINGATE/STD128\_LMKCDEY_OR | 38.6 ms | 28.4 ms |
## How to reproduce TFHE-rs benchmarks
TFHE-rs benchmarks can be easily reproduced from the [sources](https://github.com/zama-ai/tfhe-rs).
```shell
#Boolean benchmarks:

View File

@@ -1,43 +1,325 @@
# Operations
# Homomorphic Types and Operations
The table below contains an overview of the available operations in `TFHE-rs`. More details, and further examples, are given in the following sections.
## Types
`TFHE-rs` includes two main types to represent encrypted data:
- `FheUint`: this is the homomorphic equivalent of Rust `uint`
- `FheInt`: this is the homomorphic equivalent of Rust `int`
| name | symbol | FheUint/FheUint | FheUint/Uint | Uint/FheUint |
|-----------------------|-------------|--------------------|--------------------------|--------------------------|
| Neg | `-` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Add | `+` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Sub | `-` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Mul | `*` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Div | `/` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Rem | `%` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Not | `!` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| BitAnd | `&` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| BitOr | `\|` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| BitXor | `^` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Shr | `>>` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Shl | `<<` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Min | `min` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Max | `max` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Greater than | `gt` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Greater or equal than | `ge` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Lower than | `lt` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Lower or equal than | `le` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Equal | `eq` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Cast (into dest type) | `cast_into` | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |
| Cast (from src type) | `cast_from` | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |
In the same manner than many programming languages, the number of bits used to represent the data must be chosen when declaring a variable. For instance:
## Boolean Operations
```Rust
Native homomorphic Booleans support common Boolean operations.
// let clear_a: u64 = 7;
let mut a = FheUint64::try_encrypt(clear_a, &keys)?;
// let clear_b: i8 = 3;
let mut b = FheInt8::try_encrypt(clear_b, &keys)?;
// let clear_c: u128 = 2;
let mut c = FheUint128::try_encrypt(clear_c, &keys)?;
```
## Operation list
The table below contains an overview of the available operations in `TFHE-rs`. The notation `Enc` (for Encypted Data) either refers to `FheInt` or `FheUint`, for any size between 1 and 256-bits.
More details, and further examples, are given in the following sections.
| name | symbol | `Enc`/`Enc` | `Enc`/ `Int` |
|-----------------------|-------------|--------------------|--------------------------|
| Neg | `-` | :heavy_check_mark: | :heavy_check_mark: |
| Add | `+` | :heavy_check_mark: | :heavy_check_mark: |
| Sub | `-` | :heavy_check_mark: | :heavy_check_mark: |
| Mul | `*` | :heavy_check_mark: | :heavy_check_mark: |
| Div | `/` | :heavy_check_mark: | :heavy_check_mark: |
| Rem | `%` | :heavy_check_mark: | :heavy_check_mark: |
| Not | `!` | :heavy_check_mark: | :heavy_check_mark: |
| BitAnd | `&` | :heavy_check_mark: | :heavy_check_mark: |
| BitOr | `\|` | :heavy_check_mark: | :heavy_check_mark: |
| BitXor | `^` | :heavy_check_mark: | :heavy_check_mark: |
| Shr | `>>` | :heavy_check_mark: | :heavy_check_mark: |
| Shl | `<<` | :heavy_check_mark: | :heavy_check_mark: |
| Min | `min` | :heavy_check_mark: | :heavy_check_mark: |
| Max | `max` | :heavy_check_mark: | :heavy_check_mark: |
| Greater than | `gt` | :heavy_check_mark: | :heavy_check_mark: |
| Greater or equal than | `ge` | :heavy_check_mark: | :heavy_check_mark: |
| Lower than | `lt` | :heavy_check_mark: | :heavy_check_mark: |
| Lower or equal than | `le` | :heavy_check_mark: | :heavy_check_mark: |
| Equal | `eq` | :heavy_check_mark: | :heavy_check_mark: |
| Cast (into dest type) | `cast_into` | :heavy_check_mark: | :heavy_multiplication_x: |
| Cast (from src type) | `cast_from` | :heavy_check_mark: | :heavy_multiplication_x: |
## Integer
In TFHE-rs, integers are used to encrypt any messages larger than 4 bits. All supported operations are listed below.
### Arithmetic operations.
Homomorphic integer types support arithmetic operations.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
| name | symbol | type |
|----------------------------------------------------------|--------|--------|
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `-` | Unary |
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
| [Div](https://doc.rust-lang.org/std/ops/trait.Div.html)* | `/` | Binary |
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html)* | `%` | Binary |
For division by $$0$$, the convention is to return $$modulus - 1$$. For instance, for FheUint8, the modulus is $$2^8=256$$, so a division by $$0$$ will return an encryption of $$255$$.
For the remainder operator, the convention is to return the first input without any modification. For instance, for $$ct1 = FheUint8(63)$$ and $$ct2 = FheUint8(0)$$, then $$ct1 % ct2$$ will return $$FheUint8(63)$$.
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt8, FheUint8};
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;
let clear_d = -87_i64;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let mut c = FheUint8::try_encrypt(clear_c, &keys)?;
let mut d = FheInt8::try_encrypt(clear_d, &keys)?;
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
d = d - 13i8; // Clear equivalent computations: -87 - 13 = 100 in [-128, 128]
let dec_a: u8 = a.decrypt(&keys);
let dec_b: u8 = b.decrypt(&keys);
let dec_d: i8 = d.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);
assert_eq!(dec_d, (clear_d - 13) as i8);
Ok(())
}
```
### Bitwise operations.
Homomorphic integer types support some bitwise operations.
The list of supported operations is:
| name | symbol | type |
|--------------------------------------------------------------------------------------|----------------|--------|
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
| [Rotate Right](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_right) | `rotate_right` | Binary |
| [Rotate Left](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_left) | `rotate_left` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
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;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
a = a ^ &b;
b = b ^ &a;
a = a ^ &b;
let dec_a: u8 = a.decrypt(&keys);
let dec_b: u8 = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, clear_b);
assert_eq!(dec_b, clear_a);
Ok(())
}
```
### Comparisons.
Homomorphic integers support comparison operations. Since Rust does not allow the overloading of these operations, a simple function has been associated to each one.
The list of supported operations is:
| name | symbol | type |
|-----------------------------------------------------------------------------|--------|--------|
| [Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `eq` | Binary |
| [Not Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `ne` | Binary |
| [Greater Than ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `gt` | Binary |
| [Greater or Equal](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `ge` | Binary |
| [Lower ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `lt` | Binary |
| [Lower or Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `le` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt8};
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:i8 = -121;
let clear_b:i8 = 87;
let mut a = FheInt8::try_encrypt(clear_a, &keys)?;
let mut b = FheInt8::try_encrypt(clear_b, &keys)?;
let greater = a.gt(&b);
let greater_or_equal = a.ge(&b);
let lower = a.lt(&b);
let lower_or_equal = a.le(&b);
let equal = a.eq(&b);
let dec_gt : i8 = greater.decrypt(&keys);
let dec_ge : i8 = greater_or_equal.decrypt(&keys);
let dec_lt : i8 = lower.decrypt(&keys);
let dec_le : i8 = lower_or_equal.decrypt(&keys);
let dec_eq : i8 = equal.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_gt, (clear_a > clear_b ) as i8);
assert_eq!(dec_ge, (clear_a >= clear_b) as i8);
assert_eq!(dec_lt, (clear_a < clear_b ) as i8);
assert_eq!(dec_le, (clear_a <= clear_b) as i8);
assert_eq!(dec_eq, (clear_a == clear_b) as i8);
Ok(())
}
```
### Min/Max.
Homomorphic integers support the min/max operations.
| name | symbol | type |
| ---- | ------ | ------ |
| Min | `min` | Binary |
| Max | `max` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
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:u8 = 164;
let clear_b:u8 = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let min = a.min(&b);
let max = a.max(&b);
let dec_min : u8 = min.decrypt(&keys);
let dec_max : u8 = max.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_min, u8::min(clear_a, clear_b));
assert_eq!(dec_max, u8::max(clear_a, clear_b));
Ok(())
}
```
### Casting.
Casting between integer types is possible via the `cast_from` associated function
or the `cast_into` method.
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt16, FheUint8, FheUint32, FheUint16};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
// Casting requires server_key to set
// (encryptions/decryptions do not need server_key to be set)
set_server_key(server_key);
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Downcasting
let a: FheUint8 = a.cast_into();
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, clear as u8);
// Upcasting
let a: FheUint32 = a.cast_into();
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, (clear as u8) as u32);
}
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Upcasting
let a = FheUint32::cast_from(a);
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, clear as u32);
// Downcasting
let a = FheUint8::cast_from(a);
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, (clear as u32) as u8);
}
{
let clear = 12_837i16;
let a = FheInt16::encrypt(clear, &client_key);
// Casting from FheInt16 to FheUint16
let a = FheUint16::cast_from(a);
let da: u16 = a.decrypt(&client_key);
assert_eq!(da, clear as u16);
}
Ok(())
}
```
## ShortInt Operations
@@ -252,257 +534,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```
## Integer
In TFHE-rs, integers are used to encrypt any messages larger than 4 bits. All supported operations are listed below.
### Arithmetic operations.
## Boolean Operations
Homomorphic integer types support arithmetic operations.
Native homomorphic Booleans support common Boolean operations.
The list of supported operations is:
| name | symbol | type |
|----------------------------------------------------------|--------|--------|
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `-` | Unary |
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
| [Div](https://doc.rust-lang.org/std/ops/trait.Div.html)* | `/` | Binary |
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html)* | `%` | Binary |
For division by $$0$$, the convention is to return $$modulus - 1$$. For instance, for FheUint8, the modulus is $$2^8=256$$, so a division by $$0$$ will return an encryption of $$255$$.
For the remainder operator, the convention is to return the first input without any modification. For instance, for $$ct1 = FheUint8(63)$$ and $$ct2 = FheUint8(0)$$, then $$ct1 % ct2$$ will return $$FheUint8(63)$$.
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
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;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let mut c = FheUint8::try_encrypt(clear_c, &keys)?;
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);
Ok(())
}
```
### Bitwise operations.
Homomorphic integer types support some bitwise operations.
The list of supported operations is:
| name | symbol | type |
|--------------------------------------------------------------------------------------|----------------|--------|
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
| [Rotate Right](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_right) | `rotate_right` | Binary |
| [Rotate Left](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_left) | `rotate_left` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
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;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
a = a ^ &b;
b = b ^ &a;
a = a ^ &b;
let dec_a: u8 = a.decrypt(&keys);
let dec_b: u8 = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, clear_b);
assert_eq!(dec_b, clear_a);
Ok(())
}
```
### Comparisons.
Homomorphic integers support comparison operations. Since Rust does not allow the overloading of these operations, a simple function has been associated to each one.
The list of supported operations is:
| name | symbol | type |
|-----------------------------------------------------------------------------|--------|--------|
| [Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `eq` | Binary |
| [Not Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `ne` | Binary |
| [Greater Than ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `gt` | Binary |
| [Greater or Equal](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `ge` | Binary |
| [Lower ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `lt` | Binary |
| [Lower or Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `le` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
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:u8 = 164;
let clear_b:u8 = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let greater = a.gt(&b);
let greater_or_equal = a.ge(&b);
let lower = a.lt(&b);
let lower_or_equal = a.le(&b);
let equal = a.eq(&b);
let dec_gt : u8 = greater.decrypt(&keys);
let dec_ge : u8 = greater_or_equal.decrypt(&keys);
let dec_lt : u8 = lower.decrypt(&keys);
let dec_le : u8 = lower_or_equal.decrypt(&keys);
let dec_eq : u8 = equal.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_gt, (clear_a > clear_b ) as u8);
assert_eq!(dec_ge, (clear_a >= clear_b) as u8);
assert_eq!(dec_lt, (clear_a < clear_b ) as u8);
assert_eq!(dec_le, (clear_a <= clear_b) as u8);
assert_eq!(dec_eq, (clear_a == clear_b) as u8);
Ok(())
}
```
### Min/Max.
Homomorphic integers support the min/max operations.
| name | symbol | type |
| ---- | ------ | ------ |
| Min | `min` | Binary |
| Max | `max` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
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:u8 = 164;
let clear_b:u8 = 212;
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
let min = a.min(&b);
let max = a.max(&b);
let dec_min : u8 = min.decrypt(&keys);
let dec_max : u8 = max.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_min, u8::min(clear_a, clear_b));
assert_eq!(dec_max, u8::max(clear_a, clear_b));
Ok(())
}
```
### Casting.
Casting between integer types is possible via the `cast_from` associated function
or the `cast_into` method.
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheUint32, FheUint16};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
// Casting requires server_key to set
// (encryptions/decryptions do not need server_key to be set)
set_server_key(server_key);
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Downcasting
let a: FheUint8 = a.cast_into();
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, clear as u8);
// Upcasting
let a: FheUint32 = a.cast_into();
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, (clear as u8) as u32);
}
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Upcasting
let a = FheUint32::cast_from(a);
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, clear as u32);
// Downcasting
let a = FheUint8::cast_from(a);
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, (clear as u32) as u8);
}
Ok(())
}
```
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |

View File

@@ -52,11 +52,11 @@ rustup show
This crate exposes two kinds of data types. Each kind is enabled by activating its corresponding feature in the TOML line. Each kind may have multiple types:
| Kind | Features | Type(s) |
| --------- | ---------- | --------------------------------- |
| Booleans | `boolean` | Booleans |
| ShortInts | `shortint` | Short unsigned integers |
| Integers | `integer` | Arbitrary-sized unsigned integers |
| Kind | Features | Type(s) |
|-----------|------------|---------------------------|
| Booleans | `boolean` | Booleans |
| ShortInts | `shortint` | Short integers |
| Integers | `integer` | Arbitrary-sized integers |
## AVX-512

View File

@@ -53,6 +53,18 @@ pub enum Type {
FheUint128,
#[cfg(feature = "integer")]
FheUint256,
#[cfg(feature = "integer")]
FheInt8,
#[cfg(feature = "integer")]
FheInt16,
#[cfg(feature = "integer")]
FheInt32,
#[cfg(feature = "integer")]
FheInt64,
#[cfg(feature = "integer")]
FheInt128,
#[cfg(feature = "integer")]
FheInt256,
}
/// The server key of a given type was not initialized

View File

@@ -1,6 +1,11 @@
use crate::core_crypto::prelude::UnsignedNumeric;
use crate::high_level_api::internal_traits::DecryptionKey;
use crate::integer::ciphertext::RadixCiphertext;
use crate::core_crypto::prelude::{SignedNumeric, UnsignedNumeric};
use crate::high_level_api::internal_traits::{DecryptionKey, EncryptionKey};
use crate::integer::ciphertext::{
CompressedRadixCiphertext, CompressedSignedRadixCiphertext, RadixCiphertext,
};
use crate::integer::client_key::RecomposableSignedInteger;
use crate::integer::public_key::CompactPublicKey;
use crate::integer::SignedRadixCiphertext;
impl<ClearType> DecryptionKey<RadixCiphertext, ClearType> for crate::integer::ClientKey
where
@@ -10,3 +15,91 @@ where
self.decrypt_radix(ciphertext)
}
}
impl<T> EncryptionKey<(T, usize), RadixCiphertext> for crate::integer::ClientKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + UnsignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> RadixCiphertext {
self.encrypt_radix(value.0, value.1)
}
}
impl<T> EncryptionKey<(T, usize), RadixCiphertext> for crate::integer::PublicKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + UnsignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> RadixCiphertext {
self.encrypt_radix(value.0, value.1)
}
}
impl<T> EncryptionKey<(T, usize), RadixCiphertext> for crate::integer::CompressedPublicKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + UnsignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> RadixCiphertext {
self.encrypt_radix(value.0, value.1)
}
}
impl<T> EncryptionKey<(T, usize), CompressedRadixCiphertext> for crate::integer::ClientKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + UnsignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> CompressedRadixCiphertext {
self.encrypt_radix_compressed(value.0, value.1)
}
}
impl<T> EncryptionKey<(T, usize), RadixCiphertext> for CompactPublicKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + UnsignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> RadixCiphertext {
self.encrypt_radix(value.0, value.1)
}
}
// Signed Integers
impl<ClearType> DecryptionKey<SignedRadixCiphertext, ClearType> for crate::integer::ClientKey
where
ClearType: RecomposableSignedInteger,
{
fn decrypt(&self, ciphertext: &SignedRadixCiphertext) -> ClearType {
self.decrypt_signed_radix(ciphertext)
}
}
impl<T> EncryptionKey<(T, usize), SignedRadixCiphertext> for crate::integer::ClientKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + SignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> SignedRadixCiphertext {
self.encrypt_signed_radix(value.0, value.1)
}
}
impl<T> EncryptionKey<(T, usize), CompressedSignedRadixCiphertext> for crate::integer::ClientKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + SignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> CompressedSignedRadixCiphertext {
self.encrypt_signed_radix_compressed(value.0, value.1)
}
}
impl<T> EncryptionKey<(T, usize), SignedRadixCiphertext> for crate::integer::CompressedPublicKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + SignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> SignedRadixCiphertext {
self.encrypt_signed_radix(value.0, value.1)
}
}
impl<T> EncryptionKey<(T, usize), SignedRadixCiphertext> for CompactPublicKey
where
T: crate::integer::block_decomposition::DecomposableInto<u64> + SignedNumeric,
{
fn encrypt(&self, value: (T, usize)) -> SignedRadixCiphertext {
self.encrypt_signed_radix(value.0, value.1)
}
}

View File

@@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize};
use crate::core_crypto::commons::generators::DeterministicSeeder;
use crate::core_crypto::prelude::ActivatedRandomGenerator;
use crate::integer::ciphertext::{CompactCiphertextList, RadixCiphertext};
use crate::integer::ciphertext::CompactCiphertextList;
use crate::integer::public_key::CompactPublicKey;
use crate::integer::{CompressedCompactPublicKey, U256};
use crate::integer::CompressedCompactPublicKey;
use crate::shortint::EncryptionKeyChoice;
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -119,6 +119,11 @@ impl IntegerServerKey {
let Some(cks) = &client_key.key else {
return Self::default();
};
assert_eq!(
cks.parameters().message_modulus().0,
1 << 2,
"This API only supports integers with 2 bits per block (MessageModulus(16))",
);
let base_integer_key = crate::integer::ServerKey::new(cks);
let wopbs_key = client_key
.wopbs_block_parameters
@@ -191,22 +196,6 @@ impl IntegerCompactPublicKey {
Some(Self { key: Some(key) })
}
pub(in crate::high_level_api) fn try_encrypt<T>(
&self,
value: T,
num_blocks: usize,
) -> Option<RadixCiphertext>
where
T: Into<U256>,
{
let Some(key) = self.key.as_ref() else {
return None;
};
let value = value.into();
let ct = key.encrypt_radix(value, num_blocks);
Some(ct)
}
pub(in crate::high_level_api::integers) fn try_encrypt_compact<T>(
&self,
values: &[T],

View File

@@ -1,7 +1,7 @@
expand_pub_use_fhe_type!(
pub use types{
FheUint8, FheUint10, FheUint12, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128,
FheUint256
FheUint256, FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256
};
);
@@ -15,5 +15,7 @@ mod keys;
mod parameters;
mod server_key;
#[cfg(test)]
mod tests;
mod tests_signed;
#[cfg(test)]
mod tests_unsigned;
mod types;

View File

@@ -14,5 +14,8 @@ pub trait EvaluationIntegerKey<ClientKey> {
/// Trait to mark parameters type for integers
pub trait IntegerParameter: ParameterType {
type InnerCiphertext: crate::integer::ciphertext::IntegerRadixCiphertext;
type InnerCompressedCiphertext;
fn num_blocks() -> usize;
}

View File

@@ -0,0 +1,650 @@
use crate::integer::I256;
use crate::prelude::*;
use crate::{
generate_keys, set_server_key, CompactFheInt32, CompactFheInt32List, CompactPublicKey,
CompressedFheInt16, Config, ConfigBuilder, FheInt16, FheInt256, FheInt32, FheInt64, FheInt8,
FheUint64, FheUint8,
};
use rand::prelude::*;
#[test]
fn test_signed_integer_compressed() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, _) = generate_keys(config);
let clear = -1234i16;
let compressed = CompressedFheInt16::try_encrypt(clear, &client_key).unwrap();
let decompressed = FheInt16::from(compressed);
let clear_decompressed: i16 = decompressed.decrypt(&client_key);
assert_eq!(clear_decompressed, clear);
}
#[test]
fn test_integer_compressed_small() {
let mut rng = thread_rng();
let config = ConfigBuilder::all_disabled()
.enable_default_integers_small()
.build();
let (client_key, _) = generate_keys(config);
let clear = rng.gen::<i16>();
let compressed = CompressedFheInt16::try_encrypt(clear, &client_key).unwrap();
let decompressed = FheInt16::from(compressed);
let clear_decompressed: i16 = decompressed.decrypt(&client_key);
assert_eq!(clear_decompressed, clear);
}
#[test]
fn test_int32_compare() {
let mut rng = thread_rng();
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 = rng.gen::<i32>();
let clear_b = rng.gen::<i32>();
let a = FheInt32::encrypt(clear_a, &client_key);
let b = FheInt32::encrypt(clear_b, &client_key);
// Test comparing encrypted with encrypted
{
let result = &a.eq(&b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a == clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.eq(&a);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a == clear_a);
assert_eq!(decrypted_result, clear_result);
let result = &a.ne(&b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a != clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.ne(&a);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a != clear_a);
assert_eq!(decrypted_result, clear_result);
let result = &a.le(&b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a <= clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.lt(&b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a < clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.ge(&b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a >= clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.gt(&b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a > clear_b);
assert_eq!(decrypted_result, clear_result);
}
// Test comparing encrypted with clear
{
let result = &a.eq(clear_b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a == clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.eq(clear_a);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a == clear_a);
assert_eq!(decrypted_result, clear_result);
let result = &a.ne(clear_b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a != clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.ne(clear_a);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a != clear_a);
assert_eq!(decrypted_result, clear_result);
let result = &a.le(clear_b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a <= clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.lt(clear_b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a < clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.ge(clear_b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a >= clear_b);
assert_eq!(decrypted_result, clear_result);
let result = &a.gt(clear_b);
let decrypted_result: i32 = result.decrypt(&client_key);
let clear_result = i32::from(clear_a > clear_b);
assert_eq!(decrypted_result, clear_result);
}
}
#[test]
fn test_int32_bitwise() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (cks, sks) = generate_keys(config);
use rand::prelude::*;
let mut rng = rand::thread_rng();
let clear_a = rng.gen::<i32>();
let clear_b = rng.gen::<i32>();
let a = FheInt32::try_encrypt(clear_a, &cks).unwrap();
let b = FheInt32::try_encrypt(clear_b, &cks).unwrap();
set_server_key(sks);
// encrypted bitwise
{
let c = &a | &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a | clear_b);
let c = &a & &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a & clear_b);
let c = &a ^ &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a ^ clear_b);
let mut c = a.clone();
c |= &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a | clear_b);
let mut c = a.clone();
c &= &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a & clear_b);
let mut c = a.clone();
c ^= &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a ^ clear_b);
}
// clear bitwise
{
let c = &a | b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a | clear_b);
let c = &a & clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a & clear_b);
let c = &a ^ clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a ^ clear_b);
let mut c = a.clone();
c |= clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a | clear_b);
let mut c = a.clone();
c &= clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a & clear_b);
let mut c = a;
c ^= clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a ^ clear_b);
}
}
fn fhe_int64_rotate(config: Config) {
let (cks, sks) = generate_keys(config);
use rand::prelude::*;
let mut rng = thread_rng();
let clear_a = rng.gen::<i64>();
let clear_b = rng.gen_range(0u32..64u32);
let a = FheInt64::try_encrypt(clear_a, &cks).unwrap();
let b = FheUint64::try_encrypt(clear_b, &cks).unwrap();
set_server_key(sks);
// encrypted rotate
{
let c = (&a).rotate_left(&b);
let decrypted: i64 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.rotate_left(clear_b));
let c = (&a).rotate_right(&b);
let decrypted: i64 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.rotate_right(clear_b));
let mut c = a.clone();
c.rotate_right_assign(&b);
let decrypted: i64 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.rotate_right(clear_b));
let mut c = a.clone();
c.rotate_left_assign(&b);
let decrypted: i64 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.rotate_left(clear_b));
}
// clear rotate
{
let c = (&a).rotate_left(clear_b);
let decrypted: i64 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.rotate_left(clear_b));
let c = (&a).rotate_right(clear_b);
let decrypted: i64 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.rotate_right(clear_b));
let mut c = a.clone();
c.rotate_right_assign(clear_b);
let decrypted: i64 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.rotate_right(clear_b));
let mut c = a;
c.rotate_left_assign(clear_b);
let decrypted: i64 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.rotate_left(clear_b));
}
}
#[test]
fn test_int64_rotate() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
fhe_int64_rotate(config);
}
#[test]
fn test_multi_bit_rotate() {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
crate::shortint::parameters::PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
None,
)
.build();
fhe_int64_rotate(config);
}
fn fhe_int32_div_rem(config: Config) {
let (cks, sks) = generate_keys(config);
use rand::prelude::*;
let mut rng = rand::thread_rng();
let clear_a = rng.gen::<i32>();
let clear_b = loop {
let value = rng.gen::<i32>();
if value != 0 {
break value;
}
};
let a = FheInt32::try_encrypt(clear_a, &cks).unwrap();
let b = FheInt32::try_encrypt(clear_b, &cks).unwrap();
set_server_key(sks);
// encrypted div/rem
{
let c = &a / &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a / clear_b);
let c = &a % &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a % clear_b);
let (q, r) = (&a).div_rem(&b);
let decrypted_q: i32 = q.decrypt(&cks);
let decrypted_r: i32 = r.decrypt(&cks);
assert_eq!(decrypted_q, clear_a / clear_b);
assert_eq!(decrypted_r, clear_a % clear_b);
let mut c = a.clone();
c /= &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a / clear_b);
let mut c = a.clone();
c %= &b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a % clear_b);
}
// clear div/rem
{
let c = &a / clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a / clear_b);
let c = &a % clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a % clear_b);
let (q, r) = (&a).div_rem(clear_b);
let decrypted_q: i32 = q.decrypt(&cks);
let decrypted_r: i32 = r.decrypt(&cks);
assert_eq!(decrypted_q, clear_a / clear_b);
assert_eq!(decrypted_r, clear_a % clear_b);
let mut c = a.clone();
c /= clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a / clear_b);
let mut c = a;
c %= clear_b;
let decrypted: i32 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a % clear_b);
}
}
#[test]
fn test_int32_div_rem() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
fhe_int32_div_rem(config);
}
#[test]
fn test_multi_div_rem() {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
crate::shortint::parameters::PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
None,
)
.build();
fhe_int32_div_rem(config);
}
#[test]
fn test_integer_casting() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let mut rng = rand::thread_rng();
// Ensure casting works for both negative and positive values
for clear in [rng.gen_range(i16::MIN..0), rng.gen_range(0..=i16::MAX)] {
// Downcasting then Upcasting
{
let a = FheInt16::encrypt(clear, &client_key);
// Downcasting
let a: FheInt8 = a.cast_into();
let da: i8 = a.decrypt(&client_key);
assert_eq!(da, clear as i8);
// Upcasting
let a: FheInt32 = a.cast_into();
let da: i32 = a.decrypt(&client_key);
assert_eq!(da, (clear as i8) as i32);
}
// Upcasting then Downcasting
{
let a = FheInt16::encrypt(clear, &client_key);
// Upcasting
let a = FheInt32::cast_from(a);
let da: i32 = a.decrypt(&client_key);
assert_eq!(da, clear as i32);
// Downcasting
let a = FheInt8::cast_from(a);
let da: i8 = a.decrypt(&client_key);
assert_eq!(da, (clear as i32) as i8);
}
// Casting to self, it not useful but is supported
{
let a = FheInt16::encrypt(clear, &client_key);
let a = FheInt16::cast_from(a);
let da: i16 = a.decrypt(&client_key);
assert_eq!(da, clear);
}
// Casting to a smaller unsigned type, then casting to bigger signed type
{
let a = FheInt16::encrypt(clear, &client_key);
// Downcasting to un
let a: FheUint8 = a.cast_into();
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, clear as u8);
// Upcasting
let a: FheInt32 = a.cast_into();
let da: i32 = a.decrypt(&client_key);
assert_eq!(da, (clear as u8) as i32);
}
// Casting to a bigger unsigned type, then casting to smaller signed type
{
let a = FheInt16::encrypt(clear, &client_key);
// Downcasting to un
let a: FheUint64 = a.cast_into();
let da: u64 = a.decrypt(&client_key);
assert_eq!(da, clear as u64);
// Upcasting
let a: FheInt32 = a.cast_into();
let da: i32 = a.decrypt(&client_key);
assert_eq!(da, (clear as u64) as i32);
}
}
}
#[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 mut rng = rand::thread_rng();
let clear_a = rng.gen::<i8>();
let clear_b = rng.gen::<i8>();
let a = FheInt8::encrypt(clear_a, &client_key);
let b = FheInt8::encrypt(clear_b, &client_key);
let result = a.le(&b).if_then_else(&a, &b);
let decrypted_result: i8 = 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: i8 = result.decrypt(&client_key);
assert_eq!(
decrypted_result,
if clear_a <= clear_b { clear_b } else { clear_a }
);
}
#[test]
fn test_abs() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let mut rng = rand::thread_rng();
for clear in [rng.gen_range(i64::MIN..0), rng.gen_range(0..=i64::MAX)] {
let a = FheInt64::encrypt(clear, &client_key);
let abs_a = a.abs();
let decrypted_result: i64 = abs_a.decrypt(&client_key);
assert_eq!(decrypted_result, clear.abs());
}
}
#[test]
fn test_trivial_fhe_int8() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, sks) = generate_keys(config);
set_server_key(sks);
let a = FheInt8::try_encrypt_trivial(-1i8).unwrap();
let clear: i8 = a.decrypt(&client_key);
assert_eq!(clear, -1i8);
}
#[test]
fn test_trivial_fhe_int256_small() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers_small()
.build();
let (client_key, sks) = generate_keys(config);
set_server_key(sks);
let clear_a = I256::MIN;
let a = FheInt256::try_encrypt_trivial(clear_a).unwrap();
let clear: I256 = a.decrypt(&client_key);
assert_eq!(clear, clear_a);
}
#[test]
fn test_compact_public_key_big() {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
crate::shortint::parameters::parameters_compact_pk::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS,
None,
)
.build();
let (client_key, _) = generate_keys(config);
let public_key = CompactPublicKey::new(&client_key);
let a = FheInt8::try_encrypt(-1i8, &public_key).unwrap();
let clear: i8 = a.decrypt(&client_key);
assert_eq!(clear, -1i8);
}
#[test]
fn test_compact_public_key_small() {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
crate::shortint::parameters::parameters_compact_pk
::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS,
None,
)
.build();
let (client_key, _) = generate_keys(config);
let public_key = CompactPublicKey::new(&client_key);
let a = FheInt8::try_encrypt(-123i8, &public_key).unwrap();
let clear: i8 = a.decrypt(&client_key);
assert_eq!(clear, -123i8);
}
#[test]
fn test_compact_public_key_list_big() {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
crate::shortint::parameters::parameters_compact_pk::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS,
None,
)
.build();
test_compact_public_key_list(config);
}
#[test]
fn test_compact_public_key_list_small() {
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
crate::shortint::parameters::parameters_compact_pk
::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS,
None,
)
.build();
test_compact_public_key_list(config);
}
fn test_compact_public_key_list(config: Config) {
let (client_key, server_key) = generate_keys(config);
let public_key = CompactPublicKey::new(&client_key);
let mut rng = rand::thread_rng();
let clear_xs = (0..50).map(|_| rng.gen::<i32>()).collect::<Vec<_>>();
let clear_ys = (0..50).map(|_| rng.gen::<i32>()).collect::<Vec<_>>();
let compacted_xs = CompactFheInt32List::encrypt(&clear_xs, &public_key);
let compacted_ys = CompactFheInt32List::encrypt(&clear_ys, &public_key);
let exs = compacted_xs.expand();
let eys = compacted_ys.expand();
set_server_key(server_key);
let encrypted_results = exs.iter().zip(eys).map(|(x, y)| x + y).collect::<Vec<_>>();
let clear_results = clear_xs
.iter()
.zip(clear_ys)
.map(|(x, y)| x + y)
.collect::<Vec<_>>();
for (encrypted, clear) in encrypted_results.iter().zip(clear_results) {
let decrypted: i32 = encrypted.decrypt(&client_key);
assert_eq!(clear, decrypted);
}
let compact_single = CompactFheInt32::encrypt(clear_xs[0], &public_key);
let a = compact_single.expand();
let decrypted: i32 = a.decrypt(&client_key);
assert_eq!(clear_xs[0], decrypted);
}

View File

@@ -5,8 +5,8 @@ use crate::high_level_api::{generate_keys, set_server_key, ConfigBuilder, FheUin
use crate::integer::U256;
use crate::{
CompactFheUint32, CompactFheUint32List, CompactPublicKey, CompressedFheUint16,
CompressedFheUint256, CompressedPublicKey, Config, FheUint128, FheUint16, FheUint256,
FheUint32, FheUint64,
CompressedFheUint256, CompressedPublicKey, Config, FheInt32, FheInt8, FheUint128, FheUint16,
FheUint256, FheUint32, FheUint64,
};
#[test]
@@ -282,7 +282,7 @@ fn test_uint32_bitwise() {
let mut rng = rand::thread_rng();
let clear_a = rng.gen::<u32>();
let clear_b = rng.gen_range(0u32..32u32);
let clear_b = rng.gen::<u32>();
let a = FheUint32::try_encrypt(clear_a, &cks).unwrap();
let b = FheUint32::try_encrypt(clear_b, &cks).unwrap();
@@ -745,9 +745,11 @@ fn test_integer_casting() {
set_server_key(server_key);
let mut rng = rand::thread_rng();
let clear = rng.gen::<u16>();
// Downcasting then Upcasting
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Downcasting
@@ -763,7 +765,6 @@ fn test_integer_casting() {
// Upcasting then Downcasting
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Upcasting
@@ -779,12 +780,43 @@ fn test_integer_casting() {
// Casting to self, it not useful but is supported
{
let clear = 43_129u16;
let a = FheUint16::encrypt(clear, &client_key);
let a = FheUint16::cast_from(a);
let da: u16 = a.decrypt(&client_key);
assert_eq!(da, clear);
}
// Downcasting to smaller signed integer then Upcasting back to unsigned
{
let clear = rng.gen_range((i16::MAX) as u16 + 1..u16::MAX);
let a = FheUint16::encrypt(clear, &client_key);
// Downcasting
let a: FheInt8 = a.cast_into();
let da: i8 = a.decrypt(&client_key);
assert_eq!(da, clear as i8);
// Upcasting
let a: FheUint32 = a.cast_into();
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, (clear as i8) as u32);
}
// Upcasting to bigger signed integer then downcasting back to unsigned
{
let clear = rng.gen_range((i16::MAX) as u16 + 1..u16::MAX);
let a = FheUint16::encrypt(clear, &client_key);
// Upcasting
let a: FheInt32 = a.cast_into();
let da: i32 = a.decrypt(&client_key);
assert_eq!(da, clear as i32);
// Downcasting
let a: FheUint16 = a.cast_into();
let da: u16 = a.decrypt(&client_key);
assert_eq!(da, (clear as i32) as u16);
}
}
#[test]

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,16 @@
use crate::errors::{UninitializedClientKey, UnwrapResultExt};
use crate::high_level_api::integers::parameters::IntegerParameter;
use crate::high_level_api::integers::types::base::GenericInteger;
use crate::high_level_api::internal_traits::TypeIdentifier;
use crate::high_level_api::internal_traits::{EncryptionKey, TypeIdentifier};
use crate::high_level_api::traits::FheTryEncrypt;
use crate::high_level_api::ClientKey;
use crate::integer::ciphertext::CompressedRadixCiphertext;
use crate::integer::U256;
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct CompressedGenericInteger<P>
where
P: IntegerParameter,
{
pub(in crate::high_level_api::integers) ciphertext: CompressedRadixCiphertext,
pub(in crate::high_level_api::integers) ciphertext: P::InnerCompressedCiphertext,
pub(in crate::high_level_api::integers) id: P::Id,
}
@@ -21,7 +19,7 @@ where
P: IntegerParameter,
{
pub(in crate::high_level_api::integers) fn new(
inner: CompressedRadixCiphertext,
inner: P::InnerCompressedCiphertext,
id: P::Id,
) -> Self {
Self {
@@ -29,7 +27,13 @@ where
id,
}
}
}
impl<P> CompressedGenericInteger<P>
where
P: IntegerParameter,
P::InnerCompressedCiphertext: Into<P::InnerCiphertext>,
{
pub fn decompress(self) -> GenericInteger<P> {
let inner = self.ciphertext.into();
GenericInteger::new(inner, self.id)
@@ -39,6 +43,7 @@ where
impl<P> From<CompressedGenericInteger<P>> for GenericInteger<P>
where
P: IntegerParameter,
P::InnerCompressedCiphertext: Into<P::InnerCiphertext>,
{
fn from(value: CompressedGenericInteger<P>) -> Self {
let inner = value.ciphertext.into();
@@ -48,14 +53,13 @@ where
impl<P, T> FheTryEncrypt<T, ClientKey> for CompressedGenericInteger<P>
where
T: Into<U256>,
P: IntegerParameter,
P::Id: Default + TypeIdentifier,
crate::integer::ClientKey: EncryptionKey<(T, usize), P::InnerCompressedCiphertext>,
{
type Error = crate::high_level_api::errors::Error;
fn try_encrypt(value: T, key: &ClientKey) -> Result<Self, Self::Error> {
let value = value.into();
let id = P::Id::default();
let integer_client_key = key
.integer_key
@@ -63,7 +67,10 @@ where
.as_ref()
.ok_or(UninitializedClientKey(id.type_variant()))
.unwrap_display();
let inner = integer_client_key.encrypt_radix_compressed(value, P::num_blocks());
let inner = <crate::integer::ClientKey as EncryptionKey<_, _>>::encrypt(
integer_client_key,
(value, P::num_blocks()),
);
Ok(Self::new(inner, id))
}
}

View File

@@ -3,7 +3,7 @@ pub use base::GenericInteger;
expand_pub_use_fhe_type!(
pub use static_{
FheUint8, FheUint10, FheUint12, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128,
FheUint256
FheUint256, FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256
};
);

View File

@@ -14,7 +14,7 @@ use paste::paste;
macro_rules! define_static_integer_parameters {
(
Radix {
UnsignedRadix {
num_bits: $num_bits:literal,
num_block: $num_block:literal,
}
@@ -33,6 +33,9 @@ macro_rules! define_static_integer_parameters {
}
impl IntegerParameter for [<FheUint $num_bits Parameters>] {
type InnerCiphertext = crate::integer::RadixCiphertext;
type InnerCompressedCiphertext = crate::integer::ciphertext::CompressedRadixCiphertext;
fn num_blocks() -> usize {
$num_block
}
@@ -45,6 +48,41 @@ macro_rules! define_static_integer_parameters {
}
}
};
(
SignedRadix {
num_bits: $num_bits:literal,
num_block: $num_block:literal,
}
) => {
paste! {
#[doc = concat!("Id for the [FheInt", stringify!($num_bits), "] data type.")]
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct [<FheInt $num_bits Id>];
#[doc = concat!("Parameters for the [FheUint", stringify!($num_bits), "] data type.")]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct [<FheInt $num_bits Parameters>];
impl ParameterType for [<FheInt $num_bits Parameters>] {
type Id = [<FheInt $num_bits Id>];
}
impl IntegerParameter for [<FheInt $num_bits Parameters>] {
type InnerCiphertext = crate::integer::SignedRadixCiphertext;
type InnerCompressedCiphertext = crate::integer::ciphertext::CompressedSignedRadixCiphertext;
fn num_blocks() -> usize {
$num_block
}
}
impl TypeIdentifier for [<FheInt $num_bits Id>] {
fn type_variant(&self) -> $crate::high_level_api::errors::Type {
$crate::high_level_api::errors::Type::[<FheInt $num_bits>]
}
}
}
};
}
macro_rules! static_int_type {
@@ -58,7 +96,6 @@ macro_rules! static_int_type {
$(#[$outer:meta])*
$name:ident {
num_bits: $num_bits:literal,
keychain_member: $($member:ident).*,
}
) => {
paste! {
@@ -76,7 +113,7 @@ macro_rules! static_int_type {
#[cfg_attr(all(doc, not(doctest)), cfg(feature = "integer"))]
pub type [<Compact $name List>] = GenericCompactIntegerList<[<$name Parameters>]>;
impl $crate::high_level_api::keys::RefKeyFromKeyChain for [<FheUint $num_bits Id>] {
impl $crate::high_level_api::keys::RefKeyFromKeyChain for [<$name Id>] {
type Key = crate::integer::ClientKey;
fn ref_key(self, keys: &crate::high_level_api::ClientKey)
@@ -89,7 +126,7 @@ macro_rules! static_int_type {
}
}
impl $crate::high_level_api::global_state::WithGlobalKey for [<FheUint $num_bits Id>] {
impl $crate::high_level_api::global_state::WithGlobalKey for [<$name Id>] {
type Key = crate::high_level_api::integers::IntegerServerKey;
fn with_global<R, F>(self, func: F) -> Result<R, $crate::high_level_api::errors::UninitializedServerKey>
@@ -107,16 +144,15 @@ macro_rules! static_int_type {
// the `Radix` representation
(
$(#[$outer:meta])*
{
Unsigned {
num_bits: $num_bits:literal,
keychain_member: $($member:ident).*,
parameters: Radix {
num_block: $num_block:literal,
},
}
) => {
define_static_integer_parameters!(
Radix {
UnsignedRadix {
num_bits: $num_bits,
num_block: $num_block,
}
@@ -128,7 +164,35 @@ macro_rules! static_int_type {
$(#[$outer])*
[<FheUint $num_bits>] {
num_bits: $num_bits,
keychain_member: $($member).*,
}
);
}
};
// Defines a static integer type that uses
// the `Radix` representation
(
$(#[$outer:meta])*
Signed {
num_bits: $num_bits:literal,
parameters: Radix {
num_block: $num_block:literal,
},
}
) => {
define_static_integer_parameters!(
SignedRadix {
num_bits: $num_bits,
num_block: $num_block,
}
);
::paste::paste!{
static_int_type!(
@impl_types_and_key_traits,
$(#[$outer])*
[<FheInt $num_bits>] {
num_bits: $num_bits,
}
);
}
@@ -171,9 +235,8 @@ where
}
static_int_type! {
{
Unsigned {
num_bits: 8,
keychain_member: integer_key.uint8_key,
parameters: Radix {
num_block: 4,
},
@@ -181,9 +244,8 @@ static_int_type! {
}
static_int_type! {
{
Unsigned {
num_bits: 10,
keychain_member: integer_key.uint10_key,
parameters: Radix {
num_block: 5,
},
@@ -191,9 +253,8 @@ static_int_type! {
}
static_int_type! {
{
Unsigned {
num_bits: 12,
keychain_member: integer_key.uint12_key,
parameters: Radix {
num_block: 6,
},
@@ -201,9 +262,8 @@ static_int_type! {
}
static_int_type! {
{
Unsigned {
num_bits: 14,
keychain_member: integer_key.uint14_key,
parameters: Radix {
num_block: 7,
},
@@ -211,9 +271,8 @@ static_int_type! {
}
static_int_type! {
{
Unsigned {
num_bits: 16,
keychain_member: integer_key.uint16_key,
parameters: Radix {
num_block: 8,
},
@@ -221,9 +280,8 @@ static_int_type! {
}
static_int_type! {
{
Unsigned {
num_bits: 32,
keychain_member: integer_key.uint32_key,
parameters: Radix {
num_block: 16,
},
@@ -231,9 +289,8 @@ static_int_type! {
}
static_int_type! {
{
Unsigned {
num_bits: 64,
keychain_member: integer_key.uint64_key,
parameters: Radix {
num_block: 32,
},
@@ -241,9 +298,8 @@ static_int_type! {
}
static_int_type! {
{
Unsigned {
num_bits: 128,
keychain_member: integer_key.uint128_key,
parameters: Radix {
num_block: 64,
},
@@ -251,9 +307,62 @@ static_int_type! {
}
static_int_type! {
{
Unsigned {
num_bits: 256,
parameters: Radix {
num_block: 128,
},
}
}
static_int_type! {
Signed {
num_bits: 8,
parameters: Radix {
num_block: 4,
},
}
}
static_int_type! {
Signed {
num_bits: 16,
parameters: Radix {
num_block: 8,
},
}
}
static_int_type! {
Signed {
num_bits: 32,
parameters: Radix {
num_block: 16,
},
}
}
static_int_type! {
Signed {
num_bits: 64,
parameters: Radix {
num_block: 32,
},
}
}
static_int_type! {
Signed {
num_bits: 128,
parameters: Radix {
num_block: 64,
},
}
}
static_int_type! {
Signed {
num_bits: 256,
keychain_member: integer_key.uint256_key,
parameters: Radix {
num_block: 128,
},

View File

@@ -1,5 +1,9 @@
#![allow(unused_doc_comments)]
// internal helper macro to make it easier to `pub use`
// all necessary stuff tied to a FheUint/FheInt from the given `module_path`.
//
// Note: This is for Integers not Shortints
#[allow(unused)]
macro_rules! expand_pub_use_fhe_type(
(
@@ -38,7 +42,7 @@ pub use crate::high_level_api::booleans::{CompressedFheBool, FheBool, FheBoolPar
expand_pub_use_fhe_type!(
pub use crate::high_level_api::integers{
FheUint8, FheUint10, FheUint12, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128,
FheUint256
FheUint256, FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256
};
);
#[cfg(feature = "shortint")]

View File

@@ -327,7 +327,11 @@ pub(crate) fn shr_assign(lhs: &mut [u64], shift: u32, shift_type: ShiftType) {
let value_mask = u64::MAX >> shift_in_words;
let carry_mask = ((1u64 << shift_in_words) - 1u64).rotate_right(shift_in_words);
let mut carry = if sign_bit == 1 { u64::MAX } else { 0 };
let mut carry = if sign_bit == 1 {
u64::MAX & carry_mask
} else {
0
};
for word in &mut head.iter_mut().rev() {
let rotated = word.rotate_right(shift_in_words);
let value = (rotated & value_mask) | carry;

View File

@@ -98,6 +98,21 @@ mod tests {
assert_eq!(a << 1u32, I256::from(input << 1));
}
#[test]
fn test_shr() {
let input = i128::MIN;
let a = I256::from(input);
assert_eq!(a >> 1u32, I256::from(input >> 1));
let a = I256::MIN;
// We expect (MSB) 110............0 (LSB)
assert_eq!(
a >> 1u32,
// 3 is '11'
I256::from([0, 0, 0, 3 << 62])
);
}
#[test]
fn test_div_rem() {
let i64_max = I256::from(i64::MAX);

View File

@@ -404,6 +404,12 @@ impl<const N: usize> From<(u64, u64, u64, u64)> for StaticSignedBigInt<N> {
}
}
impl<const N: usize> CastFrom<Self> for StaticSignedBigInt<N> {
fn cast_from(input: Self) -> Self {
input
}
}
impl<const N: usize> CastFrom<u8> for StaticSignedBigInt<N> {
fn cast_from(input: u8) -> Self {
let mut converted = [u64::ZERO; N];

View File

@@ -2,7 +2,8 @@ use core::ops::{AddAssign, BitAnd, ShlAssign, ShrAssign};
use std::ops::{BitOrAssign, Shl, Sub};
use crate::core_crypto::prelude::{CastFrom, CastInto, Numeric};
use crate::integer::{I256, I512, U256, U512};
use crate::integer::bigint::static_signed::StaticSignedBigInt;
use crate::integer::bigint::static_unsigned::StaticUnsignedBigInt;
// These work for signed number as rust uses 2-Complements
// And Arithmetic shift for signed number (logical for unsigned)
@@ -48,9 +49,21 @@ macro_rules! impl_recomposable_decomposable {
};
}
impl_recomposable_decomposable!(
u8, u16, u32, u64, u128, U256, U512, i8, i16, i32, i64, i128, I256, I512
);
impl_recomposable_decomposable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,);
impl<const N: usize> Decomposable for StaticSignedBigInt<N> {}
impl<const N: usize> Recomposable for StaticSignedBigInt<N> {}
impl<const N: usize> RecomposableFrom<u64> for StaticSignedBigInt<N> {}
impl<const N: usize> RecomposableFrom<u8> for StaticSignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u64> for StaticSignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u8> for StaticSignedBigInt<N> {}
impl<const N: usize> Decomposable for StaticUnsignedBigInt<N> {}
impl<const N: usize> Recomposable for StaticUnsignedBigInt<N> {}
impl<const N: usize> RecomposableFrom<u64> for StaticUnsignedBigInt<N> {}
impl<const N: usize> RecomposableFrom<u8> for StaticUnsignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u64> for StaticUnsignedBigInt<N> {}
impl<const N: usize> DecomposableInto<u8> for StaticUnsignedBigInt<N> {}
#[derive(Copy, Clone)]
pub struct BlockDecomposer<T> {

View File

@@ -44,17 +44,17 @@ pub struct CompactCiphertextList {
}
impl CompactCiphertextList {
pub fn expand_one(&self) -> RadixCiphertext {
pub fn expand_one<T: IntegerRadixCiphertext>(&self) -> T {
let mut blocks = self.ct_list.expand();
blocks.truncate(self.num_blocks);
RadixCiphertext::from(blocks)
T::from(blocks)
}
pub fn ciphertext_count(&self) -> usize {
self.ct_list.ct_list.lwe_ciphertext_count().0 / self.num_blocks
}
pub fn expand(&self) -> Vec<RadixCiphertext> {
pub fn expand<T: IntegerRadixCiphertext>(&self) -> Vec<T> {
let mut all_block_iter = self.ct_list.expand().into_iter();
let num_ct = self.ciphertext_count();
let mut ciphertexts = Vec::with_capacity(num_ct);
@@ -67,7 +67,7 @@ impl CompactCiphertextList {
if ct_blocks.len() < self.num_blocks {
break;
}
let ct = RadixCiphertext::from(ct_blocks);
let ct = T::from(ct_blocks);
ciphertexts.push(ct);
}

View File

@@ -19,6 +19,7 @@ use crate::shortint::{
use serde::{Deserialize, Serialize};
pub use utils::radix_decomposition;
use crate::integer::bigint::static_signed::StaticSignedBigInt;
pub use crt::CrtClientKey;
pub use radix::RadixClientKey;
@@ -46,6 +47,8 @@ impl RecomposableSignedInteger for i32 {}
impl RecomposableSignedInteger for i64 {}
impl RecomposableSignedInteger for i128 {}
impl<const N: usize> RecomposableSignedInteger for StaticSignedBigInt<N> {}
/// A structure containing the client key, which must be kept secret.
///
/// This key can be used to encrypt both in Radix and CRT

View File

@@ -1,9 +1,10 @@
use crate::core_crypto::prelude::{SignedNumeric, UnsignedNumeric};
use serde::{Deserialize, Serialize};
use crate::integer::block_decomposition::DecomposableInto;
use crate::integer::ciphertext::{CompactCiphertextList, RadixCiphertext};
use crate::integer::encryption::{create_clear_radix_block_iterator, encrypt_words_radix_impl};
use crate::integer::ClientKey;
use crate::integer::{ClientKey, SignedRadixCiphertext};
use crate::shortint::{
CompactPublicKey as ShortintCompactPublicKey,
CompressedCompactPublicKey as ShortintCompressedCompactPublicKey,
@@ -25,11 +26,22 @@ impl CompactPublicKey {
Some(Self { key })
}
pub fn encrypt_radix<T: DecomposableInto<u64>>(
&self,
message: T,
num_blocks: usize,
) -> RadixCiphertext {
pub fn encrypt_radix<T>(&self, message: T, num_blocks: usize) -> RadixCiphertext
where
T: DecomposableInto<u64> + UnsignedNumeric,
{
encrypt_words_radix_impl(
&self.key,
message,
num_blocks,
ShortintCompactPublicKey::encrypt,
)
}
pub fn encrypt_signed_radix<T>(&self, message: T, num_blocks: usize) -> SignedRadixCiphertext
where
T: DecomposableInto<u64> + SignedNumeric,
{
encrypt_words_radix_impl(
&self.key,
message,

View File

@@ -2,6 +2,7 @@ use crate::integer::block_decomposition::DecomposableInto;
use crate::integer::ciphertext::{CrtCiphertext, RadixCiphertext};
use crate::integer::client_key::ClientKey;
use crate::integer::encryption::{encrypt_crt, encrypt_words_radix_impl};
use crate::integer::SignedRadixCiphertext;
use crate::shortint::parameters::MessageModulus;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -64,6 +65,18 @@ impl CompressedPublicKey {
)
}
pub fn encrypt_signed_radix<T: DecomposableInto<u64>>(
&self,
message: T,
num_blocks: usize,
) -> SignedRadixCiphertext {
self.encrypt_words_radix(
message,
num_blocks,
crate::shortint::CompressedPublicKey::encrypt,
)
}
pub fn encrypt_radix_without_padding(
&self,
message: u64,

View File

@@ -14,6 +14,7 @@ use super::ServerKey;
use crate::integer::block_decomposition::DecomposableInto;
use crate::integer::ciphertext::{IntegerRadixCiphertext, RadixCiphertext};
use crate::integer::encryption::encrypt_words_radix_impl;
use crate::integer::SignedRadixCiphertext;
#[cfg(test)]
mod tests;
@@ -385,6 +386,91 @@ where {
ct_res
}
/// Extends the most significant blocks using the sign bit.
/// Used to cast [SignedRadixCiphertext]
///
/// # Example
///
///```rust
/// use tfhe::integer::{gen_keys_radix, IntegerCiphertext};
/// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
///
/// let num_blocks = 4;
///
/// // Generate the client key and the server key:
/// let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_blocks);
///
/// let msg = -1i8;
///
/// let mut ct1 = cks.encrypt_signed(msg);
/// assert_eq!(ct1.blocks().len(), 4);
///
/// sks.extend_radix_with_sign_msb_assign(&mut ct1, 4);
/// assert_eq!(ct1.blocks().len(), 8);
///
/// // Decrypt
/// let res: i16 = cks.decrypt_signed(&ct1);
/// assert_eq!(-1, res);
/// ```
pub fn extend_radix_with_sign_msb_assign(
&self,
ct: &mut SignedRadixCiphertext,
num_blocks: usize,
) {
let message_modulus = self.key.message_modulus.0 as u64;
let num_bits_in_block = message_modulus.ilog2();
let padding_block_creator_lut = self.key.generate_lookup_table(|x| {
let x = x % message_modulus;
let x_sign_bit = x >> (num_bits_in_block - 1) & 1;
// padding is a message full of 1 if sign bit is one
// else padding is a zero message
(message_modulus - 1) * x_sign_bit
});
let last_block = ct.blocks.last().expect("Empty input");
let padding_block = self
.key
.apply_lookup_table(last_block, &padding_block_creator_lut);
let new_lew = num_blocks + ct.blocks.len();
ct.blocks.resize(new_lew, padding_block);
}
/// Extends the most significant blocks using the sign bit.
/// Used to cast [SignedRadixCiphertext]
///
/// # Example
///
///```rust
/// use tfhe::integer::{gen_keys_radix, IntegerCiphertext};
/// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
///
/// let num_blocks = 4;
///
/// // Generate the client key and the server key:
/// let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_blocks);
///
/// let msg = -2i8;
///
/// let ct1 = cks.encrypt_signed(msg);
/// assert_eq!(ct1.blocks().len(), 4);
///
/// let ct_res = sks.extend_radix_with_sign_msb(&ct1, 4);
/// assert_eq!(ct1.blocks().len(), 8);
///
/// // Decrypt
/// let res: i16 = cks.decrypt_signed(&ct_res);
/// assert_eq!(-2, res);
/// ```
pub fn extend_radix_with_sign_msb(
&self,
ct: &SignedRadixCiphertext,
num_blocks: usize,
) -> SignedRadixCiphertext {
let mut result = ct.clone();
self.extend_radix_with_sign_msb_assign(&mut result, num_blocks);
result
}
/// Propagate the carry of the 'index' block to the next one.
///
/// # Example

View File

@@ -16,7 +16,7 @@ use crate::core_crypto::prelude::{CastFrom, CastInto, Numeric, SignedNumeric, Un
use crate::integer::block_decomposition::DecomposableInto;
use crate::integer::ciphertext::{RadixCiphertext, SignedRadixCiphertext};
use crate::integer::server_key::radix::scalar_mul::ScalarMultiplier;
use crate::integer::{ServerKey, I256, I512, U256, U512};
use crate::integer::{IntegerCiphertext, ServerKey, I256, I512, U256, U512};
#[inline(always)]
fn is_even<T>(d: T) -> bool
@@ -298,26 +298,11 @@ impl ServerKey {
T: ScalarMultiplier + DecomposableInto<u8>,
{
let num_blocks = lhs.blocks.len();
let mut result = lhs.clone();
let message_modulus = self.key.message_modulus.0 as u64;
let num_bits_in_block = message_modulus.ilog2();
let padding_block_creator_lut = self.key.generate_lookup_table(|x| {
let x = x % message_modulus;
let x_sign_bit = x >> (num_bits_in_block - 1) & 1;
// padding is a message full of 1 if sign bit is one
// else padding is a zero message
(message_modulus - 1) * x_sign_bit
});
let padding_block = self
.key
.apply_lookup_table(&lhs.blocks[num_blocks - 1], &padding_block_creator_lut);
result.blocks.resize(2 * num_blocks, padding_block);
let mut result = self.extend_radix_with_sign_msb(lhs, num_blocks);
self.scalar_mul_assign_parallelized(&mut result, rhs);
result.blocks.rotate_left(num_blocks);
result.blocks.truncate(num_blocks);
result
let mut result = RadixCiphertext::from_blocks(result.blocks);
self.trim_radix_blocks_lsb_assign(&mut result, num_blocks);
SignedRadixCiphertext::from_blocks(result.blocks)
}
pub fn unchecked_scalar_div_parallelized<T>(
@@ -565,6 +550,84 @@ impl ServerKey {
(quotient, remainder)
}
pub fn signed_scalar_div_rem_parallelized<T>(
&self,
numerator: &SignedRadixCiphertext,
divisor: T,
) -> (SignedRadixCiphertext, SignedRadixCiphertext)
where
T: SignedReciprocable + ScalarMultiplier,
<<T as SignedReciprocable>::Unsigned as Reciprocable>::DoublePrecision: Send,
{
if !numerator.block_carries_are_empty() {
let mut tmp = numerator.clone();
self.full_propagate_parallelized(&mut tmp);
self.unchecked_signed_scalar_div_rem_parallelized(&tmp, divisor)
} else {
self.unchecked_signed_scalar_div_rem_parallelized(numerator, divisor)
}
}
pub fn signed_scalar_div_assign_parallelized<T>(
&self,
numerator: &mut SignedRadixCiphertext,
divisor: T,
) where
T: SignedReciprocable,
<<T as SignedReciprocable>::Unsigned as Reciprocable>::DoublePrecision: Send,
{
if !numerator.block_carries_are_empty() {
self.full_propagate_parallelized(numerator)
}
*numerator = self.unchecked_signed_scalar_div_parallelized(numerator, divisor);
}
pub fn signed_scalar_div_parallelized<T>(
&self,
numerator: &SignedRadixCiphertext,
divisor: T,
) -> SignedRadixCiphertext
where
T: SignedReciprocable,
<<T as SignedReciprocable>::Unsigned as Reciprocable>::DoublePrecision: Send,
{
let mut result = numerator.clone();
self.signed_scalar_div_assign_parallelized(&mut result, divisor);
result
}
pub fn signed_scalar_rem_parallelized<T>(
&self,
numerator: &SignedRadixCiphertext,
divisor: T,
) -> SignedRadixCiphertext
where
T: SignedReciprocable + ScalarMultiplier,
<<T as SignedReciprocable>::Unsigned as Reciprocable>::DoublePrecision: Send,
{
let mut result = numerator.clone();
self.signed_scalar_rem_assign_parallelized(&mut result, divisor);
result
}
pub fn signed_scalar_rem_assign_parallelized<T>(
&self,
numerator: &mut SignedRadixCiphertext,
divisor: T,
) where
T: SignedReciprocable + ScalarMultiplier,
<<T as SignedReciprocable>::Unsigned as Reciprocable>::DoublePrecision: Send,
{
if !numerator.block_carries_are_empty() {
self.full_propagate_parallelized(numerator)
}
let (_quotient, remainder) =
self.unchecked_signed_scalar_div_rem_parallelized(numerator, divisor);
*numerator = remainder;
}
/// # Note
/// This division rounds the quotient towards minus infinity
pub fn unchecked_signed_scalar_div_floor_parallelized<T>(