Compare commits

...

90 Commits

Author SHA1 Message Date
Beka Barbakadze
58169f9365 refactor(c_api): add lwe secret binary key generation , bootstrapping key generation, lwe ciphertext encryption and decryption, all functions without seed. 2023-10-19 16:27:29 +04:00
Arthur Meyre
e14be3b41a refactor(c_api): do not require a seed in the multi bit PBS keygen
BREAKING CHANGE:
function signature has changed, this is a breaking change
2023-10-19 13:15:54 +02:00
Arthur Meyre
f1c21888a7 chore(doc): encourage users to use dedicated keys to Radix or CRT 2023-10-19 09:52:22 +02:00
tmontaigu
2624beb7fa fix(integer): fix unsigned_overflowing_sub on trivials
unsigned_overflowing_sub does an independant subtraction
on each blocks with a correcting term being added to avoid
trashing the padding bit (lhs - rhs + correction).

The correction depended on rhs's degree.
e.g. if rhs's degree was in range 1..(msg_mod-1) -> correction =
     msg_mod

However if rhs's degree was zero (so rhs is a trivial 0), the correction
was also 0, however the borrow propagation rely on that correction to
always be added.
2023-10-18 19:26:01 +02:00
tmontaigu
e44c38a102 chore(ci): tell nvm to use node version 20 in wasm parallel tests 2023-10-18 19:04:27 +02:00
Arthur Meyre
4535230874 refactor(core): rename pbs_modulus_switch to fast_pbs_modulus_switch
- update docstring to reflect the change that has been done

BREAKING CHANGE:
pbs_modulus_switch is currently part of the public API and the rename is
therefore a breaking change
2023-10-17 16:53:19 +02:00
Arthur Meyre
a7b2d9b228 chore(ci): update check toolchain to latest nightly
- no new lints
2023-10-17 16:13:26 +02:00
Arthur Meyre
ab923a3ebc fix(crt): fix mul for non symmetrical parameters
- add non reg test for 32 bits mul with 5_1 parameters
2023-10-17 14:22:00 +02:00
Arthur Meyre
a0e85fb355 feat(core): add more custom moduli primitives to UnsignedInteger
As always for now the objective is to have functional custom modulus
implementations, not efficient ones

- add multiplication
- add leading_zeros
- add neg
2023-10-17 13:31:35 +02:00
Arthur Meyre
ecee305340 chore(core): change prelude algorithms imports 2023-10-17 13:31:35 +02:00
Mayeul@Zama
f08ea8cf85 fix(integer): fix max_degree formula 2023-10-17 11:35:08 +02:00
Mayeul@Zama
096e320b97 fix(crt): use 3_3 parameters for crt tests 2023-10-17 11:35:08 +02:00
Mayeul@Zama
95aac64c1c style(crt): compute modulus from base in tests 2023-10-17 11:35:08 +02:00
Mayeul@Zama
76aaa56691 fix(integer): fix small mul test 2023-10-17 11:35:08 +02:00
Mayeul@Zama
a40489bdd2 style(shortint): do not use assign ops on a cloned input 2023-10-17 11:35:08 +02:00
Mayeul@Zama
4bf617eb10 feat(shortint): cleanup input if necessary in ops 2023-10-17 11:35:08 +02:00
Mayeul@Zama
070073d229 feat(shortint): cleanup input if necessary in apply_lookup_table_bivariate 2023-10-17 11:35:08 +02:00
Arthur Meyre
6c1ca8e32b chore(core): use modular_distance instead of abs_diff in fft tests
- we are doing backwards conversions to the torus, so values could wrap
around near 0 or u64::MAX, take the modular distance which represents the
distance on the torus
2023-10-17 10:29:24 +02:00
Arthur Meyre
6523610ca4 refactor(core): refactor conversion code from f64 to i64
- observed that the subnormal case is already handled by the shift logic so
the special handling was not required
- add test for avx512 conversion
2023-10-17 10:29:24 +02:00
Arthur Meyre
41c20e22f5 chore(ci): enable AVX512 for integer and multi bit integer tests 2023-10-17 10:28:14 +02:00
J-B Orfila
4a00d25cb1 doc: updating doc for v0.4 2023-10-16 17:56:17 +02:00
tmontaigu
8c9ee64612 fix(integer): better estimate which algorithm to choose 2023-10-16 16:19:00 +02:00
tmontaigu
bfdfbfac0f chore(integer): add tests for default signed rotations/shifts 2023-10-16 16:16:07 +02:00
tmontaigu
dbe7bdcd5c feat(integer): map cmux to if_then_else 2023-10-16 16:15:49 +02:00
tmontaigu
6d77ff18ad chore(integer): add full_propagate test 2023-10-16 14:11:44 +02:00
tmontaigu
7d4d0e0b16 fix(integer): fix is_scalar_add_possible 2023-10-16 14:11:44 +02:00
Mayeul@Zama
b27762232c feat(wasm): add integers safe deserialization 2023-10-16 10:19:09 +02:00
Mayeul@Zama
f597d0f06f feat(c_api): add base and compress integers safe deserialization 2023-10-16 10:19:09 +02:00
dependabot[bot]
ee188448f3 chore(deps): bump tj-actions/changed-files from 39.2.1 to 39.2.2
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 39.2.1 to 39.2.2.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](db153baf73...408093d9ff)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 09:51:26 +02:00
Mayeul@Zama
ee49f048c7 style(integer): rename num_blocks_per_integer 2023-10-13 14:18:44 +02:00
Mayeul@Zama
a9b09ecc45 feat(c_api): add compact integer safe deserialization 2023-10-13 14:18:44 +02:00
Mayeul@Zama
efc243edc9 feat(global): refactor ciphertext conformance 2023-10-13 14:18:44 +02:00
tmontaigu
bc34411d3f feat(integer): speed-up division by using overflowing_sub
using overflowing sub allows to remove the comparison used
in the algorithm, giving significant performance boost.

before
             8        16       32      40       64       128        256
hpc7a:    `981 ms` `2.53 s` `6.41 s` `9.04 s` `16.1 s` `39.3 s` `1.55 min`
m6i:      `1.10 s` `2.97 s` `7.17 s` `10.5 s` `19.7 s` `50.2 s` `2.11 min`

afer:
             8        16       32      40       64       128        256
hpc7a:   `604 ms` `1.6 s`  `3.8 s`  `5.14 s` `9.4 s`  `22.4 s`  `54.613 s`
m6i:     `659 ms` `1.77 s` `4.4 s`  `5.9 s`  `11.5 s` `29.8 s`  `87.95 s`
2023-10-12 14:35:36 +02:00
J-B Orfila
c7923ff3ed refactor(shortint): update compact parameters 2023-10-12 11:56:50 +02:00
Arthur Meyre
7534b68e5c test(core): use polynomial tests from NTT PR
- initial work done in https://github.com/zama-ai/tfhe-rs/pull/394
- useful reworks of the tests have been waiting in that PR, this is to
have those tests while NTT usage gets validated

co-authored-by: sarah-ek <sarah.elkazdadi@zama.ai>
2023-10-12 10:40:15 +02:00
tmontaigu
655f7e6214 chore(hlapi): improve scalar type convertion 2023-10-10 17:18:32 +02:00
tmontaigu
b8556ddbd4 feat(hlapi): add C API support for FheInt 2023-10-10 17:18:32 +02:00
tmontaigu
cab7439064 fix(integer): handle trivial ct in if_then_else
if_then_else uses two calls to zero_out_if.

In zero_out_if, if the condition block given has a degree of 0
then it would return 0, without calling the predicate function.

This is not correct, as its the predicate function that
gives whether the output should be 0 or the original ciphertext.

Which meant that if if_then_else received a condition with a
degree of 0, it would always return 0.
2023-10-10 17:18:12 +02:00
tmontaigu
f8a8780651 fix(integer): remove remove if_then_else assert
unchecked_if_then_else had an assert that required
that the condition value looked like it encrypts a boolean.
This check was made using the degree.

However, the only cases where a value looks like it encrypts a boolean
value is when they are the result of a comparison (lt, le, eq, etc).

But there are other cases were the value holds a boolean value but
due to how degree works, it's not possible to know thus limiting the
use of if_then_else.

So we remove that assert, and rely on the developper knowing
its condition is 0 or 1.
2023-10-09 18:35:26 +02:00
tmontaigu
bb3c8e7d5d feat(integer): add unsigned_overflowing_sub 2023-10-09 15:39:41 +02:00
Arthur Meyre
69536960c3 chore: fix typos 2023-10-09 14:49:13 +02:00
dependabot[bot]
52a7c52a49 chore(deps): bump tj-actions/changed-files from 39.2.0 to 39.2.1
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 39.2.0 to 39.2.1.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](8238a41032...db153baf73)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 10:21:04 +02:00
tmontaigu
751c407ba5 feat(wasm): add FheInt support 2023-10-05 15:52:45 +02:00
Mayeul@Zama
492d348138 test(serialization): run tests in CI 2023-10-05 09:15:58 +02:00
Mayeul@Zama
e7df7eb5ef test(serialization): add serialization test 2023-10-05 09:15:58 +02:00
Mayeul@Zama
380ee52986 test(hlapi): test compact integer conformance 2023-10-05 09:15:58 +02:00
Mayeul@Zama
439a28f68b feat(global): impl ParameterSetConformant for ciphertexts 2023-10-05 09:15:58 +02:00
Mayeul@Zama
2eb1e37ca7 feat(global): add safe deserialization 2023-10-05 09:15:58 +02:00
Mayeul@Zama
eb1b136c45 feat(core): add to_equivalent_lwe_dimension 2023-10-05 09:15:58 +02:00
Mayeul@Zama
1376bcba7c chore(test): add type hint for rust-analyzer 2023-10-05 09:15:58 +02:00
tmontaigu
b5b4e54b9b feat(hlapi): add FheInt{8,16,32,64,128,256} 2023-10-04 20:41:19 +02:00
Arthur Meyre
23c2bd790a chore(test): fix incorrect memory buffer size in wopbs core_crypto tests 2023-10-04 14:17:33 +02:00
tmontaigu
251ee9aa0e chore(hlapi): add InnerCiphertext type to integer wrapper
Make the GenericInteger struct have a generic `InnerCiphertext`
instead of always RadixCiphertext.

This is to prepare the addition of signed types which will use a
SignedRadixCiphertext.
2023-10-03 16:26:09 +02:00
Arthur Meyre
fad066a996 refactor(core): remove a copy in the external product
- add an fft backward primitive that can use the input fourier buffer as
output as well
- gains 0.6 ms on 2_2 m6i.metal
2023-10-03 13:10:01 +02:00
tmontaigu
6ef1f22b33 feat(hlapi): tie scalar ops with corresponding clear type
Operations that used a scalar as right operand where generically
implemented meaning a user could, for example, add a u32 to a FheUint8.

Rust only allows operations between matching types, so we do the same
thing.

BREAKING CHANGE: This is a breaking change on the Rust API, but
for the better I believe. On the C API it is not a breaking change
as we already made that association as it was simpler to implement
2023-10-02 23:17:30 +02:00
tmontaigu
8cc8dba1ab feat(integer): add encryption of signed radix via compressed pk 2023-10-02 16:02:36 +02:00
tmontaigu
082328c91a feat(integer): add default signed_scalar div/rem/div_rem 2023-10-02 16:02:18 +02:00
tmontaigu
fdb6faa0a8 fix(integer): clean output quotient of division
The quotient was slowly computed by
getting a resut bit, shifting it to its position then adding it
to a quotient block, i.e quotient += bit << pos;

This meant that the output quotient was noisy, too noisy for
parameters like param_message_4_carry_4, and so the signed division
would then negate and cmux this quotient and due to the high noise,
some computations would fail, on param_message_4_carry_4.

To fix this we clean the quotient's noise before returning it.
2023-10-02 08:48:45 +02:00
Arthur Meyre
856440386f chore(csprng): the stabilized aarch64 intrisics were in Rust 1.72
- update the version accordingly
2023-09-29 18:33:39 +02:00
tmontaigu
2e8189514c feat(integer): make compact ciphertext compatible with signed 2023-09-28 20:41:38 +02:00
tmontaigu
29b2454cce feat(integer): add sign extend fn for SignedRadixCiphertext 2023-09-28 17:48:41 +02:00
tmontaigu
9ed2589c7a chore(integer): impl RecomposableSignedInteger for StaticSignedBigInt 2023-09-28 14:01:14 +02:00
tmontaigu
36b71529e6 chore(integer): make tests work with different ServerKey
This is a first step, a second step would be
to plug the non parallel radix tests so that
they are testing the same things.
2023-09-28 10:50:18 +02:00
Arthur Meyre
b738946d72 chore(core): add utils to test noise distribution for power of 2 q 2023-09-28 09:49:30 +02:00
David Testé
62f1425257 chore(bench): add missing unsigned integer operations 2023-09-28 08:47:39 +02:00
David Testé
44e491b93f style(integer): rename absolute_value functions to abs
Also add _parallelized suffix since the implementation is located in
radix_parallel directory.
2023-09-28 08:47:39 +02:00
tmontaigu
a470b26672 fix(integer): StaticSignedBigInt right shift 2023-09-27 18:37:25 +02:00
tmontaigu
015409424c chore(hlapi): remove unused keychain_member from macro 2023-09-27 14:33:24 +02:00
tmontaigu
37be751188 fix(integer): is_neg/sub/add possible
The way we did the is_neg/add/sub possible at the integer level was
incorrect in two ways.

1) We simply called the is_neg/add/sub_possible from
   the shortint impl on each block as if the were independant.
   However that is not the case, and to the check did not reflect
   actual computation.

2) We checked that we did not go beyond max degree on each block,
   However, a more correct approach would be to check that adding
   the potential carry from preceding block would not exceeding the
   current block max capacity.
2023-09-26 16:02:15 +02:00
sarah el kazdadi
2580a834af feat(core): optimize monic polynomial operations in pbs 2023-09-26 15:02:33 +02:00
David Testé
a029bd878e chore(ci): fix file exclusion for coverage reports 2023-09-26 08:58:36 +02:00
David Testé
400e7930b6 chore(ci): fix options typos for new tarpaulin version 2023-09-26 08:58:36 +02:00
David Testé
40d07c6bc3 chore(ci): speed-up boolean coverage
This is done by reducing the number of parameters set run in tests.
Using the keycache for the key switching key and public key tests also
help to reduce total run duration.
2023-09-26 08:58:36 +02:00
Mayeul@Zama
9dd2d39f1c style(global): fix typos 2023-09-25 17:27:29 +02:00
dependabot[bot]
4045a3bc2f chore(deps): bump tj-actions/changed-files from 39.0.2 to 39.2.0
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 39.0.2 to 39.2.0.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](6ee9cdc581...8238a41032)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 10:30:28 +02:00
dependabot[bot]
b4ffeccd46 chore(deps): bump actions/checkout from 4.0.0 to 4.1.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](3df4ab11eb...8ade135a41)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 10:30:22 +02:00
tmontaigu
7fe3ad3b6e feat(integer): improve scalar_mul
This changes the algorithm for scalar_mul.
The new algorithm allows to remove a lot of work.

For small precisions (16, 32, 64) the gains are in range 5%-10%
for higher precisions the gains are 25%-50%.

This also changes the mul to use the functions that sums many
clean ciphertexts in parallel. For mul, there is only a 5%-10%
improvements for 128bits and 256bits mul.
2023-09-22 15:45:07 +02:00
tmontaigu
7fdd4f9532 chore(integer): add default signed bitand/or/xor tests 2023-09-22 14:50:27 +02:00
Arthur Meyre
81eef39ddb feat(core): add parallel variant of extract_lwe_sample_from_glwe
- allows to quickly extract all coefficients packed in a GLWE cipehrtext
2023-09-22 10:55:02 +02:00
tmontaigu
b6459e3cda fix(integer): fix signed div_rem test for 0/0 2023-09-21 21:38:16 +02:00
Arthur Meyre
f2ef78c348 refactor(core): simplify closest_representable and pbs_modulus_switch
- both code were selecting the bit below the last representable bit,
extracted it and then added it to the bit above, the same effect can be
achieved by adding a 1 at the bit below the last representable bit
- update closest_representable to use an approach more like
pbs_modulus_switch yielding assembly with 42% less instructions (12 -> 7)
2023-09-21 15:54:53 +02:00
Mayeul@Zama
aef8f31621 chore(deps): update cargo dependencies 2023-09-21 15:11:13 +02:00
sarah el kazdadi
df78d178da fix(integer): replace unnecessary unsafe code in integer shift/add 2023-09-21 11:02:41 +02:00
Arthur Meyre
9297a886a4 chore(docs): fix docstring about encryption key choice 2023-09-20 16:02:55 +02:00
tmontaigu
28b4f91a32 fix(integer): only propagate if necessary after trimming
By unconditionally propagating carries after trimming
we would sometimes do work for nothing, and as propagating
carries is not cheap at all it would degrade performances.

So only propagate when necessary
2023-09-20 15:57:33 +02:00
David Testé
04fb46e41b chore(ci): print security level in parameters check
The devo profile is used to speed up the compilation phase.
2023-09-20 15:33:39 +02:00
David Testé
53da809f37 chore(ci): reduce max dimension threshold in lattice estimator 2023-09-20 09:39:50 +02:00
David Testé
723910c669 chore(ci): fix end-of-file newlines 2023-09-20 09:39:50 +02:00
David Testé
8ecf8879fb chore(ci): add end-of-file newline checks recipe 2023-09-20 09:39:50 +02:00
tmontaigu
2427f744f8 feat(integer): add unchecked implementation of signed ciphertext 2023-09-20 08:50:15 +02:00
251 changed files with 22672 additions and 7810 deletions

View File

@@ -51,7 +51,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
@@ -110,6 +110,10 @@ jobs:
run: |
make test_high_level_api
- name: Run safe deserialization tests
run: |
make test_safe_deserialization
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true

View File

@@ -50,7 +50,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
@@ -71,7 +71,7 @@ jobs:
- name: Run integer tests
run: |
BIG_TESTS_INSTANCE=TRUE make test_integer_ci
AVX512_SUPPORT=ON BIG_TESTS_INSTANCE=TRUE make test_integer_ci
- name: Slack Notification
if: ${{ always() }}

View File

@@ -50,7 +50,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
@@ -75,7 +75,7 @@ jobs:
- name: Run integer multi-bit tests
run: |
make test_integer_multi_bit_ci
AVX512_SUPPORT=ON make test_integer_multi_bit_ci
- name: Slack Notification
if: ${{ always() }}

View File

@@ -50,7 +50,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}

View File

@@ -50,7 +50,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}

View File

@@ -19,6 +19,14 @@ on:
request_id:
description: "Slab request ID"
type: string
# This input is not used in this workflow but still mandatory since a calling workflow could
# use it. If a triggering command include a user_inputs field, then the triggered workflow
# must include this very input, otherwise the workflow won't be called.
# See start_full_benchmarks.yml as example.
user_inputs:
description: "Type of benchmarks to run"
type: string
default: "weekly_benchmarks"
env:
CARGO_TERM_COLOR: always
@@ -43,7 +51,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -94,7 +102,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -21,7 +21,17 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- name: Install and run newline linter checks
if: matrix.os == 'ubuntu-latest'
run: |
wget https://github.com/fernandrone/linelint/releases/download/0.0.6/linelint-linux-amd64
echo "16b70fb7b471d6f95cbdc0b4e5dc2b0ac9e84ba9ecdc488f7bdf13df823aca4b linelint-linux-amd64" > checksum
sha256sum -c checksum || exit 1
chmod +x linelint-linux-amd64
mv linelint-linux-amd64 /usr/local/bin/linelint
make check_newline
- name: Run pcc checks
run: |

View File

@@ -50,7 +50,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
@@ -67,7 +67,7 @@ jobs:
- name: Check for file changes
id: changed-files
uses: tj-actions/changed-files@6ee9cdc5816333acda68e01cf12eedc619e28316
uses: tj-actions/changed-files@408093d9ff9c134c33b974e0722ce06b9d6e8263
with:
files_yaml: |
tfhe:
@@ -76,15 +76,16 @@ jobs:
- concrete-csprng/src/**
- name: Generate Keys
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
run: |
make GEN_KEY_CACHE_COVERAGE_ONLY=TRUE gen_key_cache
- name: Run boolean coverage
- name: Run coverage for boolean
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
run: |
make test_boolean_cov
- name: Run shortint coverage
- name: Run coverage for shortint
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
run: |
make test_shortint_cov

View File

@@ -42,7 +42,7 @@ jobs:
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}

View File

@@ -44,7 +44,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -96,7 +96,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -19,6 +19,10 @@ on:
request_id:
description: "Slab request ID"
type: string
user_inputs:
description: "Type of benchmarks to run"
type: string
default: "weekly_benchmarks"
env:
CARGO_TERM_COLOR: always
@@ -26,8 +30,33 @@ env:
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
prepare-matrix:
name: Prepare operations matrix
runs-on: ubuntu-latest
outputs:
op_flavor: ${{ steps.set_op_flavor.outputs.op_flavor }}
steps:
- name: Weekly benchmarks
if: ${{ github.event.inputs.user_inputs == 'weekly_benchmarks' }}
run: |
echo "OP_FLAVOR=[\"default\", \"default_comp\", \"default_scalar\", \"default_scalar_comp\"]" >> ${GITHUB_ENV}
- name: Quarterly benchmarks
if: ${{ github.event.inputs.user_inputs == 'quarterly_benchmarks' }}
run: |
echo "OP_FLAVOR=[\"default\", \"default_comp\", \"default_scalar\", \"default_scalar_comp\", \
\"smart\", \"smart_comp\", \"smart_scalar\", \"smart_parallelized\", \"smart_parallelized_comp\", \"smart_scalar_parallelized\", \"smart_scalar_parallelized_comp\", \
\"unchecked\", \"unchecked_comp\", \"unchecked_scalar\", \"unchecked_scalar_comp\", \
\"misc\"]" >> ${GITHUB_ENV}
- name: Set operation flavor output
id: set_op_flavor
run: |
echo "op_flavor=${{ toJSON(env.OP_FLAVOR) }}" >> ${GITHUB_OUTPUT}
integer-benchmarks:
name: Execute integer benchmarks for all operations flavor
needs: prepare-matrix
runs-on: ${{ github.event.inputs.runner_name }}
if: ${{ !cancelled() }}
continue-on-error: true
@@ -35,7 +64,7 @@ jobs:
max-parallel: 1
matrix:
command: [ integer, integer_multi_bit]
op_flavor: [ default, default_comp, default_scalar, default_scalar_comp, smart, smart_comp, smart_scalar, smart_parallelized, smart_parallelized_comp, smart_scalar_parallelized, unchecked, unchecked_comp, unchecked_scalar, unchecked_scalar_comp, misc ]
op_flavor: ${{ fromJson(needs.prepare-matrix.outputs.op_flavor) }}
steps:
- name: Instance configuration used
run: |
@@ -45,7 +74,7 @@ jobs:
echo "Request ID: ${{ inputs.request_id }}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -67,7 +96,7 @@ jobs:
override: true
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -44,7 +44,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -96,7 +96,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -28,7 +28,7 @@ jobs:
runs-on: ["self-hosted", "m1mac"]
steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af

View File

@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0

View File

@@ -17,10 +17,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- name: Checkout lattice-estimator
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: malb/lattice-estimator
path: lattice_estimator
@@ -32,7 +32,7 @@ jobs:
- name: Collect parameters
run: |
make write_params_to_file
CARGO_PROFILE=devo make write_params_to_file
- name: Perform security check
run: |

View File

@@ -19,6 +19,14 @@ on:
request_id:
description: "Slab request ID"
type: string
# This input is not used in this workflow but still mandatory since a calling workflow could
# use it. If a triggering command include a user_inputs field, then the triggered workflow
# must include this very input, otherwise the workflow won't be called.
# See start_full_benchmarks.yml as example.
user_inputs:
description: "Type of benchmarks to run"
type: string
default: "weekly_benchmarks"
env:
CARGO_TERM_COLOR: always
@@ -43,7 +51,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -84,7 +92,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -43,7 +43,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -94,7 +94,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -19,6 +19,14 @@ on:
request_id:
description: "Slab request ID"
type: string
# This input is not used in this workflow but still mandatory since a calling workflow could
# use it. If a triggering command include a user_inputs field, then the triggered workflow
# must include this very input, otherwise the workflow won't be called.
# See start_full_benchmarks.yml as example.
user_inputs:
description: "Type of benchmarks to run"
type: string
default: "weekly_benchmarks"
env:
CARGO_TERM_COLOR: always
@@ -43,7 +51,7 @@ jobs:
echo "Request ID: ${{ inputs.request_id }}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -65,7 +73,7 @@ jobs:
override: true
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -42,13 +42,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
- name: Check for file changes
id: changed-files
uses: tj-actions/changed-files@6ee9cdc5816333acda68e01cf12eedc619e28316
uses: tj-actions/changed-files@408093d9ff9c134c33b974e0722ce06b9d6e8263
with:
files_yaml: |
common_benches:
@@ -85,7 +85,7 @@ jobs:
- .github/workflows/wasm_client_benchmark.yml
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -3,9 +3,21 @@ name: Start full suite benchmarks
on:
schedule:
# Job will be triggered each Saturday at 1a.m.
# Weekly benchmarks will be triggered each Saturday at 1a.m.
- cron: '0 1 * * 6'
# Quarterly benchmarks will be triggered right before end of quarter, the 25th of the current month at 4a.m.
# These benchmarks are far longer to execute hence the reason to run them only four time a year.
- cron: '0 4 25 MAR,JUN,SEP,DEC *'
workflow_dispatch:
inputs:
benchmark_type:
description: 'Benchmark type'
required: true
default: 'weekly'
type: choice
options:
- weekly
- quarterly
jobs:
start-benchmarks:
@@ -16,21 +28,31 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Set benchmarks type as weekly
if: (github.event_name == 'workflow_dispatch' && inputs.benchmark_type == 'weekly') || github.event.schedule == '0 1 * * 6'
run: |
echo "BENCH_TYPE=weekly_benchmarks" >> "${GITHUB_ENV}"
- name: Set benchmarks type as quarterly
if: (github.event_name == 'workflow_dispatch' && inputs.benchmark_type == 'quarterly') || github.event.schedule == '0 4 25 MAR,JUN,SEP,DEC *'
run: |
echo "BENCH_TYPE=quarterly_benchmarks" >> "${GITHUB_ENV}"
- name: Start AWS job in Slab
shell: bash
run: |
echo -n '{"command": "${{ matrix.command }}", "git_ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' > command.json
echo -n '{"command": "${{ matrix.command }}", "git_ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "user_inputs": "${{ env.BENCH_TYPE }}"}' > command.json
SIGNATURE="$(slab/scripts/hmac_calculator.sh command.json '${{ secrets.JOB_SECRET }}')"
curl -v -k \
--fail-with-body \

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
- name: Save repo

View File

@@ -19,6 +19,14 @@ on:
request_id:
description: "Slab request ID"
type: string
# This input is not used in this workflow but still mandatory since a calling workflow could
# use it. If a triggering command include a user_inputs field, then the triggered workflow
# must include this very input, otherwise the workflow won't be called.
# See start_full_benchmarks.yml as example.
user_inputs:
description: "Type of benchmarks to run"
type: string
default: "weekly_benchmarks"
env:
CARGO_TERM_COLOR: always
@@ -43,7 +51,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -95,7 +103,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

14
.linelint.yml Normal file
View File

@@ -0,0 +1,14 @@
ignore:
- .git
- target
- tfhe/benchmarks_parameters
- tfhe/web_wasm_parallel_tests/node_modules
- tfhe/web_wasm_parallel_tests/dist
- keys
- coverage
rules:
# checks if file ends in a newline character
end-of-file:
enable: true
single-new-line: true

View File

@@ -16,7 +16,6 @@ PARSE_INTEGER_BENCH_CSV_FILE?=tfhe_rs_integer_benches.csv
FAST_TESTS?=FALSE
FAST_BENCH?=FALSE
BENCH_OP_FLAVOR?=DEFAULT
COVERAGE_EXCLUDED_FILES = tfhe/benches/*,apps/trivium/src/*,tfhe/examples/*,tasks/src/*
# This is done to avoid forgetting it, we still precise the RUSTFLAGS in the commands to be able to
# copy paste the command in the terminal and change them if required without forgetting the flags
export RUSTFLAGS?=-C target-cpu=native
@@ -43,6 +42,22 @@ endif
REGEX_STRING?=''
REGEX_PATTERN?=''
# Exclude these files from coverage reports
define COVERAGE_EXCLUDED_FILES
--exclude-files apps/trivium/src/trivium/* \
--exclude-files apps/trivium/src/kreyvium/* \
--exclude-files apps/trivium/src/static_deque/* \
--exclude-files apps/trivium/src/trans_ciphering/* \
--exclude-files tasks/src/* \
--exclude-files tfhe/benches/boolean/* \
--exclude-files tfhe/benches/core_crypto/* \
--exclude-files tfhe/benches/shortint/* \
--exclude-files tfhe/benches/integer/* \
--exclude-files tfhe/benches/* \
--exclude-files tfhe/examples/regex_engine/* \
--exclude-files tfhe/examples/utilities/*
endef
.PHONY: rs_check_toolchain # Echo the rust toolchain used for checks
rs_check_toolchain:
@echo $(RS_CHECK_TOOLCHAIN)
@@ -102,14 +117,27 @@ install_tarpaulin: install_rs_build_toolchain
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-tarpaulin --locked || \
( echo "Unable to install cargo tarpaulin, unknown error." && exit 1 )
.PHONY: check_linelint_installed # Check if linelint newline linter is installed
check_linelint_installed:
@printf "\n" | linelint - > /dev/null 2>&1 || \
( echo "Unable to locate linelint. Try installing it: https://github.com/fernandrone/linelint/releases" && exit 1 )
.PHONY: fmt # Format rust code
fmt: install_rs_check_toolchain
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt
.PHONT: check_fmt # Check rust code format
.PHONY: check_fmt # Check rust code format
check_fmt: install_rs_check_toolchain
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt --check
.PHONY: fix_newline # Fix newline at end of file issues to be UNIX compliant
fix_newline: check_linelint_installed
linelint -a .
.PHONY: check_newline # Check for newline at end of file to be UNIX compliant
check_newline: check_linelint_installed
linelint .
.PHONY: clippy_core # Run clippy lints on core_crypto with and without experimental features
clippy_core: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
@@ -169,7 +197,7 @@ clippy_trivium: install_rs_check_toolchain
.PHONY: clippy_all_targets # Run clippy lints on all targets (benches, examples, etc.)
clippy_all_targets:
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache,safe-deserialization \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_concrete_csprng # Run clippy lints on concrete-csprng
@@ -234,13 +262,13 @@ build_tfhe_full: install_rs_build_toolchain
.PHONY: build_c_api # Build the C API for boolean, shortint and integer
build_c_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api, \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,safe-deserialization \
-p tfhe
.PHONY: build_c_api_experimental_deterministic_fft # Build the C API for boolean, shortint and integer with experimental deterministic FFT
build_c_api_experimental_deterministic_fft: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,experimental-force_fft_algo_dif4 \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,safe-deserialization,experimental-force_fft_algo_dif4 \
-p tfhe
.PHONY: build_web_js_api # Build the js API targeting the web browser
@@ -288,15 +316,15 @@ test_boolean: install_rs_build_toolchain
.PHONY: test_boolean_cov # Run the tests of the boolean module with code coverage
test_boolean_cov: install_rs_check_toolchain install_tarpaulin
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) tarpaulin --profile $(CARGO_PROFILE) \
--out Xml --output-dir coverage/boolean --line --engine Llvm --timeout 500 \
--exclude-files $(COVERAGE_EXCLUDED_FILES) \
--out xml --output-dir coverage/boolean --line --engine llvm --timeout 500 \
$(COVERAGE_EXCLUDED_FILES) \
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache,__coverage \
-p tfhe -- boolean::
.PHONY: test_c_api_rs # Run the rust tests for the C API
test_c_api_rs: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,safe-deserialization \
-p tfhe \
c_api
@@ -329,24 +357,29 @@ test_shortint: install_rs_build_toolchain
.PHONY: test_shortint_cov # Run the tests of the shortint module with code coverage
test_shortint_cov: install_rs_check_toolchain install_tarpaulin
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) tarpaulin --profile $(CARGO_PROFILE) \
--out Xml --output-dir coverage/shortint --line --engine Llvm --timeout 500 \
--exclude-files $(COVERAGE_EXCLUDED_FILES) \
--out xml --output-dir coverage/shortint --line --engine llvm --timeout 500 \
$(COVERAGE_EXCLUDED_FILES) \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,__coverage \
-p tfhe -- shortint::
.PHONY: test_integer_ci # Run the tests for integer ci
test_integer_ci: install_rs_build_toolchain install_cargo_nextest
test_integer_ci: install_rs_check_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)"
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_CHECK_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --avx512-support "$(AVX512_SUPPORT)"
.PHONY: test_integer_multi_bit_ci # Run the tests for integer ci running only multibit tests
test_integer_multi_bit_ci: install_rs_build_toolchain install_cargo_nextest
test_integer_multi_bit_ci: install_rs_check_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --multi-bit
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_CHECK_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --multi-bit --avx512-support "$(AVX512_SUPPORT)"
.PHONY: test_safe_deserialization # Run the tests for safe deserialization
test_safe_deserialization: install_rs_build_toolchain install_cargo_nextest
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache,safe-deserialization -p tfhe -- safe_deserialization::
.PHONY: test_integer # Run all the tests for integer
test_integer: install_rs_build_toolchain
@@ -425,7 +458,7 @@ format_doc_latex:
.PHONY: check_compile_tests # Build tests in debug without running them
check_compile_tests:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --no-run \
--features=$(TARGET_ARCH_FEATURE),experimental,boolean,shortint,integer,internal-keycache \
--features=$(TARGET_ARCH_FEATURE),experimental,boolean,shortint,integer,internal-keycache,safe-deserialization \
-p tfhe
@if [[ "$(OS)" == "Linux" || "$(OS)" == "Darwin" ]]; then \
@@ -460,7 +493,8 @@ test_web_js_api_parallel: build_web_js_api_parallel
.PHONY: ci_test_web_js_api_parallel # Run tests for the web wasm api
ci_test_web_js_api_parallel: build_web_js_api_parallel
source ~/.nvm/nvm.sh && \
nvm use node && \
nvm install 20 && \
nvm use 20 && \
$(MAKE) -C tfhe/web_wasm_parallel_tests test-ci
.PHONY: no_tfhe_typo # Check we did not invert the h and f in tfhe
@@ -604,7 +638,7 @@ pcc: no_tfhe_typo no_dbg_log check_fmt lint_doc clippy_all check_compile_tests
fpcc: no_tfhe_typo no_dbg_log check_fmt lint_doc clippy_fast check_compile_tests
.PHONY: conformance # Automatically fix problems that can be fixed
conformance: fmt
conformance: fix_newline fmt
.PHONY: help # Generate list of targets with descriptions
help:

View File

@@ -17,7 +17,7 @@ path = "../../tfhe"
features = [ "boolean", "shortint", "integer", "aarch64-unix" ]
[dev-dependencies]
criterion = { version = "0.4", features = [ "html_reports" ]}
criterion = { version = "0.5.1", features = [ "html_reports" ]}
[[bench]]
name = "trivium"

View File

@@ -120,7 +120,7 @@ fn main() {
# FHE byte Trivium implementation
The same objects have also been implemented to stream bytes insead of booleans. They can be constructed and used in the same way via the functions `TriviumStreamByte::<u8>::new` and
The same objects have also been implemented to stream bytes instead of booleans. They can be constructed and used in the same way via the functions `TriviumStreamByte::<u8>::new` and
`TriviumStreamByte::<FheUint8>::new` with the same arguments as before. The `FheUint8` version is significantly slower than the `FheBool` version, because not running
with the same cryptographic parameters. Its interest lie in its trans-ciphering capabilities: `TriviumStreamByte<FheUint8>` implements the trait `TransCiphering`,
meaning it implements the functions `trans_encrypt_64`. This function takes as input a `FheUint64` and outputs a `FheUint64`, the output being

View File

@@ -1,5 +1,5 @@
//! This module implements the Kreyvium stream cipher, using booleans or FheBool
//! for the representaion of the inner bits.
//! for the representation of the inner bits.
use crate::static_deque::StaticDeque;
@@ -35,7 +35,7 @@ pub struct KreyviumStream<T> {
}
impl KreyviumStream<bool> {
/// Contructor for `KreyviumStream<bool>`: arguments are the secret key and the input vector.
/// Constructor for `KreyviumStream<bool>`: arguments are the secret key and the input vector.
/// Outputs a KreyviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(mut key: [bool; 128], mut iv: [bool; 128]) -> KreyviumStream<bool> {
@@ -118,7 +118,7 @@ where
T: KreyviumBoolInput<T> + std::marker::Send + std::marker::Sync,
for<'a> &'a T: KreyviumBoolInput<T>,
{
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
/// Internal generic constructor: arguments are already prepared registers, and an optional FHE
/// server key
fn new_from_registers(
a_register: [T; 93],

View File

@@ -1,5 +1,5 @@
//! This module implements the Kreyvium stream cipher, using u8 or FheUint8
//! for the representaion of the inner bits.
//! for the representation of the inner bits.
use crate::static_deque::{StaticByteDeque, StaticByteDequeInput};
@@ -31,7 +31,7 @@ impl KreyviumByteInput<FheUint8> for &FheUint8 {}
/// representation of bits (u8 or FheUint8). To be able to compute FHE operations, it also owns
/// an Option for a ServerKey.
/// Since the original Kreyvium registers' sizes are not a multiple of 8, these registers (which
/// store byte-like objects) have a size that is the eigth of the closest multiple of 8 above the
/// store byte-like objects) have a size that is the eighth of the closest multiple of 8 above the
/// originals' sizes.
pub struct KreyviumStreamByte<T> {
a_byte: StaticByteDeque<12, T>,
@@ -43,7 +43,7 @@ pub struct KreyviumStreamByte<T> {
}
impl KreyviumStreamByte<u8> {
/// Contructor for `KreyviumStreamByte<u8>`: arguments are the secret key and the input vector.
/// Constructor for `KreyviumStreamByte<u8>`: arguments are the secret key and the input vector.
/// Outputs a KreyviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(key_bytes: [u8; 16], iv_bytes: [u8; 16]) -> KreyviumStreamByte<u8> {
@@ -146,7 +146,7 @@ where
T: KreyviumByteInput<T> + Send,
for<'a> &'a T: KreyviumByteInput<T>,
{
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
/// Internal generic constructor: arguments are already prepared registers, and an optional FHE
/// server key
fn new_from_registers(
a_register: [T; 12],

View File

@@ -19,7 +19,7 @@ pub struct KreyviumStreamShortint {
}
impl KreyviumStreamShortint {
/// Contructor for KreyviumStreamShortint: arguments are the secret key and the input vector,
/// Constructor for KreyviumStreamShortint: arguments are the secret key and the input vector,
/// and a ServerKey reference. Outputs a KreyviumStream object already initialized (1152
/// steps have been run before returning)
pub fn new(

View File

@@ -1,6 +1,6 @@
//! This module implements the StaticByteDeque struct: a deque of bytes. The idea
//! is that this is a wrapper around StaticDeque, but StaticByteDeque has an additional
//! functionnality: it can construct the "intermediate" bytes, made of parts of other bytes.
//! functionality: it can construct the "intermediate" bytes, made of parts of other bytes.
//! This is pretending to store bits, and allows accessing bits in chunks of 8 consecutive.
use crate::static_deque::StaticDeque;

View File

@@ -5,7 +5,7 @@
use core::ops::{Index, IndexMut};
/// StaticDeque: a struct implementing a deque whose size is known at compile time.
/// It has 2 members: the static array conatining the data (never empty), and a cursor
/// It has 2 members: the static array containing the data (never empty), and a cursor
/// equal to the index of the oldest element (and the next one to be overwritten).
#[derive(Clone)]
pub struct StaticDeque<const N: usize, T> {

View File

@@ -1,5 +1,5 @@
//! This module implements the Trivium stream cipher, using booleans or FheBool
//! for the representaion of the inner bits.
//! for the representation of the inner bits.
use crate::static_deque::StaticDeque;
@@ -33,7 +33,7 @@ pub struct TriviumStream<T> {
}
impl TriviumStream<bool> {
/// Contructor for `TriviumStream<bool>`: arguments are the secret key and the input vector.
/// Constructor for `TriviumStream<bool>`: arguments are the secret key and the input vector.
/// Outputs a TriviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(key: [bool; 80], iv: [bool; 80]) -> TriviumStream<bool> {
@@ -94,7 +94,7 @@ where
T: TriviumBoolInput<T> + std::marker::Send + std::marker::Sync,
for<'a> &'a T: TriviumBoolInput<T>,
{
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
/// Internal generic constructor: arguments are already prepared registers, and an optional FHE
/// server key
fn new_from_registers(
a_register: [T; 93],

View File

@@ -1,5 +1,5 @@
//! This module implements the Trivium stream cipher, using u8 or FheUint8
//! for the representaion of the inner bits.
//! for the representation of the inner bits.
use crate::static_deque::{StaticByteDeque, StaticByteDequeInput};
@@ -31,7 +31,7 @@ impl TriviumByteInput<FheUint8> for &FheUint8 {}
/// representation of bits (u8 or FheUint8). To be able to compute FHE operations, it also owns
/// an Option for a ServerKey.
/// Since the original Trivium registers' sizes are not a multiple of 8, these registers (which
/// store byte-like objects) have a size that is the eigth of the closest multiple of 8 above the
/// store byte-like objects) have a size that is the eighth of the closest multiple of 8 above the
/// originals' sizes.
pub struct TriviumStreamByte<T> {
a_byte: StaticByteDeque<12, T>,
@@ -41,7 +41,7 @@ pub struct TriviumStreamByte<T> {
}
impl TriviumStreamByte<u8> {
/// Contructor for `TriviumStreamByte<u8>`: arguments are the secret key and the input vector.
/// Constructor for `TriviumStreamByte<u8>`: arguments are the secret key and the input vector.
/// Outputs a TriviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(key: [u8; 10], iv: [u8; 10]) -> TriviumStreamByte<u8> {
@@ -111,7 +111,7 @@ where
T: TriviumByteInput<T> + Send,
for<'a> &'a T: TriviumByteInput<T>,
{
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
/// Internal generic constructor: arguments are already prepared registers, and an optional FHE
/// server key
fn new_from_registers(
a_register: [T; 12],

View File

@@ -17,9 +17,9 @@ pub struct TriviumStreamShortint {
}
impl TriviumStreamShortint {
/// Contructor for TriviumStreamShortint: arguments are the secret key and the input vector, and
/// a ServerKey reference. Outputs a TriviumStream object already initialized (1152 steps
/// have been run before returning)
/// Constructor for TriviumStreamShortint: arguments are the secret key and the input vector,
/// and a ServerKey reference. Outputs a TriviumStream object already initialized (1152
/// steps have been run before returning)
pub fn new(
key: [Ciphertext; 80],
iv: [u64; 80],

View File

@@ -36,23 +36,24 @@ def check_security(filename):
try:
# The lattice estimator is not able to manage such large dimension.
# If we have the security for smaller `n` then we have security for larger ones.
if param.n >= 32768:
if param.n > 16384:
param = param.updated(n = 16384)
usvp_level = LWE.primal_usvp(param, red_cost_model = model)
dual_level = LWE.dual_hybrid(param, red_cost_model = model)
estimator_level = log(min(usvp_level["rop"], dual_level["rop"]),2 )
security_level = f"security level = {estimator_level} bits"
if estimator_level < 127:
print("FAIL")
reason = f"attained security level = {estimator_level} bits target is 128 bits"
print("FAIL\t({security_level})")
reason = f"attained {security_level} target is 128 bits"
to_update.append((param, reason))
continue
except Exception as err:
print("FAIL")
to_update.append((param, f"{repr(err)}"))
else:
print("OK")
print(f"OK\t({security_level})")
return to_update
@@ -72,4 +73,4 @@ if __name__ == "__main__":
print(f"[{param.tag}] reason: {reason} (param)")
sys.exit(int(1)) # Explicit conversion is needed to make this call work
else:
print("All parameters passed the security check")
print("All parameters passed the security check")

View File

@@ -9,6 +9,7 @@ documentation = "https://docs.zama.ai/tfhe-rs"
repository = "https://github.com/zama-ai/tfhe-rs"
readme = "README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
rust-version = "1.72"
[dependencies]
aes = "0.8.2"
@@ -19,8 +20,8 @@ libc = "0.2.133"
[dev-dependencies]
rand = "0.8.3"
criterion = "0.3"
clap = "=4.2.7"
criterion = "0.5.1"
clap = "=4.4.4"
[features]
parallel = ["rayon"]

View File

@@ -17,7 +17,7 @@ pub struct BytesPerChild(pub usize);
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct ByteCount(pub u128);
/// An error occuring during a generator fork.
/// An error occurring during a generator fork.
#[derive(Debug)]
pub enum ForkError {
ForkTooLarge,
@@ -145,7 +145,7 @@ pub mod generator_generic_test {
/// Checks that the PRNG roughly generates uniform numbers.
///
/// To do that, we perform an histogram of the occurences of each byte value, over a fixed
/// To do that, we perform an histogram of the occurrences of each byte value, over a fixed
/// number of samples and check that the empirical probabilities of the bins are close to
/// the theoretical probabilities.
pub fn test_roughly_uniform<G: RandomGenerator>() {

View File

@@ -3,7 +3,7 @@
//! When initializing a generator, one needs to provide a [`Seed`], which is then used as key to the
//! AES blockcipher. As a consequence, the quality of the outputs of the generator is directly
//! conditioned by the quality of this seed. This module proposes different mechanisms to deliver
//! seeds that can accomodate varying scenarios.
//! seeds that can accommodate varying scenarios.
/// A seed value, used to initialize a generator.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -15,7 +15,7 @@ pub trait Seeder {
fn seed(&mut self) -> Seed;
/// Check whether the seeder can be used on the current machine. This function may check if some
/// required CPU features are available or if some OS features are availble for example.
/// required CPU features are available or if some OS features are available for example.
fn is_available() -> bool
where
Self: Sized;

View File

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

View File

@@ -9,6 +9,7 @@ function usage() {
echo "--rust-toolchain The toolchain to run the tests with default: stable"
echo "--multi-bit Run multi-bit tests only: default off"
echo "--cargo-profile The cargo profile used to build tests"
echo "--avx512-support Set to ON to enable avx512"
echo
}
@@ -16,6 +17,7 @@ RUST_TOOLCHAIN="+stable"
multi_bit=""
not_multi_bit="_multi_bit"
cargo_profile="release"
avx512_feature=""
while [ -n "$1" ]
do
@@ -40,6 +42,13 @@ do
cargo_profile="$1"
;;
"--avx512-support" )
shift
if [[ "$1" == "ON" ]]; then
avx512_feature=nightly-avx512
fi
;;
*)
echo "Unknown param : $1"
exit 1
@@ -104,7 +113,7 @@ and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3_ks_pbs
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--features="${ARCH_FEATURE}",integer,internal-keycache,"${avx512_feature}" \
--test-threads "${n_threads}" \
-E "$filter_expression"
@@ -112,7 +121,7 @@ and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3_ks_pbs
cargo "${RUST_TOOLCHAIN}" test \
--profile "${cargo_profile}" \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--features="${ARCH_FEATURE}",integer,internal-keycache,"${avx512_feature}" \
--doc \
-- integer::
fi
@@ -148,7 +157,7 @@ and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3_ks_pbs
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--features="${ARCH_FEATURE}",integer,internal-keycache,"${avx512_feature}" \
--test-threads $num_threads \
-E "$filter_expression"
@@ -156,7 +165,7 @@ and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3_ks_pbs
cargo "${RUST_TOOLCHAIN}" test \
--profile "${cargo_profile}" \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--features="${ARCH_FEATURE}",integer,internal-keycache,"${avx512_feature}" \
--doc \
-- --test-threads="$(${nproc_bin})" integer::
fi

View File

@@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "3.1"
clap = "=4.4.4"
lazy_static = "1.4"
log = "0.4"
simplelog = "0.12"

2
tfhe/.gitignore vendored
View File

@@ -1 +1 @@
build/
build/

View File

@@ -26,16 +26,16 @@ rand = "0.8.5"
rand_distr = "0.4.3"
paste = "1.0.7"
lazy_static = { version = "1.4.0" }
criterion = "0.4.0"
criterion = "0.5.1"
doc-comment = "0.3.3"
serde_json = "1.0.94"
# clap has to be pinned as its minimum supported rust version
# changes often between minor releases, which breaks our CI
clap = { version = "=4.2.7", features = ["derive"] }
clap = { version = "=4.4.4", features = ["derive"] }
# Used in user documentation
bincode = "1.3.3"
fs2 = { version = "0.4.3" }
itertools = "0.10.5"
itertools = "0.11.0"
# For erf and normality test
libm = "0.2.6"
# Begin regex-engine deps
@@ -46,7 +46,7 @@ log = "0.4.19"
# End regex-engine deps
[build-dependencies]
cbindgen = { version = "0.24.3", optional = true }
cbindgen = { version = "0.26.0", optional = true }
[dependencies]
concrete-csprng = { version = "0.4.0", path= "../concrete-csprng", features = [
@@ -64,7 +64,7 @@ dyn-stack = { version = "0.9" }
paste = { version = "1.0.7", optional = true }
fs2 = { version = "0.4.3", optional = true }
# While we wait for repeat_n in rust standard library
itertools = "0.10.5"
itertools = "0.11.0"
# wasm deps
wasm-bindgen = { version = "0.2.86", features = [
@@ -73,7 +73,7 @@ wasm-bindgen = { version = "0.2.86", features = [
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 }
serde-wasm-bindgen = { version = "0.6.0", optional = true }
getrandom = { version = "0.2.8", optional = true }
bytemuck = "1.13.1"
@@ -82,14 +82,15 @@ bytemuck = "1.13.1"
boolean = ["dep:paste"]
shortint = ["dep:paste"]
integer = ["shortint", "dep:paste"]
internal-keycache = ["lazy_static", "dep:fs2", "bincode", "dep:paste"]
internal-keycache = ["lazy_static", "dep:fs2", "dep:bincode", "dep:paste"]
safe-deserialization = ["dep:bincode"]
# Experimental section
experimental = []
experimental-force_fft_algo_dif4 = []
# End experimental section
__c_api = ["cbindgen", "bincode", "dep:paste"]
__c_api = ["cbindgen", "dep:bincode", "dep:paste"]
boolean-c-api = ["boolean", "__c_api"]
shortint-c-api = ["shortint", "__c_api"]
high-level-c-api = ["boolean-c-api", "shortint-c-api", "integer", "__c_api"]
@@ -101,7 +102,8 @@ __wasm_api = [
"serde-wasm-bindgen",
"getrandom",
"getrandom/js",
"bincode",
"dep:bincode",
"safe-deserialization",
]
boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
@@ -182,6 +184,12 @@ path = "benches/integer/bench.rs"
harness = false
required-features = ["integer", "internal-keycache"]
[[bench]]
name = "integer-signed-bench"
path = "benches/integer/signed_bench.rs"
harness = false
required-features = ["integer", "internal-keycache"]
[[bench]]
name = "keygen"
path = "benches/keygen/bench.rs"

View File

@@ -43,7 +43,7 @@ pub fn write_to_json_boolean<T: Into<CryptoParametersRecord<u32>>>(
// Put all `bench_function` in one place
// so the keygen is only run once per parameters saving time.
fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
fn benches(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
let mut bench_group = c.benchmark_group("gates_benches");
let cks = ClientKey::new(&params);
@@ -83,15 +83,15 @@ fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
}
fn bench_default_parameters(c: &mut Criterion) {
benchs(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
benches(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
}
fn bench_default_parameters_ks_pbs(c: &mut Criterion) {
benchs(c, DEFAULT_PARAMETERS_KS_PBS, "DEFAULT_PARAMETERS_KS_PBS");
benches(c, DEFAULT_PARAMETERS_KS_PBS, "DEFAULT_PARAMETERS_KS_PBS");
}
fn bench_low_prob_parameters(c: &mut Criterion) {
benchs(
benches(
c,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165",
@@ -99,7 +99,7 @@ fn bench_low_prob_parameters(c: &mut Criterion) {
}
fn bench_low_prob_parameters_ks_pbs(c: &mut Criterion) {
benchs(
benches(
c,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS",
@@ -107,5 +107,5 @@ fn bench_low_prob_parameters_ks_pbs(c: &mut Criterion) {
}
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
benchs(c, TFHE_LIB_PARAMETERS, " TFHE_LIB_PARAMETERS");
benches(c, TFHE_LIB_PARAMETERS, " TFHE_LIB_PARAMETERS");
}

View File

@@ -650,15 +650,23 @@ define_server_key_bench_fn!(method_name: smart_bitxor, display_name: bitxor);
define_server_key_bench_fn!(method_name: smart_add_parallelized, display_name: add);
define_server_key_bench_fn!(method_name: smart_sub_parallelized, display_name: sub);
define_server_key_bench_fn!(method_name: smart_mul_parallelized, display_name: mul);
define_server_key_bench_fn!(method_name: smart_div_parallelized, display_name: div);
define_server_key_bench_fn!(method_name: smart_div_rem_parallelized, display_name: div_mod);
define_server_key_bench_fn!(method_name: smart_rem_parallelized, display_name: rem);
define_server_key_bench_fn!(method_name: smart_bitand_parallelized, display_name: bitand);
define_server_key_bench_fn!(method_name: smart_bitxor_parallelized, display_name: bitxor);
define_server_key_bench_fn!(method_name: smart_bitor_parallelized, display_name: bitor);
define_server_key_bench_fn!(method_name: smart_rotate_right_parallelized, display_name: rotate_right);
define_server_key_bench_fn!(method_name: smart_rotate_left_parallelized, display_name: rotate_left);
define_server_key_bench_fn!(method_name: smart_right_shift_parallelized, display_name: right_shift);
define_server_key_bench_fn!(method_name: smart_left_shift_parallelized, display_name: left_shift);
define_server_key_bench_default_fn!(method_name: add_parallelized, display_name: add);
define_server_key_bench_default_fn!(method_name: sub_parallelized, display_name: sub);
define_server_key_bench_default_fn!(method_name: mul_parallelized, display_name: mul);
define_server_key_bench_default_fn!(method_name: div_parallelized, display_name: div);
define_server_key_bench_default_fn!(method_name: rem_parallelized, display_name: modulo);
define_server_key_bench_default_fn!(method_name: div_rem_parallelized, display_name: div_mod);
define_server_key_bench_default_fn!(method_name: bitand_parallelized, display_name: bitand);
define_server_key_bench_default_fn!(method_name: bitxor_parallelized, display_name: bitxor);
define_server_key_bench_default_fn!(method_name: bitor_parallelized, display_name: bitor);
@@ -671,7 +679,11 @@ define_server_key_bench_default_fn!(method_name: unchecked_bitand, display_name:
define_server_key_bench_default_fn!(method_name: unchecked_bitor, display_name: bitor);
define_server_key_bench_default_fn!(method_name: unchecked_bitxor, display_name: bitxor);
define_server_key_bench_default_fn!(method_name: unchecked_add_parallelized, display_name: add);
define_server_key_bench_default_fn!(method_name: unchecked_mul_parallelized, display_name: mul);
define_server_key_bench_default_fn!(method_name: unchecked_div_parallelized, display_name: div);
define_server_key_bench_default_fn!(method_name: unchecked_rem_parallelized, display_name: modulo);
define_server_key_bench_default_fn!(method_name: unchecked_div_rem_parallelized, display_name: div_mod);
define_server_key_bench_default_fn!(
method_name: unchecked_bitand_parallelized,
display_name: bitand
@@ -684,6 +696,38 @@ define_server_key_bench_default_fn!(
method_name: unchecked_bitxor_parallelized,
display_name: bitxor
);
define_server_key_bench_default_fn!(
method_name: unchecked_rotate_right_parallelized,
display_name: rotate_right
);
define_server_key_bench_default_fn!(
method_name: unchecked_rotate_left_parallelized,
display_name: rotate_left
);
define_server_key_bench_default_fn!(
method_name: unchecked_right_shift_parallelized,
display_name: right_shift
);
define_server_key_bench_default_fn!(
method_name: unchecked_left_shift_parallelized,
display_name: left_shift
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_bitand_parallelized,
display_name: bitand,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_bitor_parallelized,
display_name: bitor,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_bitxor_parallelized,
display_name: bitxor,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_add,
@@ -716,6 +760,46 @@ define_server_key_bench_scalar_fn!(
display_name: mul,
rng_func: mul_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_div_parallelized,
display_name: div,
rng_func: div_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_rem_parallelized,
display_name: modulo,
rng_func: div_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_div_rem_parallelized,
display_name: div_mod,
rng_func: div_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_bitand_parallelized,
display_name: bitand,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_bitor_parallelized,
display_name: bitor,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_bitxor_parallelized,
display_name: bitxor,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_rotate_left_parallelized,
display_name: rotate_left,
rng_func: shift_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_rotate_right_parallelized,
display_name: rotate_right,
rng_func: shift_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_add_parallelized,
@@ -742,6 +826,11 @@ define_server_key_bench_scalar_default_fn!(
display_name: modulo,
rng_func: div_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_div_rem_parallelized,
display_name: div_mod,
rng_func: div_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_left_shift_parallelized,
display_name: left_shift,
@@ -762,6 +851,22 @@ define_server_key_bench_scalar_default_fn!(
display_name: rotate_right,
rng_func: shift_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_bitand_parallelized,
display_name: bitand,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_bitor_parallelized,
display_name: bitor,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_bitxor_parallelized,
display_name: bitxor,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_eq_parallelized,
display_name: equal,
@@ -803,6 +908,47 @@ define_server_key_bench_scalar_default_fn!(
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_eq_parallelized,
display_name: equal,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_ne_parallelized,
display_name: not_equal,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_le_parallelized,
display_name: less_or_equal,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_lt_parallelized,
display_name: less_than,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_ge_parallelized,
display_name: greater_or_equal,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_gt_parallelized,
display_name: greater_than,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_max_parallelized,
display_name: max,
rng_func: default_scalar
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_min_parallelized,
display_name: min,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_add,
display_name: add,
@@ -818,10 +964,50 @@ define_server_key_bench_scalar_default_fn!(
display_name: mul,
rng_func: mul_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_div_parallelized,
display_name: div,
rng_func: div_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_rem_parallelized,
display_name: modulo,
rng_func: div_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_div_rem_parallelized,
display_name: div_mod,
rng_func: div_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_rotate_right_parallelized,
display_name: rotate_right,
rng_func: shift_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_rotate_left_parallelized,
display_name: rotate_left,
rng_func: shift_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_right_shift_parallelized,
display_name: right_shift,
rng_func: shift_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_left_shift_parallelized,
display_name: left_shift,
rng_func: shift_scalar
);
define_server_key_bench_unary_fn!(method_name: smart_neg, display_name: negation);
define_server_key_bench_unary_fn!(method_name: smart_neg_parallelized, display_name: negation);
define_server_key_bench_unary_fn!(method_name: smart_abs_parallelized, display_name: abs);
define_server_key_bench_unary_default_fn!(method_name: neg_parallelized, display_name: negation);
define_server_key_bench_unary_default_fn!(method_name: abs_parallelized, display_name: abs);
define_server_key_bench_unary_default_fn!(method_name: unchecked_abs_parallelized, display_name: abs);
define_server_key_bench_unary_fn!(method_name: full_propagate, display_name: carry_propagation);
define_server_key_bench_unary_fn!(
@@ -832,6 +1018,7 @@ define_server_key_bench_unary_fn!(
define_server_key_bench_default_fn!(method_name: unchecked_max, display_name: max);
define_server_key_bench_default_fn!(method_name: unchecked_min, display_name: min);
define_server_key_bench_default_fn!(method_name: unchecked_eq, display_name: equal);
define_server_key_bench_default_fn!(method_name: unchecked_ne, display_name: not_equal);
define_server_key_bench_default_fn!(method_name: unchecked_lt, display_name: less_than);
define_server_key_bench_default_fn!(method_name: unchecked_le, display_name: less_or_equal);
define_server_key_bench_default_fn!(method_name: unchecked_gt, display_name: greater_than);
@@ -840,6 +1027,7 @@ define_server_key_bench_default_fn!(method_name: unchecked_ge, display_name: gre
define_server_key_bench_default_fn!(method_name: unchecked_max_parallelized, display_name: max);
define_server_key_bench_default_fn!(method_name: unchecked_min_parallelized, display_name: min);
define_server_key_bench_default_fn!(method_name: unchecked_eq_parallelized, display_name: equal);
define_server_key_bench_default_fn!(method_name: unchecked_ne_parallelized, display_name: not_equal);
define_server_key_bench_default_fn!(
method_name: unchecked_lt_parallelized,
display_name: less_than
@@ -857,9 +1045,35 @@ define_server_key_bench_default_fn!(
display_name: greater_or_equal
);
define_server_key_bench_scalar_default_fn!(method_name: unchecked_scalar_max_parallelized, display_name: max,rng_func: default_scalar);
define_server_key_bench_scalar_default_fn!(method_name: unchecked_scalar_min_parallelized, display_name: min,rng_func: default_scalar);
define_server_key_bench_scalar_default_fn!(method_name: unchecked_scalar_eq_parallelized, display_name: equal,rng_func: default_scalar);
define_server_key_bench_scalar_default_fn!(method_name: unchecked_scalar_ne_parallelized, display_name: not_equal,rng_func: default_scalar);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_lt_parallelized,
display_name: less_than,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_le_parallelized,
display_name: less_or_equal,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_gt_parallelized,
display_name: greater_than,
rng_func: default_scalar
);
define_server_key_bench_scalar_default_fn!(
method_name: unchecked_scalar_ge_parallelized,
display_name: greater_or_equal,
rng_func: default_scalar
);
define_server_key_bench_fn!(method_name: smart_max, display_name: max);
define_server_key_bench_fn!(method_name: smart_min, display_name: min);
define_server_key_bench_fn!(method_name: smart_eq, display_name: equal);
define_server_key_bench_fn!(method_name: smart_ne, display_name: not_equal);
define_server_key_bench_fn!(method_name: smart_lt, display_name: less_than);
define_server_key_bench_fn!(method_name: smart_le, display_name: less_or_equal);
define_server_key_bench_fn!(method_name: smart_gt, display_name: greater_than);
@@ -868,6 +1082,7 @@ define_server_key_bench_fn!(method_name: smart_ge, display_name: greater_or_equa
define_server_key_bench_fn!(method_name: smart_max_parallelized, display_name: max);
define_server_key_bench_fn!(method_name: smart_min_parallelized, display_name: min);
define_server_key_bench_fn!(method_name: smart_eq_parallelized, display_name: equal);
define_server_key_bench_fn!(method_name: smart_ne_parallelized, display_name: not_equal);
define_server_key_bench_fn!(method_name: smart_lt_parallelized, display_name: less_than);
define_server_key_bench_fn!(
method_name: smart_le_parallelized,
@@ -923,6 +1138,7 @@ criterion_group!(
smart_max,
smart_min,
smart_eq,
smart_ne,
smart_lt,
smart_le,
smart_gt,
@@ -931,12 +1147,21 @@ criterion_group!(
criterion_group!(
smart_parallelized_ops,
smart_neg_parallelized,
smart_abs_parallelized,
smart_add_parallelized,
smart_sub_parallelized,
smart_mul_parallelized,
// smart_div_parallelized,
// smart_rem_parallelized,
smart_div_rem_parallelized, // For ciphertext div == rem == div_rem
smart_bitand_parallelized,
smart_bitor_parallelized,
smart_bitxor_parallelized,
smart_rotate_right_parallelized,
smart_rotate_left_parallelized,
smart_right_shift_parallelized,
smart_left_shift_parallelized,
);
criterion_group!(
@@ -944,6 +1169,7 @@ criterion_group!(
smart_max_parallelized,
smart_min_parallelized,
smart_eq_parallelized,
smart_ne_parallelized,
smart_lt_parallelized,
smart_le_parallelized,
smart_gt_parallelized,
@@ -952,12 +1178,14 @@ criterion_group!(
criterion_group!(
default_parallelized_ops,
neg_parallelized,
abs_parallelized,
add_parallelized,
sub_parallelized,
mul_parallelized,
div_parallelized,
rem_parallelized,
neg_parallelized,
// div_parallelized,
// rem_parallelized,
div_rem_parallelized,
bitand_parallelized,
bitnot_parallelized,
bitor_parallelized,
@@ -993,6 +1221,26 @@ criterion_group!(
smart_scalar_add_parallelized,
smart_scalar_sub_parallelized,
smart_scalar_mul_parallelized,
smart_scalar_div_parallelized,
smart_scalar_rem_parallelized, // For scalar rem == div_rem
// smart_scalar_div_rem_parallelized,
smart_scalar_bitand_parallelized,
smart_scalar_bitor_parallelized,
smart_scalar_bitxor_parallelized,
smart_scalar_rotate_right_parallelized,
smart_scalar_rotate_left_parallelized,
);
criterion_group!(
smart_scalar_parallelized_ops_comp,
smart_scalar_max_parallelized,
smart_scalar_min_parallelized,
smart_scalar_eq_parallelized,
smart_scalar_ne_parallelized,
smart_scalar_lt_parallelized,
smart_scalar_le_parallelized,
smart_scalar_gt_parallelized,
smart_scalar_ge_parallelized,
);
criterion_group!(
@@ -1002,10 +1250,14 @@ criterion_group!(
scalar_mul_parallelized,
scalar_div_parallelized,
scalar_rem_parallelized,
// scalar_div_rem_parallelized,
scalar_left_shift_parallelized,
scalar_right_shift_parallelized,
scalar_rotate_left_parallelized,
scalar_rotate_right_parallelized,
scalar_bitand_parallelized,
scalar_bitor_parallelized,
scalar_bitxor_parallelized,
);
criterion_group!(
@@ -1030,11 +1282,40 @@ criterion_group!(
unchecked_bitxor,
);
criterion_group!(
unchecked_parallelized_ops,
unchecked_abs_parallelized,
unchecked_add_parallelized,
unchecked_mul_parallelized,
// unchecked_div_parallelized,
// unchecked_rem_parallelized,
unchecked_div_rem_parallelized,
unchecked_bitand_parallelized,
unchecked_bitor_parallelized,
unchecked_bitxor_parallelized,
unchecked_rotate_right_parallelized,
unchecked_rotate_left_parallelized,
unchecked_right_shift_parallelized,
unchecked_left_shift_parallelized,
);
criterion_group!(
unchecked_parallelized_ops_comp,
unchecked_eq_parallelized,
unchecked_ne_parallelized,
unchecked_gt_parallelized,
unchecked_ge_parallelized,
unchecked_lt_parallelized,
unchecked_max_parallelized,
unchecked_min_parallelized,
);
criterion_group!(
unchecked_ops_comp,
unchecked_max,
unchecked_min,
unchecked_eq,
unchecked_ne,
unchecked_lt,
unchecked_le,
unchecked_gt,
@@ -1046,20 +1327,28 @@ criterion_group!(
unchecked_scalar_add,
unchecked_scalar_sub,
unchecked_scalar_mul_parallelized,
unchecked_bitand_parallelized,
unchecked_bitor_parallelized,
unchecked_bitxor_parallelized,
unchecked_scalar_div_parallelized,
unchecked_scalar_rem_parallelized,
// unchecked_scalar_div_rem_parallelized,
unchecked_scalar_bitand_parallelized,
unchecked_scalar_bitor_parallelized,
unchecked_scalar_bitxor_parallelized,
unchecked_scalar_rotate_right_parallelized,
unchecked_scalar_rotate_left_parallelized,
unchecked_scalar_right_shift_parallelized,
unchecked_scalar_left_shift_parallelized,
);
criterion_group!(
unchecked_scalar_ops_comp,
unchecked_max_parallelized,
unchecked_min_parallelized,
unchecked_eq_parallelized,
unchecked_lt_parallelized,
unchecked_le_parallelized,
unchecked_gt_parallelized,
unchecked_ge_parallelized,
unchecked_scalar_max_parallelized,
unchecked_scalar_min_parallelized,
unchecked_scalar_eq_parallelized,
unchecked_scalar_ne_parallelized,
unchecked_scalar_lt_parallelized,
unchecked_scalar_le_parallelized,
unchecked_scalar_gt_parallelized,
unchecked_scalar_ge_parallelized,
);
criterion_group!(misc, full_propagate, full_propagate_parallelized);
@@ -1078,7 +1367,9 @@ fn main() {
"smart_parallelized" => smart_parallelized_ops(),
"smart_parallelized_comp" => smart_parallelized_ops_comp(),
"smart_scalar_parallelized" => smart_scalar_parallelized_ops(),
"smart_scalar_parallelized_comp" => smart_scalar_parallelized_ops_comp(),
"unchecked" => unchecked_ops(),
"unchecked_parallelized" => unchecked_parallelized_ops(),
"unchecked_comp" => unchecked_ops_comp(),
"unchecked_scalar" => unchecked_scalar_ops(),
"unchecked_scalar_comp" => unchecked_scalar_ops_comp(),

View File

@@ -0,0 +1,811 @@
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use std::env;
use criterion::{criterion_group, Criterion};
use itertools::iproduct;
use rand::prelude::*;
use rand::Rng;
use std::vec::IntoIter;
use tfhe::integer::keycache::KEY_CACHE;
use tfhe::integer::{RadixCiphertext, ServerKey, SignedRadixCiphertext, I256};
use tfhe::keycache::NamedParam;
use tfhe::shortint::parameters::{
PARAM_MESSAGE_2_CARRY_2_KS_PBS, PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
};
fn gen_random_i256(rng: &mut ThreadRng) -> I256 {
let clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
tfhe::integer::I256::from((clearlow, clearhigh))
}
/// An iterator that yields a succession of combinations
/// of parameters and a num_block to achieve a certain bit_size ciphertext
/// in radix decomposition
struct ParamsAndNumBlocksIter {
params_and_bit_sizes:
itertools::Product<IntoIter<tfhe::shortint::PBSParameters>, IntoIter<usize>>,
}
impl Default for ParamsAndNumBlocksIter {
fn default() -> Self {
let is_multi_bit = match env::var("__TFHE_RS_BENCH_TYPE") {
Ok(val) => val.to_lowercase() == "multi_bit",
Err(_) => false,
};
let is_fast_bench = match env::var("__TFHE_RS_FAST_BENCH") {
Ok(val) => val.to_lowercase() == "true",
Err(_) => false,
};
if is_multi_bit {
let params = vec![PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS.into()];
let bit_sizes = if is_fast_bench {
vec![32]
} else {
vec![8, 16, 32, 40, 64]
};
let params_and_bit_sizes = iproduct!(params, bit_sizes);
Self {
params_and_bit_sizes,
}
} else {
// FIXME One set of parameter is tested since we want to benchmark only quickest
// operations.
let params = vec![
PARAM_MESSAGE_2_CARRY_2_KS_PBS.into(),
// PARAM_MESSAGE_3_CARRY_3_KS_PBS.into(),
// PARAM_MESSAGE_4_CARRY_4_KS_PBS.into(),
];
let bit_sizes = if is_fast_bench {
vec![32]
} else {
vec![8, 16, 32, 40, 64, 128, 256]
};
let params_and_bit_sizes = iproduct!(params, bit_sizes);
Self {
params_and_bit_sizes,
}
}
}
}
impl Iterator for ParamsAndNumBlocksIter {
type Item = (tfhe::shortint::PBSParameters, usize, usize);
fn next(&mut self) -> Option<Self::Item> {
let (param, bit_size) = self.params_and_bit_sizes.next()?;
let num_block =
(bit_size as f64 / param.message_modulus().0.ilog2() as f64).ceil() as usize;
Some((param, num_block, bit_size))
}
}
/// Base function to bench a server key function that is a binary operation, input ciphertext will
/// contain only zero carries
fn bench_server_key_signed_binary_function_clean_inputs<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
sample_size: usize,
) where
F: Fn(&ServerKey, &SignedRadixCiphertext, &SignedRadixCiphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
.sample_size(sample_size)
.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_two_values = || {
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
let ct_1 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
(ct_0, ct_1)
};
b.iter_batched(
encrypt_two_values,
|(ct_0, ct_1)| {
binary_op(&sks, &ct_0, &ct_1);
},
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()
}
/// Shifts and rotations require a special function as the rhs,
/// i.e. the shift amount has to be a positive radix type.
fn bench_server_key_signed_shift_function_clean_inputs<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
) where
F: Fn(&ServerKey, &SignedRadixCiphertext, &RadixCiphertext),
{
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_two_values = || {
let clear_1 = rng.gen_range(0u128..bit_size as u128);
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
let ct_1 = cks.encrypt_radix(clear_1, num_block);
(ct_0, ct_1)
};
b.iter_batched(
encrypt_two_values,
|(ct_0, ct_1)| {
binary_op(&sks, &ct_0, &ct_1);
},
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()
}
/// Base function to bench a server key function that is a unary operation, input ciphertext will
/// contain only zero carries
fn bench_server_key_unary_function_clean_inputs<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
unary_fn: F,
) where
F: Fn(&ServerKey, &SignedRadixCiphertext),
{
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_one_value =
|| cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
b.iter_batched(
encrypt_one_value,
|ct_0| {
unary_fn(&sks, &ct_0);
},
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()
}
fn signed_if_then_else_parallelized(c: &mut Criterion) {
let bench_name = "integer::signed::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 ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
let ct_1 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), 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_binary_signed_clean_inputs_fn (
(method_name: $server_key_method:ident, display_name:$name:ident $(,)?) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_signed_binary_function_clean_inputs(
c,
concat!("integer::signed::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
server_key.$server_key_method(lhs, rhs);
},
15 /* sample_size */
)
}
};
(
method_name: $server_key_method:ident,
display_name:$name:ident,
sample_size: $sample_size:expr $(,)?
) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_signed_binary_function_clean_inputs(
c,
concat!("integer::signed::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
server_key.$server_key_method(lhs, rhs);
},
$sample_size
)
}
}
);
macro_rules! define_server_key_bench_unary_signed_clean_input_fn (
(method_name: $server_key_method:ident, display_name:$name:ident $(,)?) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_unary_function_clean_inputs(
c,
concat!("integer::signed::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs| {
server_key.$server_key_method(lhs);
},
)
}
};
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_mul_parallelized,
display_name: mul
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_bitand_parallelized,
display_name: bitand
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_bitor_parallelized,
display_name: bitand
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_bitxor_parallelized,
display_name: bitand
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_eq_parallelized,
display_name: eq
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_ne_parallelized,
display_name: ne
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_le_parallelized,
display_name: le
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_lt_parallelized,
display_name: lt
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_ge_parallelized,
display_name: ge
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_gt_parallelized,
display_name: gt
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_max_parallelized,
display_name: max
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_min_parallelized,
display_name: min
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_div_rem_parallelized,
display_name: div_rem,
sample_size: 10,
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_div_rem_floor_parallelized,
display_name: div_rem_floor,
sample_size: 10,
);
fn unchecked_left_shift_parallelized(c: &mut Criterion) {
bench_server_key_signed_shift_function_clean_inputs(
c,
concat!("integer::signed::", "unchecked_left_shift_parallelized"),
"left_shift",
|server_key, lhs, rhs| {
server_key.unchecked_left_shift_parallelized(lhs, rhs);
},
)
}
fn unchecked_right_shift_parallelized(c: &mut Criterion) {
bench_server_key_signed_shift_function_clean_inputs(
c,
concat!("integer::signed::", "unchecked_right_shift_parallelized"),
"right_shift",
|server_key, lhs, rhs| {
server_key.unchecked_right_shift_parallelized(lhs, rhs);
},
)
}
fn unchecked_rotate_left_parallelized(c: &mut Criterion) {
bench_server_key_signed_shift_function_clean_inputs(
c,
concat!("integer::signed::", "unchecked_rotate_left_parallelized"),
"rotate_left",
|server_key, lhs, rhs| {
server_key.unchecked_rotate_left_parallelized(lhs, rhs);
},
)
}
fn unchecked_rotate_right_parallelized(c: &mut Criterion) {
bench_server_key_signed_shift_function_clean_inputs(
c,
concat!("integer::signed::", "unchecked_rotate_right_parallelized"),
"rotate_right",
|server_key, lhs, rhs| {
server_key.unchecked_rotate_right_parallelized(lhs, rhs);
},
)
}
define_server_key_bench_unary_signed_clean_input_fn!(
method_name: unchecked_abs_parallelized,
display_name: abs,
);
criterion_group!(
unchecked_ops,
unchecked_mul_parallelized,
unchecked_left_shift_parallelized,
unchecked_right_shift_parallelized,
unchecked_rotate_left_parallelized,
unchecked_rotate_right_parallelized,
unchecked_bitand_parallelized,
unchecked_bitor_parallelized,
unchecked_bitxor_parallelized,
unchecked_abs_parallelized,
unchecked_div_rem_parallelized,
unchecked_div_rem_floor_parallelized,
);
criterion_group!(
unchecked_ops_comp,
unchecked_eq_parallelized,
unchecked_ne_parallelized,
unchecked_ge_parallelized,
unchecked_gt_parallelized,
unchecked_le_parallelized,
unchecked_lt_parallelized,
unchecked_max_parallelized,
unchecked_min_parallelized,
);
//================================================================================
// Scalar Benches
//================================================================================
type ScalarType = I256;
fn bench_server_key_binary_scalar_function_clean_inputs<F, G>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
rng_func: G,
) where
F: Fn(&ServerKey, &mut SignedRadixCiphertext, ScalarType),
G: Fn(&mut ThreadRng, usize) -> ScalarType,
{
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() {
if bit_size > ScalarType::BITS as usize {
break;
}
let param_name = param.name();
let range = range_for_signed_bit_size(bit_size);
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits_scalar_{bit_size}");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let encrypt_one_value = || {
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
let clear_1 = rng_func(&mut rng, bit_size);
assert!(
range.contains(&clear_1),
"{:?} is not within the range {:?}",
clear_1,
range
);
(ct_0, clear_1)
};
b.iter_batched(
encrypt_one_value,
|(mut ct_0, clear_1)| {
binary_op(&sks, &mut ct_0, clear_1);
},
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()
}
fn range_for_signed_bit_size(bit_size: usize) -> std::ops::RangeInclusive<ScalarType> {
assert!(bit_size <= ScalarType::BITS as usize);
assert!(bit_size > 0);
let modulus = ScalarType::ONE << (bit_size - 1);
// if clear_bit_size == ScalarType::BITS then modulus==T::MIN
// -T::MIN = -T::MIN so we sill have our correct lower value
// (in two's complement which rust uses)
let lowest = modulus.wrapping_neg();
// if clear_bit_size == 128 then modulus==T::MIN
// T::MIN - 1 = T::MAX (in two's complement which rust uses)
let highest = modulus.wrapping_sub(ScalarType::ONE);
lowest..=highest
}
/// Creates a bitmask where bit_size bits are 1s, rest are 0s
/// Only works if ScalarType in signed
fn positive_bit_mask_for_bit_size(bit_size: usize) -> ScalarType {
assert!(bit_size <= ScalarType::BITS as usize);
assert!(bit_size > 0);
let minus_one = -ScalarType::ONE; // (In two's complement this is full of 1s)
// The last bit of bit_size can only be set for when value is positive
let bitmask = (minus_one) >> (ScalarType::BITS as usize - bit_size - 1);
// flib msb as they would still be one due to '>>' being arithmetic shift
bitmask ^ ((minus_one) << (bit_size - 1))
}
fn negative_bit_mask_for_bit_size(bit_size: usize) -> ScalarType {
assert!(bit_size <= ScalarType::BITS as usize);
assert!(bit_size > 0);
let minus_one = -ScalarType::ONE; // (In two's complement this is full of 1s)
let bitmask = (minus_one) >> (ScalarType::BITS as usize - bit_size);
// flib msb as they would still be one due to '>>' being arithmetic shift
bitmask ^ ((minus_one) << bit_size)
}
// We have to do this complex stuff because we cannot impl
// rand::distributions::Distribution<I256> because benches are considered out of the crate
// so neither I256 nor rand::distributions::Distribution belong to the benches.
//
// rand::distributions::Distribution can't be implemented in tfhe sources
// in a way that it becomes available to the benches, because rand is a dev dependency
fn gen_random_i256_in_range(rng: &mut ThreadRng, bit_size: usize) -> I256 {
let value = gen_random_i256(rng);
if value >= I256::ZERO {
value & positive_bit_mask_for_bit_size(bit_size)
} else {
(value & negative_bit_mask_for_bit_size(bit_size)) | -I256::ONE
}
}
// Functions used to apply different way of selecting a scalar based on the context.
fn default_scalar(rng: &mut ThreadRng, clear_bit_size: usize) -> ScalarType {
gen_random_i256_in_range(rng, clear_bit_size)
}
fn shift_scalar(_rng: &mut ThreadRng, _clear_bit_size: usize) -> ScalarType {
// Shifting by one is the worst case scenario.
ScalarType::ONE
}
fn div_scalar(rng: &mut ThreadRng, clear_bit_size: usize) -> ScalarType {
loop {
let scalar = gen_random_i256_in_range(rng, clear_bit_size);
if scalar != ScalarType::ZERO {
return scalar;
}
}
}
macro_rules! define_server_key_bench_binary_scalar_clean_inputs_fn (
(method_name: $server_key_method:ident, display_name:$name:ident, rng_func:$($rng_fn:tt)*) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_scalar_function_clean_inputs(
c,
concat!("integer::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
server_key.$server_key_method(lhs, rhs);
}, $($rng_fn)*)
}
}
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_left_shift_parallelized,
display_name: scalar_left_shift,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_right_shift_parallelized,
display_name: scalar_right_shift,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_rotate_right_parallelized,
display_name: scalar_rotate_right,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_rotate_left_parallelized,
display_name: scalar_rotate_left,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_mul_parallelized,
display_name: scalar_mul,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_bitand_parallelized,
display_name: scalar_bitand,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_bitor_parallelized,
display_name: scalar_bitor,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_bitxor_parallelized,
display_name: scalar_bitxor,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_eq_parallelized,
display_name: scalar_eq,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_ne_parallelized,
display_name: scalar_ne,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_le_parallelized,
display_name: scalar_le,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_lt_parallelized,
display_name: scalar_lt,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_ge_parallelized,
display_name: scalar_ge,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_gt_parallelized,
display_name: scalar_gt,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_max_parallelized,
display_name: scalar_max,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_min_parallelized,
display_name: scalar_min,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_signed_scalar_div_rem_parallelized,
display_name: scalar_div_rem,
rng_func: div_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_signed_scalar_div_parallelized,
display_name: scalar_div,
rng_func: div_scalar
);
criterion_group!(
unchecked_scalar_ops,
unchecked_scalar_left_shift_parallelized,
unchecked_scalar_right_shift_parallelized,
unchecked_scalar_rotate_right_parallelized,
unchecked_scalar_rotate_left_parallelized,
unchecked_scalar_bitand_parallelized,
unchecked_scalar_bitor_parallelized,
unchecked_scalar_bitxor_parallelized,
unchecked_scalar_mul_parallelized,
unchecked_signed_scalar_div_rem_parallelized,
unchecked_signed_scalar_div_parallelized,
unchecked_div_rem_floor_parallelized,
);
criterion_group!(
unchecked_scalar_ops_comp,
unchecked_scalar_eq_parallelized,
unchecked_scalar_ne_parallelized,
unchecked_scalar_le_parallelized,
unchecked_scalar_lt_parallelized,
unchecked_scalar_ge_parallelized,
unchecked_scalar_gt_parallelized,
unchecked_scalar_max_parallelized,
unchecked_scalar_min_parallelized,
);
criterion_group!(default_ops, signed_if_then_else_parallelized,);
fn main() {
match env::var("__TFHE_RS_BENCH_OP_FLAVOR") {
Ok(val) => {
match val.to_lowercase().as_str() {
"unchecked" => unchecked_ops(),
"unchecked_comp" => unchecked_ops_comp(),
"unchecked_scalar" => unchecked_scalar_ops(),
"unchecked_scalar_comp" => unchecked_scalar_ops_comp(),
_ => panic!("unknown benchmark operations flavor"),
};
}
Err(_) => {
unchecked_ops();
unchecked_ops_comp();
unchecked_scalar_ops();
unchecked_scalar_ops_comp();
}
};
Criterion::default().configure_from_args().final_summary();
}

View File

@@ -37,4 +37,3 @@ foreach (testsourcefile ${TEST_CASES})
# Enabled asserts even in release mode
add_definitions(-UNDEBUG)
endforeach (testsourcefile ${TEST_CASES})

View File

@@ -110,6 +110,134 @@ int uint256_public_key(const ClientKey *client_key, const PublicKey *public_key)
return ok;
}
int int256_client_key(const ClientKey *client_key) {
int ok;
FheInt256 *lhs = NULL;
FheInt256 *rhs = NULL;
FheInt256 *result = NULL;
FheInt64 *cast_result = NULL;
// This is +1
I256 lhs_clear = {1, 0, 0, 0};
// This is -1
I256 rhs_clear = {UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX};
I256 result_clear = {0};
ok = fhe_int256_try_encrypt_with_client_key_i256(lhs_clear, client_key, &lhs);
assert(ok == 0);
ok = fhe_int256_try_encrypt_with_client_key_i256(rhs_clear, client_key, &rhs);
assert(ok == 0);
ok = fhe_int256_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_int256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
// We did 1 + (-1), so we expect 0
assert(result_clear.w0 == 0);
assert(result_clear.w1 == 0);
assert(result_clear.w2 == 0);
assert(result_clear.w3 == 0);
fhe_int256_destroy(result);
ok = fhe_int256_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_int256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
// We did 1 - (-1), so we expect 2
assert(result_clear.w0 == 2);
assert(result_clear.w1 == 0);
assert(result_clear.w2 == 0);
assert(result_clear.w3 == 0);
// try some casting
ok = fhe_int256_cast_into_fhe_int64(result, &cast_result);
assert(ok == 0);
int64_t u64_clear;
ok = fhe_int64_decrypt(cast_result, client_key, &u64_clear);
assert(ok == 0);
assert(u64_clear == 2);
fhe_int256_destroy(lhs);
fhe_int256_destroy(rhs);
fhe_int256_destroy(result);
fhe_int64_destroy(cast_result);
return ok;
}
int int256_encrypt_trivial(const ClientKey *client_key) {
int ok;
FheInt256 *lhs = NULL;
FheInt256 *rhs = NULL;
FheInt256 *result = NULL;
I256 lhs_clear = {1, 2, 3, 4};
I256 rhs_clear = {5, 6, 7, 8};
I256 result_clear = {0};
ok = fhe_int256_try_encrypt_trivial_i256(lhs_clear, &lhs);
assert(ok == 0);
ok = fhe_int256_try_encrypt_trivial_i256(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_int256_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_int256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 6);
assert(result_clear.w1 == 8);
assert(result_clear.w2 == 10);
assert(result_clear.w3 == 12);
fhe_int256_destroy(lhs);
fhe_int256_destroy(rhs);
fhe_int256_destroy(result);
return ok;
}
int int256_public_key(const ClientKey *client_key, const PublicKey *public_key) {
int ok;
FheInt256 *lhs = NULL;
FheInt256 *rhs = NULL;
FheInt256 *result = NULL;
// This is +1
I256 lhs_clear = {1, 0, 0, 0};
// This is -1
I256 rhs_clear = {UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX};
I256 result_clear = {0};
ok = fhe_int256_try_encrypt_with_public_key_i256(lhs_clear, public_key, &lhs);
assert(ok == 0);
ok = fhe_int256_try_encrypt_with_public_key_i256(rhs_clear, public_key, &rhs);
assert(ok == 0);
ok = fhe_int256_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_int256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
// We did 1 - (-1), so we expect 2
assert(result_clear.w0 == 2);
assert(result_clear.w1 == 0);
assert(result_clear.w2 == 0);
assert(result_clear.w3 == 0);
fhe_int256_destroy(lhs);
fhe_int256_destroy(rhs);
fhe_int256_destroy(result);
return ok;
}
int main(void) {
int ok = 0;
ConfigBuilder *builder;
@@ -132,6 +260,10 @@ int main(void) {
uint256_encrypt_trivial(client_key);
uint256_public_key(client_key, public_key);
int256_client_key(client_key);
int256_encrypt_trivial(client_key);
int256_public_key(client_key, public_key);
client_key_destroy(client_key);
public_key_destroy(public_key);
server_key_destroy(server_key);

View File

@@ -78,7 +78,7 @@ int uint256_encrypt_trivial(const ClientKey *client_key) {
return ok;
}
int uint256_public_key(const ClientKey *client_key,
int uint256_compact_public_key(const ClientKey *client_key,
const CompressedCompactPublicKey *compressed_public_key) {
int ok;
CompactPublicKey *public_key = NULL;
@@ -112,7 +112,7 @@ int uint256_public_key(const ClientKey *client_key,
lhs = expand_output[0];
rhs = expand_output[1];
// We can destroy the compact list
// The expanded ciphertext are independant from it
// The expanded ciphertext are independent from it
compact_fhe_uint256_list_destroy(list);
ok = fhe_uint256_sub(lhs, rhs, &result);
@@ -158,6 +158,80 @@ int uint256_public_key(const ClientKey *client_key,
return ok;
}
int int32_compact_public_key(const ClientKey *client_key,
const CompressedCompactPublicKey *compressed_public_key) {
int ok;
CompactPublicKey *public_key = NULL;
FheInt32 *lhs = NULL;
FheInt32 *rhs = NULL;
FheInt32 *result = NULL;
CompactFheInt32List *list = NULL;
int32_t result_clear = 0;
int32_t clears[2] = {-9482394, 98712234};
ok = compressed_compact_public_key_decompress(compressed_public_key, &public_key);
assert(ok == 0);
// Compact list example
{
ok = compact_fhe_int32_list_try_encrypt_with_compact_public_key_i32(&clears[0], 2,
public_key, &list);
assert(ok == 0);
size_t len = 0;
ok = compact_fhe_int32_list_len(list, &len);
assert(ok == 0);
assert(len == 2);
FheInt32 *expand_output[2] = {NULL};
ok = compact_fhe_int32_list_expand(list, &expand_output[0], 2);
assert(ok == 0);
// transfer ownership
lhs = expand_output[0];
rhs = expand_output[1];
// We can destroy the compact list
// The expanded ciphertext are independent from it
compact_fhe_int32_list_destroy(list);
ok = fhe_int32_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_int32_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear == clears[0] - clears[1]);
fhe_int32_destroy(lhs);
fhe_int32_destroy(rhs);
fhe_int32_destroy(result);
}
{
ok = fhe_int32_try_encrypt_with_compact_public_key_i32(clears[0], public_key, &lhs);
assert(ok == 0);
ok = fhe_int32_try_encrypt_with_compact_public_key_i32(clears[1], public_key, &rhs);
assert(ok == 0);
ok = fhe_int32_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_int32_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear == clears[0] + clears[1]);
fhe_int32_destroy(lhs);
fhe_int32_destroy(rhs);
fhe_int32_destroy(result);
}
compact_public_key_destroy(public_key);
return ok;
}
int main(void) {
int ok = 0;
{
@@ -180,7 +254,8 @@ int main(void) {
uint256_client_key(client_key);
uint256_encrypt_trivial(client_key);
uint256_public_key(client_key, compressed_public_key);
uint256_compact_public_key(client_key, compressed_public_key);
int32_compact_public_key(client_key, compressed_public_key);
client_key_destroy(client_key);
compressed_compact_public_key_destroy(compressed_public_key);
@@ -207,7 +282,8 @@ int main(void) {
uint256_client_key(client_key);
uint256_encrypt_trivial(client_key);
uint256_public_key(client_key, compressed_public_key);
uint256_compact_public_key(client_key, compressed_public_key);
int32_compact_public_key(client_key, compressed_public_key);
client_key_destroy(client_key);
compressed_compact_public_key_destroy(compressed_public_key);

View File

@@ -64,6 +64,67 @@ int uint8_public_key(const ClientKey *client_key, const PublicKey *public_key) {
return ok;
}
int uint8_safe_serialization(const ClientKey *client_key, const ServerKey *server_key) {
int ok;
CompactFheUint8 *lhs = NULL;
CompactFheUint8 *deserialized_lhs = NULL;
CompactFheUint8 *result = NULL;
Buffer value_buffer = {.pointer = NULL, .length = 0};
Buffer cks_buffer = {.pointer = NULL, .length = 0};
BufferView deser_view = {.pointer = NULL, .length = 0};
ClientKey *deserialized_client_key = NULL;
const uint64_t max_serialization_size = UINT64_C(1) << UINT64_C(20);
uint8_t lhs_clear = 123;
ok = client_key_serialize(client_key, &cks_buffer);
assert(ok == 0);
deser_view.pointer = cks_buffer.pointer;
deser_view.length = cks_buffer.length;
ok = client_key_deserialize(deser_view, &deserialized_client_key);
assert(ok == 0);
struct CompactPublicKey *public_key;
ok = compact_public_key_new(deserialized_client_key, &public_key);
assert(ok == 0);
ok = compact_fhe_uint8_try_encrypt_with_compact_public_key_u8(lhs_clear, public_key, &lhs);
assert(ok == 0);
ok = compact_fhe_uint8_safe_serialize(lhs, &value_buffer, max_serialization_size);
assert(ok == 0);
deser_view.pointer = value_buffer.pointer;
deser_view.length = value_buffer.length;
ok = compact_fhe_uint8_safe_deserialize_conformant(deser_view, max_serialization_size, server_key,
&deserialized_lhs);
assert(ok == 0);
FheUint8 *expanded = NULL;
ok = compact_fhe_uint8_expand(deserialized_lhs, &expanded);
assert(ok == 0);
uint8_t clear;
ok = fhe_uint8_decrypt(expanded, deserialized_client_key, &clear);
assert(ok == 0);
assert(clear == lhs_clear);
if (value_buffer.pointer != NULL) {
destroy_buffer(&value_buffer);
}
compact_fhe_uint8_destroy(lhs);
compact_fhe_uint8_destroy(deserialized_lhs);
compact_fhe_uint8_destroy(result);
fhe_uint8_destroy(expanded);
return ok;
}
int uint8_serialization(const ClientKey *client_key) {
int ok;
FheUint8 *lhs = NULL;
@@ -159,6 +220,8 @@ int main(void) {
assert(ok == 0);
ok = uint8_serialization(client_key);
assert(ok == 0);
ok = uint8_safe_serialization(client_key, server_key);
assert(ok == 0);
ok = uint8_compressed(client_key);
assert(ok == 0);

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)
@@ -56,4 +56,3 @@
## API references
* [docs.rs](https://docs.rs/tfhe/)

View File

@@ -504,7 +504,7 @@ Pattern | Description
`/^abc$/` | Matches with content that equals exactly `abc` (case sensitive)
`/^abc$/i` | Matches with content that equals `abc` (case insensitive)
`/abc/` | Matches with content that contains somewhere `abc`
`/ab?c/` | Matches with content that contains somewhere `abc` or somwhere `ab`
`/ab?c/` | Matches with content that contains somewhere `abc` or somewhere `ab`
`/^ab*c$/` | For example, matches with: `ac`, `abc`, `abbbbc`
`/^[a-c]b\|cd$/` | Matches with: `ab`, `bb`, `cb`, `cd`
`/^[a-c]b\|cd$/i` | Matches with: `ab`, `Ab`, `aB`, ..., `cD`, `CD`

View File

@@ -249,7 +249,7 @@ pub fn main() {
println!("Checking result...");
assert_eq!(6, pbs_multiplication_result);
println!(
"Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
"Multiplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
);
}
```

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. These parameters are chosen 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;
@@ -93,6 +93,10 @@ Each operation may come in different 'flavors':
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
{% hint style="info" %}
If you don't know which flavor to use, you should use the `default` one.
{% endhint %}
## How to use each operation type
Let's try to do a circuit evaluation using the different flavors of already introduced operations. For a very small circuit, the `unchecked` flavor may be enough to do the computation correctly. Otherwise, `checked` and `smart` are the best options.
@@ -162,12 +166,15 @@ fn main() {
assert!(result.is_ok());
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
assert!(result.is_ok());
let result = server_key.checked_add_assign(&mut ct_1, &ct_3);
assert!(result.is_err());
// We use the client key to decrypt the output of the circuit:
// Only the scalar multiplication could be done
let output: u64 = client_key.decrypt(&ct_1);
assert_eq!(output, (msg1 * scalar) % modulus as u64);
assert_eq!(output, ((msg1 * scalar) - msg2) % modulus as u64);
}
```
@@ -208,6 +215,14 @@ fn main() {
}
```
{% hint style="warning" %}
You must avoid cloning the inputs when calling `smart` operations to preserve performance. For instance, you SHOULD NOT have these kind of patterns in the code:
```Rust
sks.smart_add(&mut a.clone(), &mut b.clone());
```
{% endhint %}
The main advantage of the default flavor is to ensure predictable timings, as long as only this kind of operation is used. Only the parallelized version of the operations is provided.
{% hint style="warning" %}

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

@@ -141,7 +141,7 @@ use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We create keys for radix represention to create 16 bits integers
// We create keys for radix representation to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, 8);

View File

@@ -37,8 +37,14 @@ Each operation may come in different 'flavors':
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
{% hint style="info" %}
If you don't know which flavor to use, you should use the `default` one.
{% endhint %}
## How to use operation types
Let's try to do a circuit evaluation using the different flavors of operations that we have already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise,`checked` and `smart` are the best options.
Let's do a scalar multiplication, a subtraction, and a multiplication.
@@ -367,14 +373,14 @@ fn main() {
let modulus = client_key.parameters.message_modulus().0 as u64;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let mut ct_1 = client_key.encrypt(msg1);
let mut ct_2 = client_key.encrypt(msg2);
// Compute the lookup table for the bivariate functions
let acc = server_key.generate_lookup_table_bivariate(|x,y| (x.count_ones()
+ y.count_ones()) as u64 % modulus );
let ct_res = server_key.smart_apply_lookup_table_bivariate(&ct_1, &mut ct_2, &acc);
let ct_res = server_key.smart_apply_lookup_table_bivariate(&mut ct_1, &mut ct_2, &acc);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_res);

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 unsigned integers smaller than 8 bits. The steps to homomorphically evaluate a circuit are described below.
## Key generation

View File

@@ -1,101 +1,106 @@
# Benchmarks
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.
Due to their nature, homomorphic operations are naturally slower than their cleartext equivalents. 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 %}
## Integer
This measures the execution time for some operation sets of tfhe-rs::integer (the unsigned version). Note that the timings for `FheInt` (i.e., the signed integers) are similar.
| Operation \ Size | `FheUint8` | `FheUint16` | `FheUint32` | `FheUint64` | `FheUint128` | `FheUint256` |
|--------------------------------------------------------|------------|-------------|-------------|-------------|--------------|--------------|
| 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 (`/`, `%`) | 729 ms | 1.93 s | 4.81 s | 12.2 s | 30.7 s | 89.6 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\_KS\_PBS, 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 may be reduced by using `unchecked`, `checked`, or `smart`.
## Shortint
This measures the execution time for some operations using various parameter sets of tfhe-rs::shortint. Except for `unchecked_add`, all timings are related to the `default` operations. This flavor ensures predictable timings for an operation along the entire circuit by clearing the carry space after each operation.
This uses the Concrete FFT + AVX-512 configuration.
| 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 |
## 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 |
| 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 | fftw | spqlios-fma |
| ------------------------------------------------ | ------ | ----------- |
| default\_128bit\_gate\_bootstrapping\_parameters | 28.9ms | 15.7ms |
Using the same m6i.metal machine as the one for tfhe-rs, the timings are:
### OpenFHE.
| Parameter set | spqlios-fma |
|--------------------------------------------------|-------------|
| default\_128bit\_gate\_bootstrapping\_parameters | 15.4 ms |
| Parameter set | GINX | GINX (Intel HEXL) |
| ------------- | ----- | ----------------- |
| STD\_128 | 172ms | 78ms |
| MEDIUM | 113ms | 50.2ms |
### OpenFHE (v1.1.1).
Following the official instructions from OpenFHE, `clang14` and the following command are used 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 used is as follows:
```bash
export CXX=clang++
export CC=clang
scripts/configure.sh
Release -> y
hexl -> y
scripts/build-openfhe-development-hexl.sh
```
Using the same m6i.metal machine as the one for tfhe-rs, 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 |
## Integer
This measures the execution time for some operation sets of tfhe-rs::integer.
## How to reproduce TFHE-rs benchmarks
| 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 |
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`.
## Shortint
This measures the execution time for some operations using various parameter sets of tfhe-rs::shortint.
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 |
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 |
## How to reproduce benchmarks
TFHE-rs benchmarks can easily be reproduced from the [sources](https://github.com/zama-ai/tfhe-rs).
TFHE-rs benchmarks can be easily reproduced from [source](https://github.com/zama-ai/tfhe-rs).
```shell
#Boolean benchmarks:
make bench_boolean
make AVX512_SUPPORT=ON bench_boolean
#Integer benchmarks:
make bench_integer
#Shortint benchmarks:
make bench_shortint
```
If the host machine supports AVX-512, then the argument `AVX512_SUPPORT=ON' should be added, e.g.:
```shell
#Integer benchmarks:
make AVX512_SUPPORT=ON bench_integer
#Shortint benchmarks:
make AVX512_SUPPORT=ON bench_shortint
```
If the host machine does not support AVX512, then turning on `AVX512_SUPPORT` will not provide any speed-up.

View File

@@ -4,12 +4,22 @@
## Importing into your project
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`.
If you are using an `x86` machine:
```toml
tfhe = { version = "0.4.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
```
If you are using an `ARM` machine:
```toml
tfhe = { version = "0.4.0", features = [ "boolean", "shortint", "integer", "aarch64-unix" ] }
```
{% hint style="info" %}
You need to use a Rust version >= 1.72 to compile TFHE-rs.
{% endhint %}
{% 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 %}
@@ -25,7 +35,3 @@ TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (
| Linux | `x86_64-unix` | `aarch64-unix`\* |
| macOS | `x86_64-unix` | `aarch64-unix`\* |
| Windows | `x86_64` | Unsupported |
{% hint style="info" %}
Users who have ARM devices can compile TFHE-rs using a stable toolchain with version >= 1.72 (see [Configuration](../how_to/rust_configuration.md) for more details).
{% endhint %}

View File

@@ -1,47 +1,386 @@
# 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 unsigned integers `u8, u16, ...`
- `FheInt`: this is the homomorphic equivalent of Rust (signed) integers `i8, i16, ...`
| 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 as many programming languages, the number of bits used to represent the data must be chosen when declaring a variable. For instance:
## Boolean Operations
```Rust
// let clear_a: u64 = 7;
let mut a = FheUint64::try_encrypt(clear_a, &keys)?;
Native homomorphic Booleans support common Boolean operations.
// 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) 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: |
| Ternary operator | `if_then_else` | :heavy_check_mark: | :heavy_multiplication_x: |
## Integer
In `TFHE-rs`, integers are used to encrypt all messages which are 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, if `ct1 = FheUint8(63)` and `ct2 = FheUint8(0)` then `ct1 % ct2` will return `FheUint8(63)`.
A simple example of 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 of 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.
Due to some Rust limitations, it is not possible to overload the comparison symbols because of the inner definition of the operations. This is because Rust expects to have a Boolean as an output, whereas a ciphertext is returned when using homomorphic types.
You will need to use different methods instead of using symbols for the comparisons. These methods follow the same naming conventions as the two standard Rust traits:
* [PartialOrd](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html)
* [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
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 of 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 of 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(())
}
```
### Ternary conditional operator.
The ternary conditional operator allows computing conditional instructions of the form `if cond { choice_if } else { choice_else }`.
| name | symbol | type |
|------------------|----------------|---------|
| Ternary operator | `if_then_else` | Ternary |
The syntax is `encrypted_condition.if_then_else(encrypted_choice_if, encrypted_choice_else)`. The `encrypted_condition` should be an encryption of 0 or 1 in order to be valid.
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt32};
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 = 32i32;
let clear_b = -45i32;
// Encrypting the input data using the (private) client_key
// FheInt32: Encrypted equivalent to i32
let encrypted_a = FheInt32::try_encrypt(clear_a, &client_key)?;
let encrypted_b = FheInt32::try_encrypt(clear_b, &client_key)?;
// On the server side:
set_server_key(server_keys);
// Clear equivalent computations: 32 > -45
let encrypted_comp = &encrypted_a.gt(&encrypted_b);
let clear_res: i32 = encrypted_comp.decrypt(&client_key);
assert_eq!(clear_res, (clear_a > clear_b) as i32);
// `encrypted_comp` contains the result of the comparison, i.e.,
// a boolean value. This acts as a condition on which the
// `if_then_else` function can be applied on.
// Clear equivalent computations:
// if 32 > -45 {result = 32} else {result = -45}
let encrypted_res = &encrypted_comp.if_then_else(&encrypted_a, &encrypted_b);
let clear_res: i32 = encrypted_res.decrypt(&client_key);
assert_eq!(clear_res, clear_a);
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
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.
Native small homomorphic integer types (e.g., FheUint3 or FheUint4) can easily compute various operations. In general, computing over encrypted data in `TFHE-rs` 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}$$.
@@ -49,7 +388,7 @@ In Rust, operations on native types are modular. For example, computations on `u
Small homomorphic integer types support all common arithmetic operations, meaning `+`, `-`, `x`, `/`, `mod`.
The division operation implements a subtlety: since data is encrypted, it is possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns the max possible value for the message.
The division operation presents a subtlety: since data is encrypted, it is possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns the maximum possible value for the message.
The list of supported operations is:
@@ -62,7 +401,7 @@ The list of supported operations is:
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html) | `%` | Binary |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `-` | Unary |
A simple example on how to use these operations:
A simple example of how to use these operations:
```rust
use tfhe::prelude::*;
@@ -114,7 +453,7 @@ The list of supported operations is:
| [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:
A simple example of how to use these operations:
```rust
use tfhe::prelude::*;
@@ -169,7 +508,7 @@ The list of supported operations is:
| [Lower or Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `le` | Binary |
A simple example on how to use these operations:
A simple example of how to use these operations:
```rust
use tfhe::prelude::*;
@@ -197,7 +536,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
The shortint type also supports the computation of univariate functions, which make use of TFHE's _programmable bootstrapping_.
A simple example on how to use these operations:
A simple example of how to use these operations:
```rust
use tfhe::prelude::*;
@@ -252,257 +591,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

@@ -139,6 +139,3 @@ let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
```

View File

@@ -77,7 +77,7 @@ The figure below illustrates this problem in the case of an addition, where an e
### Programmable BootStrapping (PBS)
The bootstrapping of TFHE has the particularity of being programmable: this means that any function can be homomorphically computed over an encrypted input, while also reducing the noise. These functions are represented by look-up tables. The computation of a PBS is in general either preceded or followed by a keyswitch, which is an operation used to change the encryption key. The output ciphertext is then encrypted with the same key as the input one. To do this, two (public) evaluation keys are required: a boostrapping key and a keyswitching key. These operations are quite complex to describe, more information about these operations (or about TFHE in general) can be found here [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
The bootstrapping of TFHE has the particularity of being programmable: this means that any function can be homomorphically computed over an encrypted input, while also reducing the noise. These functions are represented by look-up tables. The computation of a PBS is in general either preceded or followed by a keyswitch, which is an operation used to change the encryption key. The output ciphertext is then encrypted with the same key as the input one. To do this, two (public) evaluation keys are required: a bootstrapping key and a keyswitching key. These operations are quite complex to describe, more information about these operations (or about TFHE in general) can be found here [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
### Carry.

View File

@@ -84,7 +84,7 @@ fn main() {
This example shows how to compress the classical public keys.
{% hint style="warning" %}
It is not currently recommended to use the CompressedPublicKey to encrypt ciphertexts without first decompressing it. In case the resulting PublicKey is too large to fit in memory the encryption with the CompressedPublicKey will be very slow, this is a known problem and will be adressed in future releases.
It is not currently recommended to use the CompressedPublicKey to encrypt ciphertexts without first decompressing it. In case the resulting PublicKey is too large to fit in memory the encryption with the CompressedPublicKey will be very slow, this is a known problem and will be addressed in future releases.
{% endhint %}
```rust

View File

@@ -63,6 +63,3 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
```

View File

@@ -49,4 +49,3 @@ fn main() {
assert_eq!(clear, 255u8);
}
```

View File

@@ -1,6 +1,6 @@
# Using the right toolchain for TFHE-rs.
TFHE-rs only requires a nighlty toolchain for building the C API and using advanced SIMD instructions, otherwise you can use a stable toolchain (with version >= 1.72 for ARM devices)
TFHE-rs only requires a nightly toolchain for building the C API and using advanced SIMD instructions, otherwise you can use a stable toolchain (with version >= 1.72)
Install the needed Rust toolchain:
```shell
@@ -27,7 +27,7 @@ cargo +nightly test
```shell
# This should not be necessary by default, but if you want to make sure your configuration is
# correct you can still set the overriden toolchain to stable
# correct you can still set the overridden toolchain to stable
rustup override set stable
# cargo will use the `stable` toolchain.
cargo build
@@ -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

@@ -1,4 +1,4 @@
# Trival Ciphertext
# Trivial Ciphertext
Sometimes, the server side needs to initialize a value.
For example, when computing the sum of a list of ciphertext,

View File

@@ -1,10 +1,6 @@
use clap::{Arg, ArgAction, Command};
use tfhe::boolean;
use tfhe::boolean::parameters::{
BooleanParameters, DEFAULT_PARAMETERS, DEFAULT_PARAMETERS_KS_PBS,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165, PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
TFHE_LIB_PARAMETERS,
};
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, DEFAULT_PARAMETERS_KS_PBS};
use tfhe::keycache::NamedParam;
use tfhe::shortint::keycache::{KEY_CACHE, KEY_CACHE_KSK, KEY_CACHE_WOPBS};
use tfhe::shortint::parameters::key_switching::{
@@ -97,13 +93,8 @@ fn client_server_keys() {
)];
generate_wopbs_keys(&WOPBS_PARAMS);
const BOOLEAN_PARAMS: [BooleanParameters; 5] = [
DEFAULT_PARAMETERS,
DEFAULT_PARAMETERS_KS_PBS,
TFHE_LIB_PARAMETERS,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
];
const BOOLEAN_PARAMS: [BooleanParameters; 2] =
[DEFAULT_PARAMETERS, DEFAULT_PARAMETERS_KS_PBS];
generate_boolean_keys(&BOOLEAN_PARAMS);
} else {
generate_pbs_keys(&ALL_PARAMETER_VEC);

View File

@@ -0,0 +1,623 @@
const test = require('node:test');
const assert = require('node:assert').strict;
const { performance } = require('perf_hooks');
const {
init_panic_hook,
ShortintParametersName,
ShortintParameters,
TfheClientKey,
TfhePublicKey,
TfheCompressedPublicKey,
TfheCompactPublicKey,
TfheCompressedServerKey,
TfheConfigBuilder,
CompressedFheInt8,
FheInt8,
FheInt32,
CompactFheInt32,
CompactFheInt32List,
CompressedFheInt128,
FheInt128,
CompressedFheInt256,
CompactFheInt256,
CompactFheInt256List,
FheInt256
} = require("../pkg/tfhe.js");
const I256_MIN = BigInt("-57896044618658097711785492504343953926634992332820282019728792003956564819968");
const I256_MAX = BigInt("28948022309329048855892746252171976963317496166410141009864396001978282409983");
const I128_MIN = BigInt("-170141183460469231731687303715884105728");
const I32_MIN = -2147483648;
// This is useful to debug test
init_panic_hook();
test('hlapi_client_key_encrypt_decrypt_int8_big', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers()
.build();
let clientKey = TfheClientKey.generate(config);
let clear = -73;
let encrypted = FheInt8.encrypt_with_client_key(clear, clientKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, clear);
let serialized = encrypted.serialize();
let deserialized = FheInt8.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, clear);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt8.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, clear);
});
test('hlapi_compressed_public_client_int8_big', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers()
.build();
let clientKey = TfheClientKey.generate(config);
let clear = -128;
let compressed_encrypted = CompressedFheInt8.encrypt_with_client_key(clear, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
let compressed_deserialized = CompressedFheInt8.deserialize(compressed_serialized);
let decompressed = compressed_deserialized.decompress()
let decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, clear);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheInt8.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
let safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, clear);
});
test('hlapi_public_key_encrypt_decrypt_int32_small', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers_small()
.build();
let clientKey = TfheClientKey.generate(config);
let publicKey = TfhePublicKey.new(clientKey);
let encrypted = FheInt32.encrypt_with_public_key(I32_MIN, publicKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I32_MIN);
let serialized = encrypted.serialize();
let deserialized = FheInt32.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I32_MIN);
let safe_serialized = encrypted.safe_serialize(BigInt(1000000));
let safe_deserialized = FheInt32.safe_deserialize(safe_serialized, BigInt(1000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I32_MIN);
});
test('hlapi_decompress_public_key_then_encrypt_decrypt_int32_small', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers_small()
.build();
let clientKey = TfheClientKey.generate(config);
var startTime = performance.now()
let compressedPublicKey = TfheCompressedPublicKey.new(clientKey);
var endTime = performance.now()
let data = compressedPublicKey.serialize()
let publicKey = compressedPublicKey.decompress();
var startTime = performance.now()
let encrypted = FheInt32.encrypt_with_public_key(I32_MIN, publicKey);
var endTime = performance.now()
let ser = encrypted.serialize();
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I32_MIN);
let serialized = encrypted.serialize();
let deserialized = FheInt32.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I32_MIN);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt32.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I32_MIN);
});
test('hlapi_client_key_encrypt_decrypt_int128_big', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers()
.build();
let clientKey = TfheClientKey.generate(config);
let encrypted = FheInt128.encrypt_with_client_key(I128_MIN, clientKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I128_MIN);
let serialized = encrypted.serialize();
let deserialized = FheInt128.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I128_MIN);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt128.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I128_MIN);
// Compressed
let compressed_encrypted = CompressedFheInt128.encrypt_with_client_key(I128_MIN, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
let compressed_deserialized = CompressedFheInt128.deserialize(compressed_serialized);
let decompressed = compressed_deserialized.decompress()
decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I128_MIN);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheInt128.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, I128_MIN);
});
test('hlapi_client_key_encrypt_decrypt_int128_small', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers_small()
.build();
let clientKey = TfheClientKey.generate(config);
let encrypted = FheInt128.encrypt_with_client_key(I128_MIN, clientKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I128_MIN);
let serialized = encrypted.serialize();
let deserialized = FheInt128.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I128_MIN);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt128.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I128_MIN);
// Compressed
let compressed_encrypted = CompressedFheInt128.encrypt_with_client_key(I128_MIN, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
let compressed_deserialized = CompressedFheInt128.deserialize(compressed_serialized);
let decompressed = compressed_deserialized.decompress()
decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I128_MIN);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheInt128.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, I128_MIN);
});
test('hlapi_client_key_encrypt_decrypt_int256_big', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers()
.build();
let clientKey = TfheClientKey.generate(config);
let round_trip_encrypt = (value) => {
let encrypted = FheInt256.encrypt_with_client_key(value, clientKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, value);
let serialized = encrypted.serialize();
let deserialized = FheInt256.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, value);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, value);
// Compressed
let compressed_encrypted = CompressedFheInt256.encrypt_with_client_key(value, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
let compressed_deserialized = CompressedFheInt256.deserialize(compressed_serialized);
let decompressed = compressed_deserialized.decompress();
decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, value);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheInt256.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, value);
};
round_trip_encrypt(I256_MIN);
round_trip_encrypt(I256_MAX);
round_trip_encrypt(BigInt(-1));
round_trip_encrypt(BigInt(1));
round_trip_encrypt(BigInt(-128));
round_trip_encrypt(BigInt(128));
});
test('hlapi_client_key_encrypt_decrypt_int256_small', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers_small()
.build();
let clientKey = TfheClientKey.generate(config);
let round_trip_encrypt = (value) => {
let encrypted = FheInt256.encrypt_with_client_key(value, clientKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, value);
let serialized = encrypted.serialize();
let deserialized = FheInt256.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, value);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, value);
// Compressed
let compressed_encrypted = CompressedFheInt256.encrypt_with_client_key(value, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
let compressed_deserialized = CompressedFheInt256.deserialize(compressed_serialized);
let decompressed = compressed_deserialized.decompress()
decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, value);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheInt256.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, value);
}
round_trip_encrypt(I256_MIN);
round_trip_encrypt(I256_MAX);
round_trip_encrypt(BigInt(-1));
round_trip_encrypt(BigInt(1));
round_trip_encrypt(BigInt(-128));
round_trip_encrypt(BigInt(128));
});
test('hlapi_decompress_public_key_then_encrypt_decrypt_int256_small', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers_small()
.build();
let clientKey = TfheClientKey.generate(config);
let compressedPublicKey = TfheCompressedPublicKey.new(clientKey);
let publicKey = compressedPublicKey.decompress();
let encrypted = FheInt256.encrypt_with_public_key(I256_MIN, publicKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I256_MIN);
let serialized = encrypted.serialize();
let deserialized = FheInt256.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I256_MIN);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I256_MIN);
});
test('hlapi_public_key_encrypt_decrypt_int256_small', (t) => {
let config = TfheConfigBuilder.all_disabled()
.enable_default_integers_small()
.build();
let clientKey = TfheClientKey.generate(config);
let publicKey = TfhePublicKey.new(clientKey);
let encrypted = FheInt256.encrypt_with_public_key(I256_MIN, publicKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I256_MIN);
let serialized = encrypted.serialize();
let deserialized = FheInt256.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I256_MIN);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I256_MIN);
});
//////////////////////////////////////////////////////////////////////////////
/// 32 bits compact
//////////////////////////////////////////////////////////////////////////////
function hlapi_compact_public_key_encrypt_decrypt_int32_single(config) {
let clientKey = TfheClientKey.generate(config);
let publicKey = TfheCompactPublicKey.new(clientKey);
let encrypted = FheInt32.encrypt_with_compact_public_key(I32_MIN, publicKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I32_MIN);
let serialized = encrypted.serialize();
let deserialized = FheInt32.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I32_MIN);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt32.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I32_MIN);
}
test('hlapi_compact_public_key_encrypt_decrypt_int32_big_single', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int32_single(config);
});
test('hlapi_compact_public_key_encrypt_decrypt_int32_small_single', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int32_single(config);
});
function hlapi_compact_public_key_encrypt_decrypt_int32_single_compact(config) {
let clientKey = TfheClientKey.generate(config);
let publicKey = TfheCompactPublicKey.new(clientKey);
let compact_encrypted = CompactFheInt32.encrypt_with_compact_public_key(I32_MIN, publicKey);
let encrypted = compact_encrypted.expand();
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I32_MIN);
let serialized = compact_encrypted.serialize();
let deserialized = CompactFheInt32.deserialize(serialized);
let deserialized_decrypted = deserialized.expand().decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I32_MIN);
let safe_serialized = compact_encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = CompactFheInt32.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.expand().decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I32_MIN);
}
test('hlapi_compact_public_key_encrypt_decrypt_int32_small_single_compact', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int32_single_compact(config);
});
test('hlapi_compact_public_key_encrypt_decrypt_int32_big_single_compact', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int32_single_compact(config);
});
function hlapi_compact_public_key_encrypt_decrypt_int32_list_compact(config) {
let clientKey = TfheClientKey.generate(config);
let publicKey = TfheCompactPublicKey.new(clientKey);
let values = [0, 1, 2394, I32_MIN];
let compact_list = CompactFheInt32List.encrypt_with_compact_public_key(values, publicKey);
{
let encrypted_list = compact_list.expand();
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}
}
let serialized_list = compact_list.serialize();
let deserialized_list = CompactFheInt32List.deserialize(serialized_list);
let encrypted_list = deserialized_list.expand();
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}
}
test('hlapi_compact_public_key_encrypt_decrypt_int32_small_list_compact', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int32_list_compact(config);
});
test('hlapi_compact_public_key_encrypt_decrypt_int32_big_list_compact', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int32_list_compact(config);
});
//////////////////////////////////////////////////////////////////////////////
/// 256 bits compact
//////////////////////////////////////////////////////////////////////////////
function hlapi_compact_public_key_encrypt_decrypt_int256_single(config) {
let clientKey = TfheClientKey.generate(config);
let publicKey = TfheCompactPublicKey.new(clientKey);
let encrypted = FheInt256.encrypt_with_compact_public_key(I256_MIN, publicKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I256_MIN);
let serialized = encrypted.serialize();
let deserialized = FheInt256.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I256_MIN);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheInt256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I256_MIN);
}
test('hlapi_compact_public_key_encrypt_decrypt_int256_big_single', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int256_single(config);
});
test('hlapi_compact_public_key_encrypt_decrypt_int256_small_single', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int256_single(config);
});
function hlapi_compact_public_key_encrypt_decrypt_int256_single_compact(config) {
let clientKey = TfheClientKey.generate(config);
let publicKey = TfheCompactPublicKey.new(clientKey);
let compact_encrypted = CompactFheInt256.encrypt_with_compact_public_key(I256_MIN, publicKey);
let encrypted = compact_encrypted.expand();
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, I256_MIN);
let serialized = compact_encrypted.serialize();
let deserialized = CompactFheInt256.deserialize(serialized);
let deserialized_decrypted = deserialized.expand().decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, I256_MIN);
let safe_serialized = compact_encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = CompactFheInt256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.expand().decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, I256_MIN);
}
test('hlapi_compact_public_key_encrypt_decrypt_int256_small_single_compact', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int256_single_compact(config);
});
test('hlapi_compact_public_key_encrypt_decrypt_int256_big_single_compact', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int256_single_compact(config);
});
function hlapi_compact_public_key_encrypt_decrypt_int256_list_compact(config) {
let clientKey = TfheClientKey.generate(config);
let publicKey = TfheCompactPublicKey.new(clientKey);
let values = [BigInt(0), BigInt(1), BigInt(2394), BigInt(-2309840239), BigInt(I32_MIN), I256_MIN, I128_MIN];
let compact_list = CompactFheInt256List.encrypt_with_compact_public_key(values, publicKey);
{
let encrypted_list = compact_list.expand();
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}
}
let serialized_list = compact_list.serialize();
let deserialized_list = CompactFheInt256List.deserialize(serialized_list);
let encrypted_list = deserialized_list.expand();
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}
}
test('hlapi_compact_public_key_encrypt_decrypt_int256_small_list_compact', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int256_list_compact(config);
});
test('hlapi_compact_public_key_encrypt_decrypt_int256_big_list_compact', (t) => {
const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS);
let config = TfheConfigBuilder.all_disabled()
.enable_custom_integers(block_params)
.build();
hlapi_compact_public_key_encrypt_decrypt_int256_list_compact(config);
});

View File

@@ -29,9 +29,9 @@ const U256_MAX = BigInt("1157920892373161954235709850086879078532699846656405640
const U128_MAX = BigInt("340282366920938463463374607431768211455");
const U32_MAX = 4294967295;
// This use full to debug test
//
// Note that the test hlapi_panic
// This is useful to debug test
//
// Note that the test hlapi_panic
// purposefully creates a panic, to some panic message
// will be printed and tess will be ok
init_panic_hook();
@@ -108,6 +108,11 @@ test('hlapi_client_key_encrypt_decrypt_uint8_big', (t) => {
let deserialized = FheUint8.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, clear);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint8.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, clear);
});
test('hlapi_compressed_public_client_uint8_big', (t) => {
@@ -125,6 +130,13 @@ test('hlapi_compressed_public_client_uint8_big', (t) => {
let decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, clear);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheUint8.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
let safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, clear);
});
test('hlapi_public_key_encrypt_decrypt_uint32_small', (t) => {
@@ -139,11 +151,16 @@ test('hlapi_public_key_encrypt_decrypt_uint32_small', (t) => {
let encrypted = FheUint32.encrypt_with_public_key(U32_MAX, publicKey);
let decrypted = encrypted.decrypt(clientKey);
assert.deepStrictEqual(decrypted, U32_MAX);
let serialized = encrypted.serialize();
let deserialized = FheUint32.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U32_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(1000000));
let safe_deserialized = FheUint32.safe_deserialize(safe_serialized, BigInt(1000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U32_MAX);
});
test('hlapi_decompress_public_key_then_encrypt_decrypt_uint32_small', (t) => {
@@ -174,6 +191,11 @@ test('hlapi_decompress_public_key_then_encrypt_decrypt_uint32_small', (t) => {
let deserialized = FheUint32.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U32_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint32.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U32_MAX);
});
test('hlapi_client_key_encrypt_decrypt_uint128_big', (t) => {
@@ -193,6 +215,11 @@ test('hlapi_client_key_encrypt_decrypt_uint128_big', (t) => {
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U128_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint128.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U128_MAX);
// Compressed
let compressed_encrypted = CompressedFheUint128.encrypt_with_client_key(U128_MAX, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
@@ -201,6 +228,13 @@ test('hlapi_client_key_encrypt_decrypt_uint128_big', (t) => {
decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, U128_MAX);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheUint128.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, U128_MAX);
});
test('hlapi_client_key_encrypt_decrypt_uint128_small', (t) => {
@@ -220,6 +254,11 @@ test('hlapi_client_key_encrypt_decrypt_uint128_small', (t) => {
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U128_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint128.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U128_MAX);
// Compressed
let compressed_encrypted = CompressedFheUint128.encrypt_with_client_key(U128_MAX, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
@@ -228,6 +267,13 @@ test('hlapi_client_key_encrypt_decrypt_uint128_small', (t) => {
decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, U128_MAX);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheUint128.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, U128_MAX);
});
test('hlapi_client_key_encrypt_decrypt_uint256_big', (t) => {
@@ -247,6 +293,11 @@ test('hlapi_client_key_encrypt_decrypt_uint256_big', (t) => {
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U256_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U256_MAX);
// Compressed
let compressed_encrypted = CompressedFheUint256.encrypt_with_client_key(U256_MAX, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
@@ -255,6 +306,13 @@ test('hlapi_client_key_encrypt_decrypt_uint256_big', (t) => {
decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, U256_MAX);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheUint256.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, U256_MAX);
});
test('hlapi_client_key_encrypt_decrypt_uint256_small', (t) => {
@@ -274,6 +332,11 @@ test('hlapi_client_key_encrypt_decrypt_uint256_small', (t) => {
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U256_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U256_MAX);
// Compressed
let compressed_encrypted = CompressedFheUint256.encrypt_with_client_key(U256_MAX, clientKey);
let compressed_serialized = compressed_encrypted.serialize();
@@ -282,6 +345,13 @@ test('hlapi_client_key_encrypt_decrypt_uint256_small', (t) => {
decrypted = decompressed.decrypt(clientKey);
assert.deepStrictEqual(decrypted, U256_MAX);
let compressed_safe_serialized = compressed_encrypted.safe_serialize(BigInt(10000000));
let compressed_safe_deserialized = CompressedFheUint256.safe_deserialize(compressed_safe_serialized, BigInt(10000000));
let safe_decompressed = compressed_safe_deserialized.decompress()
safe_decrypted = safe_decompressed.decrypt(clientKey);
assert.deepStrictEqual(safe_decrypted, U256_MAX);
});
test('hlapi_decompress_public_key_then_encrypt_decrypt_uint256_small', (t) => {
@@ -303,6 +373,11 @@ test('hlapi_decompress_public_key_then_encrypt_decrypt_uint256_small', (t) => {
let deserialized = FheUint256.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U256_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U256_MAX);
});
test('hlapi_public_key_encrypt_decrypt_uint256_small', (t) => {
@@ -322,14 +397,19 @@ test('hlapi_public_key_encrypt_decrypt_uint256_small', (t) => {
let deserialized = FheUint256.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U256_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U256_MAX);
});
//////////////////////////////////////////////////////////////////////////////
/// 32 bits compact
/// 32 bits compact
//////////////////////////////////////////////////////////////////////////////
function hlapi_compact_public_key_encrypt_decrypt_uint32_single(config) {
let clientKey = TfheClientKey.generate(config);
let publicKey = TfheCompactPublicKey.new(clientKey);
@@ -342,6 +422,11 @@ function hlapi_compact_public_key_encrypt_decrypt_uint32_single(config) {
let deserialized = FheUint32.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U32_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint32.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U32_MAX);
}
test('hlapi_compact_public_key_encrypt_decrypt_uint32_big_single', (t) => {
@@ -375,6 +460,11 @@ function hlapi_compact_public_key_encrypt_decrypt_uint32_single_compact(config)
let deserialized = CompactFheUint32.deserialize(serialized);
let deserialized_decrypted = deserialized.expand().decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U32_MAX);
let safe_serialized = compact_encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = CompactFheUint32.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.expand().decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U32_MAX);
}
test('hlapi_compact_public_key_encrypt_decrypt_uint32_small_single_compact', (t) => {
@@ -408,8 +498,7 @@ function hlapi_compact_public_key_encrypt_decrypt_uint32_list_compact(config) {
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}
@@ -420,8 +509,7 @@ function hlapi_compact_public_key_encrypt_decrypt_uint32_list_compact(config) {
let encrypted_list = deserialized_list.expand();
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}
@@ -447,7 +535,7 @@ test('hlapi_compact_public_key_encrypt_decrypt_uint32_big_list_compact', (t) =>
//////////////////////////////////////////////////////////////////////////////
/// 256 bits compact
/// 256 bits compact
//////////////////////////////////////////////////////////////////////////////
function hlapi_compact_public_key_encrypt_decrypt_uint256_single(config) {
@@ -462,6 +550,11 @@ function hlapi_compact_public_key_encrypt_decrypt_uint256_single(config) {
let deserialized = FheUint256.deserialize(serialized);
let deserialized_decrypted = deserialized.decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U256_MAX);
let safe_serialized = encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = FheUint256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U256_MAX);
}
test('hlapi_compact_public_key_encrypt_decrypt_uint256_big_single', (t) => {
@@ -495,6 +588,11 @@ function hlapi_compact_public_key_encrypt_decrypt_uint256_single_compact(config)
let deserialized = CompactFheUint256.deserialize(serialized);
let deserialized_decrypted = deserialized.expand().decrypt(clientKey);
assert.deepStrictEqual(deserialized_decrypted, U256_MAX);
let safe_serialized = compact_encrypted.safe_serialize(BigInt(10000000));
let safe_deserialized = CompactFheUint256.safe_deserialize(safe_serialized, BigInt(10000000));
let safe_deserialized_decrypted = safe_deserialized.expand().decrypt(clientKey);
assert.deepStrictEqual(safe_deserialized_decrypted, U256_MAX);
}
test('hlapi_compact_public_key_encrypt_decrypt_uint256_small_single_compact', (t) => {
@@ -528,8 +626,7 @@ function hlapi_compact_public_key_encrypt_decrypt_uint256_list_compact(config) {
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}
@@ -540,8 +637,7 @@ function hlapi_compact_public_key_encrypt_decrypt_uint256_list_compact(config) {
let encrypted_list = deserialized_list.expand();
assert.deepStrictEqual(encrypted_list.length, values.length);
for (let i = 0; i < values.length; i++)
{
for (let i = 0; i < values.length; i++) {
let decrypted = encrypted_list[i].decrypt(clientKey);
assert.deepStrictEqual(decrypted, values[i]);
}

View File

@@ -160,7 +160,7 @@ impl Bootstrapper {
&mut self,
cks: &ClientKey,
) -> Result<ServerKey, Box<dyn std::error::Error>> {
let standard_bootstraping_key: LweBootstrapKeyOwned<u32> =
let standard_bootstrapping_key: LweBootstrapKeyOwned<u32> =
par_allocate_and_generate_new_lwe_bootstrap_key(
&cks.lwe_secret_key,
&cks.glwe_secret_key,
@@ -173,14 +173,14 @@ impl Bootstrapper {
// creation of the bootstrapping key in the Fourier domain
let mut fourier_bsk = FourierLweBootstrapKey::new(
standard_bootstraping_key.input_lwe_dimension(),
standard_bootstraping_key.glwe_size(),
standard_bootstraping_key.polynomial_size(),
standard_bootstraping_key.decomposition_base_log(),
standard_bootstraping_key.decomposition_level_count(),
standard_bootstrapping_key.input_lwe_dimension(),
standard_bootstrapping_key.glwe_size(),
standard_bootstrapping_key.polynomial_size(),
standard_bootstrapping_key.decomposition_base_log(),
standard_bootstrapping_key.decomposition_level_count(),
);
let fft = Fft::new(standard_bootstraping_key.polynomial_size());
let fft = Fft::new(standard_bootstrapping_key.polynomial_size());
let fft = fft.as_view();
self.computation_buffers.resize(
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized_requirement(fft)
@@ -190,7 +190,7 @@ impl Bootstrapper {
// Conversion to fourier domain
par_convert_standard_lwe_bootstrap_key_to_fourier(
&standard_bootstraping_key,
&standard_bootstrapping_key,
&mut fourier_bsk,
);

View File

@@ -41,7 +41,7 @@ pub(crate) trait BinaryGatesAssignEngine<L, R, K> {
fn xnor_assign(&mut self, ct_left: L, ct_right: R, server_key: &K);
}
/// Trait to be able to acces thread_local
/// Trait to be able to access thread_local
/// engines in a generic way
pub(crate) trait WithThreadLocalEngine {
fn with_thread_local_mut<R, F>(func: F) -> R

View File

@@ -1,15 +1,18 @@
use crate::boolean::keycache::KEY_CACHE;
use crate::boolean::prelude::*;
#[test]
fn test_cast_boolean() {
let (client_key_1, _server_key_1): (ClientKey, ServerKey) = gen_keys();
let (client_key_2, _server_key_2): (ClientKey, ServerKey) = gen_keys();
let keys_1 = KEY_CACHE.get_from_param(DEFAULT_PARAMETERS);
let client_key_1 = keys_1.client_key();
let keys_2 = KEY_CACHE.get_from_param(DEFAULT_PARAMETERS);
let client_key_2 = keys_2.client_key();
let ksk_params = BooleanKeySwitchingParameters::new(
client_key_2.parameters.ks_base_log,
client_key_2.parameters.ks_level,
);
let ksk = KeySwitchingKey::new(&client_key_1, &client_key_2, ksk_params);
let ksk = KeySwitchingKey::new(client_key_1, client_key_2, ksk_params);
let mut ct_true = client_key_1.encrypt(true);
ct_true = ksk.cast(&ct_true);
@@ -24,14 +27,16 @@ fn test_cast_boolean() {
#[test]
fn test_cast_into_boolean() {
let (client_key_1, server_key_1): (ClientKey, ServerKey) = gen_keys();
let (client_key_2, _server_key_2): (ClientKey, ServerKey) = gen_keys();
let keys_1 = KEY_CACHE.get_from_param(DEFAULT_PARAMETERS);
let (client_key_1, server_key_1) = (keys_1.client_key(), keys_1.server_key());
let keys_2 = KEY_CACHE.get_from_param(DEFAULT_PARAMETERS);
let client_key_2 = keys_2.client_key();
let ksk_params = BooleanKeySwitchingParameters::new(
client_key_2.parameters.ks_base_log,
client_key_2.parameters.ks_level,
);
let ksk = KeySwitchingKey::new(&client_key_1, &client_key_2, ksk_params);
let ksk = KeySwitchingKey::new(client_key_1, client_key_2, ksk_params);
let ct_true = client_key_1.encrypt(true);
let mut ct_cast = server_key_1.trivial_encrypt(false);

View File

@@ -36,7 +36,7 @@ use serde::{Deserialize, Serialize};
/// 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
/// key`)[`super::public_key::PublicKey`] sizes are much more manageable and should 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)]

View File

@@ -88,27 +88,32 @@ impl CompressedPublicKey {
#[cfg(test)]
mod tests {
use crate::boolean::keycache::KEY_CACHE;
use crate::boolean::prelude::{
BinaryBooleanGates, BooleanParameters, ClientKey, CompressedPublicKey, ServerKey,
DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
BinaryBooleanGates, BooleanParameters, CompressedPublicKey, DEFAULT_PARAMETERS,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
};
use crate::boolean::random_boolean;
#[cfg(not(feature = "__coverage"))]
const NB_TEST: usize = 32;
#[cfg(feature = "__coverage")]
const NB_TEST: usize = 1;
#[test]
fn test_compressed_public_key_default_parameters() {
test_compressed_public_key(DEFAULT_PARAMETERS);
}
#[cfg(not(feature = "__coverage"))]
#[test]
fn 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) {
let cks = ClientKey::new(&parameters);
let sks = ServerKey::new(&cks);
let cpks = CompressedPublicKey::new(&cks);
let keys = KEY_CACHE.get_from_param(parameters);
let (cks, sks) = (keys.client_key(), keys.server_key());
let cpks = CompressedPublicKey::new(cks);
for _ in 0..NB_TEST {
let b1 = random_boolean();

View File

@@ -80,29 +80,34 @@ impl From<CompressedPublicKey> for PublicKey {
#[cfg(test)]
mod tests {
use crate::boolean::keycache::KEY_CACHE;
use crate::boolean::prelude::{
BinaryBooleanGates, BooleanParameters, ClientKey, CompressedPublicKey, ServerKey,
DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
BinaryBooleanGates, BooleanParameters, CompressedPublicKey, DEFAULT_PARAMETERS,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
};
use crate::boolean::random_boolean;
use super::PublicKey;
#[cfg(not(feature = "__coverage"))]
const NB_TEST: usize = 32;
#[cfg(feature = "__coverage")]
const NB_TEST: usize = 1;
#[test]
fn test_public_key_default_parameters() {
test_public_key(DEFAULT_PARAMETERS);
}
#[cfg(not(feature = "__coverage"))]
#[test]
fn test_public_key_tfhe_lib_parameters() {
test_public_key(PARAMETERS_ERROR_PROB_2_POW_MINUS_165);
}
fn test_public_key(parameters: BooleanParameters) {
let cks = ClientKey::new(&parameters);
let sks = ServerKey::new(&cks);
let pks = PublicKey::new(&cks);
let keys = KEY_CACHE.get_from_param(parameters);
let (cks, sks) = (keys.client_key(), keys.server_key());
let pks = PublicKey::new(cks);
for _ in 0..NB_TEST {
let b1 = random_boolean();
@@ -129,15 +134,16 @@ mod tests {
test_public_key(DEFAULT_PARAMETERS);
}
#[cfg(not(feature = "__coverage"))]
#[test]
fn 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) {
let cks = ClientKey::new(&parameters);
let sks = ServerKey::new(&cks);
let cpks = CompressedPublicKey::new(&cks);
let keys = KEY_CACHE.get_from_param(parameters);
let (cks, sks) = (keys.client_key(), keys.server_key());
let cpks = CompressedPublicKey::new(cks);
let pks = PublicKey::from(cpks);
for _ in 0..NB_TEST {

View File

@@ -69,6 +69,7 @@ mod default_parameters_tests {
}
}
#[cfg(not(feature = "__coverage"))]
mod low_prob_parameters_tests {
use super::*;
use crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165;
@@ -161,6 +162,7 @@ mod default_parameters_ks_pbs_tests {
}
}
#[cfg(not(feature = "__coverage"))]
mod low_prob_parameters_ks_pbs_tests {
use super::*;
use crate::boolean::parameters::PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS;
@@ -207,6 +209,7 @@ mod low_prob_parameters_ks_pbs_tests {
}
}
#[cfg(not(feature = "__coverage"))]
mod tfhe_lib_parameters_tests {
use super::*;
use crate::boolean::parameters::TFHE_LIB_PARAMETERS;

View File

@@ -1,6 +1,157 @@
use super::utils::*;
use std::os::raw::c_int;
#[no_mangle]
pub unsafe extern "C" fn core_crypto_generate_binary_lwe_secret_key(
output_lwe_sk_ptr: *mut u64,
lwe_sk_dim: usize,
) -> c_int {
catch_panic(|| {
use crate::core_crypto::prelude::*;
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
// Create the LweSecretKey
let output_lwe_sk_slice = std::slice::from_raw_parts_mut(output_lwe_sk_ptr, lwe_sk_dim);
let mut lwe_sk = LweSecretKey::from_container(output_lwe_sk_slice);
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
generate_binary_lwe_secret_key(&mut lwe_sk, &mut secret_generator);
})
}
#[no_mangle]
pub unsafe extern "C" fn core_crypto_lwe_encrypt(
output_ct_ptr: *mut u64,
pt: u64,
lwe_sk_ptr: *const u64,
lwe_sk_dim: usize,
lwe_encryption_std_dev: f64,
) -> c_int {
catch_panic(|| {
use crate::core_crypto::prelude::*;
let lwe_sk_slice = std::slice::from_raw_parts(lwe_sk_ptr, lwe_sk_dim);
let lwe_sk = LweSecretKey::from_container(lwe_sk_slice);
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let plaintext = Plaintext(pt);
let output_ct = std::slice::from_raw_parts_mut(output_ct_ptr, lwe_sk_dim + 1);
let mut ct = LweCiphertext::from_container(output_ct, CiphertextModulus::new_native());
let lwe_encryption_std_dev = StandardDev(lwe_encryption_std_dev);
encrypt_lwe_ciphertext(
&lwe_sk,
&mut ct,
plaintext,
lwe_encryption_std_dev,
&mut encryption_generator,
);
})
}
#[no_mangle]
pub unsafe extern "C" fn core_crypto_lwe_decrypt(
output_pt: *mut u64,
input_ct_ptr: *const u64,
lwe_sk_ptr: *const u64,
lwe_sk_dim: usize,
) -> c_int {
catch_panic(|| {
use crate::core_crypto::prelude::*;
let lwe_sk_slice = std::slice::from_raw_parts(lwe_sk_ptr, lwe_sk_dim);
let lwe_sk = LweSecretKey::from_container(lwe_sk_slice);
let input_ct = std::slice::from_raw_parts(input_ct_ptr, lwe_sk_dim + 1);
let ct = LweCiphertext::from_container(input_ct, CiphertextModulus::new_native());
let plaintext = decrypt_lwe_ciphertext(&lwe_sk, &ct);
*output_pt = plaintext.0;
})
}
#[no_mangle]
pub unsafe extern "C" fn core_crypto_par_generate_lwe_bootstrapping_key(
output_bsk_ptr: *mut u64,
bsk_base_log: usize,
bsk_level_count: usize,
input_lwe_sk_ptr: *const u64,
input_lwe_sk_dim: usize,
output_glwe_sk_ptr: *const u64,
output_glwe_sk_dim: usize,
output_glwe_sk_poly_size: usize,
glwe_encryption_std_dev: f64,
) -> c_int {
catch_panic(|| {
use crate::core_crypto::prelude::*;
let input_lwe_sk_slice = std::slice::from_raw_parts(input_lwe_sk_ptr, input_lwe_sk_dim);
let input_lwe_sk = LweSecretKey::from_container(input_lwe_sk_slice);
let output_glwe_sk_dim = GlweDimension(output_glwe_sk_dim);
let output_glwe_sk_poly_size = PolynomialSize(output_glwe_sk_poly_size);
let output_glwe_sk_size =
glwe_ciphertext_mask_size(output_glwe_sk_dim, output_glwe_sk_poly_size);
let output_glwe_sk_slice =
std::slice::from_raw_parts(output_glwe_sk_ptr, output_glwe_sk_size);
let output_glwe_sk =
GlweSecretKey::from_container(output_glwe_sk_slice, output_glwe_sk_poly_size);
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_random_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let lwe_base_log = DecompositionBaseLog(bsk_base_log);
let lwe_level_count = DecompositionLevelCount(bsk_level_count);
let lwe_slice_len = {
let bsk = LweBootstrapKeyOwned::new(
0u64,
output_glwe_sk.glwe_dimension().to_glwe_size(),
output_glwe_sk.polynomial_size(),
lwe_base_log,
lwe_level_count,
input_lwe_sk.lwe_dimension(),
CiphertextModulus::new_native(),
);
bsk.into_container().len()
};
let bsk_slice = std::slice::from_raw_parts_mut(output_bsk_ptr, lwe_slice_len);
let mut bsk = LweBootstrapKey::from_container(
bsk_slice,
output_glwe_sk.glwe_dimension().to_glwe_size(),
output_glwe_sk.polynomial_size(),
lwe_base_log,
lwe_level_count,
CiphertextModulus::new_native(),
);
let glwe_encryption_std_dev = StandardDev(glwe_encryption_std_dev);
par_generate_lwe_bootstrap_key(
&input_lwe_sk,
&output_glwe_sk,
&mut bsk,
glwe_encryption_std_dev,
&mut encryption_random_generator,
)
})
}
#[no_mangle]
pub unsafe extern "C" fn core_crypto_lwe_multi_bit_bootstrapping_key_element_size(
input_lwe_sk_dim: usize,
@@ -46,12 +197,8 @@ pub unsafe extern "C" fn core_crypto_par_generate_lwe_multi_bit_bootstrapping_ke
lwe_multi_bit_level_count: usize,
lwe_multi_bit_grouping_factor: usize,
glwe_encryption_std_dev: f64,
seed_low_bytes: u64,
seed_high_bytes: u64,
) -> c_int {
catch_panic(|| {
use crate::core_crypto::commons::generators::DeterministicSeeder;
use crate::core_crypto::commons::math::random::Seed;
use crate::core_crypto::prelude::*;
let input_lwe_sk_slice = std::slice::from_raw_parts(input_lwe_sk_ptr, input_lwe_sk_dim);
@@ -66,17 +213,10 @@ pub unsafe extern "C" fn core_crypto_par_generate_lwe_multi_bit_bootstrapping_ke
let output_glwe_sk =
GlweSecretKey::from_container(output_glwe_sk_slice, output_glwe_sk_poly_size);
let seed_low_bytes: u128 = seed_low_bytes.into();
let seed_high_bytes: u128 = seed_high_bytes.into();
let seed = (seed_high_bytes << 64) | seed_low_bytes;
let mut deterministic_seeder =
DeterministicSeeder::<ActivatedRandomGenerator>::new(Seed(seed));
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_random_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
deterministic_seeder.seed(),
&mut deterministic_seeder,
);
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let lwe_multi_bit_base_log = DecompositionBaseLog(lwe_multi_bit_base_log);
let lwe_multi_bit_level_count = DecompositionLevelCount(lwe_multi_bit_level_count);

View File

@@ -0,0 +1,22 @@
/// w0 and w1 are words in little endian order
/// using two's complement representation
#[repr(C)]
#[derive(Copy, Clone)]
pub struct I128 {
pub w0: u64,
pub w1: u64,
}
impl From<i128> for I128 {
fn from(value: i128) -> Self {
let w0 = (value & (u64::MAX as i128)) as u64;
let w1 = (value >> 64) as u64;
Self { w0, w1 }
}
}
impl From<I128> for i128 {
fn from(value: I128) -> Self {
((value.w1 as i128) << 64u128) | value.w0 as i128
}
}

View File

@@ -0,0 +1,27 @@
/// w0, w1, w2, w3 are words in little endian order
/// using two's complement representation
#[repr(C)]
#[derive(Copy, Clone)]
pub struct I256 {
pub w0: u64,
pub w1: u64,
pub w2: u64,
pub w3: u64,
}
impl From<crate::integer::I256> for I256 {
fn from(value: crate::integer::I256) -> Self {
Self {
w0: value.0[0],
w1: value.0[1],
w2: value.0[2],
w3: value.0[3],
}
}
}
impl From<I256> for crate::integer::I256 {
fn from(value: I256) -> Self {
Self([value.w0, value.w1, value.w2, value.w3])
}
}

View File

@@ -1,10 +1,12 @@
use crate::c_api::high_level_api::keys::{ClientKey, CompactPublicKey, PublicKey};
use crate::c_api::high_level_api::keys::CompactPublicKey;
use crate::high_level_api::prelude::*;
use std::ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
};
use crate::c_api::high_level_api::i128::I128;
use crate::c_api::high_level_api::i256::I256;
use crate::c_api::high_level_api::u128::U128;
use crate::c_api::high_level_api::u256::U256;
use crate::c_api::utils::*;
@@ -12,10 +14,17 @@ use std::os::raw::c_int;
/// Implement C functions for all the operations supported by a integer type,
/// which should also be accessible from C API
///
/// We require the shift amount to be an unsigned type,
/// so to be able to use that macro for signed integers (which have a signed clear type)
/// we also accept an additional clear_shift_type
macro_rules! impl_operations_for_integer_type {
(
name: $name:ident,
clear_scalar_type: $clear_scalar_type:ty
fhe_unsigned_type: $fhe_unsigned_type:ty,
clear_scalar_type: $clear_scalar_type:ty,
clear_shift_type: $clear_shift_type:ty
$(,)?
) => {
impl_binary_fn_on_type!($name =>
add,
@@ -24,8 +33,6 @@ macro_rules! impl_operations_for_integer_type {
bitand,
bitor,
bitxor,
shl,
shr,
eq,
ne,
ge,
@@ -37,6 +44,15 @@ macro_rules! impl_operations_for_integer_type {
div,
rem,
);
// handle shift separately as they require
// rhs to be an unsigned type
impl_binary_fn_on_type!(
lhs_type: $name,
rhs_type: $fhe_unsigned_type,
binary_fn_names: shl, shr, rotate_right, rotate_left,
);
impl_binary_assign_fn_on_type!($name =>
add_assign,
sub_assign,
@@ -44,11 +60,18 @@ macro_rules! impl_operations_for_integer_type {
bitand_assign,
bitor_assign,
bitxor_assign,
shl_assign,
shr_assign,
div_assign,
rem_assign,
);
// handle shift separately as they require
// rhs to be an unsigned type
impl_binary_assign_fn_on_type!(
lhs_type: $name,
rhs_type: $fhe_unsigned_type,
binary_fn_names: shl_assign, shr_assign, rotate_right_assign, rotate_left_assign,
);
impl_scalar_binary_fn_on_type!($name, $clear_scalar_type =>
add,
sub,
@@ -56,8 +79,6 @@ macro_rules! impl_operations_for_integer_type {
bitand,
bitor,
bitxor,
shl,
shr,
eq,
ne,
ge,
@@ -66,8 +87,6 @@ macro_rules! impl_operations_for_integer_type {
lt,
min,
max,
rotate_right,
rotate_left,
div,
rem,
);
@@ -78,12 +97,23 @@ macro_rules! impl_operations_for_integer_type {
bitand_assign,
bitor_assign,
bitxor_assign,
div_assign,
rem_assign,
);
// handle shift separately as they require
// rhs to be an unsigned type
impl_scalar_binary_fn_on_type!($name, $clear_shift_type =>
shl,
shr,
rotate_right,
rotate_left,
);
impl_scalar_binary_assign_fn_on_type!($name, $clear_shift_type =>
shl_assign,
shr_assign,
rotate_right_assign,
rotate_left_assign,
div_assign,
rem_assign,
);
impl_unary_fn_on_type!($name => neg, not);
@@ -104,8 +134,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 rhs = <$clear_scalar_type as $crate::c_api::high_level_api::utils::CApiIntegerType
>::to_rust(rhs);
let (q, r) = (&lhs.0).div_rem(rhs);
@@ -153,29 +183,53 @@ macro_rules! impl_operations_for_integer_type {
*result = Box::into_raw(Box::new($name(r)));
})
}
// map cmux to if_then_else
pub unsafe extern "C" fn [<$name:snake _cmux>](
condition_ct: *const $name,
then_ct: *const $name,
else_ct: *const $name,
result: *mut *mut $name,
) -> c_int {
[<$name:snake _if_then_else>](condition_ct, then_ct, else_ct, result)
}
}
};
}
/// Creates a type that will act as an opaque wrapper
/// aroung a tfhe integer.
/// around a tfhe integer.
///
/// It also implements binary operations for this wrapper type
macro_rules! create_integer_wrapper_type {
(
name: $name:ident,
clear_scalar_type: $clear_scalar_type:ty
fhe_unsigned_type: $fhe_unsigned_type:ty,
clear_scalar_type: $clear_scalar_type:ty,
clear_shift_type: $clear_shift_type:ty
$(,)?
) => {
pub struct $name($crate::high_level_api::$name);
impl_destroy_on_type!($name);
impl_operations_for_integer_type!(name: $name, clear_scalar_type: $clear_scalar_type);
impl_operations_for_integer_type!(
name: $name,
fhe_unsigned_type: $fhe_unsigned_type,
clear_scalar_type: $clear_scalar_type,
clear_shift_type: $clear_shift_type,
);
impl_serialize_deserialize_on_type!($name);
impl_clone_on_type!($name);
impl_safe_serialize_on_type!($name);
impl_safe_deserialize_conformant_integer!($name, crate::high_level_api::safe_deserialize_conformant_integer);
// The compressed version of the ciphertext type
::paste::paste! {
pub struct [<Compressed $name>]($crate::high_level_api::[<Compressed $name>]);
@@ -186,6 +240,11 @@ macro_rules! create_integer_wrapper_type {
impl_serialize_deserialize_on_type!([<Compressed $name>]);
impl_safe_serialize_on_type!([<Compressed $name>]);
impl_safe_deserialize_conformant_integer!([<Compressed $name>], crate::high_level_api::safe_deserialize_conformant_compressed_integer);
#[no_mangle]
pub unsafe extern "C" fn [<compressed_ $name:snake _decompress>](
sself: *const [<Compressed $name>],
@@ -200,6 +259,35 @@ macro_rules! create_integer_wrapper_type {
}
}
// The compact version of the ciphertext type
::paste::paste! {
pub struct [<Compact $name>]($crate::high_level_api::[<Compact $name>]);
impl_destroy_on_type!([<Compact $name>]);
impl_clone_on_type!([<Compact $name>]);
impl_serialize_deserialize_on_type!([<Compact $name>]);
impl_safe_serialize_on_type!([<Compact $name>]);
impl_safe_deserialize_conformant_integer!([<Compact $name>], crate::high_level_api::safe_deserialize_conformant_compact_integer);
#[no_mangle]
pub unsafe extern "C" fn [<compact_ $name:snake _expand>](
sself: *const [<Compact $name>],
output: *mut *mut $name,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
check_ptr_is_non_null_and_aligned(output).unwrap();
let list = $crate::c_api::utils::get_ref_checked(sself).unwrap();
let expanded = list.0.expand();
*output = Box::into_raw(Box::new($name(expanded)));
})
}
}
// The compact list version of the ciphertext type
::paste::paste! {
pub struct [<Compact $name List>]($crate::high_level_api::[<Compact $name List>]);
@@ -243,6 +331,20 @@ macro_rules! create_integer_wrapper_type {
}
}
};
// This entry point is meant for unsigned types
(
name: $name:ident,
clear_scalar_type: $clear_scalar_type:ty
) => {
create_integer_wrapper_type!(
name: $name,
fhe_unsigned_type: $name,
clear_scalar_type: $clear_scalar_type,
clear_shift_type: $clear_scalar_type,
);
};
}
create_integer_wrapper_type!(name: FheUint8, clear_scalar_type: u8);
@@ -260,6 +362,7 @@ impl_try_encrypt_trivial_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8)
impl_try_encrypt_with_client_key_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
impl_try_encrypt_with_public_key_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint8{crate::high_level_api::CompactFheUint8}, u8);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint8{crate::high_level_api::CompressedFheUint8}, u8);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint8List{crate::high_level_api::CompactFheUint8List}, u8);
@@ -268,6 +371,7 @@ impl_try_encrypt_trivial_on_type!(FheUint10{crate::high_level_api::FheUint10}, u
impl_try_encrypt_with_client_key_on_type!(FheUint10{crate::high_level_api::FheUint10}, u16);
impl_try_encrypt_with_public_key_on_type!(FheUint10{crate::high_level_api::FheUint10}, u16);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint10{crate::high_level_api::FheUint10}, u16);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint10{crate::high_level_api::CompactFheUint10}, u16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint10{crate::high_level_api::CompressedFheUint10}, u16);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint10List{crate::high_level_api::CompactFheUint10List}, u16);
@@ -276,6 +380,7 @@ impl_try_encrypt_trivial_on_type!(FheUint12{crate::high_level_api::FheUint12}, u
impl_try_encrypt_with_client_key_on_type!(FheUint12{crate::high_level_api::FheUint12}, u16);
impl_try_encrypt_with_public_key_on_type!(FheUint12{crate::high_level_api::FheUint12}, u16);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint12{crate::high_level_api::FheUint12}, u16);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint12{crate::high_level_api::CompactFheUint12}, u16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint12{crate::high_level_api::CompressedFheUint12}, u16);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint12List{crate::high_level_api::CompactFheUint12List}, u16);
@@ -284,6 +389,7 @@ impl_try_encrypt_trivial_on_type!(FheUint14{crate::high_level_api::FheUint14}, u
impl_try_encrypt_with_client_key_on_type!(FheUint14{crate::high_level_api::FheUint14}, u16);
impl_try_encrypt_with_public_key_on_type!(FheUint14{crate::high_level_api::FheUint14}, u16);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint14{crate::high_level_api::FheUint14}, u16);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint14{crate::high_level_api::CompactFheUint14}, u16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint14{crate::high_level_api::CompressedFheUint14}, u16);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint14List{crate::high_level_api::CompactFheUint14List}, u16);
@@ -292,6 +398,7 @@ impl_try_encrypt_trivial_on_type!(FheUint16{crate::high_level_api::FheUint16}, u
impl_try_encrypt_with_client_key_on_type!(FheUint16{crate::high_level_api::FheUint16}, u16);
impl_try_encrypt_with_public_key_on_type!(FheUint16{crate::high_level_api::FheUint16}, u16);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint16{crate::high_level_api::FheUint16}, u16);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint16{crate::high_level_api::CompactFheUint16}, u16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint16{crate::high_level_api::CompressedFheUint16}, u16);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint16List{crate::high_level_api::CompactFheUint16List}, u16);
@@ -299,6 +406,7 @@ impl_decrypt_on_type!(FheUint32, u32);
impl_try_encrypt_trivial_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
impl_try_encrypt_with_client_key_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
impl_try_encrypt_with_public_key_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint32{crate::high_level_api::CompactFheUint32}, u32);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint32{crate::high_level_api::CompressedFheUint32}, u32);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint32List{crate::high_level_api::CompactFheUint32List}, u32);
@@ -308,92 +416,108 @@ impl_try_encrypt_trivial_on_type!(FheUint64{crate::high_level_api::FheUint64}, u
impl_try_encrypt_with_client_key_on_type!(FheUint64{crate::high_level_api::FheUint64}, u64);
impl_try_encrypt_with_public_key_on_type!(FheUint64{crate::high_level_api::FheUint64}, u64);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint64{crate::high_level_api::FheUint64}, u64);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint64{crate::high_level_api::CompactFheUint64}, u64);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint64{crate::high_level_api::CompressedFheUint64}, u64);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint64List{crate::high_level_api::CompactFheUint64List}, u64);
#[no_mangle]
pub unsafe extern "C" fn fhe_uint128_try_encrypt_trivial_u128(
value: U128,
result: *mut *mut FheUint128,
) -> c_int {
catch_panic(|| {
let value = u128::from(value);
impl_decrypt_on_type!(FheUint128, U128);
impl_try_encrypt_trivial_on_type!(FheUint128{crate::high_level_api::FheUint128}, U128);
impl_try_encrypt_with_client_key_on_type!(FheUint128{crate::high_level_api::FheUint128}, U128);
impl_try_encrypt_with_public_key_on_type!(FheUint128{crate::high_level_api::FheUint128}, U128);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint128{crate::high_level_api::FheUint128}, U128);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint128{crate::high_level_api::CompactFheUint128}, U128);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint128{crate::high_level_api::CompressedFheUint128}, U128);
let inner = <crate::high_level_api::FheUint128>::try_encrypt_trivial(value).unwrap();
impl_decrypt_on_type!(FheUint256, U256);
impl_try_encrypt_trivial_on_type!(FheUint256{crate::high_level_api::FheUint256}, U256);
impl_try_encrypt_with_client_key_on_type!(FheUint256{crate::high_level_api::FheUint256}, U256);
impl_try_encrypt_with_public_key_on_type!(FheUint256{crate::high_level_api::FheUint256}, U256);
impl_try_encrypt_with_compact_public_key_on_type!(FheUint256{crate::high_level_api::FheUint256}, U256);
impl_try_encrypt_with_compact_public_key_on_type!(CompactFheUint256{crate::high_level_api::CompactFheUint256}, U256);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint256{crate::high_level_api::CompressedFheUint256}, U256);
*result = Box::into_raw(Box::new(FheUint128(inner)));
})
}
create_integer_wrapper_type!(
name: FheInt8,
fhe_unsigned_type: FheUint8,
clear_scalar_type: i8,
clear_shift_type: u8,
);
create_integer_wrapper_type!(
name: FheInt16,
fhe_unsigned_type: FheUint16,
clear_scalar_type: i16,
clear_shift_type: u16,
);
create_integer_wrapper_type!(
name: FheInt32,
fhe_unsigned_type: FheUint32,
clear_scalar_type: i32,
clear_shift_type: u32,
);
create_integer_wrapper_type!(
name: FheInt64,
fhe_unsigned_type: FheUint64,
clear_scalar_type: i64,
clear_shift_type: u64,
);
create_integer_wrapper_type!(
name: FheInt128,
fhe_unsigned_type: FheUint128,
clear_scalar_type: I128,
clear_shift_type: U128,
);
create_integer_wrapper_type!(
name: FheInt256,
fhe_unsigned_type: FheUint256,
clear_scalar_type: I256,
clear_shift_type: U256,
);
#[no_mangle]
pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_client_key_u128(
value: U128,
client_key: *const ClientKey,
result: *mut *mut FheUint128,
) -> c_int {
catch_panic(|| {
let client_key = get_ref_checked(client_key).unwrap();
impl_decrypt_on_type!(FheInt8, i8);
impl_try_encrypt_trivial_on_type!(FheInt8{crate::high_level_api::FheInt8}, i8);
impl_try_encrypt_with_client_key_on_type!(FheInt8{crate::high_level_api::FheInt8}, i8);
impl_try_encrypt_with_public_key_on_type!(FheInt8{crate::high_level_api::FheInt8}, i8);
impl_try_encrypt_with_compact_public_key_on_type!(FheInt8{crate::high_level_api::FheInt8}, i8);
impl_try_encrypt_with_client_key_on_type!(CompressedFheInt8{crate::high_level_api::CompressedFheInt8}, i8);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheInt8List{crate::high_level_api::CompactFheInt8List}, i8);
let value = u128::from(value);
impl_decrypt_on_type!(FheInt16, i16);
impl_try_encrypt_trivial_on_type!(FheInt16{crate::high_level_api::FheInt16}, i16);
impl_try_encrypt_with_client_key_on_type!(FheInt16{crate::high_level_api::FheInt16}, i16);
impl_try_encrypt_with_public_key_on_type!(FheInt16{crate::high_level_api::FheInt16}, i16);
impl_try_encrypt_with_compact_public_key_on_type!(FheInt16{crate::high_level_api::FheInt16}, i16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheInt16{crate::high_level_api::CompressedFheInt16}, i16);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheInt16List{crate::high_level_api::CompactFheInt16List}, i16);
let inner = <crate::high_level_api::FheUint128>::try_encrypt(value, &client_key.0).unwrap();
impl_decrypt_on_type!(FheInt32, i32);
impl_try_encrypt_trivial_on_type!(FheInt32{crate::high_level_api::FheInt32}, i32);
impl_try_encrypt_with_client_key_on_type!(FheInt32{crate::high_level_api::FheInt32}, i32);
impl_try_encrypt_with_public_key_on_type!(FheInt32{crate::high_level_api::FheInt32}, i32);
impl_try_encrypt_with_compact_public_key_on_type!(FheInt32{crate::high_level_api::FheInt32}, i32);
impl_try_encrypt_with_client_key_on_type!(CompressedFheInt32{crate::high_level_api::CompressedFheInt32}, i32);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheInt32List{crate::high_level_api::CompactFheInt32List}, i32);
*result = Box::into_raw(Box::new(FheUint128(inner)));
})
}
impl_decrypt_on_type!(FheInt64, i64);
impl_try_encrypt_trivial_on_type!(FheInt64{crate::high_level_api::FheInt64}, i64);
impl_try_encrypt_with_client_key_on_type!(FheInt64{crate::high_level_api::FheInt64}, i64);
impl_try_encrypt_with_public_key_on_type!(FheInt64{crate::high_level_api::FheInt64}, i64);
impl_try_encrypt_with_compact_public_key_on_type!(FheInt64{crate::high_level_api::FheInt64}, i64);
impl_try_encrypt_with_client_key_on_type!(CompressedFheInt64{crate::high_level_api::CompressedFheInt64}, i64);
impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheInt64List{crate::high_level_api::CompactFheInt64List}, i64);
#[no_mangle]
pub unsafe extern "C" fn compressed_fhe_uint128_try_encrypt_with_client_key_u128(
value: U128,
client_key: *const ClientKey,
result: *mut *mut CompressedFheUint128,
) -> c_int {
catch_panic(|| {
let client_key = get_ref_checked(client_key).unwrap();
impl_decrypt_on_type!(FheInt128, I128);
impl_try_encrypt_trivial_on_type!(FheInt128{crate::high_level_api::FheInt128}, I128);
impl_try_encrypt_with_client_key_on_type!(FheInt128{crate::high_level_api::FheInt128}, I128);
impl_try_encrypt_with_public_key_on_type!(FheInt128{crate::high_level_api::FheInt128}, I128);
impl_try_encrypt_with_compact_public_key_on_type!(FheInt128{crate::high_level_api::FheInt128}, I128);
impl_try_encrypt_with_client_key_on_type!(CompressedFheInt128{crate::high_level_api::CompressedFheInt128}, I128);
let value = u128::from(value);
let inner =
<crate::high_level_api::CompressedFheUint128>::try_encrypt(value, &client_key.0)
.unwrap();
*result = Box::into_raw(Box::new(CompressedFheUint128(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_public_key_u128(
value: U128,
public_key: *const PublicKey,
result: *mut *mut FheUint128,
) -> c_int {
catch_panic(|| {
let public_key = get_ref_checked(public_key).unwrap();
let value = u128::from(value);
let inner = <crate::high_level_api::FheUint128>::try_encrypt(value, &public_key.0).unwrap();
*result = Box::into_raw(Box::new(FheUint128(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_compact_public_key_u128(
value: U128,
public_key: *const CompactPublicKey,
result: *mut *mut FheUint128,
) -> c_int {
catch_panic(|| {
let public_key = get_ref_checked(public_key).unwrap();
let value = u128::from(value);
let inner = <crate::high_level_api::FheUint128>::try_encrypt(value, &public_key.0).unwrap();
*result = Box::into_raw(Box::new(FheUint128(inner)));
})
}
impl_decrypt_on_type!(FheInt256, I256);
impl_try_encrypt_trivial_on_type!(FheInt256{crate::high_level_api::FheInt256}, I256);
impl_try_encrypt_with_client_key_on_type!(FheInt256{crate::high_level_api::FheInt256}, I256);
impl_try_encrypt_with_public_key_on_type!(FheInt256{crate::high_level_api::FheInt256}, I256);
impl_try_encrypt_with_compact_public_key_on_type!(FheInt256{crate::high_level_api::FheInt256}, I256);
impl_try_encrypt_with_client_key_on_type!(CompressedFheInt256{crate::high_level_api::CompressedFheInt256}, I256);
#[no_mangle]
pub unsafe extern "C" fn compact_fhe_uint256_list_try_encrypt_with_compact_public_key_u128(
@@ -415,101 +539,6 @@ pub unsafe extern "C" fn compact_fhe_uint256_list_try_encrypt_with_compact_publi
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint128_decrypt(
encrypted_value: *const FheUint128,
client_key: *const ClientKey,
result: *mut U128,
) -> c_int {
catch_panic(|| {
let client_key = get_ref_checked(client_key).unwrap();
let encrypted_value = get_ref_checked(encrypted_value).unwrap();
let inner: u128 = encrypted_value.0.decrypt(&client_key.0);
*result = U128::from(inner);
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint256_try_encrypt_trivial_u256(
value: U256,
result: *mut *mut FheUint256,
) -> c_int {
catch_panic(|| {
let value = crate::integer::U256::from(value);
let inner = <crate::high_level_api::FheUint256>::try_encrypt_trivial(value).unwrap();
*result = Box::into_raw(Box::new(FheUint256(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_client_key_u256(
value: U256,
client_key: *const ClientKey,
result: *mut *mut FheUint256,
) -> c_int {
catch_panic(|| {
let client_key = get_ref_checked(client_key).unwrap();
let value = crate::integer::U256::from(value);
let inner = <crate::high_level_api::FheUint256>::try_encrypt(value, &client_key.0).unwrap();
*result = Box::into_raw(Box::new(FheUint256(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn compressed_fhe_uint256_try_encrypt_with_client_key_u256(
value: U256,
client_key: *const ClientKey,
result: *mut *mut CompressedFheUint256,
) -> c_int {
catch_panic(|| {
let client_key = get_ref_checked(client_key).unwrap();
let value = crate::integer::U256::from(value);
let inner =
<crate::high_level_api::CompressedFheUint256>::try_encrypt(value, &client_key.0)
.unwrap();
*result = Box::into_raw(Box::new(CompressedFheUint256(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_public_key_u256(
value: U256,
public_key: *const PublicKey,
result: *mut *mut FheUint256,
) -> c_int {
catch_panic(|| {
let public_key = get_ref_checked(public_key).unwrap();
let value = crate::integer::U256::from(value);
let inner = <crate::high_level_api::FheUint256>::try_encrypt(value, &public_key.0).unwrap();
*result = Box::into_raw(Box::new(FheUint256(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_compact_public_key_u256(
value: U256,
public_key: *const CompactPublicKey,
result: *mut *mut FheUint256,
) -> c_int {
catch_panic(|| {
let public_key = get_ref_checked(public_key).unwrap();
let value = crate::integer::U256::from(value);
let inner = <crate::high_level_api::FheUint256>::try_encrypt(value, &public_key.0).unwrap();
*result = Box::into_raw(Box::new(FheUint256(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn compact_fhe_uint256_list_try_encrypt_with_compact_public_key_u256(
input: *const U256,
@@ -535,17 +564,46 @@ pub unsafe extern "C" fn compact_fhe_uint256_list_try_encrypt_with_compact_publi
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint256_decrypt(
encrypted_value: *const FheUint256,
client_key: *const ClientKey,
result: *mut U256,
pub unsafe extern "C" fn compact_fhe_int128_list_try_encrypt_with_compact_public_key_i128(
input: *const I128,
input_len: usize,
public_key: *const CompactPublicKey,
result: *mut *mut CompactFheInt128List,
) -> c_int {
catch_panic(|| {
let client_key = get_ref_checked(client_key).unwrap();
let encrypted_value = get_ref_checked(encrypted_value).unwrap();
let public_key = get_ref_checked(public_key).unwrap();
let inner: crate::integer::U256 = encrypted_value.0.decrypt(&client_key.0);
*result = U256::from(inner);
let slc = ::std::slice::from_raw_parts(input, input_len);
let values = slc.iter().copied().map(i128::from).collect::<Vec<_>>();
let inner =
<crate::high_level_api::CompactFheInt128List>::try_encrypt(&values, &public_key.0)
.unwrap();
*result = Box::into_raw(Box::new(CompactFheInt128List(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn compact_fhe_int256_list_try_encrypt_with_compact_public_key_i256(
input: *const I256,
input_len: usize,
public_key: *const CompactPublicKey,
result: *mut *mut CompactFheInt256List,
) -> c_int {
catch_panic(|| {
let public_key = get_ref_checked(public_key).unwrap();
let slc = ::std::slice::from_raw_parts(input, input_len);
let values = slc
.iter()
.copied()
.map(crate::integer::I256::from)
.collect::<Vec<_>>();
let inner =
<crate::high_level_api::CompactFheInt256List>::try_encrypt(&values, &public_key.0)
.unwrap();
*result = Box::into_raw(Box::new(CompactFheInt256List(inner)));
})
}
@@ -570,12 +628,34 @@ macro_rules! define_casting_operation(
}
);
define_casting_operation!(FheUint8 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint10 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint12 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint14 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint16 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint32 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint64 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint128 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint256 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint8 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheUint10 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheUint12 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheUint14 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheUint16 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheUint32 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheUint64 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheUint128 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheUint256 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheInt8 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheInt16 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheInt32 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheInt64 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheInt128 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);
define_casting_operation!(FheInt256 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256,
FheInt8, FheInt16, FheInt32, FheInt64, FheInt128, FheInt256);

View File

@@ -4,6 +4,10 @@ mod utils;
pub mod booleans;
pub mod config;
#[cfg(feature = "integer")]
pub mod i128;
#[cfg(feature = "integer")]
pub mod i256;
#[cfg(feature = "integer")]
pub mod integers;
pub mod keys;
#[cfg(feature = "integer")]

View File

@@ -1,3 +1,48 @@
/// 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 CApiIntegerType:
From<Self::RustEquivalent>
{
type RustEquivalent: From<Self>;
fn to_rust(self) -> Self::RustEquivalent {
Self::RustEquivalent::from(self)
}
}
macro_rules! impl_c_api_integer_type(
// For when the C Integer type is _not_ the same as the Rust Integer type
($c_type:ty => $rust_type:ty) => {
impl CApiIntegerType for $c_type {
type RustEquivalent = $rust_type;
}
};
// For when the C Integer type is the same as Rust Integer type
($type:ty) => {
impl CApiIntegerType for $type {
type RustEquivalent = $type;
}
};
);
impl_c_api_integer_type!(bool);
impl_c_api_integer_type!(u8);
impl_c_api_integer_type!(u16);
impl_c_api_integer_type!(u32);
impl_c_api_integer_type!(u64);
impl_c_api_integer_type!(i8);
impl_c_api_integer_type!(i16);
impl_c_api_integer_type!(i32);
impl_c_api_integer_type!(i64);
impl_c_api_integer_type!(crate::c_api::high_level_api::u128::U128 => u128);
impl_c_api_integer_type!(crate::c_api::high_level_api::i128::I128 => i128);
impl_c_api_integer_type!(crate::c_api::high_level_api::u256::U256 => crate::integer::U256);
impl_c_api_integer_type!(crate::c_api::high_level_api::i256::I256 => crate::integer::I256);
macro_rules! impl_destroy_on_type {
($wrapper_type:ty) => {
::paste::paste! {
@@ -27,6 +72,7 @@ macro_rules! impl_try_encrypt_with_client_key_on_type {
result: *mut *mut $wrapper_type,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let value = <$input_type as $crate::c_api::high_level_api::utils::CApiIntegerType>::to_rust(value);
let client_key = $crate::c_api::utils::get_ref_checked(client_key).unwrap();
let inner = <$wrapped_type>::try_encrypt(value, &client_key.0).unwrap();
@@ -48,6 +94,8 @@ macro_rules! impl_try_encrypt_with_public_key_on_type {
result: *mut *mut $wrapper_type,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let value = <$input_type as $crate::c_api::high_level_api::utils::CApiIntegerType>::to_rust(value);
let public_key = $crate::c_api::utils::get_ref_checked(public_key).unwrap();
let inner = <$wrapped_type>::try_encrypt(value, &public_key.0).unwrap();
@@ -69,6 +117,8 @@ macro_rules! impl_try_encrypt_with_compact_public_key_on_type {
result: *mut *mut $wrapper_type,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let value = <$input_type as $crate::c_api::high_level_api::utils::CApiIntegerType>::to_rust(value);
let public_key = $crate::c_api::utils::get_ref_checked(public_key).unwrap();
let inner = <$wrapped_type>::try_encrypt(value, &public_key.0).unwrap();
@@ -111,6 +161,7 @@ macro_rules! impl_try_encrypt_trivial_on_type {
result: *mut *mut $wrapper_type,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let value = <$input_type as $crate::c_api::high_level_api::utils::CApiIntegerType>::to_rust(value);
let inner = <$wrapped_type>::try_encrypt_trivial(value).unwrap();
@@ -133,7 +184,11 @@ macro_rules! impl_decrypt_on_type {
let client_key = $crate::c_api::utils::get_ref_checked(client_key).unwrap();
let encrypted_value = $crate::c_api::utils::get_ref_checked(encrypted_value).unwrap();
*result = encrypted_value.0.decrypt(&client_key.0);
type RustScalarType_ = <$output_type as $crate::c_api::high_level_api::utils::CApiIntegerType>::RustEquivalent;
let rust_clear: RustScalarType_ = encrypted_value.0.decrypt(&client_key.0);
*result = <$output_type>::from(rust_clear);
})
}
}
@@ -206,15 +261,93 @@ macro_rules! impl_serialize_deserialize_on_type {
};
}
macro_rules! impl_safe_serialize_on_type {
($wrapper_type:ty) => {
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _safe_serialize>](
sself: *const $wrapper_type,
result: *mut crate::c_api::buffer::Buffer,
serialized_size_limit: u64,
) -> ::std::os::raw::c_int {
crate::c_api::utils::catch_panic(|| {
crate::c_api::utils::check_ptr_is_non_null_and_aligned(result).unwrap();
let mut buffer = vec![];
let sself = crate::c_api::utils::get_ref_checked(sself).unwrap();
crate::high_level_api::safe_serialize(&sself.0, &mut buffer, serialized_size_limit)
.unwrap();
*result = buffer.into();
})
}
}
};
}
macro_rules! impl_safe_deserialize_conformant_integer {
($wrapper_type:ty, $function_name:path) => {
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _safe_deserialize_conformant>](
buffer_view: crate::c_api::buffer::BufferView,
serialized_size_limit: u64,
server_key: *const crate::c_api::high_level_api::keys::ServerKey,
result: *mut *mut $wrapper_type,
) -> ::std::os::raw::c_int {
crate::c_api::utils::catch_panic(|| {
crate::c_api::utils::check_ptr_is_non_null_and_aligned(result).unwrap();
let sk = crate::c_api::utils::get_ref_checked(server_key).unwrap();
let buffer_view: &[u8] = buffer_view.into();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let object: $wrapper_type = $wrapper_type(
$function_name(
buffer_view,
serialized_size_limit,
&sk.0,
)
.unwrap(),
);
let heap_allocated_object = Box::new(object);
*result = Box::into_raw(heap_allocated_object);
})
}
}
};
}
macro_rules! impl_binary_fn_on_type {
($wrapper_type:ty => $($binary_fn_name:ident),* $(,)?) => {
$(
::paste::paste! {
// More general binary fn case,
// where the type of the left-hand side can be different
// than the type of the right-hand side.
//
// The result type is the one of the left-hand side.
//
// In practice, this is used for shifts on signed type,
// where lhs is a signed type and rhs is an unsigned type
(
lhs_type: $lhs_type:ty,
rhs_type: $rhs_type:ty,
binary_fn_names: $($binary_fn_name:ident),*
$(,)?
) => {
$( // unroll binary_fn_names
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _ $binary_fn_name>](
lhs: *const $wrapper_type,
rhs: *const $wrapper_type,
result: *mut *mut $wrapper_type,
pub unsafe extern "C" fn [<$lhs_type:snake _ $binary_fn_name>](
lhs: *const $lhs_type,
rhs: *const $rhs_type,
result: *mut *mut $lhs_type,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
@@ -222,11 +355,21 @@ macro_rules! impl_binary_fn_on_type {
let inner = (&lhs.0).$binary_fn_name(&rhs.0);
*result = Box::into_raw(Box::new($wrapper_type(inner)));
*result = Box::into_raw(Box::new($lhs_type(inner)));
})
}
}
)*
)*
};
// Usual binary fn case, where lhs, rhs and result are all of the same type
($wrapper_type:ty => $($binary_fn_name:ident),* $(,)?) => {
impl_binary_fn_on_type!(
lhs_type: $wrapper_type,
rhs_type: $wrapper_type,
binary_fn_names: $($binary_fn_name),*
);
};
}
@@ -254,13 +397,24 @@ macro_rules! impl_unary_fn_on_type {
#[cfg(feature = "integer")]
macro_rules! impl_binary_assign_fn_on_type {
($wrapper_type:ty => $($binary_assign_fn_name:ident),* $(,)?) => {
// More general binary fn case,
// where the type of the left-hand side can be different
// than the type of the right-hand side.
//
// In practice, this is used for shifts on signed type,
// where lhs is a signed type and rhs is an unsigned type
(
lhs_type: $lhs_type:ty,
rhs_type: $rhs_type:ty,
binary_fn_names: $($binary_assign_fn_name:ident),*
$(,)?
) => {
$(
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _ $binary_assign_fn_name>](
lhs: *mut $wrapper_type,
rhs: *const $wrapper_type,
pub unsafe extern "C" fn [<$lhs_type:snake _ $binary_assign_fn_name>](
lhs: *mut $lhs_type,
rhs: *const $rhs_type,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
@@ -272,53 +426,13 @@ 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)
}
($wrapper_type:ty => $($binary_assign_fn_name:ident),* $(,)?) => {
impl_binary_assign_fn_on_type!(
lhs_type: $wrapper_type,
rhs_type: $wrapper_type,
binary_fn_names: $($binary_assign_fn_name),*
);
};
}
#[cfg(feature = "integer")]
@@ -334,7 +448,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 rhs = <$scalar_type as $crate::c_api::high_level_api::utils::CApiIntegerType>::to_rust(rhs);
let inner = (&lhs.0).$binary_fn_name(rhs);
@@ -358,8 +472,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);
let rhs = <$scalar_type as $crate::c_api::high_level_api::utils::CApiIntegerType
>::to_rust(rhs);
lhs.0.$binary_assign_fn_name(rhs);
})

View File

@@ -119,7 +119,7 @@ pub unsafe extern "C" fn shortint_server_key_generate_bivariate_pbs_lookup_table
pub unsafe extern "C" fn shortint_server_key_bivariate_programmable_bootstrap(
server_key: *const ShortintServerKey,
lookup_table: *const ShortintBivariatePBSLookupTable,
ct_left: *const ShortintCiphertext,
ct_left: *mut ShortintCiphertext,
ct_right: *mut ShortintCiphertext,
result: *mut *mut ShortintCiphertext,
) -> c_int {
@@ -132,14 +132,14 @@ pub unsafe extern "C" fn shortint_server_key_bivariate_programmable_bootstrap(
let server_key = get_ref_checked(server_key).unwrap();
let lookup_table = get_ref_checked(lookup_table).unwrap();
let ct_left = get_ref_checked(ct_left).unwrap();
let ct_left = get_mut_checked(ct_left).unwrap();
let ct_right = get_mut_checked(ct_right).unwrap();
let res = crate::shortint::engine::ShortintEngine::with_thread_local_mut(|engine| {
engine
.smart_apply_lookup_table_bivariate(
&server_key.0,
&ct_left.0,
&mut ct_left.0,
&mut ct_right.0,
&lookup_table.0,
)

View File

@@ -17,7 +17,7 @@ pub fn check_ptr_is_non_null_and_aligned<T>(ptr: *const T) -> Result<(), String>
let expected_alignment = std::mem::align_of::<T>();
if ptr as usize % expected_alignment != 0 {
return Err(format!(
"pointer is misaligned, expected {expected_alignment} bytes alignement, got pointer: \
"pointer is misaligned, expected {expected_alignment} bytes alignment, got pointer: \
{ptr:p}. You May have mixed some pointers in your function call. If that's not the \
case check tfhe.h for alignment constants for plain data types allocation.",
));

70
tfhe/src/conformance.rs Normal file
View File

@@ -0,0 +1,70 @@
/// A trait for objects which can be checked to be conformant with a parameter set
pub trait ParameterSetConformant {
type ParameterSet;
fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool;
}
/// A constraint on a list size
/// The list must be composed of a number `n` of groups of size `group_size` which means list size
/// must be a multiple of `group_size`.
/// Moreover, `n` must be:
/// - bigger or equal to `min_inclusive_group_count`
/// - smaller of equal to `max_inclusive_group_count`
#[derive(Copy, Clone)]
pub struct ListSizeConstraint {
min_inclusive_group_count: usize,
max_inclusive_group_count: usize,
group_size: usize,
}
impl ListSizeConstraint {
pub fn exact_size(size: usize) -> ListSizeConstraint {
ListSizeConstraint {
min_inclusive_group_count: size,
max_inclusive_group_count: size,
group_size: 1,
}
}
pub fn try_size_in_range(
min_inclusive: usize,
max_inclusive: usize,
) -> Result<ListSizeConstraint, String> {
if max_inclusive < min_inclusive {
return Err("max_inclusive < min_inclusive".to_owned());
}
Ok(ListSizeConstraint {
min_inclusive_group_count: min_inclusive,
max_inclusive_group_count: max_inclusive,
group_size: 1,
})
}
pub fn try_size_of_group_in_range(
group_size: usize,
min_inclusive_group_count: usize,
max_inclusive_group_count: usize,
) -> Result<ListSizeConstraint, String> {
if max_inclusive_group_count < min_inclusive_group_count {
return Err("max_inclusive < min_inclusive".to_owned());
}
Ok(ListSizeConstraint {
min_inclusive_group_count,
max_inclusive_group_count,
group_size,
})
}
pub fn multiply_group_size(&self, group_size_multiplier: usize) -> Self {
ListSizeConstraint {
min_inclusive_group_count: self.min_inclusive_group_count,
max_inclusive_group_count: self.max_inclusive_group_count,
group_size: self.group_size * group_size_multiplier,
}
}
pub fn is_valid(&self, size: usize) -> bool {
size % self.group_size == 0
&& size >= self.min_inclusive_group_count * self.group_size
&& size <= self.max_inclusive_group_count * self.group_size
}
}

View File

@@ -831,7 +831,7 @@ where
/// Convenience function to share the core logic of the seeded GLWE encryption between all
/// functions needing it.
pub fn encrypt_seeded_glwe_ciphertext_with_exsiting_generator<
pub fn encrypt_seeded_glwe_ciphertext_with_existing_generator<
Scalar,
KeyCont,
OutputCont,
@@ -988,7 +988,7 @@ pub fn encrypt_seeded_glwe_ciphertext<Scalar, KeyCont, InputCont, OutputCont, No
noise_seeder,
);
encrypt_seeded_glwe_ciphertext_with_exsiting_generator(
encrypt_seeded_glwe_ciphertext_with_existing_generator(
glwe_secret_key,
output_glwe_ciphertext,
input_plaintext_list,

View File

@@ -644,7 +644,7 @@ where
slice_wrapping_opposite_assign(ct.as_mut());
}
/// Mulitply the left-hand side [`GLWE ciphertext`](`GlweCiphertext`) by the right-hand side
/// Multiply the left-hand side [`GLWE ciphertext`](`GlweCiphertext`) by the right-hand side
/// cleartext updating it in-place.
///
/// # Example
@@ -727,7 +727,7 @@ pub fn glwe_ciphertext_cleartext_mul_assign<Scalar, InCont>(
slice_wrapping_scalar_mul_assign(lhs.as_mut(), rhs.0);
}
/// Mulitply the left-hand side [`GLWE ciphertext`](`GlweCiphertext`) by the right-hand side
/// Multiply the left-hand side [`GLWE ciphertext`](`GlweCiphertext`) by the right-hand side
/// cleartext writing the result in the output [`GLWE ciphertext`](`GlweCiphertext`).
///
/// # Example

View File

@@ -2,11 +2,13 @@
//! _sample extract_ in the literature. Allowing to extract a single
//! [`LWE Ciphertext`](`LweCiphertext`) from a given [`GLWE ciphertext`](`GlweCiphertext`).
use crate::core_crypto::algorithms::misc::divide_ceil;
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::commons::numeric::UnsignedInteger;
use crate::core_crypto::commons::parameters::{MonomialDegree, *};
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use rayon::prelude::*;
/// Extract the nth coefficient from the body of a [`GLWE Ciphertext`](`GlweCiphertext`) as an
/// [`LWE ciphertext`](`LweCiphertext`).
@@ -95,13 +97,18 @@ pub fn extract_lwe_sample_from_glwe_ciphertext<Scalar, InputCont, OutputCont>(
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert!(
input_glwe.glwe_size().to_glwe_dimension().0 * input_glwe.polynomial_size().0
== output_lwe.lwe_size().to_lwe_dimension().0,
let in_lwe_dim = input_glwe
.glwe_size()
.to_glwe_dimension()
.to_equivalent_lwe_dimension(input_glwe.polynomial_size());
let out_lwe_dim = output_lwe.lwe_size().to_lwe_dimension();
assert_eq!(
in_lwe_dim, out_lwe_dim,
"Mismatch between equivalent LweDimension of input ciphertext and output ciphertext. \
Got {:?} for input and {:?} for output.",
LweDimension(input_glwe.glwe_size().to_glwe_dimension().0 * input_glwe.polynomial_size().0),
output_lwe.lwe_size().to_lwe_dimension(),
in_lwe_dim, out_lwe_dim,
);
assert_eq!(
@@ -139,3 +146,294 @@ pub fn extract_lwe_sample_from_glwe_ciphertext<Scalar, InputCont, OutputCont>(
lwe_mask_poly.rotate_left(opposite_count);
}
}
/// Parallel variant of [`extract_lwe_sample_from_glwe_ciphertext`] performing a sample extract on
/// all coefficients from a [`GlweCiphertext`] in an output [`LweCiphertextList`].
///
/// This will use all threads available in the current rayon thread pool.
///
/// # Formal definition
///
/// This operation is usually referred to as a _sample extract_ in the literature.
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GlweCiphertext creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// // Create the plaintext
/// let msg = 3u64;
/// let encoded_msg = msg << 60;
/// let mut plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(polynomial_size.0));
///
/// // Create a new GlweCiphertext
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
///
/// encrypt_glwe_ciphertext(
/// &glwe_secret_key,
/// &mut glwe,
/// &plaintext_list,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// // Now we get the equivalent LweSecretKey from the GlweSecretKey
/// let equivalent_lwe_sk = glwe_secret_key.clone().into_lwe_secret_key();
///
/// let mut extracted_samples = LweCiphertextList::new(
/// 0u64,
/// equivalent_lwe_sk.lwe_dimension().to_lwe_size(),
/// LweCiphertextCount(glwe.polynomial_size().0),
/// ciphertext_modulus,
/// );
///
/// // Use all threads available in the current rayon thread pool
/// par_extract_lwe_sample_from_glwe_ciphertext(&glwe, &mut extracted_samples);
///
/// let mut output_plaintext_list = PlaintextList::new(
/// 0u64,
/// PlaintextCount(extracted_samples.lwe_ciphertext_count().0),
/// );
///
/// decrypt_lwe_ciphertext_list(
/// &equivalent_lwe_sk,
/// &extracted_samples,
/// &mut output_plaintext_list,
/// );
///
/// // Round and remove encoding
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
///
/// output_plaintext_list
/// .iter_mut()
/// .for_each(|x| *x.0 = decomposer.closest_representable(*x.0) >> 60);
///
/// // We check we recover our msg stored in all slots of the GlweCiphertext
/// assert!(output_plaintext_list.iter().all(|x| *x.0 == msg));
/// ```
pub fn par_extract_lwe_sample_from_glwe_ciphertext<Scalar, InputCont, OutputCont>(
input_glwe: &GlweCiphertext<InputCont>,
output_lwe_list: &mut LweCiphertextList<OutputCont>,
) where
Scalar: UnsignedInteger + Send + Sync,
InputCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
{
let thread_count = ThreadCount(rayon::current_num_threads());
par_extract_lwe_sample_from_glwe_ciphertext_with_thread_count(
input_glwe,
output_lwe_list,
thread_count,
);
}
/// Parallel variant of [`extract_lwe_sample_from_glwe_ciphertext`] performing a sample extract on
/// all coefficients from a [`GlweCiphertext`] in an output [`LweCiphertextList`].
///
/// This will try to use `thread_count` threads for the computation, if this number is bigger than
/// the available number of threads in the current rayon thread pool then only the number of
/// available threads will be used. Note that `thread_count` cannot be 0.
///
/// # Formal definition
///
/// This operation is usually referred to as a _sample extract_ in the literature.
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GlweCiphertext creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// // Create the plaintext
/// let msg = 3u64;
/// let encoded_msg = msg << 60;
/// let mut plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(polynomial_size.0));
///
/// // Create a new GlweCiphertext
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
///
/// encrypt_glwe_ciphertext(
/// &glwe_secret_key,
/// &mut glwe,
/// &plaintext_list,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// // Now we get the equivalent LweSecretKey from the GlweSecretKey
/// let equivalent_lwe_sk = glwe_secret_key.clone().into_lwe_secret_key();
///
/// let mut extracted_samples = LweCiphertextList::new(
/// 0u64,
/// equivalent_lwe_sk.lwe_dimension().to_lwe_size(),
/// LweCiphertextCount(glwe.polynomial_size().0),
/// ciphertext_modulus,
/// );
///
/// // Try to use 4 threads for the keyswitch if enough are available
/// // in the current rayon thread pool
/// par_extract_lwe_sample_from_glwe_ciphertext_with_thread_count(
/// &glwe,
/// &mut extracted_samples,
/// ThreadCount(4),
/// );
///
/// let mut output_plaintext_list = PlaintextList::new(
/// 0u64,
/// PlaintextCount(extracted_samples.lwe_ciphertext_count().0),
/// );
///
/// decrypt_lwe_ciphertext_list(
/// &equivalent_lwe_sk,
/// &extracted_samples,
/// &mut output_plaintext_list,
/// );
///
/// // Round and remove encoding
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
///
/// output_plaintext_list
/// .iter_mut()
/// .for_each(|x| *x.0 = decomposer.closest_representable(*x.0) >> 60);
///
/// // We check we recover our msg stored in all slots of the GlweCiphertext
/// assert!(output_plaintext_list.iter().all(|x| *x.0 == msg));
/// ```
pub fn par_extract_lwe_sample_from_glwe_ciphertext_with_thread_count<
Scalar,
InputCont,
OutputCont,
>(
input_glwe: &GlweCiphertext<InputCont>,
output_lwe_list: &mut LweCiphertextList<OutputCont>,
thread_count: ThreadCount,
) where
Scalar: UnsignedInteger + Send + Sync,
InputCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
{
let in_lwe_dim = input_glwe
.glwe_size()
.to_glwe_dimension()
.to_equivalent_lwe_dimension(input_glwe.polynomial_size());
let out_lwe_dim = output_lwe_list.lwe_size().to_lwe_dimension();
assert_eq!(
in_lwe_dim, out_lwe_dim,
"Mismatch between equivalent LweDimension of input ciphertext and output ciphertext. \
Got {:?} for input and {:?} for output.",
in_lwe_dim, out_lwe_dim,
);
assert!(
input_glwe.polynomial_size().0 <= output_lwe_list.lwe_ciphertext_count().0,
"The output LweCiphertextList does not have enough space ({:?}) \
to extract all input GlweCiphertext coefficients ({})",
output_lwe_list.lwe_ciphertext_count(),
input_glwe.polynomial_size().0
);
assert_eq!(
input_glwe.ciphertext_modulus(),
output_lwe_list.ciphertext_modulus(),
"Mismatched moduli between input_glwe ({:?}) and output_lwe ({:?})",
input_glwe.ciphertext_modulus(),
output_lwe_list.ciphertext_modulus()
);
let polynomial_size = input_glwe.polynomial_size();
let (glwe_mask, glwe_body) = input_glwe.get_mask_and_body();
let thread_count = thread_count.0.min(rayon::current_num_threads());
let chunk_size = divide_ceil(polynomial_size.0, thread_count);
glwe_body
.as_ref()
.par_chunks(chunk_size)
.zip(output_lwe_list.par_chunks_mut(chunk_size))
.enumerate()
.for_each(
|(chunk_idx, (glwe_body_chunk, mut output_lwe_list_chunk))| {
for (coeff_idx, (glwe_coeff, mut output_lwe)) in glwe_body_chunk
.iter()
.zip(output_lwe_list_chunk.iter_mut())
.enumerate()
{
let nth = chunk_idx * chunk_size + coeff_idx;
let (mut lwe_mask, lwe_body) = output_lwe.get_mut_mask_and_body();
// We copy the body
*lwe_body.data = *glwe_coeff;
// We copy the mask (each polynomial is in the wrong order)
lwe_mask.as_mut().copy_from_slice(glwe_mask.as_ref());
// We compute the number of elements which must be
// turned into their opposite
let opposite_count = input_glwe.polynomial_size().0 - nth - 1;
// We loop through the polynomials
for lwe_mask_poly in lwe_mask
.as_mut()
.chunks_exact_mut(input_glwe.polynomial_size().0)
{
// We reverse the polynomial
lwe_mask_poly.reverse();
// We compute the opposite of the proper coefficients
slice_wrapping_opposite_assign(&mut lwe_mask_poly[0..opposite_count]);
// We rotate the polynomial properly
lwe_mask_poly.rotate_left(opposite_count);
}
}
},
);
}

View File

@@ -31,7 +31,7 @@ pub fn generate_lwe_compact_public_key<Scalar, InputKeyCont, OutputKeyCont, Gen>
assert!(
lwe_secret_key.lwe_dimension() == output.lwe_dimension(),
"Mismatched LweDimension between input LweSecretKey {:?} \
and ouptut LweCompactPublicKey {:?}",
and output LweCompactPublicKey {:?}",
lwe_secret_key.lwe_dimension(),
output.lwe_dimension()
);
@@ -97,7 +97,7 @@ pub fn generate_seeded_lwe_compact_public_key<Scalar, InputKeyCont, OutputKeyCon
assert!(
lwe_secret_key.lwe_dimension() == output.lwe_dimension(),
"Mismatched LweDimension between input LweSecretKey {:?} \
and ouptut LweCompactPublicKey {:?}",
and output LweCompactPublicKey {:?}",
lwe_secret_key.lwe_dimension(),
output.lwe_dimension()
);

View File

@@ -1573,7 +1573,7 @@ pub fn encrypt_lwe_ciphertext_with_compact_public_key<
{
assert!(
output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(),
"Mismatch between LweDimension of output cipertext and input public key. \
"Mismatch between LweDimension of output ciphertext and input public key. \
Got {:?} in output, and {:?} in public key.",
output.lwe_size().to_lwe_dimension(),
lwe_compact_public_key.lwe_dimension()
@@ -1581,7 +1581,7 @@ pub fn encrypt_lwe_ciphertext_with_compact_public_key<
assert!(
lwe_compact_public_key.ciphertext_modulus() == output.ciphertext_modulus(),
"Mismatch between CiphertextModulus of output cipertext and input public key. \
"Mismatch between CiphertextModulus of output ciphertext and input public key. \
Got {:?} in output, and {:?} in public key.",
output.ciphertext_modulus(),
lwe_compact_public_key.ciphertext_modulus()
@@ -1728,7 +1728,7 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key<
{
assert!(
output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(),
"Mismatch between LweDimension of output cipertext and input public key. \
"Mismatch between LweDimension of output ciphertext and input public key. \
Got {:?} in output, and {:?} in public key.",
output.lwe_size().to_lwe_dimension(),
lwe_compact_public_key.lwe_dimension()
@@ -1736,7 +1736,7 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key<
assert!(
lwe_compact_public_key.ciphertext_modulus() == output.ciphertext_modulus(),
"Mismatch between CiphertextModulus of output cipertext and input public key. \
"Mismatch between CiphertextModulus of output ciphertext and input public key. \
Got {:?} in output, and {:?} in public key.",
output.ciphertext_modulus(),
lwe_compact_public_key.ciphertext_modulus()
@@ -1744,7 +1744,7 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key<
assert!(
output.lwe_ciphertext_count().0 == encoded.plaintext_count().0,
"Mismatch between LweCiphertextCount of output cipertext and \
"Mismatch between LweCiphertextCount of output ciphertext and \
PlaintextCount of input list. Got {:?} in output, and {:?} in input plaintext list.",
output.lwe_ciphertext_count(),
encoded.plaintext_count()
@@ -1936,7 +1936,7 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key<
{
assert!(
output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(),
"Mismatch between LweDimension of output cipertext and input public key. \
"Mismatch between LweDimension of output ciphertext and input public key. \
Got {:?} in output, and {:?} in public key.",
output.lwe_size().to_lwe_dimension(),
lwe_compact_public_key.lwe_dimension()
@@ -1944,7 +1944,7 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key<
assert!(
lwe_compact_public_key.ciphertext_modulus() == output.ciphertext_modulus(),
"Mismatch between CiphertextModulus of output cipertext and input public key. \
"Mismatch between CiphertextModulus of output ciphertext and input public key. \
Got {:?} in output, and {:?} in public key.",
output.ciphertext_modulus(),
lwe_compact_public_key.ciphertext_modulus()
@@ -1952,7 +1952,7 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key<
assert!(
output.lwe_ciphertext_count().0 == encoded.plaintext_count().0,
"Mismatch between LweCiphertextCount of output cipertext and \
"Mismatch between LweCiphertextCount of output ciphertext and \
PlaintextCount of input list. Got {:?} in output, and {:?} in input plaintext list.",
output.lwe_ciphertext_count(),
encoded.plaintext_count()

View File

@@ -10,7 +10,7 @@ use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use rayon::prelude::*;
/// Keyswitch an [`LWE ciphertext`](`LweCiphertext`) encrytped under an
/// Keyswitch an [`LWE ciphertext`](`LweCiphertext`) encrypted under an
/// [`LWE secret key`](`LweSecretKey`) to another [`LWE secret key`](`LweSecretKey`).
///
/// # Formal Definition

View File

@@ -388,7 +388,7 @@ where
slice_wrapping_opposite_assign(ct.as_mut());
}
/// Mulitply the left-hand side [`LWE ciphertext`](`LweCiphertext`) by the right-hand side cleartext
/// Multiply the left-hand side [`LWE ciphertext`](`LweCiphertext`) by the right-hand side cleartext
/// updating it in-place.
///
/// # Example
@@ -455,7 +455,7 @@ pub fn lwe_ciphertext_cleartext_mul_assign<Scalar, InCont>(
slice_wrapping_scalar_mul_assign(lhs.as_mut(), rhs.0);
}
/// Mulitply the left-hand side [`LWE ciphertext`](`LweCiphertext`) by the right-hand side cleartext
/// Multiply the left-hand side [`LWE ciphertext`](`LweCiphertext`) by the right-hand side cleartext
/// writing the result in the output [`LWE ciphertext`](`LweCiphertext`).
///
/// # Example

View File

@@ -6,7 +6,7 @@ use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::fft_impl::common::pbs_modulus_switch;
use crate::core_crypto::fft_impl::common::fast_pbs_modulus_switch;
use crate::core_crypto::fft_impl::fft64::crypto::ggsw::{
add_external_product_assign, add_external_product_assign_scratch, update_with_fmadd_factor,
};
@@ -61,7 +61,7 @@ pub fn prepare_multi_bit_ggsw_mem_optimized<
monomial_degree.wrapping_add(selection_bit.wrapping_mul(mask_element));
}
let switched_degree = pbs_modulus_switch(
let switched_degree = fast_pbs_modulus_switch(
monomial_degree,
polynomial_size,
ModulusSwitchOffset(0),
@@ -289,7 +289,7 @@ pub fn prepare_multi_bit_ggsw_mem_optimized<
/// println!("Checking result...");
/// assert_eq!(6, pbs_multiplication_result);
/// println!(
/// "Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// "Multiplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// );
/// ```
pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
@@ -307,7 +307,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
assert_eq!(
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
"Mimatched input LweDimension. LweCiphertext input LweDimension {:?}. \
"Mismatched input LweDimension. LweCiphertext input LweDimension {:?}. \
FourierLweMultiBitBootstrapKey input LweDimension {:?}.",
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
@@ -316,7 +316,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
assert_eq!(
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
"Mimatched GlweSize. Accumulator GlweSize {:?}. \
"Mismatched GlweSize. Accumulator GlweSize {:?}. \
FourierLweMultiBitBootstrapKey GlweSize {:?}.",
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
@@ -325,7 +325,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
assert_eq!(
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
"Mimatched PolynomialSize. Accumulator PolynomialSize {:?}. \
"Mismatched PolynomialSize. Accumulator PolynomialSize {:?}. \
FourierLweMultiBitBootstrapKey PolynomialSize {:?}.",
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
@@ -370,7 +370,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
let work_queue = Mutex::new(work_queue);
let lut_poly_size = accumulator.polynomial_size();
let monomial_degree = pbs_modulus_switch(
let monomial_degree = fast_pbs_modulus_switch(
*lwe_body.data,
lut_poly_size,
ModulusSwitchOffset(0),
@@ -558,7 +558,7 @@ pub fn multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, OutputCont
assert_eq!(
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
"Mimatched input LweDimension. LweCiphertext input LweDimension {:?}. \
"Mismatched input LweDimension. LweCiphertext input LweDimension {:?}. \
FourierLweMultiBitBootstrapKey input LweDimension {:?}.",
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
@@ -567,7 +567,7 @@ pub fn multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, OutputCont
assert_eq!(
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
"Mimatched GlweSize. Accumulator GlweSize {:?}. \
"Mismatched GlweSize. Accumulator GlweSize {:?}. \
FourierLweMultiBitBootstrapKey GlweSize {:?}.",
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
@@ -576,7 +576,7 @@ pub fn multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, OutputCont
assert_eq!(
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
"Mimatched PolynomialSize. Accumulator PolynomialSize {:?}. \
"Mismatched PolynomialSize. Accumulator PolynomialSize {:?}. \
FourierLweMultiBitBootstrapKey PolynomialSize {:?}.",
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
@@ -617,7 +617,7 @@ pub fn multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, OutputCont
let work_queue = &work_queue;
let lut_poly_size = accumulator.polynomial_size();
let monomial_degree = pbs_modulus_switch(
let monomial_degree = fast_pbs_modulus_switch(
*lwe_body.data,
lut_poly_size,
ModulusSwitchOffset(0),
@@ -700,7 +700,7 @@ pub fn multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, OutputCont
monomial_degree.wrapping_add(selection_bit.wrapping_mul(mask_element));
}
let switched_degree = pbs_modulus_switch(
let switched_degree = fast_pbs_modulus_switch(
monomial_degree,
lut_poly_size,
ModulusSwitchOffset(0),
@@ -1025,7 +1025,7 @@ pub fn multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, OutputCont
/// println!("Checking result...");
/// assert_eq!(6, pbs_multiplication_result);
/// println!(
/// "Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// "Multiplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// );
/// ```
pub fn multi_bit_programmable_bootstrap_lwe_ciphertext<
@@ -1051,7 +1051,7 @@ pub fn multi_bit_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
"Mimatched input LweDimension. LweCiphertext input LweDimension {:?}. \
"Mismatched input LweDimension. LweCiphertext input LweDimension {:?}. \
FourierLweMultiBitBootstrapKey input LweDimension {:?}.",
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
@@ -1060,7 +1060,7 @@ pub fn multi_bit_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
output.lwe_size().to_lwe_dimension(),
multi_bit_bsk.output_lwe_dimension(),
"Mimatched output LweDimension. LweCiphertext output LweDimension {:?}. \
"Mismatched output LweDimension. LweCiphertext output LweDimension {:?}. \
FourierLweMultiBitBootstrapKey output LweDimension {:?}.",
output.lwe_size().to_lwe_dimension(),
multi_bit_bsk.output_lwe_dimension(),
@@ -1069,7 +1069,7 @@ pub fn multi_bit_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
"Mimatched GlweSize. Accumulator GlweSize {:?}. \
"Mismatched GlweSize. Accumulator GlweSize {:?}. \
FourierLweMultiBitBootstrapKey GlweSize {:?}.",
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
@@ -1078,7 +1078,7 @@ pub fn multi_bit_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
"Mimatched PolynomialSize. Accumulator PolynomialSize {:?}. \
"Mismatched PolynomialSize. Accumulator PolynomialSize {:?}. \
FourierLweMultiBitBootstrapKey PolynomialSize {:?}.",
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
@@ -1145,7 +1145,7 @@ pub fn multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
"Mimatched input LweDimension. LweCiphertext input LweDimension {:?}. \
"Mismatched input LweDimension. LweCiphertext input LweDimension {:?}. \
FourierLweMultiBitBootstrapKey input LweDimension {:?}.",
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
@@ -1154,7 +1154,7 @@ pub fn multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
output.lwe_size().to_lwe_dimension(),
multi_bit_bsk.output_lwe_dimension(),
"Mimatched output LweDimension. LweCiphertext output LweDimension {:?}. \
"Mismatched output LweDimension. LweCiphertext output LweDimension {:?}. \
FourierLweMultiBitBootstrapKey output LweDimension {:?}.",
output.lwe_size().to_lwe_dimension(),
multi_bit_bsk.output_lwe_dimension(),
@@ -1163,7 +1163,7 @@ pub fn multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
"Mimatched GlweSize. Accumulator GlweSize {:?}. \
"Mismatched GlweSize. Accumulator GlweSize {:?}. \
FourierLweMultiBitBootstrapKey GlweSize {:?}.",
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
@@ -1172,7 +1172,7 @@ pub fn multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
"Mimatched PolynomialSize. Accumulator PolynomialSize {:?}. \
"Mismatched PolynomialSize. Accumulator PolynomialSize {:?}. \
FourierLweMultiBitBootstrapKey PolynomialSize {:?}.",
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
@@ -1257,7 +1257,7 @@ pub fn std_prepare_multi_bit_ggsw<Scalar, GgswBufferCont, TmpGgswBufferCont, Ggs
monomial_degree.wrapping_add(selection_bit.wrapping_mul(mask_element));
}
let switched_degree = pbs_modulus_switch(
let switched_degree = fast_pbs_modulus_switch(
monomial_degree,
polynomial_size,
ModulusSwitchOffset(0),
@@ -1295,7 +1295,7 @@ pub fn std_multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>
assert_eq!(
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
"Mimatched input LweDimension. LweCiphertext input LweDimension {:?}. \
"Mismatched input LweDimension. LweCiphertext input LweDimension {:?}. \
FourierLweMultiBitBootstrapKey input LweDimension {:?}.",
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
@@ -1304,7 +1304,7 @@ pub fn std_multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>
assert_eq!(
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
"Mimatched GlweSize. Accumulator GlweSize {:?}. \
"Mismatched GlweSize. Accumulator GlweSize {:?}. \
FourierLweMultiBitBootstrapKey GlweSize {:?}.",
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
@@ -1313,7 +1313,7 @@ pub fn std_multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>
assert_eq!(
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
"Mimatched PolynomialSize. Accumulator PolynomialSize {:?}. \
"Mismatched PolynomialSize. Accumulator PolynomialSize {:?}. \
FourierLweMultiBitBootstrapKey PolynomialSize {:?}.",
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
@@ -1330,7 +1330,7 @@ pub fn std_multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>
assert_eq!(
input.ciphertext_modulus(),
multi_bit_bsk.ciphertext_modulus(),
"Mimatched CiphertextModulus. LweCiphertext CiphertextModulus {:?}. \
"Mismatched CiphertextModulus. LweCiphertext CiphertextModulus {:?}. \
LweMultiBitBootstrapKey CiphertextModulus {:?}.",
input.ciphertext_modulus(),
multi_bit_bsk.ciphertext_modulus(),
@@ -1363,7 +1363,7 @@ pub fn std_multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>
let work_queue = Mutex::new(work_queue);
let lut_poly_size = accumulator.polynomial_size();
let monomial_degree = pbs_modulus_switch(
let monomial_degree = fast_pbs_modulus_switch(
*lwe_body.data,
lut_poly_size,
ModulusSwitchOffset(0),
@@ -1577,7 +1577,7 @@ pub fn std_multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, Output
assert_eq!(
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
"Mimatched input LweDimension. LweCiphertext input LweDimension {:?}. \
"Mismatched input LweDimension. LweCiphertext input LweDimension {:?}. \
FourierLweMultiBitBootstrapKey input LweDimension {:?}.",
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
@@ -1586,7 +1586,7 @@ pub fn std_multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, Output
assert_eq!(
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
"Mimatched GlweSize. Accumulator GlweSize {:?}. \
"Mismatched GlweSize. Accumulator GlweSize {:?}. \
FourierLweMultiBitBootstrapKey GlweSize {:?}.",
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
@@ -1595,7 +1595,7 @@ pub fn std_multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, Output
assert_eq!(
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
"Mimatched PolynomialSize. Accumulator PolynomialSize {:?}. \
"Mismatched PolynomialSize. Accumulator PolynomialSize {:?}. \
FourierLweMultiBitBootstrapKey PolynomialSize {:?}.",
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
@@ -1612,7 +1612,7 @@ pub fn std_multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, Output
assert_eq!(
accumulator.ciphertext_modulus(),
multi_bit_bsk.ciphertext_modulus(),
"Mimatched CiphertextModulus. Accumulator CiphertextModulus {:?}. \
"Mismatched CiphertextModulus. Accumulator CiphertextModulus {:?}. \
LweMultiBitBootstrapKey CiphertextModulus {:?}.",
accumulator.ciphertext_modulus(),
multi_bit_bsk.ciphertext_modulus(),
@@ -1645,7 +1645,7 @@ pub fn std_multi_bit_deterministic_blind_rotate_assign<Scalar, InputCont, Output
let work_queue = &work_queue;
let lut_poly_size = accumulator.polynomial_size();
let monomial_degree = pbs_modulus_switch(
let monomial_degree = fast_pbs_modulus_switch(
*lwe_body.data,
lut_poly_size,
ModulusSwitchOffset(0),
@@ -1856,7 +1856,7 @@ pub fn std_multi_bit_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
"Mimatched input LweDimension. LweCiphertext input LweDimension {:?}. \
"Mismatched input LweDimension. LweCiphertext input LweDimension {:?}. \
FourierLweMultiBitBootstrapKey input LweDimension {:?}.",
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
@@ -1865,7 +1865,7 @@ pub fn std_multi_bit_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
output.lwe_size().to_lwe_dimension(),
multi_bit_bsk.output_lwe_dimension(),
"Mimatched output LweDimension. LweCiphertext output LweDimension {:?}. \
"Mismatched output LweDimension. LweCiphertext output LweDimension {:?}. \
FourierLweMultiBitBootstrapKey output LweDimension {:?}.",
output.lwe_size().to_lwe_dimension(),
multi_bit_bsk.output_lwe_dimension(),
@@ -1874,7 +1874,7 @@ pub fn std_multi_bit_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
"Mimatched GlweSize. Accumulator GlweSize {:?}. \
"Mismatched GlweSize. Accumulator GlweSize {:?}. \
FourierLweMultiBitBootstrapKey GlweSize {:?}.",
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
@@ -1883,7 +1883,7 @@ pub fn std_multi_bit_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
"Mimatched PolynomialSize. Accumulator PolynomialSize {:?}. \
"Mismatched PolynomialSize. Accumulator PolynomialSize {:?}. \
FourierLweMultiBitBootstrapKey PolynomialSize {:?}.",
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
@@ -1950,7 +1950,7 @@ pub fn std_multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
"Mimatched input LweDimension. LweCiphertext input LweDimension {:?}. \
"Mismatched input LweDimension. LweCiphertext input LweDimension {:?}. \
FourierLweMultiBitBootstrapKey input LweDimension {:?}.",
input.lwe_size().to_lwe_dimension(),
multi_bit_bsk.input_lwe_dimension(),
@@ -1959,7 +1959,7 @@ pub fn std_multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
output.lwe_size().to_lwe_dimension(),
multi_bit_bsk.output_lwe_dimension(),
"Mimatched output LweDimension. LweCiphertext output LweDimension {:?}. \
"Mismatched output LweDimension. LweCiphertext output LweDimension {:?}. \
FourierLweMultiBitBootstrapKey output LweDimension {:?}.",
output.lwe_size().to_lwe_dimension(),
multi_bit_bsk.output_lwe_dimension(),
@@ -1968,7 +1968,7 @@ pub fn std_multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
"Mimatched GlweSize. Accumulator GlweSize {:?}. \
"Mismatched GlweSize. Accumulator GlweSize {:?}. \
FourierLweMultiBitBootstrapKey GlweSize {:?}.",
accumulator.glwe_size(),
multi_bit_bsk.glwe_size(),
@@ -1977,7 +1977,7 @@ pub fn std_multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext<
assert_eq!(
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),
"Mimatched PolynomialSize. Accumulator PolynomialSize {:?}. \
"Mismatched PolynomialSize. Accumulator PolynomialSize {:?}. \
FourierLweMultiBitBootstrapKey PolynomialSize {:?}.",
accumulator.polynomial_size(),
multi_bit_bsk.polynomial_size(),

View File

@@ -36,7 +36,7 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // This example recreates a PBS by combining a blind rotate and a smaple extract.
/// // This example recreates a PBS by combining a blind rotate and a sample extract.
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
@@ -226,7 +226,7 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
/// println!("Checking result...");
/// assert_eq!(6, pbs_multiplication_result);
/// println!(
/// "Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// "Multiplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// );
/// ```
pub fn blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
@@ -1011,7 +1011,7 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
/// println!("Checking result...");
/// assert_eq!(6, pbs_multiplication_result);
/// println!(
/// "Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// "Multiplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// );
/// ```
pub fn programmable_bootstrap_lwe_ciphertext<Scalar, InputCont, OutputCont, AccCont, KeyCont>(
@@ -1321,7 +1321,7 @@ pub fn programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement<Scalar>(
/// println!("Checking result...");
/// assert_eq!(6, pbs_multiplication_result);
/// println!(
/// "Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// "Multiplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
/// );
/// ```
pub fn programmable_bootstrap_f128_lwe_ciphertext<Scalar, InputCont, OutputCont, AccCont, KeyCont>(

View File

@@ -132,7 +132,7 @@ pub fn generate_circuit_bootstrap_lwe_pfpksk_list<
PolynomialCount(1),
);
// We apply the x -> -x function so instead of putting one in the first coeff of the
// polynomial, we put Scalar::MAX == - Sclar::One so that we can use a single function in
// polynomial, we put Scalar::MAX == - Scalar::One so that we can use a single function in
// the loop avoiding branching
last_polynomial_as_list.get_mut(0)[0] = Scalar::MAX;
@@ -269,7 +269,7 @@ pub fn par_generate_circuit_bootstrap_lwe_pfpksk_list<
PolynomialCount(1),
);
// We apply the x -> -x function so instead of putting one in the first coeff of the
// polynomial, we put Scalar::MAX == - Sclar::One so that we can use a single function in
// polynomial, we put Scalar::MAX == - Scalar::One so that we can use a single function in
// the loop avoiding branching
last_polynomial_as_list.get_mut(0)[0] = Scalar::MAX;

View File

@@ -64,6 +64,74 @@ where
y.wrapping_rem(Scalar::ONE.shl(log2_modulo))
}
/// Compute the smallest signed difference between two torus elements
pub fn torus_modular_diff<T: UnsignedInteger>(
first: T,
other: T,
modulus: CiphertextModulus<T>,
) -> f64 {
if modulus.is_native_modulus() {
let bits = T::BITS as i32;
// Using the [0; 1[ torus to reason
// Example with first = 0.1 and other = 0.9
// d0 = first - other = -0.8 = 0.2 mod 1
// d1 = other - first = 0.8
// d0 < d1 return 0.2
// if other and first are inverted we get
// d0 = 0.8
// d1 = 0.2
// d1 <= d0 return -0.2, the minus here can be seen as taking first as a reference
// In the first example adding 0.2 to other (0.9 + 0.2 mod 1 = 0.1) gets us to first
// In the second example adding -0.2 to other (0.1 - 0.2 mod 1 = 0.9) gets us to first
let d0 = first.wrapping_sub(other);
let d1 = other.wrapping_sub(first);
if d0 < d1 {
let d: f64 = d0.cast_into();
d / 2_f64.powi(bits)
} else {
let d: f64 = d1.cast_into();
-d / 2_f64.powi(bits)
}
} else {
let custom_modulus = T::cast_from(modulus.get_custom_modulus());
let d0 = first.wrapping_sub_custom_mod(other, custom_modulus);
let d1 = other.wrapping_sub_custom_mod(first, custom_modulus);
if d0 < d1 {
let d: f64 = d0.cast_into();
let cm_f: f64 = custom_modulus.cast_into();
d / cm_f
} else {
let d: f64 = d1.cast_into();
let cm_f: f64 = custom_modulus.cast_into();
-d / cm_f
}
}
}
// Our representation of non native power of 2 moduli puts the information in the MSBs and leaves
// the LSBs empty, this is what this function is checking
pub fn check_content_respects_mod<Scalar: UnsignedInteger, Input: AsRef<[Scalar]>>(
input: &Input,
modulus: CiphertextModulus<Scalar>,
) -> bool {
if modulus.is_native_modulus() {
true
} else if modulus.is_power_of_two() {
// If our modulus is 2^60, the scaling is 2^4 = 00...00010000, minus 1 = 00...00001111
// we want the bits under the mask to be 0
let power_2_diff_mask = modulus.get_power_of_two_scaling_to_native_torus() - Scalar::ONE;
input
.as_ref()
.iter()
.all(|&x| (x & power_2_diff_mask) == Scalar::ZERO)
} else {
// non native, not power of two
let scalar_modulus: Scalar = modulus.get_custom_modulus().cast_into();
input.as_ref().iter().all(|&x| x < scalar_modulus)
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -105,6 +105,45 @@ pub fn polynomial_wrapping_add_multisum_assign<Scalar, OutputCont, InputCont1, I
}
}
fn polynomial_wrapping_add_mul_assign_schoolbook<Scalar, OutputCont, InputCont1, InputCont2>(
output: &mut Polynomial<OutputCont>,
lhs: &Polynomial<InputCont1>,
rhs: &Polynomial<InputCont2>,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
InputCont1: Container<Element = Scalar>,
InputCont2: Container<Element = Scalar>,
{
fn implementation<Scalar: UnsignedInteger>(
mut output: Polynomial<&mut [Scalar]>,
lhs: Polynomial<&[Scalar]>,
rhs: Polynomial<&[Scalar]>,
) {
let polynomial_size = output.polynomial_size();
let degree = output.degree();
for (lhs_degree, &lhs_coeff) in lhs.iter().enumerate() {
for (rhs_degree, &rhs_coeff) in rhs.iter().enumerate() {
let target_degree = lhs_degree + rhs_degree;
if target_degree <= degree {
let output_coefficient = &mut output.as_mut()[target_degree];
*output_coefficient =
(*output_coefficient).wrapping_add(lhs_coeff.wrapping_mul(rhs_coeff));
} else {
let target_degree = target_degree % polynomial_size.0;
let output_coefficient = &mut output.as_mut()[target_degree];
*output_coefficient =
(*output_coefficient).wrapping_sub(lhs_coeff.wrapping_mul(rhs_coeff));
}
}
}
}
implementation(output.as_mut_view(), lhs.as_view(), rhs.as_view());
}
/// Add the result of the product between two polynomials, reduced modulo $(X^{N}+1)$, to the
/// output polynomial.
///
@@ -155,24 +194,7 @@ pub fn polynomial_wrapping_add_mul_assign<Scalar, OutputCont, InputCont1, InputC
polynomial_karatsuba_wrapping_mul(&mut tmp, lhs, rhs);
polynomial_wrapping_add_assign(output, &tmp);
} else {
let degree = output.degree();
for (lhs_degree, &lhs_coeff) in lhs.iter().enumerate() {
for (rhs_degree, &rhs_coeff) in rhs.iter().enumerate() {
let target_degree = lhs_degree + rhs_degree;
if target_degree <= degree {
let output_coefficient = &mut output.as_mut()[target_degree];
*output_coefficient =
(*output_coefficient).wrapping_add(lhs_coeff.wrapping_mul(rhs_coeff));
} else {
let target_degree = target_degree % polynomial_size.0;
let output_coefficient = &mut output.as_mut()[target_degree];
*output_coefficient =
(*output_coefficient).wrapping_sub(lhs_coeff.wrapping_mul(rhs_coeff));
}
}
}
polynomial_wrapping_add_mul_assign_schoolbook(output, lhs, rhs);
}
}
@@ -259,6 +281,18 @@ pub fn polynomial_wrapping_monic_monomial_mul_assign<Scalar, OutputCont>(
.for_each(|a| *a = a.wrapping_neg());
}
/// performs the operation: dst = -src, with wrapping arithmetic
fn copy_with_neg<Scalar: UnsignedInteger>(dst: &mut [Scalar], src: &[Scalar]) {
for (dst, src) in dst.iter_mut().zip(src) {
*dst = src.wrapping_neg();
}
}
/// performs the operation: dst = src
fn copy_without_neg<Scalar: UnsignedInteger>(dst: &mut [Scalar], src: &[Scalar]) {
dst.copy_from_slice(src);
}
/// Divides (mod $(X^{N}+1)$), the input polynomial with a monic monomial of a given degree i.e.
/// $X^{degree}$.
///
@@ -297,24 +331,25 @@ pub fn polynomial_wrapping_monic_monomial_div<Scalar, OutputCont, InputCont>(
let polynomial_size = output.polynomial_size().0;
let remaining_degree = monomial_degree.0 % polynomial_size;
let src_slice = &input[remaining_degree..];
let src_slice_len = src_slice.len();
let dst_slice = &mut output[..src_slice_len];
dst_slice.copy_from_slice(src_slice);
for (dst, &src) in output[polynomial_size - remaining_degree..]
.iter_mut()
.zip(input[..remaining_degree].iter())
{
*dst = src.wrapping_neg();
}
let full_cycles_count = monomial_degree.0 / polynomial_size;
if full_cycles_count % 2 != 0 {
output
.as_mut()
.iter_mut()
.for_each(|a| *a = a.wrapping_neg());
if full_cycles_count % 2 == 0 {
copy_without_neg(
&mut output[..polynomial_size - remaining_degree],
&input[remaining_degree..],
);
copy_with_neg(
&mut output[polynomial_size - remaining_degree..],
&input[..remaining_degree],
);
} else {
copy_with_neg(
&mut output[..polynomial_size - remaining_degree],
&input[remaining_degree..],
);
copy_without_neg(
&mut output[polynomial_size - remaining_degree..],
&input[..remaining_degree],
);
}
}
@@ -356,24 +391,101 @@ pub fn polynomial_wrapping_monic_monomial_mul<Scalar, OutputCont, InputCont>(
let polynomial_size = output.polynomial_size().0;
let remaining_degree = monomial_degree.0 % polynomial_size;
for (dst, &src) in output[..remaining_degree]
.iter_mut()
.zip(input[polynomial_size - remaining_degree..].iter())
{
*dst = src.wrapping_neg();
let full_cycles_count = monomial_degree.0 / polynomial_size;
if full_cycles_count % 2 == 0 {
copy_with_neg(
&mut output[..remaining_degree],
&input[polynomial_size - remaining_degree..],
);
copy_without_neg(
&mut output[remaining_degree..],
&input[..polynomial_size - remaining_degree],
);
} else {
copy_without_neg(
&mut output[..remaining_degree],
&input[polynomial_size - remaining_degree..],
);
copy_with_neg(
&mut output[remaining_degree..],
&input[..polynomial_size - remaining_degree],
);
}
}
/// Multiply (mod $(X^{N}+1)$), the input polynomial with a monic monomial of a given degree i.e.
/// $X^{degree}$, then subtract the input from the result and assign to the output.
///
/// output = input * X^degree - input
///
/// # Note
///
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
/// unsigned integer capacity.
pub(crate) fn polynomial_wrapping_monic_monomial_mul_and_subtract<Scalar, OutputCont, InputCont>(
output: &mut Polynomial<OutputCont>,
input: &Polynomial<InputCont>,
monomial_degree: MonomialDegree,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
InputCont: Container<Element = Scalar>,
{
assert!(
output.polynomial_size() == input.polynomial_size(),
"Output polynomial size {:?} is not the same as input polynomial size {:?}.",
output.polynomial_size(),
input.polynomial_size(),
);
/// performs the operation: dst = -src - src_orig, with wrapping arithmetic
fn copy_with_neg_and_subtract<Scalar: UnsignedInteger>(
dst: &mut [Scalar],
src: &[Scalar],
src_orig: &[Scalar],
) {
for ((dst, src), src_orig) in dst.iter_mut().zip(src).zip(src_orig) {
*dst = src.wrapping_neg().wrapping_sub(*src_orig);
}
}
let dst_slice = &mut output[remaining_degree..];
let dst_slice_len = dst_slice.len();
let src_slice = &input[..dst_slice_len];
dst_slice.copy_from_slice(src_slice);
/// performs the operation: dst = src - src_orig, with wrapping arithmetic
fn copy_without_neg_and_subtract<Scalar: UnsignedInteger>(
dst: &mut [Scalar],
src: &[Scalar],
src_orig: &[Scalar],
) {
for ((dst, src), src_orig) in dst.iter_mut().zip(src).zip(src_orig) {
*dst = src.wrapping_sub(*src_orig);
}
}
let polynomial_size = output.polynomial_size().0;
let remaining_degree = monomial_degree.0 % polynomial_size;
let full_cycles_count = monomial_degree.0 / polynomial_size;
if full_cycles_count % 2 != 0 {
output
.as_mut()
.iter_mut()
.for_each(|a| *a = a.wrapping_neg());
if full_cycles_count % 2 == 0 {
copy_with_neg_and_subtract(
&mut output[..remaining_degree],
&input[polynomial_size - remaining_degree..],
&input[..remaining_degree],
);
copy_without_neg_and_subtract(
&mut output[remaining_degree..],
&input[..polynomial_size - remaining_degree],
&input[remaining_degree..],
);
} else {
copy_without_neg_and_subtract(
&mut output[..remaining_degree],
&input[polynomial_size - remaining_degree..],
&input[..remaining_degree],
);
copy_with_neg_and_subtract(
&mut output[remaining_degree..],
&input[..polynomial_size - remaining_degree],
&input[remaining_degree..],
);
}
}
@@ -419,6 +531,45 @@ pub fn polynomial_wrapping_sub_multisum_assign<Scalar, OutputCont, InputCont1, I
}
}
fn polynomial_wrapping_sub_mul_assign_schoolbook<Scalar, OutputCont, InputCont1, InputCont2>(
output: &mut Polynomial<OutputCont>,
lhs: &Polynomial<InputCont1>,
rhs: &Polynomial<InputCont2>,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
InputCont1: Container<Element = Scalar>,
InputCont2: Container<Element = Scalar>,
{
fn implementation<Scalar: UnsignedInteger>(
mut output: Polynomial<&mut [Scalar]>,
lhs: Polynomial<&[Scalar]>,
rhs: Polynomial<&[Scalar]>,
) {
let polynomial_size = output.polynomial_size();
let degree = output.degree();
for (lhs_degree, &lhs_coeff) in lhs.iter().enumerate() {
for (rhs_degree, &rhs_coeff) in rhs.iter().enumerate() {
let target_degree = lhs_degree + rhs_degree;
if target_degree <= degree {
let output_coefficient = &mut output.as_mut()[target_degree];
*output_coefficient =
(*output_coefficient).wrapping_sub(lhs_coeff.wrapping_mul(rhs_coeff));
} else {
let target_degree = target_degree % polynomial_size.0;
let output_coefficient = &mut output.as_mut()[target_degree];
*output_coefficient =
(*output_coefficient).wrapping_add(lhs_coeff.wrapping_mul(rhs_coeff));
}
}
}
}
implementation(output.as_mut_view(), lhs.as_view(), rhs.as_view());
}
/// Subtract the result of the product between two polynomials, reduced modulo $(X^{N}+1)$, to the
/// output polynomial.
///
@@ -469,28 +620,11 @@ pub fn polynomial_wrapping_sub_mul_assign<Scalar, OutputCont, InputCont1, InputC
polynomial_karatsuba_wrapping_mul(&mut tmp, lhs, rhs);
polynomial_wrapping_sub_assign(output, &tmp);
} else {
let degree = output.degree();
for (lhs_degree, &lhs_coeff) in lhs.iter().enumerate() {
for (rhs_degree, &rhs_coeff) in rhs.iter().enumerate() {
let target_degree = lhs_degree + rhs_degree;
if target_degree <= degree {
let output_coefficient = &mut output.as_mut()[target_degree];
*output_coefficient =
(*output_coefficient).wrapping_sub(lhs_coeff.wrapping_mul(rhs_coeff));
} else {
let target_degree = target_degree % polynomial_size.0;
let output_coefficient = &mut output.as_mut()[target_degree];
*output_coefficient =
(*output_coefficient).wrapping_add(lhs_coeff.wrapping_mul(rhs_coeff));
}
}
}
polynomial_wrapping_sub_mul_assign_schoolbook(output, lhs, rhs);
}
}
/// Fill the ouptut polynomial, with the result of the product of two polynomials, reduced modulo
/// Fill the output polynomial, with the result of the product of two polynomials, reduced modulo
/// $(X^{N} + 1)$ with the schoolbook algorithm Complexity: $O(N^{2})$
///
/// # Note
@@ -707,7 +841,7 @@ mod test {
/// for random polynomial multiplication
fn test_multiply_karatsuba<T: UnsignedTorus>() {
// 50 times the test
for _i in 0..50 {
for _ in 0..50 {
// random source
let mut rng = rand::thread_rng();
@@ -730,7 +864,7 @@ mod test {
let mut ka_mul = Polynomial::new(T::ZERO, polynomial_size);
// compute the schoolbook
polynomial_wrapping_mul(&mut sb_mul, &poly_1, &poly_2);
polynomial_wrapping_add_mul_assign_schoolbook(&mut sb_mul, &poly_1, &poly_2);
// compute the karatsuba
polynomial_karatsuba_wrapping_mul(&mut ka_mul, &poly_1, &poly_2);
@@ -740,6 +874,72 @@ mod test {
}
}
/// test if we have the same result when using schoolbook or ntt/karatsuba
/// for random polynomial multiplication
fn test_add_mul<T: UnsignedTorus>() {
for polynomial_log in 4..=12 {
for _ in 0..10 {
let polynomial_size = PolynomialSize(1 << polynomial_log);
let mut generator = new_random_generator();
// generate two random Torus polynomials
let mut poly_1 = Polynomial::new(T::ZERO, polynomial_size);
generator.fill_slice_with_random_uniform::<T>(poly_1.as_mut());
let poly_1 = poly_1;
let mut poly_2 = Polynomial::new(T::ZERO, polynomial_size);
generator.fill_slice_with_random_uniform::<T>(poly_2.as_mut());
let poly_2 = poly_2;
// copy this polynomial
let mut sb_mul = Polynomial::new(T::ZERO, polynomial_size);
let mut ka_mul = Polynomial::new(T::ZERO, polynomial_size);
// compute the schoolbook
polynomial_wrapping_add_mul_assign_schoolbook(&mut sb_mul, &poly_1, &poly_2);
// compute the ntt/karatsuba
polynomial_wrapping_add_mul_assign(&mut ka_mul, &poly_1, &poly_2);
// test
assert_eq!(&sb_mul, &ka_mul);
}
}
}
/// test if we have the same result when using schoolbook or ntt/karatsuba
/// for random polynomial multiplication
fn test_sub_mul<T: UnsignedTorus>() {
for polynomial_log in 4..=12 {
for _ in 0..10 {
let polynomial_size = PolynomialSize(1 << polynomial_log);
let mut generator = new_random_generator();
// generate two random Torus polynomials
let mut poly_1 = Polynomial::new(T::ZERO, polynomial_size);
generator.fill_slice_with_random_uniform::<T>(poly_1.as_mut());
let poly_1 = poly_1;
let mut poly_2 = Polynomial::new(T::ZERO, polynomial_size);
generator.fill_slice_with_random_uniform::<T>(poly_2.as_mut());
let poly_2 = poly_2;
// copy this polynomial
let mut sb_mul = Polynomial::new(T::ZERO, polynomial_size);
let mut ka_mul = Polynomial::new(T::ZERO, polynomial_size);
// compute the schoolbook
polynomial_wrapping_sub_mul_assign_schoolbook(&mut sb_mul, &poly_1, &poly_2);
// compute the ntt/karatsuba
polynomial_wrapping_sub_mul_assign(&mut ka_mul, &poly_1, &poly_2);
// test
assert_eq!(&sb_mul, &ka_mul);
}
}
}
#[test]
pub fn test_multiply_divide_unit_monomial_u32() {
test_multiply_divide_unit_monomial::<u32>()
@@ -759,4 +959,24 @@ mod test {
pub fn test_multiply_karatsuba_u64() {
test_multiply_karatsuba::<u64>()
}
#[test]
pub fn test_add_mul_u32() {
test_add_mul::<u32>()
}
#[test]
pub fn test_add_mul_u64() {
test_add_mul::<u64>()
}
#[test]
pub fn test_sub_mul_u32() {
test_sub_mul::<u32>()
}
#[test]
pub fn test_sub_mul_u64() {
test_sub_mul::<u64>()
}
}

Some files were not shown because too many files have changed in this diff Show More