Compare commits

..

126 Commits

Author SHA1 Message Date
J-B Orfila
b69222df1c doc: updated examples 2023-10-03 19:51:59 +02:00
J-B Orfila
934a78496a doc: updating doc for v0.4 2023-10-03 15:26:12 +02:00
tmontaigu
6d49993629 WIP add FheInt{8,16,32,64,128,256} 2023-09-28 10:48:34 +02:00
tmontaigu
66a193eaca WIP lol 2023-09-28 10:44:16 +02:00
tmontaigu
a5fa497096 WIP 2023-09-28 09:56:24 +02:00
tmontaigu
7e3bf8e08a WIP 2023-09-27 19:14:24 +02:00
tmontaigu
9cb98cca2e feat(integer): add default signed_div_rem 2023-09-27 19:09:52 +02:00
tmontaigu
23d25a8281 feat(integer): add encryption of compressed signed radix 2023-09-27 19:09:25 +02:00
tmontaigu
ef26dc0e9e feat(integer): make compact ciphertext compatible with signed 2023-09-27 19:08:39 +02:00
tmontaigu
2e2f1200dc feat(integer): add sign extend fn for SignedRadixCiphertext 2023-09-27 09:00:19 +02:00
tmontaigu
0a27ed44f7 chore(integer): impl RecomposableSignedInteger for StaticSignedBigInt 2023-09-26 18:52:14 +02:00
tmontaigu
37a47e63a4 fix(integer): StaticSignedBigInt right shift 2023-09-26 18:52:14 +02:00
tmontaigu
417feb6a20 chore(hlapi): remove unused keychain_member from macro 2023-09-26 18:52:13 +02:00
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
Arthur Meyre
422e1f23d5 feat(core): add GLWE linear algebra primitives
- add appropriate tests and doctest
2023-09-19 11:41:16 +02:00
sarah el kazdadi
30a5ade17f fix(csprng): enable target_feature attributes for functions using simd intrinsics 2023-09-19 09:19:47 +02:00
tmontaigu
6cdd41c22f fix(integer): fix is_neg_possible
shortint's is_neg_possible did not check the degree on the correct value.
It check the degree on the value that should be added to the next block
not on the value that actually becomes the degree.

integer's is neg possible had the same problem so we also fix it
and also check the next block can 'receive' the value that should be added to it.

Our tests did not catch that as they were not testing non empty carry case
2023-09-18 17:19:48 +02:00
Mayeul@Zama
f369bec394 feat(core): add par_convert_standard_lwe_multi_bit_bootstrap_key_to_fourier 2023-09-18 14:35:06 +02:00
Mayeul@Zama
df4e9c69c7 feat(core): add par_convert_bootstrap_key_fourier 2023-09-18 14:35:06 +02:00
Mayeul@Zama
0e3d129906 feat(core): add par_fill_with_forward_fourier 2023-09-18 14:35:06 +02:00
Mayeul@Zama
682e455c94 feat(core): add par_convert_polynomials_list_to_fourier 2023-09-18 14:35:06 +02:00
Mayeul@Zama
b553a68fa9 chore(docs): simplify improved formula in dark market 2023-09-18 09:58:13 +02:00
Mayeul@Zama
be95eadf79 chore(docs): remove fallible directory change 2023-09-18 09:58:13 +02:00
Mayeul@Zama
0213a11a0c chore(docs): refactor dark market 2023-09-18 09:58:13 +02:00
Mayeul@Zama
413fde3b3b chore(docs): doc fixes and improvements 2023-09-18 09:58:13 +02:00
sarah el kazdadi
40f8ac9adf feat(core): replace unsafe simd intrinsics 2023-09-18 09:30:17 +02:00
dependabot[bot]
2ab25c1084 chore(deps): bump rtCamp/action-slack-notify from 2.2.0 to 2.2.1
Bumps [rtCamp/action-slack-notify](https://github.com/rtcamp/action-slack-notify) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/rtcamp/action-slack-notify/releases)
- [Commits](https://github.com/rtcamp/action-slack-notify/compare/v2.2.0...b24d75fe0e728a4bf9fc42ee217caa686d141ee8)

---
updated-dependencies:
- dependency-name: rtCamp/action-slack-notify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 09:25:56 +02:00
dependabot[bot]
86c62b70e5 chore(deps): bump JS-DevTools/npm-publish from 2.2.1 to 2.2.2
Bumps [JS-DevTools/npm-publish](https://github.com/js-devtools/npm-publish) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/js-devtools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](5a85faf05d...fe72237be0)

---
updated-dependencies:
- dependency-name: JS-DevTools/npm-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 09:25:49 +02:00
dependabot[bot]
18d790fc26 chore(deps): bump actions-ecosystem/action-add-labels
Bumps [actions-ecosystem/action-add-labels](https://github.com/actions-ecosystem/action-add-labels) from 1.1.0 to 1.1.3.
- [Release notes](https://github.com/actions-ecosystem/action-add-labels/releases)
- [Commits](bd52874380...18f1af5e35)

---
updated-dependencies:
- dependency-name: actions-ecosystem/action-add-labels
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 09:25:38 +02:00
dependabot[bot]
e9e3dae786 chore(deps): bump actions/checkout from 3.5.3 to 4.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 4.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.5.3...3df4ab11eba7bda6032a0b82a6bb43b11571feac)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 09:25:33 +02:00
dependabot[bot]
9b1dccbcb4 chore(deps): bump tj-actions/changed-files from 39.0.1 to 39.0.2
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 39.0.1 to 39.0.2.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](246636f5fa...6ee9cdc581)

---
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-09-18 09:25:28 +02:00
David Testé
cef011dd91 chore(ci): add compact keys to parameters security checks 2023-09-18 09:12:51 +02:00
Arthur Meyre
19f7d5af5c chore(test): remove some unused imports 2023-09-15 18:27:57 +02:00
Arthur Meyre
95ca5a80dc feat(tfhe): plug parallel decompression in higher level APIs
- boolean, shortint and integer have been updated to benefit from paralle
decompression
2023-09-15 18:27:57 +02:00
Arthur Meyre
b5fded34d1 feat(core): add multi bit BSK parallel decompression
- added decompression equivalence test
2023-09-15 18:27:57 +02:00
Arthur Meyre
0c3b09c83d chore(core): update multi bit BSK decompression to match encryption
- test passes
2023-09-15 18:27:57 +02:00
Arthur Meyre
85a19d30a9 feat(core): add KSK parallel decompression
- update to check decompression equivalence
2023-09-15 18:27:57 +02:00
Arthur Meyre
f58132c391 feat(core): add GGSW par decompression, add LWE BSK par decompression
- add par decompression equivalency test
2023-09-15 18:27:57 +02:00
Arthur Meyre
099bff84aa refactor(core): use forking to decompress GGSW ciphertext list
- existing BSK equivalency test passes which means the change is compatible
2023-09-15 18:27:57 +02:00
Arthur Meyre
42ad474a46 feat(core): add parallel decompression to GGSW ciphertext
- added equivalence test for parallel decompression
2023-09-15 18:27:57 +02:00
Arthur Meyre
9f6827b803 chore(core): update code to use the newly introduced MaskRandomGenerator 2023-09-15 18:27:57 +02:00
Arthur Meyre
d23c0df449 refactor(core): update decompression code for LweCiphertextList
- update related algorithms
2023-09-15 18:27:57 +02:00
Arthur Meyre
229bfeebe4 chore(core): remove unsafe new_unchecked on CiphertextModulus
- the function has been renamed to new and is now generally available
2023-09-15 18:27:16 +02:00
David Testé
48aab9d494 chore(ci): add boolean layer to code coverage 2023-09-15 11:00:23 +02:00
David Testé
e4769a8212 chore(ci): do not trigger code coverage on pr sync
Automatic code coverage will be enable again, once all the layers of the
library have coverage implemented.
2023-09-15 08:33:10 +02:00
David Testé
79bdaaba20 chore(ci): disable codecov patch status
This is done to avoid noisy reports in GitHub since coverage in all the
library layers haven't been implemented yet.
2023-09-15 08:33:10 +02:00
Arthur Meyre
02a14fff7c feat(core): add parallel LWE packing keyswitch
- update test to check equivalence of parallel and serial algorithm
2023-09-14 14:45:35 +02:00
Arthur Meyre
72cce4c5b2 chore(core): move thread_count computation before buffer allocations
- for parallel LWE KS and LWE PFPKS
- remove useless type annotation as well
2023-09-14 14:45:35 +02:00
David Testé
a317c4b9dd chore(ci): run code coverage workflow on aws ec2 instance 2023-09-14 13:32:09 +02:00
David Testé
2e2bd5ba29 chore(ci): use aws ami with missing packages installed
libssl-dev and pkg-config packages were missing to be able to install
cargo tarpaulin.
2023-09-14 13:32:09 +02:00
David Testé
827d8d8708 chore(ci): run coverage only if source files have changed 2023-09-14 13:32:09 +02:00
David Testé
bf434be347 chore(ci): exclude unwanted files from coverage 2023-09-14 13:32:09 +02:00
Arthur Meyre
ed83fbb460 chore(tfhe): remove unused deps, drop once_cell, enable paste when needed
- bump to 1.72 for std lib OnceLock and stabilized ARM intrisics
2023-09-14 10:52:34 +02:00
David Testé
0aad2e669b chore(ci): notify slack about coverage only in case of failure 2023-09-13 15:25:32 +02:00
David Testé
cd68a3bd1c chore(ci): execute clippy all-targets on benchmark code
The use of internal-keycache feature is mandatory to ensure clippy
is building against benchmark code.
2023-09-13 11:49:33 +02:00
Arthur Meyre
b77286bcbc chore(bench): fix pbs bench code which had issues with name type
- clippy will be hardened in a subsequent commit
2023-09-13 11:49:33 +02:00
Mayeul@Zama
609f83bbff style(shortint): smart always take mut references 2023-09-13 09:44:46 +02:00
David Testé
2a8ebb81d8 chore(ci): fix trivium test recipes 2023-09-13 09:09:39 +02:00
David Testé
1a2a17a6ab chore(ci): avoid running full suite several times on approval
If two or more reviewers approve a Pull Request successively with
no new commits in between, the full test suite would have been run
for each approval. With this commit, the full test suite would be
run again upon approval only if a push has occurred.
2023-09-13 09:09:20 +02:00
David Testé
0080caf95d chore(ci): add code coverage workflow
Coverage is performed and then data are sent to Codecov to handle
reports.
2023-09-13 09:09:03 +02:00
David Testé
c26238533b chore(ci): add key cache keys generation for coverage 2023-09-13 09:09:03 +02:00
David Testé
b29936d844 test(shortint): add key switching key handling in keycache
This mainly done to speed up coverage test by avoiding generating
key on each tests.
2023-09-13 09:09:03 +02:00
David Testé
25914cc727 test(shortint): make tests suite lighter for coverage target 2023-09-13 09:09:03 +02:00
David Testé
ca229e369b chore(ci): add make recipe for shortint test coverage
Tarpaulin is the cargo module used to test coverage.
2023-09-13 09:09:03 +02:00
Asher
4a99e54c0d chore(docs): typo in readme 2023-09-12 18:34:06 +02:00
tmontaigu
2383591351 chore(hlapi): move integrations tests into src dir 2023-09-12 10:32:42 +02:00
tmontaigu
dc464f398d chore(trivium): fix module inception 2023-09-12 10:32:26 +02:00
Mayeul@Zama
ce70b5758a refactor(shortint): replace apply_msg_identity_lut_assign by message_extract 2023-09-11 17:38:15 +02:00
Mayeul@Zama
1c76a08373 refactor(shortint): replace clear_carry by message_extract 2023-09-11 17:38:15 +02:00
Mayeul@Zama
9b19bd1e8b refactor(shortint): remove comp_assign operations 2023-09-11 17:38:15 +02:00
Arthur Meyre
a3dde21240 refactor(core): add NoiseRandomGenerator
- update EncryptionRandomGenerator to make use of that generator
2023-09-11 17:29:47 +02:00
Arthur Meyre
005e1afe2f refactor(core): add MaskRandomGenerator
- update EncryptionRandomGenerator to make use of that generator
- remove fork_n and par_fork_n on EncryptionRandomGenerator which were lazy
shortcuts to get multi bit PBS working faster
- MaskRandomGenerator will be usable for parallel decompression, which will
be done in a subsequent commit
2023-09-11 17:29:47 +02:00
Arthur Meyre
17c404b77d chore(core): re-organize encryption.rs to have smaller files
- preparatory work for refactor around mask and noise generators to allow
parallel decompression
2023-09-11 17:29:47 +02:00
Arthur Meyre
1403971d15 feat(core): add parallel LWE PFPKS and LWE KS
- add utility algorithms where applicable
2023-09-11 13:25:43 +02:00
Arthur Meyre
94ad69bfa3 chore(docs): update toolchain requirements 2023-09-11 13:05:33 +02:00
Arthur Meyre
bc129ba0ed chore(csprng): add dieharder test suite 2023-09-11 13:05:33 +02:00
Arthur Meyre
462834a12e chore(tfhe): use concrete-csprng 0.4.0 allows to use stable for M1/M2 macs 2023-09-11 13:05:33 +02:00
Arthur Meyre
ebeee1d6f8 chore(ci): add concrete-csprng to the CI 2023-09-11 13:05:33 +02:00
Arthur Meyre
d0e1a582e1 chore(csprng): add code base taken from concrete-core repo 2023-09-11 13:05:33 +02:00
Arthur Meyre
546cb369a8 chore(c_api): mark some functions manipulating pointers as unsafe
- restrict visibility to the c_api

BREAKING CHANGE:
change in visibility specifier is a breaking change though those functions
were not meant to be used by external users
2023-09-11 12:49:57 +02:00
dependabot[bot]
445af7ab97 chore(deps): bump tj-actions/changed-files from 38.2.1 to 39.0.1
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 38.2.1 to 39.0.1.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](2f7246cb26...246636f5fa)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 10:05:38 +02:00
dependabot[bot]
23f8c69bae chore(deps): bump actions/upload-artifact from 3.1.2 to 3.1.3
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](0b7f8abb15...a8a3f3ad30)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 10:05:29 +02:00
dependabot[bot]
b8df207b68 chore(deps): bump actions/checkout from 3.6.0 to 4.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](f43a0e5ff2...3df4ab11eb)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 10:05:17 +02:00
sarah el kazdadi
03688aee4c feat(fft): use monomial fft for multibit pbs 2023-09-08 18:27:43 +02:00
David Testé
5a3652f398 chore(ci): add clippy target for trivium application 2023-09-08 16:49:27 +02:00
David Testé
ae3c261d1e chore(bench): add 128 and 256 bit sizes for multi-bit benchmarks 2023-09-08 13:25:28 +02:00
tmontaigu
95dcf95e88 fix(integer): make trim_radix_msb handle dirty inputs 2023-09-08 10:29:25 +02:00
J-B Orfila
b92d6400f4 chore(bench): add new parameter sets to bench 2023-09-07 14:49:20 +02:00
David Testé
9ba27b4082 chore(ci): add multiplier to get effective pbs throughput value 2023-09-06 11:02:47 +02:00
dependabot[bot]
b164c90d75 chore(deps): bump tj-actions/changed-files from 38.1.3 to 38.2.1
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 38.1.3 to 38.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](c860b5c47f...2f7246cb26)

---
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-04 16:11:54 +02:00
Arthur Meyre
ff893ca6ef chore(wopbs): rework the inner part of WopLUTBase to be easier to use
BREAKING CHANGE: Wopbs LUT structs for shortint and integer have changed
APIs
2023-09-04 13:19:37 +02:00
tmontaigu
2dd1e13dad fix(integer): add assert on scalar bits in scalar_div
To work the scalar div requires that the scalar type (u32, u64, etc)
has at least as many bits of precision that what the ciphertext
encrypts (e.g. if the ciphertext encrypt 32bits, the scalar type must
have at least 32 bits)

We were missing the assert which lead to misuse, so we add it.
2023-09-01 18:04:51 +02:00
tmontaigu
4393fce861 feat(integer): be smarter about parallel carry propagation usage
The parallel carry propagation algorithm is a lot faster
than the non parallel one, however to actually be faster
it requires the CPU to have enough threads.

This adds checks so that we use the parallel algorithm
only if this condition is met.

This should improve performance on smaller CPUs
typically found in standard laptops/desktops.
2023-09-01 18:04:34 +02:00
Arthur Meyre
4f10cfa6dd chore(tfhe): bump version to 0.4.0 2023-08-31 14:29:17 +02:00
Mayeul@Zama
a6e4488de2 feat(js_test): add warning for expected error lor 2023-08-31 09:28:28 +02:00
Mayeul@Zama
878f3fa448 style(shortint): allow needless_pass_by_ref_mut on smart ops 2023-08-31 09:28:28 +02:00
Mayeul@Zama
90b887a56f style(tfhe): rename module 2023-08-31 09:28:28 +02:00
Mayeul@Zama
7dc52cf4ef chore(toolchain): fix clippy lints 2023-08-31 09:28:28 +02:00
Mayeul@Zama
a69333ed37 chore(toolchain): format rust 2023-08-31 09:28:28 +02:00
Mayeul@Zama
ffad25449e chore(toolchain): update toolchain 2023-08-31 09:28:28 +02:00
tmontaigu
6d471856c7 chore(docs): add section about writing trait bounds
This how to shows how to write the trait bounds of function
for the high level API.
2023-08-30 17:14:45 +02:00
David Testé
65749cb39b chore(bench): benchmark shortint ops against multi-bit parameters 2023-08-30 13:31:46 +02:00
David Testé
bf36316c12 chore(bench): benchmark server key creation from compressed one 2023-08-30 13:31:46 +02:00
David Testé
d98bb0eb86 chore(bench): measure bootstrap compressed key sizes 2023-08-30 13:31:46 +02:00
Arthur Meyre
5747af6dce feat(core): add LwePackingKeyswitchKey entity and algorithms
BREAKING CHANGE:
- more stringent checks for ciphertext moduli on some operations
- uniformized order of certain parameters for keyswitching primitives
- uniformized some primitives name
2023-08-30 10:54:35 +02:00
324 changed files with 24463 additions and 8765 deletions

View File

@@ -51,7 +51,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
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@f43a0e5ff2bd294095638e18286ca9a3d1956744
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@f43a0e5ff2bd294095638e18286ca9a3d1956744
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@f43a0e5ff2bd294095638e18286ca9a3d1956744
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@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}

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@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -88,13 +88,13 @@ jobs:
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_boolean
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
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@f43a0e5ff2bd294095638e18286ca9a3d1956744
- 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: |

112
.github/workflows/code_coverage.yml vendored Normal file
View File

@@ -0,0 +1,112 @@
name: Code Coverage
env:
CARGO_TERM_COLOR: always
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUSTFLAGS: "-C target-cpu=native"
on:
# Allows you to run this workflow manually from the Actions tab as an alternative.
workflow_dispatch:
# All the inputs are provided by Slab
inputs:
instance_id:
description: "AWS instance ID"
type: string
instance_image_id:
description: "AWS instance AMI ID"
type: string
instance_type:
description: "AWS instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
type: string
fork_repo:
description: 'Name of forked repo as user/repo'
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
type: string
jobs:
code-coverage:
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ inputs.runner_name }}
steps:
# Step used for log purpose.
- name: Instance configuration used
run: |
echo "ID: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Fork repo: ${{ inputs.fork_repo }}"
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: stable
default: true
- name: Check for file changes
id: changed-files
uses: tj-actions/changed-files@8238a4103220c636f2dad328ead8a7c8dbe316a3
with:
files_yaml: |
tfhe:
- tfhe/src/**
concrete_csprng:
- 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 coverage for boolean
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
run: |
make test_boolean_cov
- name: Run coverage for shortint
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
run: |
make test_shortint_cov
- name: Upload tfhe coverage to Codecov
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./coverage/
fail_ci_if_error: true
files: shortint/cobertura.xml,boolean/cobertura.xml
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@b24d75fe0e728a4bf9fc42ee217caa686d141ee8
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Code coverage finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -0,0 +1,74 @@
name: CSPRNG randomness testing Workflow
env:
CARGO_TERM_COLOR: always
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUSTFLAGS: "-C target-cpu=native"
on:
# Allows you to run this workflow manually from the Actions tab as an alternative.
workflow_dispatch:
# All the inputs are provided by Slab
inputs:
instance_id:
description: "AWS instance ID"
type: string
instance_image_id:
description: "AWS instance AMI ID"
type: string
instance_type:
description: "AWS instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
type: string
fork_repo:
description: 'Name of forked repo as user/repo'
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
type: string
jobs:
csprng-randomness-teting:
name: CSPRNG randomness testing
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ inputs.runner_name }}
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: stable
default: true
- name: Dieharder randomness test suite
run: |
make dieharder_csprng
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@b24d75fe0e728a4bf9fc42ee217caa686d141ee8
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "concrete-csprng randomness check finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

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@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -69,7 +69,7 @@ jobs:
parse_integer_benches
- name: Upload csv results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_csv_integer
path: ${{ env.PARSE_INTEGER_BENCH_CSV_FILE }}
@@ -90,13 +90,13 @@ jobs:
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_integer
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -45,7 +45,7 @@ jobs:
echo "Request ID: ${{ inputs.request_id }}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -67,7 +67,7 @@ jobs:
override: true
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab
@@ -91,7 +91,7 @@ jobs:
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_${{ matrix.command }}_${{ matrix.op_flavor }}
path: ${{ env.RESULTS_FILENAME }}

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@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -69,7 +69,7 @@ jobs:
parse_integer_benches
- name: Upload csv results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_csv_integer
path: ${{ env.PARSE_INTEGER_BENCH_CSV_FILE }}
@@ -90,13 +90,13 @@ jobs:
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_integer
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
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@f43a0e5ff2bd294095638e18286ca9a3d1956744
- 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@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -49,7 +49,7 @@ jobs:
- name: Publish web package
if: ${{ inputs.push_web_package }}
uses: JS-DevTools/npm-publish@5a85faf05d2ade2d5b6682bfe5359915d5159c6c
uses: JS-DevTools/npm-publish@fe72237be0920f7a0cafd6a966c9b929c9466e9b
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json
@@ -65,7 +65,7 @@ jobs:
- name: Publish Node package
if: ${{ inputs.push_node_package }}
uses: JS-DevTools/npm-publish@5a85faf05d2ade2d5b6682bfe5359915d5159c6c
uses: JS-DevTools/npm-publish@fe72237be0920f7a0cafd6a966c9b929c9466e9b
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
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@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- name: Checkout lattice-estimator
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
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

@@ -43,7 +43,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -78,13 +78,13 @@ jobs:
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_pbs
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
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@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -88,13 +88,13 @@ jobs:
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_shortint
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -43,7 +43,7 @@ jobs:
echo "Request ID: ${{ inputs.request_id }}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -65,7 +65,7 @@ jobs:
override: true
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab
@@ -104,7 +104,7 @@ jobs:
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_shortint_${{ matrix.op_flavor }}
path: ${{ env.RESULTS_FILENAME }}

View File

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

View File

@@ -16,12 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

View File

@@ -13,11 +13,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
- name: Save repo
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: repo-archive
path: '.'

View File

@@ -12,6 +12,16 @@ jobs:
permissions:
pull-requests: write
steps:
- name: Get current labels
uses: snnaplab/get-labels-action@f426df40304808ace3b5282d4f036515f7609576
- name: Remove approved label
if: ${{ github.event_name == 'pull_request' && contains(fromJSON(env.LABELS), 'approved') }}
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: approved
- name: Launch fast tests
if: ${{ github.event_name == 'pull_request' }}
uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
@@ -20,8 +30,17 @@ jobs:
message: |
@slab-ci cpu_fast_test
- name: Add approved label
uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf
if: ${{ github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && !contains(fromJSON(env.LABELS), 'approved') }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: approved
# PR label 'approved' presence is checked to avoid running the full test suite several times
# in case of multiple approvals without new commits in between.
- name: Launch full tests suite
if: ${{ github.event_name == 'pull_request_review' && github.event.review.state == 'approved' }}
if: ${{ github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && !contains(fromJSON(env.LABELS), 'approved') }}
uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
with:
allow-repeats: true
@@ -32,3 +51,4 @@ jobs:
@slab-ci cpu_integer_test
@slab-ci cpu_multi_bit_test
@slab-ci cpu_wasm_test
@slab-ci csprng_randomness_testing

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@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0
@@ -89,13 +89,13 @@ jobs:
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_wasm
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
repository: zama-ai/slab
path: slab

7
.gitignore vendored
View File

@@ -14,5 +14,8 @@ target/
/tfhe/benchmarks_parameters
**/*.csv
# Directory to use the NIST STS test suite built
/sts_testing
# dieharder run log
dieharder_run.log
# Coverage reports
./coverage/

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

104
Makefile
View File

@@ -11,6 +11,7 @@ AVX512_SUPPORT?=OFF
WASM_RUSTFLAGS:=
BIG_TESTS_INSTANCE?=FALSE
GEN_KEY_CACHE_MULTI_BIT_ONLY?=FALSE
GEN_KEY_CACHE_COVERAGE_ONLY?=FALSE
PARSE_INTEGER_BENCH_CSV_FILE?=tfhe_rs_integer_benches.csv
FAST_TESTS?=FALSE
FAST_BENCH?=FALSE
@@ -31,10 +32,32 @@ else
MULTI_BIT_ONLY=
endif
ifeq ($(GEN_KEY_CACHE_COVERAGE_ONLY),TRUE)
COVERAGE_ONLY=--coverage-only
else
COVERAGE_ONLY=
endif
# Variables used only for regex_engine example
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)
@@ -79,14 +102,42 @@ install_node:
$(SHELL) -i -c 'nvm install node' || \
( echo "Unable to install node, unknown error." && exit 1 )
.PHONY: install_dieharder # Install dieharder for apt distributions or macOS
install_dieharder:
@dieharder -h > /dev/null 2>&1 || \
if [[ "$(OS)" == "Linux" ]]; then \
sudo apt update && sudo apt install -y dieharder; \
elif [[ "$(OS)" == "Darwin" ]]; then\
brew install dieharder; \
fi || ( echo "Unable to install dieharder, unknown error." && exit 1 )
.PHONY: install_tarpaulin # Install tarpaulin to perform code coverage
install_tarpaulin: install_rs_build_toolchain
@cargo tarpaulin --version > /dev/null 2>&1 || \
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 \
@@ -137,10 +188,16 @@ clippy_tasks:
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
-p tasks -- --no-deps -D warnings
.PHONY: clippy_trivium # Run clippy lints on Trivium app
clippy_trivium: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy -p tfhe-trivium \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
-p tfhe -- --no-deps -D warnings
.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 \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_concrete_csprng # Run clippy lints on concrete-csprng
@@ -151,7 +208,7 @@ clippy_concrete_csprng:
.PHONY: clippy_all # Run all clippy targets
clippy_all: clippy clippy_boolean clippy_shortint clippy_integer clippy_all_targets clippy_c_api \
clippy_js_wasm_api clippy_tasks clippy_core clippy_concrete_csprng
clippy_js_wasm_api clippy_tasks clippy_core clippy_concrete_csprng clippy_trivium
.PHONY: clippy_fast # Run main clippy targets
clippy_fast: clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core \
@@ -161,8 +218,8 @@ clippy_concrete_csprng
gen_key_cache: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example generates_test_keys \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- \
$(MULTI_BIT_ONLY)
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache -p tfhe -- \
$(MULTI_BIT_ONLY) $(COVERAGE_ONLY)
.PHONY: build_core # Build core_crypto without experimental features
build_core: install_rs_build_toolchain install_rs_check_toolchain
@@ -256,6 +313,14 @@ test_boolean: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
.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 \
$(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) \
@@ -289,6 +354,14 @@ test_shortint: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- shortint::
.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 \
$(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
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
@@ -338,14 +411,12 @@ test_examples: test_sha256_bool test_regex_engine
.PHONY: test_trivium # Run tests for trivium
test_trivium: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
trivium --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
-- --test-threads=1
-p tfhe-trivium -- --test-threads=1 trivium::
.PHONY: test_kreyvium # Run tests for kreyvium
test_kreyvium: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
kreyvium --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
-- --test-threads=1
-p tfhe-trivium -- --test-threads=1 kreyvium::
.PHONY: test_concrete_csprng # Run concrete-csprng tests
test_concrete_csprng:
@@ -428,6 +499,10 @@ no_tfhe_typo:
no_dbg_log:
@./scripts/no_dbg_calls.sh
.PHONY: dieharder_csprng # Run the dieharder test suite on our CSPRNG implementation
dieharder_csprng: install_dieharder build_concrete_csprng
./scripts/dieharder_test.sh
#
# Benchmarks
#
@@ -454,6 +529,15 @@ bench_shortint: install_rs_check_toolchain
--bench shortint-bench \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
.PHONY: bench_shortint_multi_bit # Run benchmarks for shortint using multi-bit parameters
bench_shortint_multi_bit: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_TYPE=MULTI_BIT \
__TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) \
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench shortint-bench \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe --
.PHONY: bench_boolean # Run benchmarks for boolean
bench_boolean: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
@@ -548,7 +632,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

@@ -47,7 +47,7 @@ tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64-un
```toml
tfhe = { version = "*", features = ["boolean", "shortint", "integer", "aarch64-unix"] }
```
Note: users with ARM devices must use `TFHE-rs` by compiling using the `nightly` toolchain.
Note: users with ARM devices must compile `TFHE-rs` using a stable toolchain with version >= 1.72.
+ For x86_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND)
@@ -92,10 +92,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// On the server side:
set_server_key(server_keys);
// Clear equivalent computations: 1344 * 8 = 10752
// Clear equivalent computations: 1344 * 5 = 6720
let encrypted_res_mul = &encrypted_a * &encrypted_b;
// Clear equivalent computations: 1344 >> 8 = 42
// Clear equivalent computations: 1344 >> 5 = 42
encrypted_a = &encrypted_res_mul >> &encrypted_b;
// Clear equivalent computations: let casted_a = a as u8;

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> {
@@ -80,9 +80,9 @@ impl KreyviumStream<FheBool> {
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_register = [false; 93].map(|x| FheBool::encrypt_trivial(x));
let mut b_register = [false; 84].map(|x| FheBool::encrypt_trivial(x));
let mut c_register = [false; 111].map(|x| FheBool::encrypt_trivial(x));
let mut a_register = [false; 93].map(FheBool::encrypt_trivial);
let mut b_register = [false; 84].map(FheBool::encrypt_trivial);
let mut c_register = [false; 111].map(FheBool::encrypt_trivial);
for i in 0..93 {
a_register[i] = key[128 - 93 + i].clone();
@@ -99,7 +99,7 @@ impl KreyviumStream<FheBool> {
key.reverse();
iv.reverse();
let iv = iv.map(|x| FheBool::encrypt_trivial(x));
let iv = iv.map(FheBool::encrypt_trivial);
unset_server_key();
KreyviumStream::<FheBool>::new_from_registers(
@@ -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],
@@ -149,7 +149,7 @@ where
}
/// Computes one turn of the stream, updating registers and outputting the new bit.
pub fn next(&mut self) -> T {
pub fn next_bool(&mut self) -> T {
match &self.fhe_key {
Some(sk) => set_server_key(sk.clone()),
None => (),

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};
@@ -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> {
@@ -54,18 +54,15 @@ impl KreyviumStreamByte<u8> {
let mut c_byte_reg = [0u8; 14];
// Copy key bits into a register
for b in 0..12 {
a_byte_reg[b] = key_bytes[b + 4];
}
a_byte_reg.copy_from_slice(&key_bytes[4..]);
// Copy iv bits into a register
for b in 0..11 {
b_byte_reg[b] = iv_bytes[b + 5];
}
b_byte_reg.copy_from_slice(&iv_bytes[5..]);
// Copy a lot of ones in the c register
c_byte_reg[0] = 252;
for b in 1..8 {
c_byte_reg[b] = 255;
}
c_byte_reg[1..8].fill(255);
// Copy iv bits in the c register
c_byte_reg[8] = (iv_bytes[0] << 4) | 31;
for b in 9..14 {
@@ -100,23 +97,22 @@ impl KreyviumStreamByte<FheUint8> {
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_byte_reg = [0u8; 12].map(|x| FheUint8::encrypt_trivial(x));
let mut b_byte_reg = [0u8; 11].map(|x| FheUint8::encrypt_trivial(x));
let mut c_byte_reg = [0u8; 14].map(|x| FheUint8::encrypt_trivial(x));
let mut a_byte_reg = [0u8; 12].map(FheUint8::encrypt_trivial);
let mut b_byte_reg = [0u8; 11].map(FheUint8::encrypt_trivial);
let mut c_byte_reg = [0u8; 14].map(FheUint8::encrypt_trivial);
// Copy key bits into a register
for b in 0..12 {
a_byte_reg[b] = key_bytes[b + 4].clone();
}
a_byte_reg.clone_from_slice(&key_bytes[4..]);
// Copy iv bits into a register
for b in 0..11 {
b_byte_reg[b] = FheUint8::encrypt_trivial(iv_bytes[b + 5]);
}
// Copy a lot of ones in the c register
c_byte_reg[0] = FheUint8::encrypt_trivial(252u8);
for b in 1..8 {
c_byte_reg[b] = FheUint8::encrypt_trivial(255u8);
}
c_byte_reg[1..8].fill_with(|| FheUint8::encrypt_trivial(255u8));
// Copy iv bits in the c register
c_byte_reg[8] = FheUint8::encrypt_trivial((&iv_bytes[0] << 4u8) | 31u8);
for b in 9..14 {
@@ -150,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],
@@ -292,6 +288,6 @@ where
impl KreyviumStreamByte<FheUint8> {
pub fn get_server_key(&self) -> &ServerKey {
&self.fhe_key.as_ref().unwrap()
self.fhe_key.as_ref().unwrap()
}
}

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(
@@ -75,7 +75,7 @@ impl KreyviumStreamShortint {
}
/// Computes one turn of the stream, updating registers and outputting the new bit.
pub fn next(&mut self) -> Ciphertext {
pub fn next_ct(&mut self) -> Ciphertext {
let [o, a, b, c] = self.get_output_and_values(0);
self.a.push(a);
@@ -149,7 +149,7 @@ impl KreyviumStreamShortint {
.unchecked_add_assign(&mut new_c, c5);
self.internal_server_key
.unchecked_add_assign(&mut new_c, &temp_b);
self.internal_server_key.clear_carry_assign(&mut new_c);
self.internal_server_key.message_extract_assign(&mut new_c);
new_c
},
|| {

View File

@@ -1,3 +1,4 @@
#[allow(clippy::module_inception)]
mod kreyvium;
pub use kreyvium::KreyviumStream;

View File

@@ -56,7 +56,7 @@ fn get_hexadecimal_string_from_lsb_first_stream(a: Vec<bool>) -> String {
_ => (),
};
}
return hexadecimal;
hexadecimal
}
fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
@@ -65,7 +65,7 @@ fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
for test in a {
hexadecimal.push_str(&format!("{:02X?}", test));
}
return hexadecimal;
hexadecimal
}
fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
@@ -73,7 +73,7 @@ fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
for test in a {
hexadecimal.push_str(&format!("{:016X?}", test));
}
return hexadecimal;
hexadecimal
}
#[test]
@@ -86,7 +86,7 @@ fn kreyvium_test_1() {
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
vec.push(kreyvium.next());
vec.push(kreyvium.next_bool());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
@@ -105,7 +105,7 @@ fn kreyvium_test_2() {
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
vec.push(kreyvium.next());
vec.push(kreyvium.next_bool());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
@@ -124,7 +124,7 @@ fn kreyvium_test_3() {
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
vec.push(kreyvium.next());
vec.push(kreyvium.next_bool());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
@@ -161,7 +161,7 @@ fn kreyvium_test_4() {
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
vec.push(kreyvium.next());
vec.push(kreyvium.next_bool());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);

View File

@@ -1,3 +1,4 @@
#[allow(clippy::module_inception)]
mod static_deque;
pub use static_deque::StaticDeque;
mod static_byte_deque;

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;
@@ -77,7 +77,7 @@ where
}
let byte_next: &T = &self.deque[i / 8 + 1];
return (byte << bit_idx) | (byte_next >> (8 - bit_idx as u8));
(byte << bit_idx) | (byte_next >> (8 - bit_idx))
}
}
@@ -101,7 +101,7 @@ mod tests {
assert!(deque.bit(7) == 0);
// second youngest: 128
assert!(deque.bit(8 + 0) == 0);
assert!(deque.bit(8) == 0);
assert!(deque.bit(8 + 1) == 0);
assert!(deque.bit(8 + 2) == 0);
assert!(deque.bit(8 + 3) == 0);
@@ -111,7 +111,7 @@ mod tests {
assert!(deque.bit(8 + 7) > 0);
// oldest: 64
assert!(deque.bit(16 + 0) == 0);
assert!(deque.bit(16) == 0);
assert!(deque.bit(16 + 1) == 0);
assert!(deque.bit(16 + 2) == 0);
assert!(deque.bit(16 + 3) == 0);

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 @@
mod trivium;
pub use trivium::TriviumStream;
mod trivium_bool;
pub use trivium_bool::TriviumStream;
mod trivium_byte;
pub use trivium_byte::TriviumStreamByte;

View File

@@ -56,7 +56,7 @@ fn get_hexadecimal_string_from_lsb_first_stream(a: Vec<bool>) -> String {
_ => (),
};
}
return hexadecimal;
hexadecimal
}
fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
@@ -65,7 +65,7 @@ fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
for test in a {
hexadecimal.push_str(&format!("{:02X?}", test));
}
return hexadecimal;
hexadecimal
}
fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
@@ -73,7 +73,7 @@ fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
for test in a {
hexadecimal.push_str(&format!("{:016X?}", test));
}
return hexadecimal;
hexadecimal
}
#[test]
@@ -89,7 +89,7 @@ fn trivium_test_1() {
let mut vec = Vec::<bool>::with_capacity(512 * 8);
while vec.len() < 512 * 8 {
vec.push(trivium.next());
vec.push(trivium.next_bool());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
@@ -114,7 +114,7 @@ fn trivium_test_2() {
let mut vec = Vec::<bool>::with_capacity(512 * 8);
while vec.len() < 512 * 8 {
vec.push(trivium.next());
vec.push(trivium.next_bool());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
@@ -139,7 +139,7 @@ fn trivium_test_3() {
let mut vec = Vec::<bool>::with_capacity(512 * 8);
while vec.len() < 512 * 8 {
vec.push(trivium.next());
vec.push(trivium.next_bool());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
@@ -182,7 +182,7 @@ fn trivium_test_4() {
let mut vec = Vec::<bool>::with_capacity(131072 * 8);
while vec.len() < 131072 * 8 {
vec.push(trivium.next());
vec.push(trivium.next_bool());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);

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> {
@@ -66,9 +66,9 @@ impl TriviumStream<FheBool> {
// Initialization of Trivium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_register = [false; 93].map(|x| FheBool::encrypt_trivial(x));
let mut b_register = [false; 84].map(|x| FheBool::encrypt_trivial(x));
let mut c_register = [false; 111].map(|x| FheBool::encrypt_trivial(x));
let mut a_register = [false; 93].map(FheBool::encrypt_trivial);
let mut b_register = [false; 84].map(FheBool::encrypt_trivial);
let mut c_register = [false; 111].map(FheBool::encrypt_trivial);
for i in 0..80 {
a_register[93 - 80 + i] = key[i].clone();
@@ -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],
@@ -121,7 +121,7 @@ where
}
/// Computes one turn of the stream, updating registers and outputting the new bit.
pub fn next(&mut self) -> T {
pub fn next_bool(&mut self) -> T {
match &self.fhe_key {
Some(sk) => set_server_key(sk.clone()),
None => (),

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};
@@ -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> {
@@ -81,9 +81,9 @@ impl TriviumStreamByte<FheUint8> {
// Initialization of Trivium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_byte_reg = [0u8; 12].map(|x| FheUint8::encrypt_trivial(x));
let mut b_byte_reg = [0u8; 11].map(|x| FheUint8::encrypt_trivial(x));
let mut c_byte_reg = [0u8; 14].map(|x| FheUint8::encrypt_trivial(x));
let mut a_byte_reg = [0u8; 12].map(FheUint8::encrypt_trivial);
let mut b_byte_reg = [0u8; 11].map(FheUint8::encrypt_trivial);
let mut c_byte_reg = [0u8; 14].map(FheUint8::encrypt_trivial);
for i in 0..10 {
a_byte_reg[12 - 10 + i] = key[i].clone();
@@ -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],
@@ -236,6 +236,6 @@ where
impl TriviumStreamByte<FheUint8> {
pub fn get_server_key(&self) -> &ServerKey {
&self.fhe_key.as_ref().unwrap()
self.fhe_key.as_ref().unwrap()
}
}

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],
@@ -63,7 +63,7 @@ impl TriviumStreamShortint {
}
/// Computes one turn of the stream, updating registers and outputting the new bit.
pub fn next(&mut self) -> Ciphertext {
pub fn next_ct(&mut self) -> Ciphertext {
let [o, a, b, c] = self.get_output_and_values(0);
self.a.push(a);
@@ -113,7 +113,7 @@ impl TriviumStreamShortint {
.unchecked_add_assign(&mut new_a, a5);
self.internal_server_key
.unchecked_add_assign(&mut new_a, &temp_c);
self.internal_server_key.clear_carry_assign(&mut new_a);
self.internal_server_key.message_extract_assign(&mut new_a);
new_a
},
|| {
@@ -122,7 +122,7 @@ impl TriviumStreamShortint {
.unchecked_add_assign(&mut new_b, b5);
self.internal_server_key
.unchecked_add_assign(&mut new_b, &temp_a);
self.internal_server_key.clear_carry_assign(&mut new_b);
self.internal_server_key.message_extract_assign(&mut new_b);
new_b
},
)
@@ -135,7 +135,7 @@ impl TriviumStreamShortint {
.unchecked_add_assign(&mut new_c, c5);
self.internal_server_key
.unchecked_add_assign(&mut new_c, &temp_b);
self.internal_server_key.clear_carry_assign(&mut new_c);
self.internal_server_key.message_extract_assign(&mut new_c);
new_c
},
|| {

View File

@@ -108,12 +108,25 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
)
)
# This is a special case where PBS are blasted as vector LWE ciphertext with
# variable length to saturate the machine. To get the actual throughput we need to
# multiply by the length of the vector.
if "PBS_throughput" in test_name and "chunk" in test_name:
try:
multiplier = int(test_name.split("chunk")[0].split("_")[-1])
except ValueError:
parsing_failures.append((full_name,
"failed to extract throughput multiplier"))
continue
else:
multiplier = 1
if stat_name == "mean" and compute_throughput:
test_suffix = "ops-per-sec"
test_name_parts.append(test_suffix)
result_values.append(
_create_point(
compute_ops_per_second(value),
multiplier * compute_ops_per_second(value),
"_".join(test_name_parts),
bench_class,
"throughput",
@@ -129,7 +142,7 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
test_name_parts.append(test_suffix)
result_values.append(
_create_point(
compute_ops_per_dollar(value, hardware_hourly_cost),
multiplier * compute_ops_per_dollar(value, hardware_hourly_cost),
"_".join(test_name_parts),
bench_class,
"throughput",

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

@@ -1,16 +1,16 @@
[profile.cpu-big]
region = "eu-west-3"
image_id = "ami-0ab73f5bd11708a85"
image_id = "ami-051942e4055555752"
instance_type = "m6i.32xlarge"
[profile.cpu-small]
region = "eu-west-3"
image_id = "ami-0ab73f5bd11708a85"
image_id = "ami-051942e4055555752"
instance_type = "m6i.4xlarge"
[profile.bench]
region = "eu-west-3"
image_id = "ami-0ab73f5bd11708a85"
image_id = "ami-051942e4055555752"
instance_type = "m6i.metal"
[command.cpu_test]
@@ -77,3 +77,13 @@ check_run_name = "PBS CPU AWS Benchmarks"
workflow = "wasm_client_benchmark.yml"
profile = "cpu-small"
check_run_name = "WASM Client AWS Benchmarks"
[command.csprng_randomness_testing]
workflow = "csprng_randomness_testing.yml"
profile = "cpu-small"
check_run_name = "CSPRNG randomness testing"
[command.code_coverage]
workflow = "code_coverage.yml"
profile = "cpu-small"
check_run_name = "Code coverage"

4
codecov.yml Normal file
View File

@@ -0,0 +1,4 @@
coverage:
status:
# Disable patch checks in GitHub until all tfhe-rs layers have coverage implemented.
patch: false

View File

@@ -19,8 +19,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

@@ -29,37 +29,39 @@ use concrete_csprng::seeders::UnixSeeder as ActivatedSeeder;
use concrete_csprng::seeders::Seeder;
use std::io::prelude::*;
use std::io::{stdout, Stdout};
use std::io::{stdout, StdoutLock};
fn write_bytes(buffer: &mut [u8], generator: &mut ActivatedRandomGenerator, stdout: &mut Stdout) {
fn write_bytes(
buffer: &mut [u8],
generator: &mut ActivatedRandomGenerator,
stdout: &mut StdoutLock<'_>,
) -> std::io::Result<()> {
buffer.iter_mut().zip(generator).for_each(|(b, g)| *b = g);
stdout.write_all(buffer).unwrap();
stdout.write_all(buffer)
}
fn infinite_bytes_generation(
buffer: &mut [u8],
generator: &mut ActivatedRandomGenerator,
stdout: &mut Stdout,
stdout: &mut StdoutLock<'_>,
) {
loop {
write_bytes(buffer, generator, stdout);
}
while write_bytes(buffer, generator, stdout).is_ok() {}
}
fn bytes_generation(
bytes_total: usize,
buffer: &mut [u8],
generator: &mut ActivatedRandomGenerator,
stdout: &mut Stdout,
stdout: &mut StdoutLock<'_>,
) {
let quotient = bytes_total / buffer.len();
let remaining = bytes_total % buffer.len();
for _ in 0..quotient {
write_bytes(buffer, generator, stdout);
write_bytes(buffer, generator, stdout).unwrap();
}
write_bytes(&mut buffer[0..remaining], generator, stdout)
write_bytes(&mut buffer[0..remaining], generator, stdout).unwrap()
}
pub fn main() {
@@ -90,10 +92,16 @@ pub fn main() {
let new_seeder = || ActivatedSeeder;
let mut seeder = new_seeder();
let mut generator = ActivatedRandomGenerator::new(seeder.seed());
let mut stdout = stdout();
let seed = seeder.seed();
// Don't print on std out
eprintln!("seed={seed:?}");
let mut generator = ActivatedRandomGenerator::new(seed);
let stdout = stdout();
let mut buffer = [0u8; 16];
// lock stdout as there is a single thread running
let mut stdout = stdout.lock();
match matches.get_one::<usize>("bytes_total") {
Some(&total) => {
bytes_generation(total, &mut buffer, &mut generator, &mut stdout);

View File

@@ -35,17 +35,23 @@ impl AesBlockCipher for ArmAesBlockCipher {
}
fn generate_batch(&mut self, AesIndex(aes_ctr): AesIndex) -> [u8; BYTES_PER_BATCH] {
let mut output = [0u8; BYTES_PER_BATCH];
// We want 128 bytes of output, the ctr gives 128 bit message (16 bytes)
for (i, out) in output.chunks_exact_mut(16).enumerate() {
let encrypted = unsafe {
#[target_feature(enable = "aes,neon")]
unsafe fn implementation(
this: &ArmAesBlockCipher,
AesIndex(aes_ctr): AesIndex,
) -> [u8; BYTES_PER_BATCH] {
let mut output = [0u8; BYTES_PER_BATCH];
// We want 128 bytes of output, the ctr gives 128 bit message (16 bytes)
for (i, out) in output.chunks_exact_mut(16).enumerate() {
// Safe because we prevent the user from creating the Generator
// on non-supported hardware
encrypt(aes_ctr + (i as u128), &self.round_keys)
};
out.copy_from_slice(&encrypted.to_ne_bytes());
let encrypted = encrypt(aes_ctr + (i as u128), &this.round_keys);
out.copy_from_slice(&encrypted.to_ne_bytes());
}
output
}
output
// SAFETY: we checked for aes and neon availability in `Self::new`
unsafe { implementation(self, AesIndex(aes_ctr)) }
}
}
@@ -55,6 +61,7 @@ impl AesBlockCipher for ArmAesBlockCipher {
///
/// You must make sure the CPU's arch is`aarch64` and has
/// `neon` and `aes` features.
#[inline(always)]
unsafe fn sub_word(word: u32) -> u32 {
let data = vreinterpretq_u8_u32(vdupq_n_u32(word));
let zero_key = vdupq_n_u8(0u8);
@@ -68,14 +75,17 @@ unsafe fn sub_word(word: u32) -> u32 {
vgetq_lane_u32::<0>(vreinterpretq_u32_u8(temp))
}
#[inline(always)]
fn uint8x16_t_to_u128(input: uint8x16_t) -> u128 {
unsafe { transmute(input) }
}
#[inline(always)]
fn u128_to_uint8x16_t(input: u128) -> uint8x16_t {
unsafe { transmute(input) }
}
#[target_feature(enable = "aes,neon")]
unsafe fn generate_round_keys(key: AesKey) -> [uint8x16_t; NUM_ROUND_KEYS] {
let mut round_keys: [uint8x16_t; NUM_ROUND_KEYS] = std::mem::zeroed();
round_keys[0] = u128_to_uint8x16_t(key.0);
@@ -109,6 +119,7 @@ unsafe fn generate_round_keys(key: AesKey) -> [uint8x16_t; NUM_ROUND_KEYS] {
///
/// You must make sure the CPU's arch is`aarch64` and has
/// `neon` and `aes` features.
#[inline(always)]
unsafe fn encrypt(message: u128, keys: &[uint8x16_t; NUM_ROUND_KEYS]) -> u128 {
// Notes:
// According the [ARM Manual](https://developer.arm.com/documentation/ddi0487/gb/):

View File

@@ -1,7 +1,7 @@
use crate::generators::aes_ctr::{AesBlockCipher, AesIndex, AesKey, BYTES_PER_BATCH};
use std::arch::x86_64::{
__m128i, _mm_aesenc_si128, _mm_aesenclast_si128, _mm_aeskeygenassist_si128, _mm_load_si128,
_mm_shuffle_epi32, _mm_slli_si128, _mm_store_si128, _mm_xor_si128,
__m128i, _mm_aesenc_si128, _mm_aesenclast_si128, _mm_aeskeygenassist_si128, _mm_shuffle_epi32,
_mm_slli_si128, _mm_store_si128, _mm_xor_si128,
};
use std::mem::transmute;
@@ -25,26 +25,36 @@ impl AesBlockCipher for AesniBlockCipher {
)
}
let round_keys = generate_round_keys(key);
// SAFETY: we checked for aes and sse2 availability
let round_keys = unsafe { generate_round_keys(key) };
AesniBlockCipher { round_keys }
}
fn generate_batch(&mut self, AesIndex(aes_ctr): AesIndex) -> [u8; BYTES_PER_BATCH] {
si128arr_to_u8arr(aes_encrypt_many(
&u128_to_si128(aes_ctr),
&u128_to_si128(aes_ctr + 1),
&u128_to_si128(aes_ctr + 2),
&u128_to_si128(aes_ctr + 3),
&u128_to_si128(aes_ctr + 4),
&u128_to_si128(aes_ctr + 5),
&u128_to_si128(aes_ctr + 6),
&u128_to_si128(aes_ctr + 7),
&self.round_keys,
))
#[target_feature(enable = "sse2,aes")]
unsafe fn implementation(
this: &AesniBlockCipher,
AesIndex(aes_ctr): AesIndex,
) -> [u8; BYTES_PER_BATCH] {
si128arr_to_u8arr(aes_encrypt_many(
u128_to_si128(aes_ctr),
u128_to_si128(aes_ctr + 1),
u128_to_si128(aes_ctr + 2),
u128_to_si128(aes_ctr + 3),
u128_to_si128(aes_ctr + 4),
u128_to_si128(aes_ctr + 5),
u128_to_si128(aes_ctr + 6),
u128_to_si128(aes_ctr + 7),
&this.round_keys,
))
}
// SAFETY: we checked for aes and sse2 availability in `Self::new`
unsafe { implementation(self, AesIndex(aes_ctr)) }
}
}
fn generate_round_keys(key: AesKey) -> [__m128i; 11] {
#[target_feature(enable = "sse2,aes")]
unsafe fn generate_round_keys(key: AesKey) -> [__m128i; 11] {
let key = u128_to_si128(key.0);
let mut keys: [__m128i; 11] = [u128_to_si128(0); 11];
aes_128_key_expansion(key, &mut keys);
@@ -54,27 +64,19 @@ fn generate_round_keys(key: AesKey) -> [__m128i; 11] {
// Uses aes to encrypt many values at once. This allows a substantial speedup (around 30%)
// compared to the naive approach.
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn aes_encrypt_many(
message_1: &__m128i,
message_2: &__m128i,
message_3: &__m128i,
message_4: &__m128i,
message_5: &__m128i,
message_6: &__m128i,
message_7: &__m128i,
message_8: &__m128i,
message_1: __m128i,
message_2: __m128i,
message_3: __m128i,
message_4: __m128i,
message_5: __m128i,
message_6: __m128i,
message_7: __m128i,
message_8: __m128i,
keys: &[__m128i; 11],
) -> [__m128i; 8] {
unsafe {
let message_1 = _mm_load_si128(message_1 as *const __m128i);
let message_2 = _mm_load_si128(message_2 as *const __m128i);
let message_3 = _mm_load_si128(message_3 as *const __m128i);
let message_4 = _mm_load_si128(message_4 as *const __m128i);
let message_5 = _mm_load_si128(message_5 as *const __m128i);
let message_6 = _mm_load_si128(message_6 as *const __m128i);
let message_7 = _mm_load_si128(message_7 as *const __m128i);
let message_8 = _mm_load_si128(message_8 as *const __m128i);
let mut tmp_1 = _mm_xor_si128(message_1, keys[0]);
let mut tmp_2 = _mm_xor_si128(message_2, keys[0]);
let mut tmp_3 = _mm_xor_si128(message_3, keys[0]);
@@ -125,6 +127,7 @@ fn aes_128_assist(temp1: __m128i, temp2: __m128i) -> __m128i {
temp1
}
#[inline(always)]
fn aes_128_key_expansion(key: __m128i, keys: &mut [__m128i; 11]) {
let (mut temp1, mut temp2): (__m128i, __m128i);
temp1 = key;
@@ -163,6 +166,7 @@ fn aes_128_key_expansion(key: __m128i, keys: &mut [__m128i; 11]) {
}
}
#[inline(always)]
fn u128_to_si128(input: u128) -> __m128i {
unsafe { transmute(input) }
}
@@ -172,6 +176,7 @@ fn si128_to_u128(input: __m128i) -> u128 {
unsafe { transmute(input) }
}
#[inline(always)]
fn si128arr_to_u8arr(input: [__m128i; 8]) -> [u8; BYTES_PER_BATCH] {
unsafe { transmute(input) }
}
@@ -217,7 +222,7 @@ mod test {
let mut keys: [__m128i; 11] = [u128_to_si128(0); 11];
aes_128_key_expansion(key, &mut keys);
let ciphertexts = aes_encrypt_many(
&message, &message, &message, &message, &message, &message, &message, &message, &keys,
message, message, message, message, message, message, message, message, &keys,
);
for ct in &ciphertexts {
assert_eq!(CIPHERTEXT, si128_to_u128(*ct));

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

@@ -8,7 +8,7 @@ pub struct RdseedSeeder;
impl Seeder for RdseedSeeder {
fn seed(&mut self) -> Seed {
Seed(rdseed_random_m128())
Seed(unsafe { rdseed_random_m128() })
}
fn is_available() -> bool {
@@ -17,7 +17,8 @@ impl Seeder for RdseedSeeder {
}
// Generates a random 128 bits value from rdseed
fn rdseed_random_m128() -> u128 {
#[target_feature(enable = "rdseed")]
unsafe fn rdseed_random_m128() -> u128 {
let mut rand1: u64 = 0;
let mut rand2: u64 = 0;
let mut output_bytes = [0u8; 16];

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;

77
scripts/dieharder_test.sh Executable file
View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# dieharder does not support running a subset of its tests, so we'll check which ones are not good
# and ignore the output from those tests in the final log
set -e
DIEHARDER_RUN_LOG_FILE="dieharder_run.log"
bad_tests="$(dieharder -l | \
# select lines with the -d
grep -w '\-d' | \
# forget about the good tests
grep -v -i 'good' | \
# get the test id
cut -d ' ' -f 4 | \
# nice formatting
xargs)"
bad_test_filter=""
for bad_test in ${bad_tests}; do
bad_test_filter="${bad_test_filter:+${bad_test_filter}|}$(dieharder -d "${bad_test}" -t 1 -p 1 -D test_name | xargs)"
done
echo "The following tests will be ignored as they are marked as either 'suspect' or 'do not use': "
echo ""
echo "${bad_test_filter}"
echo ""
# by default we may have no pv just forward the input
pv="cat"
if which pv > /dev/null; then
pv="pv -t -a -b"
fi
rm -f "${DIEHARDER_RUN_LOG_FILE}"
# ignore potential errors and parse the log afterwards
set +e
# We are writing in both cases
# shellcheck disable=SC2094
./target/release/examples/generate 2>"${DIEHARDER_RUN_LOG_FILE}" | \
$pv | \
# -a: all tests
# -g 200: get random bytes from input
# -Y 1: disambiguate results, i.e. if a weak result appear check if it's a random failure/weakness
# -k 2: better maths formulas to determine some test statistics
dieharder -a -g 200 -Y 1 -k 2 | \
tee -a "${DIEHARDER_RUN_LOG_FILE}"
set -e
printf "\n\n"
cat "${DIEHARDER_RUN_LOG_FILE}"
if ! grep -q -i "failed" < "${DIEHARDER_RUN_LOG_FILE}"; then
echo "All tests passed!"
exit 0
fi
printf "\n\n"
failed_tests="$(grep -i "failed" < "${DIEHARDER_RUN_LOG_FILE}")"
true_failed_test="$(grep -i "failed" < "${DIEHARDER_RUN_LOG_FILE}" | { grep -v -E "${bad_test_filter}" || true; } | sed -z '$ s/\n$//')"
if [[ "${true_failed_test}" == "" ]]; then
echo "There were test failures, but the tests were either marked as 'suspect' or 'do not use'"
echo "${failed_tests}"
exit 0
fi
echo "The following tests failed:"
echo "${true_failed_test}"
exit 1

View File

@@ -1,71 +0,0 @@
#!/usr/bin/env bash
set -e
mkdir -p sts_testing
cd sts_testing
if [[ ! -f sts-2_1_2.zip ]]; then
wget "https://csrc.nist.gov/CSRC/media/Projects/Random-Bit-Generation/documents/sts-2_1_2.zip"
echo "0238d2f1d26e120e3cc748ed2d4c674cdc636de37fc4027c76cc2a394fff9157 sts-2_1_2.zip" > checksum
shasum -a 256 -c checksum
fi
# q: quiet, o: overwrite
unzip -q -o sts-2_1_2.zip
cd sts-2.1.2/sts-2.1.2/
make clean
make -j GCCFLAGS="-c -Wall -O3"
rm -rf bytes.bin
echo "Generating bytes..."
# 1_000_000 bits = 125_000 bytes
# recommended number of bit streams = 200
# 125_000 * 200 = 25_000_000
RUSTFLAGS="-C target-cpu=native" cargo run --profile release \
--example generate --features=x86_64-unix -p concrete-csprng -- \
--bytes_total 125000000 >> bytes.bin
echo "Running analysis... this may take a while"
# Loop if we get this shit: "igamc: UNDERFLOW"
set +e # assess may return non 0 in case we get something wonky going on
for retry in {1..10}; do
# 0: use input file
# Input file name
# 1: Run all tests on sequences
# 0: Confirm
# 200: number of bit streams
# 1: binary input mode (we wrote bytes to the bin file)
printf "0\nbytes.bin\n1\n0\n1000\n1\n" | ./assess 1000000 > sts_run.log
cat sts_run.log
if ! grep -q -i 'underflow' sts_run.log; then
# did not find any underflow, break out of retrying
break
fi
echo "Underflow detected in attempt ${retry}, retrying..."
done
# re-enable errors
set -e
# Let's have a nice output
cat experiments/AlgorithmTesting/finalAnalysisReport.txt
# Reports indicate failed tests with a * which does not appear in a report where everything worked
# -F indicates we want to match the fixed string * (and not a regex)
if ! grep -q -F '*' experiments/AlgorithmTesting/finalAnalysisReport.txt; then
# Exit code was != 0 which means * was not found
printf "\n\nStatistical tests passed!\n"
exit 0
else
# * found, some tests failed
printf "\n\nStatistical tests failed!\n"
exit 1
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

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
@@ -17,7 +17,7 @@ exclude = [
"/js_on_wasm_tests/",
"/web_wasm_parallel_tests/",
]
rust-version = "1.67"
rust-version = "1.72"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -26,26 +26,27 @@ 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"
num_cpus = "1.15"
itertools = "0.11.0"
# For erf and normality test
libm = "0.2.6"
# Begin regex-engine deps
test-case = "3.1.0"
combine = "4.6.6"
env_logger = "0.10.0"
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 = [
@@ -56,15 +57,14 @@ lazy_static = { version = "1.4.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
rayon = { version = "1.5.0" }
bincode = { version = "1.3.3", optional = true }
concrete-fft = { version = "0.2.1", features = ["serde", "fft128"] }
pulp = "0.11"
concrete-fft = { version = "0.3.0", features = ["serde", "fft128"] }
pulp = "0.13"
aligned-vec = { version = "0.5", features = ["serde"] }
dyn-stack = { version = "0.9" }
once_cell = "1.13"
paste = "1.0.7"
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,22 +73,23 @@ 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"
[features]
boolean = []
shortint = []
integer = ["shortint"]
internal-keycache = ["lazy_static", "fs2", "bincode"]
# paste is used by the HL API
boolean = ["dep:paste"]
shortint = ["dep:paste"]
integer = ["shortint", "dep:paste"]
internal-keycache = ["lazy_static", "dep:fs2", "bincode", "dep:paste"]
# Experimental section
experimental = []
experimental-force_fft_algo_dif4 = []
# End experimental section
__c_api = ["cbindgen", "bincode"]
__c_api = ["cbindgen", "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"]
@@ -120,6 +121,7 @@ generator_aarch64_aes = ["concrete-csprng/generator_aarch64_aes"]
# Private features
__profiling = []
__coverage = []
seeder_unix = ["concrete-csprng/seeder_unix"]
seeder_x86_64_rdseed = ["concrete-csprng/seeder_x86_64_rdseed"]
@@ -180,6 +182,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

@@ -5,18 +5,20 @@ use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tfhe::boolean::client_key::ClientKey;
use tfhe::boolean::parameters::{
BooleanParameters, DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
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::prelude::{BinaryBooleanGates, DEFAULT_PARAMETERS_KS_PBS};
use tfhe::boolean::prelude::BinaryBooleanGates;
use tfhe::boolean::server_key::ServerKey;
criterion_group!(
gates_benches,
bench_default_parameters,
bench_tfhe_lib_parameters,
bench_default_parameters_ks_pbs,
bench_tfhe_lib_parameters_pbs,
bench_low_prob_parameters,
bench_low_prob_parameters_ks_pbs,
bench_tfhe_lib_parameters,
);
criterion_main!(gates_benches);
@@ -41,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);
@@ -81,24 +83,29 @@ 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) {
benches(c, DEFAULT_PARAMETERS_KS_PBS, "DEFAULT_PARAMETERS_KS_PBS");
}
fn bench_low_prob_parameters(c: &mut Criterion) {
benches(
c,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165",
);
}
fn bench_low_prob_parameters_ks_pbs(c: &mut Criterion) {
benches(
c,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS",
);
}
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
benchs(
c,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
"TFHE_LIB_PARAMETERS",
);
}
fn bench_default_parameters_ks_pbs(c: &mut Criterion) {
benchs(c, DEFAULT_PARAMETERS_KS_PBS, "DEFAULT_PARAMETERS_KS_PBS");
}
fn bench_tfhe_lib_parameters_pbs(c: &mut Criterion) {
benchs(
c,
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
"TFHE_LIB_PARAMETERS_KS_PBS",
);
benches(c, TFHE_LIB_PARAMETERS, " TFHE_LIB_PARAMETERS");
}

View File

@@ -66,8 +66,8 @@ criterion_group!(
criterion_main!(pbs_group, multi_bit_pbs_group, pbs_throughput_group);
fn benchmark_parameters<Scalar: UnsignedInteger>(
) -> Vec<(&'static str, CryptoParametersRecord<Scalar>)> {
fn benchmark_parameters<Scalar: UnsignedInteger>() -> Vec<(String, CryptoParametersRecord<Scalar>)>
{
if Scalar::BITS == 64 {
SHORTINT_BENCH_PARAMS
.iter()
@@ -83,7 +83,7 @@ fn benchmark_parameters<Scalar: UnsignedInteger>(
} else if Scalar::BITS == 32 {
BOOLEAN_BENCH_PARAMS
.iter()
.map(|(name, params)| (*name, params.to_owned().into()))
.map(|(name, params)| (name.to_string(), params.to_owned().into()))
.collect()
} else {
vec![]
@@ -91,7 +91,7 @@ fn benchmark_parameters<Scalar: UnsignedInteger>(
}
fn throughput_benchmark_parameters<Scalar: UnsignedInteger>(
) -> Vec<(&'static str, CryptoParametersRecord<Scalar>)> {
) -> Vec<(String, CryptoParametersRecord<Scalar>)> {
if Scalar::BITS == 64 {
vec![
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
@@ -111,18 +111,15 @@ fn throughput_benchmark_parameters<Scalar: UnsignedInteger>(
} else if Scalar::BITS == 32 {
BOOLEAN_BENCH_PARAMS
.iter()
.map(|(name, params)| (*name, params.to_owned().into()))
.map(|(name, params)| (name.to_string(), params.to_owned().into()))
.collect()
} else {
vec![]
}
}
fn multi_bit_benchmark_parameters<Scalar: UnsignedInteger + Default>() -> Vec<(
&'static str,
CryptoParametersRecord<Scalar>,
LweBskGroupingFactor,
)> {
fn multi_bit_benchmark_parameters<Scalar: UnsignedInteger + Default>(
) -> Vec<(String, CryptoParametersRecord<Scalar>, LweBskGroupingFactor)> {
if Scalar::BITS == 64 {
vec![
PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_2_KS_PBS,
@@ -243,7 +240,7 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize> + Serialize>(c: &mu
write_to_json(
&id,
*params,
*name,
name,
"pbs",
&OperatorType::Atomic,
bit_size,
@@ -332,7 +329,7 @@ fn multi_bit_pbs<
write_to_json(
&id,
*params,
*name,
name,
"pbs",
&OperatorType::Atomic,
bit_size,
@@ -421,7 +418,7 @@ fn multi_bit_deterministic_pbs<
write_to_json(
&id,
*params,
*name,
name,
"pbs",
&OperatorType::Atomic,
bit_size,
@@ -478,8 +475,6 @@ fn pbs_throughput<Scalar: UnsignedTorus + CastInto<usize> + Sync + Send + Serial
params.ciphertext_modulus.unwrap(),
);
let lwe_vec = lwe_vec;
let fft = Fft::new(params.polynomial_size.unwrap());
let fft = fft.as_view();
@@ -543,7 +538,7 @@ fn pbs_throughput<Scalar: UnsignedTorus + CastInto<usize> + Sync + Send + Serial
write_to_json(
&id,
*params,
*name,
name,
"pbs",
&OperatorType::Atomic,
bit_size,

View File

@@ -27,6 +27,9 @@ use tfhe::shortint::parameters::{
/// It must be as big as the largest bit size tested
type ScalarType = U256;
const FAST_BENCH_BIT_SIZES: [usize; 1] = [32];
const BENCH_BIT_SIZES: [usize; 7] = [8, 16, 32, 40, 64, 128, 256];
fn gen_random_u256(rng: &mut ThreadRng) -> U256 {
let clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
@@ -54,15 +57,14 @@ impl Default for ParamsAndNumBlocksIter {
Err(_) => false,
};
let bit_sizes = if is_fast_bench {
FAST_BENCH_BIT_SIZES.to_vec()
} else {
BENCH_BIT_SIZES.to_vec()
};
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,
@@ -75,13 +77,6 @@ impl Default for ParamsAndNumBlocksIter {
// 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,

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_absolute_value,
display_name: absolute_value,
);
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_absolute_value,
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

@@ -7,7 +7,9 @@ use std::env;
use criterion::{criterion_group, Criterion};
use tfhe::keycache::NamedParam;
use tfhe::shortint::parameters::*;
use tfhe::shortint::{Ciphertext, ClassicPBSParameters, ServerKey, ShortintParameterSet};
use tfhe::shortint::{
Ciphertext, ClassicPBSParameters, CompressedServerKey, ServerKey, ShortintParameterSet,
};
use rand::Rng;
use tfhe::shortint::keycache::{KEY_CACHE, KEY_CACHE_WOPBS};
@@ -39,20 +41,59 @@ const SERVER_KEY_BENCH_PARAMS_EXTENDED: [ClassicPBSParameters; 15] = [
PARAM_MESSAGE_8_CARRY_0_KS_PBS,
];
const SERVER_KEY_MULTI_BIT_BENCH_PARAMS: [MultiBitPBSParameters; 2] = [
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
];
const SERVER_KEY_MULTI_BIT_BENCH_PARAMS_EXTENDED: [MultiBitPBSParameters; 6] = [
PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_3_CARRY_3_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_3_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_3_CARRY_3_GROUP_3_KS_PBS,
];
enum BenchParamsSet {
Standard,
Extended,
}
fn benchmark_parameters(params_set: BenchParamsSet) -> Vec<PBSParameters> {
let is_multi_bit = match env::var("__TFHE_RS_BENCH_TYPE") {
Ok(val) => val.to_lowercase() == "multi_bit",
Err(_) => false,
};
if is_multi_bit {
let params = match params_set {
BenchParamsSet::Standard => SERVER_KEY_MULTI_BIT_BENCH_PARAMS.to_vec(),
BenchParamsSet::Extended => SERVER_KEY_MULTI_BIT_BENCH_PARAMS_EXTENDED.to_vec(),
};
params.iter().map(|p| (*p).into()).collect()
} else {
let params = match params_set {
BenchParamsSet::Standard => SERVER_KEY_BENCH_PARAMS.to_vec(),
BenchParamsSet::Extended => SERVER_KEY_BENCH_PARAMS_EXTENDED.to_vec(),
};
params.iter().map(|p| (*p).into()).collect()
}
}
fn bench_server_key_unary_function<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
unary_op: F,
params: &[ClassicPBSParameters],
params_set: BenchParamsSet,
) where
F: Fn(&ServerKey, &mut Ciphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
for param in params.iter() {
let param: PBSParameters = (*param).into();
let keys = KEY_CACHE.get_from_param(param);
for param in benchmark_parameters(params_set).iter() {
let keys = KEY_CACHE.get_from_param(*param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
@@ -72,7 +113,7 @@ fn bench_server_key_unary_function<F>(
write_to_json::<u64, _>(
&bench_id,
param,
*param,
param.name(),
display_name,
&OperatorType::Atomic,
@@ -89,15 +130,14 @@ fn bench_server_key_binary_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[ClassicPBSParameters],
params_set: BenchParamsSet,
) where
F: Fn(&ServerKey, &mut Ciphertext, &mut Ciphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
for param in params.iter() {
let param: PBSParameters = (*param).into();
let keys = KEY_CACHE.get_from_param(param);
for param in benchmark_parameters(params_set).iter() {
let keys = KEY_CACHE.get_from_param(*param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
@@ -119,7 +159,7 @@ fn bench_server_key_binary_function<F>(
write_to_json::<u64, _>(
&bench_id,
param,
*param,
param.name(),
display_name,
&OperatorType::Atomic,
@@ -136,15 +176,14 @@ fn bench_server_key_binary_scalar_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[ClassicPBSParameters],
params_set: BenchParamsSet,
) where
F: Fn(&ServerKey, &mut Ciphertext, u8),
{
let mut bench_group = c.benchmark_group(bench_name);
for param in params {
let param: PBSParameters = (*param).into();
let keys = KEY_CACHE.get_from_param(param);
for param in benchmark_parameters(params_set).iter() {
let keys = KEY_CACHE.get_from_param(*param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
@@ -165,7 +204,7 @@ fn bench_server_key_binary_scalar_function<F>(
write_to_json::<u64, _>(
&bench_id,
param,
*param,
param.name(),
display_name,
&OperatorType::Atomic,
@@ -182,15 +221,14 @@ fn bench_server_key_binary_scalar_division_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[ClassicPBSParameters],
params_set: BenchParamsSet,
) where
F: Fn(&ServerKey, &mut Ciphertext, u8),
{
let mut bench_group = c.benchmark_group(bench_name);
for param in params {
let param: PBSParameters = (*param).into();
let keys = KEY_CACHE.get_from_param(param);
for param in benchmark_parameters(params_set).iter() {
let keys = KEY_CACHE.get_from_param(*param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
@@ -215,7 +253,7 @@ fn bench_server_key_binary_scalar_division_function<F>(
write_to_json::<u64, _>(
&bench_id,
param,
*param,
param.name(),
display_name,
&OperatorType::Atomic,
@@ -227,12 +265,11 @@ fn bench_server_key_binary_scalar_division_function<F>(
bench_group.finish()
}
fn carry_extract(c: &mut Criterion) {
fn carry_extract_bench(c: &mut Criterion, params_set: BenchParamsSet) {
let mut bench_group = c.benchmark_group("carry_extract");
for param in SERVER_KEY_BENCH_PARAMS {
let param: PBSParameters = param.into();
let keys = KEY_CACHE.get_from_param(param);
for param in benchmark_parameters(params_set).iter() {
let keys = KEY_CACHE.get_from_param(*param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
@@ -252,7 +289,7 @@ fn carry_extract(c: &mut Criterion) {
write_to_json::<u64, _>(
&bench_id,
param,
*param,
param.name(),
"carry_extract",
&OperatorType::Atomic,
@@ -264,12 +301,11 @@ fn carry_extract(c: &mut Criterion) {
bench_group.finish()
}
fn programmable_bootstrapping(c: &mut Criterion) {
fn programmable_bootstrapping_bench(c: &mut Criterion, params_set: BenchParamsSet) {
let mut bench_group = c.benchmark_group("programmable_bootstrap");
for param in SERVER_KEY_BENCH_PARAMS {
let param: PBSParameters = param.into();
let keys = KEY_CACHE.get_from_param(param);
for param in benchmark_parameters(params_set).iter() {
let keys = KEY_CACHE.get_from_param(*param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
@@ -292,7 +328,7 @@ fn programmable_bootstrapping(c: &mut Criterion) {
write_to_json::<u64, _>(
&bench_id,
param,
*param,
param.name(),
"pbs",
&OperatorType::Atomic,
@@ -304,6 +340,54 @@ fn programmable_bootstrapping(c: &mut Criterion) {
bench_group.finish();
}
fn server_key_from_compressed_key(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("uncompress_key");
bench_group
.sample_size(10)
.measurement_time(std::time::Duration::from_secs(60));
let mut params = SERVER_KEY_BENCH_PARAMS_EXTENDED
.iter()
.map(|p| (*p).into())
.collect::<Vec<PBSParameters>>();
let multi_bit_params = SERVER_KEY_MULTI_BIT_BENCH_PARAMS_EXTENDED
.iter()
.map(|p| (*p).into())
.collect::<Vec<PBSParameters>>();
params.extend(&multi_bit_params);
for param in params.iter() {
let keys = KEY_CACHE.get_from_param(*param);
let sks_compressed = CompressedServerKey::new(keys.client_key());
let bench_id = format!("shortint::uncompress_key::{}", param.name());
bench_group.bench_function(&bench_id, |b| {
let clone_compressed_key = || sks_compressed.clone();
b.iter_batched(
clone_compressed_key,
|sks_cloned| {
let _ = ServerKey::from(sks_cloned);
},
criterion::BatchSize::PerIteration,
)
});
write_to_json::<u64, _>(
&bench_id,
*param,
param.name(),
"uncompress_key",
&OperatorType::Atomic,
param.message_modulus().0.ilog2(),
vec![param.message_modulus().0.ilog2()],
);
}
bench_group.finish();
}
// TODO: remove?
fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("programmable_bootstrap");
@@ -333,7 +417,7 @@ fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
}
macro_rules! define_server_key_unary_bench_fn (
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
(method_name:$server_key_method:ident, display_name:$name:ident, $params_set:expr) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_unary_function(
c,
@@ -341,13 +425,13 @@ macro_rules! define_server_key_unary_bench_fn (
stringify!($name),
|server_key, lhs| {
let _ = server_key.$server_key_method(lhs);},
$params)
$params_set)
}
}
);
macro_rules! define_server_key_bench_fn (
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
(method_name:$server_key_method:ident, display_name:$name:ident, $params_set:expr) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_function(
c,
@@ -355,13 +439,13 @@ macro_rules! define_server_key_bench_fn (
stringify!($name),
|server_key, lhs, rhs| {
let _ = server_key.$server_key_method(lhs, rhs);},
$params)
$params_set)
}
}
);
macro_rules! define_server_key_scalar_bench_fn (
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
(method_name:$server_key_method:ident, display_name:$name:ident, $params_set:expr) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_scalar_function(
c,
@@ -369,13 +453,13 @@ macro_rules! define_server_key_scalar_bench_fn (
stringify!($name),
|server_key, lhs, rhs| {
let _ = server_key.$server_key_method(lhs, rhs);},
$params)
$params_set)
}
}
);
macro_rules! define_server_key_scalar_div_bench_fn (
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
(method_name:$server_key_method:ident, display_name:$name:ident, $params_set:expr) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_scalar_division_function(
c,
@@ -383,7 +467,19 @@ macro_rules! define_server_key_scalar_div_bench_fn (
stringify!($name),
|server_key, lhs, rhs| {
let _ = server_key.$server_key_method(lhs, rhs);},
$params)
$params_set)
}
}
);
macro_rules! define_custom_bench_fn (
(function_name:$function:ident, $params_set:expr) => {
fn $function(c: &mut Criterion) {
::paste::paste! {
[<$function _bench>](
c,
$params_set)
}
}
}
);
@@ -391,251 +487,258 @@ macro_rules! define_server_key_scalar_div_bench_fn (
define_server_key_unary_bench_fn!(
method_name: unchecked_neg,
display_name: negation,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: unchecked_add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
BenchParamsSet::Extended
);
define_server_key_bench_fn!(
method_name: unchecked_sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
BenchParamsSet::Extended
);
define_server_key_bench_fn!(
method_name: unchecked_mul_lsb,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
BenchParamsSet::Extended
);
define_server_key_bench_fn!(
method_name: unchecked_mul_msb,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: unchecked_div,
display_name: div,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
BenchParamsSet::Extended
);
define_server_key_bench_fn!(
method_name: smart_bitand,
display_name: bitand,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: smart_bitor,
display_name: bitor,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: smart_bitxor,
display_name: bitxor,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: smart_add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: smart_sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: smart_mul_lsb,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: bitand,
display_name: bitand,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: bitor,
display_name: bitor,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: bitxor,
display_name: bitxor,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: mul,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: div,
display_name: div,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: greater,
display_name: greater,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: greater_or_equal,
display_name: greater_or_equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: less,
display_name: less,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: less_or_equal,
display_name: less_or_equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: equal,
display_name: equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: not_equal,
display_name: not_equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_unary_bench_fn!(
method_name: neg,
display_name: negation,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: unchecked_greater,
display_name: greater_than,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: unchecked_less,
display_name: less_than,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_bench_fn!(
method_name: unchecked_equal,
display_name: equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
BenchParamsSet::Extended
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
BenchParamsSet::Extended
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_mul,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
BenchParamsSet::Extended
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_left_shift,
display_name: left_shift,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_right_shift,
display_name: right_shift,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_div_bench_fn!(
method_name: unchecked_scalar_div,
display_name: div,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
BenchParamsSet::Extended
);
define_server_key_scalar_div_bench_fn!(
method_name: unchecked_scalar_mod,
display_name: modulo,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_mul,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_left_shift,
display_name: left_shift,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_right_shift,
display_name: right_shift,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_div_bench_fn!(
method_name: scalar_div,
display_name: div,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_div_bench_fn!(
method_name: scalar_mod,
display_name: modulo,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_greater,
display_name: greater,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_greater_or_equal,
display_name: greater_or_equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_less,
display_name: less,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_bench_fn!(
method_name: scalar_less_or_equal,
display_name: less_or_equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_div_bench_fn!(
method_name: scalar_equal,
display_name: equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_server_key_scalar_div_bench_fn!(
method_name: scalar_not_equal,
display_name: not_equal,
&SERVER_KEY_BENCH_PARAMS
BenchParamsSet::Standard
);
define_custom_bench_fn!(function_name: carry_extract, BenchParamsSet::Standard);
define_custom_bench_fn!(
function_name: programmable_bootstrapping,
BenchParamsSet::Standard
);
criterion_group!(
@@ -709,6 +812,8 @@ criterion_group!(
scalar_not_equal
);
criterion_group!(misc, server_key_from_compressed_key);
mod casting;
criterion_group!(
casting,
@@ -722,6 +827,7 @@ fn main() {
casting();
default_ops();
default_scalar_ops();
misc();
}
match env::var("__TFHE_RS_BENCH_OP_FLAVOR") {

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

@@ -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);

View File

@@ -5,13 +5,13 @@
## 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)
## Tutorials
* [Homomorphic Parity Bit](tutorials/parity_bit.md)
* [Homomorphic Case Changing on Latin String](tutorials/latin_fhe_string.md)
* [Homomorphic Case Changing on Ascii String](tutorials/ascii_fhe_string.md)
## How To
* [Configure Rust](how_to/rust_configuration.md)
@@ -19,6 +19,7 @@
* [Compress Ciphertexts/Keys](how_to/compress.md)
* [Use Public Key Encryption](how_to/public_key.md)
* [Use Trivial Ciphertext](how_to/trivial_ciphertext.md)
* [Generic Function Bounds](how_to/trait_bounds.md)
* [Use Parallelized PBS](how_to/parallelized_pbs.md)
* [Use the C API](how_to/c_api.md)
* [Use the JS on WASM API](how_to/js_on_wasm_api.md)
@@ -55,4 +56,3 @@
## API references
* [docs.rs](https://docs.rs/tfhe/)

View File

@@ -102,25 +102,23 @@ for buy_order in buy_orders.iter_mut() {
#### The complete algorithm in plain Rust:
```rust
fn volume_match_plain(sell_orders: &mut Vec<u16>, buy_orders: &mut Vec<u16>) {
fn fill_orders(orders: &mut [u16], total_volume: u16) {
let mut volume_left_to_transact = total_volume;
for order in orders {
let filled_amount = std::cmp::min(volume_left_to_transact, *order);
*order = filled_amount;
volume_left_to_transact -= filled_amount;
}
}
pub fn volume_match(sell_orders: &mut [u16], buy_orders: &mut [u16]) {
let total_sell_volume: u16 = sell_orders.iter().sum();
let total_buy_volume: u16 = buy_orders.iter().sum();
let total_volume = std::cmp::min(total_buy_volume, total_sell_volume);
let mut volume_left_to_transact = total_volume;
for sell_order in sell_orders.iter_mut() {
let filled_amount = std::cmp::min(volume_left_to_transact, *sell_order);
*sell_order = filled_amount;
volume_left_to_transact -= filled_amount;
}
let mut volume_left_to_transact = total_volume;
for buy_order in buy_orders.iter_mut() {
let filled_amount = std::cmp::min(volume_left_to_transact, *buy_order);
*buy_order = filled_amount;
volume_left_to_transact -= filled_amount;
}
fill_orders(sell_orders, total_volume);
fill_orders(buy_orders, total_volume);
}
```
@@ -155,15 +153,17 @@ Now, we can start implementing the algorithm with FHE:
1. Calculate the total sell volume and the total buy volume.
```rust
let mut total_sell_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for sell_order in sell_orders.iter_mut() {
server_key.smart_add_assign(&mut total_sell_volume, sell_order);
fn vector_sum(server_key: &ServerKey, orders: &mut [RadixCiphertext]) -> RadixCiphertext {
let mut total_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for order in orders {
server_key.smart_add_assign(&mut total_volume, order);
}
total_volume
}
let mut total_buy_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for buy_order in buy_orders.iter_mut() {
server_key.smart_add_assign(&mut total_buy_volume, buy_order);
}
let mut total_sell_volume = vector_sum(server_key, sell_orders);
let mut total_buy_volume = vector_sum(server_key, buy_orders);
```
2. Find the total volume that will be transacted by taking the minimum of the total sell volume and the total buy
@@ -177,17 +177,21 @@ let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_v
reduce code duplication since the code for filling buy orders and sell orders are the same.
```rust
let fill_orders = |orders: &mut [RadixCiphertext]| {
let mut volume_left_to_transact = total_volume.clone();
for mut order in orders.iter_mut() {
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, &mut order);
fn fill_orders(
server_key: &ServerKey,
orders: &mut [RadixCiphertext],
total_volume: RadixCiphertext,
) {
let mut volume_left_to_transact = total_volume;
for order in orders {
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, order);
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
};
}
fill_orders(sell_orders);
fill_orders(buy_orders);
fill_orders(server_key, sell_orders, total_volume.clone());
fill_orders(server_key, buy_orders, total_volume);
```
#### The complete algorithm in TFHE-rs:
@@ -195,36 +199,40 @@ fill_orders(buy_orders);
```rust
const NUMBER_OF_BLOCKS: usize = 8;
fn volume_match_fhe(
fn vector_sum(server_key: &ServerKey, orders: &mut [RadixCiphertext]) -> RadixCiphertext {
let mut total_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for order in orders {
server_key.smart_add_assign(&mut total_volume, order);
}
total_volume
}
fn fill_orders(
server_key: &ServerKey,
orders: &mut [RadixCiphertext],
total_volume: RadixCiphertext,
) {
let mut volume_left_to_transact = total_volume;
for order in orders {
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, order);
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
}
pub fn volume_match(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
let mut total_sell_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for sell_order in sell_orders.iter_mut() {
server_key.smart_add_assign(&mut total_sell_volume, sell_order);
}
let mut total_buy_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for buy_order in buy_orders.iter_mut() {
server_key.smart_add_assign(&mut total_buy_volume, buy_order);
}
let mut total_sell_volume = vector_sum(server_key, sell_orders);
let mut total_buy_volume = vector_sum(server_key, buy_orders);
let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_volume);
let fill_orders = |orders: &mut [RadixCiphertext]| {
let mut volume_left_to_transact = total_volume.clone();
for mut order in orders.iter_mut() {
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, &mut order);
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
};
fill_orders(sell_orders);
fill_orders(buy_orders);
fill_orders(server_key, sell_orders, total_volume.clone());
fill_orders(server_key, buy_orders, total_volume);
}
```
### Optimizing the implementation
@@ -235,63 +243,73 @@ fn volume_match_fhe(
* We can parallelize vector sum with Rayon and `reduce` operation.
```rust
let parallel_vector_sum = |vec: &mut [RadixCiphertext]| {
vec.to_vec().into_par_iter().reduce(
fn vector_sum(server_key: &ServerKey, orders: Vec<RadixCiphertext>) -> RadixCiphertext {
orders.into_par_iter().reduce(
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
server_key.smart_add_parallelized(&mut acc, &mut ele)
},
)
};
}
```
* We can run vector summation on `buy_orders` and `sell_orders` in parallel since these operations do not depend on each other.
```rust
let (mut total_sell_volume, mut total_buy_volume) =
rayon::join(|| vector_sum(sell_orders), || vector_sum(buy_orders));
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|| vector_sum(server_key, sell_orders.to_owned()),
|| vector_sum(server_key, buy_orders.to_owned()),
);
```
* We can match sell and buy orders in parallel since the matching does not depend on each other.
```rust
rayon::join(|| fill_orders(sell_orders), || fill_orders(buy_orders));
rayon::join(
|| fill_orders(server_key, sell_orders, total_volume.clone()),
|| fill_orders(server_key, buy_orders, total_volume.clone()),
);
```
#### Optimized algorithm
```rust
fn volume_match_fhe_parallelized(
fn vector_sum(server_key: &ServerKey, orders: Vec<RadixCiphertext>) -> RadixCiphertext {
orders.into_par_iter().reduce(
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
server_key.smart_add_parallelized(&mut acc, &mut ele)
},
)
}
fn fill_orders(
server_key: &ServerKey,
orders: &mut [RadixCiphertext],
total_volume: RadixCiphertext,
) {
let mut volume_left_to_transact = total_volume;
for order in orders {
let mut filled_amount =
server_key.smart_min_parallelized(&mut volume_left_to_transact, order);
server_key.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
}
pub fn volume_match(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
let parallel_vector_sum = |vec: &mut [RadixCiphertext]| {
vec.to_vec().into_par_iter().reduce(
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
server_key.smart_add_parallelized(&mut acc, &mut ele)
},
)
};
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|| parallel_vector_sum(sell_orders),
|| parallel_vector_sum(buy_orders),
|| vector_sum(server_key, sell_orders.to_owned()),
|| vector_sum(server_key, buy_orders.to_owned()),
);
let total_volume =
server_key.smart_min_parallelized(&mut total_sell_volume, &mut total_buy_volume);
let fill_orders = |orders: &mut [RadixCiphertext]| {
let mut volume_left_to_transact = total_volume.clone();
for mut order in orders.iter_mut() {
let mut filled_amount =
server_key.smart_min_parallelized(&mut volume_left_to_transact, &mut order);
server_key
.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
};
rayon::join(|| fill_orders(sell_orders), || fill_orders(buy_orders));
rayon::join(
|| fill_orders(server_key, sell_orders, total_volume.clone()),
|| fill_orders(server_key, buy_orders, total_volume.clone()),
);
}
```
@@ -312,14 +330,19 @@ We will call the new list the "prefix sum" of the array.
The new version for the plain `fill_orders` is as follows:
```rust
let fill_orders = |orders: &mut [u64], prefix_sum: &[u64], total_orders: u64|{
fn fill_orders(total_orders: u16, orders: &mut [u16], prefix_sum_arr: &[u16]) {
orders.iter().for_each(|order : &mut u64| {
if (total_orders >= prefix_sum[i]) {
continue;
} else if total_orders >= prefix_sum.get(i-1).unwrap_or(0) {
*order = total_orders - prefix_sum.get(i-1).unwrap_or(0);
} else {
let previous_prefix_sum = if i == 0 { 0 } else { prefix_sum_arr[i - 1] };
let diff = total_orders as i64 - previous_prefix_sum as i64;
if (diff < 0) {
*order = 0;
} else if diff < order {
*order = diff as u16;
} else {
// *order = *order;
continue;
}
});
};
@@ -328,11 +351,15 @@ let fill_orders = |orders: &mut [u64], prefix_sum: &[u64], total_orders: u64|{
To write this new function we need transform the conditional code into a mathematical expression since FHE does not support conditional operations.
```rust
let fill_orders = |orders: &mut [u64], prefix_sum: &[u64], total_orders: u64| {
orders.iter().for_each(|order| : &mut){
*order = *order + ((total_orders >= prefix_sum - std::cmp::min(total_orders, prefix_sum.get(i - 1).unwrap_or(&0).clone()) - *order);
fn fill_orders(total_orders: u16, orders: &mut [u16], prefix_sum_arr: &[u16]) {
for (i, order) in orders.iter_mut().enumerate() {
let previous_prefix_sum = if i == 0 { 0 } else { prefix_sum_arr[i - 1] };
*order = (total_orders as i64 - previous_prefix_sum as i64)
.max(0)
.min(*order as i64) as u16;
}
};
}
```
New `fill_order` function requires a prefix sum array. We are going to calculate this prefix sum array in parallel
@@ -345,108 +372,129 @@ So we modify how the algorithm is implemented, but we don't change the algorithm
Here is the modified version of the algorithm in TFHE-rs:
```rust
fn volume_match_fhe_modified(
fn compute_prefix_sum(server_key: &ServerKey, arr: &[RadixCiphertext]) -> Vec<RadixCiphertext> {
if arr.is_empty() {
return arr.to_vec();
}
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
.into_par_iter()
.map(|i| {
if i < arr.len() {
arr[i].clone()
} else {
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
}
})
.collect();
for d in 0..prefix_sum.len().ilog2() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
});
}
let last = prefix_sum.last().unwrap().clone();
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for d in (0..prefix_sum.len().ilog2()).rev() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let temp = chunk.last().unwrap().clone();
let mut mid = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut mid);
chunk[(length - 1) / 2] = temp;
});
}
prefix_sum.push(last);
prefix_sum[1..=arr.len()].to_vec()
}
fn fill_orders(
server_key: &ServerKey,
total_orders: &RadixCiphertext,
orders: &mut [RadixCiphertext],
prefix_sum_arr: &[RadixCiphertext],
) {
orders
.into_par_iter()
.enumerate()
.for_each(move |(i, order)| {
// (total_orders - previous_prefix_sum).max(0)
let mut diff = if i == 0 {
total_orders.clone()
} else {
let previous_prefix_sum = &prefix_sum_arr[i - 1];
// total_orders - previous_prefix_sum
let mut diff = server_key.smart_sub_parallelized(
&mut total_orders.clone(),
&mut previous_prefix_sum.clone(),
);
// total_orders > prefix_sum
let mut cond = server_key.smart_gt_parallelized(
&mut total_orders.clone(),
&mut previous_prefix_sum.clone(),
);
// (total_orders - previous_prefix_sum) * (total_orders > previous_prefix_sum)
// = (total_orders - previous_prefix_sum).max(0)
server_key.smart_mul_parallelized(&mut cond, &mut diff)
};
// (total_orders - previous_prefix_sum).max(0).min(*order);
*order = server_key.smart_min_parallelized(&mut diff, order);
});
}
/// FHE implementation of the volume matching algorithm.
///
/// In this function, the implemented algorithm is modified to utilize more concurrency.
///
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
/// [server_key]. The amount of the orders that are successfully filled is written over the original
/// order count.
pub fn volume_match(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
let compute_prefix_sum = |arr: &[RadixCiphertext]| {
if arr.is_empty() {
return arr.to_vec();
}
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
.into_par_iter()
.map(|i| {
if i < arr.len() {
arr[i].clone()
} else {
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
}
})
.collect();
// Up sweep
for d in 0..(prefix_sum.len().ilog2() as u32) {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
});
}
// Down sweep
let last = prefix_sum.last().unwrap().clone();
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for d in (0..(prefix_sum.len().ilog2() as u32)).rev() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let t = chunk.last().unwrap().clone();
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left);
chunk[(length - 1) / 2] = t;
});
}
prefix_sum.push(last);
prefix_sum[1..=arr.len()].to_vec()
};
println!("Creating prefix sum arrays...");
let time = Instant::now();
let (prefix_sum_sell_orders, prefix_sum_buy_orders) = rayon::join(
|| compute_prefix_sum(sell_orders),
|| compute_prefix_sum(buy_orders),
|| compute_prefix_sum(server_key, sell_orders),
|| compute_prefix_sum(server_key, buy_orders),
);
println!("Created prefix sum arrays in {:?}", time.elapsed());
let fill_orders = |total_orders: &RadixCiphertext,
orders: &mut [RadixCiphertext],
prefix_sum_arr: &[RadixCiphertext]| {
orders
.into_par_iter()
.enumerate()
.for_each(move |(i, order)| {
server_key.smart_add_assign_parallelized(
order,
&mut server_key.smart_mul_parallelized(
&mut server_key
.smart_ge_parallelized(&mut order.clone(), &mut total_orders.clone()),
&mut server_key.smart_sub_parallelized(
&mut server_key.smart_sub_parallelized(
&mut total_orders.clone(),
&mut server_key.smart_min_parallelized(
&mut total_orders.clone(),
&mut prefix_sum_arr
.get(i - 1)
.unwrap_or(
&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
)
.clone(),
),
),
&mut order.clone(),
),
),
);
});
};
let zero = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
let total_buy_orders = &mut prefix_sum_buy_orders
.last()
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
.clone();
let total_buy_orders = prefix_sum_buy_orders.last().unwrap_or(&zero);
let total_sell_orders = &mut prefix_sum_sell_orders
.last()
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
.clone();
let total_sell_orders = prefix_sum_sell_orders.last().unwrap_or(&zero);
println!("Matching orders...");
let time = Instant::now();
rayon::join(
|| fill_orders(total_sell_orders, buy_orders, &prefix_sum_buy_orders),
|| fill_orders(total_buy_orders, sell_orders, &prefix_sum_sell_orders),
|| {
fill_orders(
server_key,
total_sell_orders,
buy_orders,
&prefix_sum_buy_orders,
)
},
|| {
fill_orders(
server_key,
total_buy_orders,
sell_orders,
&prefix_sum_sell_orders,
)
},
);
println!("Matched orders in {:?}", time.elapsed());
}

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

@@ -136,7 +136,11 @@ fn sha256(padded_input: Vec<bool>) -> [bool; 256] {
The key idea is that we can replace each bit of ```padded_input``` with a Fully Homomorphic Encryption of the same bit value, and operate over the encrypted values using homomorphic operations. To achieve this we need to change the function signatures and deal with the borrowing rules of the Ciphertext type (which represents an encrypted bit) but the structure of the sha256 function remains the same. The part of the code that requires more consideration is the implementation of the sha256 operations, since they will use homomorphic boolean operations internally.
Homomorphic operations are really expensive, so we have to remove their unnecessary use and maximize parallelization in order to speed up the program. To simplify our code we use the Rayon crate which provides parallel iterators and efficiently manages threads. Let's now take a look at each sha256 operation!
Homomorphic operations are really expensive, so we have to remove their unnecessary use and maximize parallelization in order to speed up the program. To simplify our code we use the Rayon crate which provides parallel iterators and efficiently manages threads.
The final code is available at https://github.com/zama-ai/tfhe-rs/tree/main/tfhe/examples/sha256_bool
Let's now take a look at each sha256 operation!
#### Rotate Right and Shift Right

View File

@@ -9,7 +9,7 @@ Welcome to this tutorial about `TFHE-rs` `core_crypto` module.
To use `TFHE-rs`, it first has to be added as a dependency in the `Cargo.toml`:
```toml
tfhe = { version = "0.3.0", features = [ "x86_64-unix" ] }
tfhe = { version = "0.4.0", features = [ "x86_64-unix" ] }
```
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available (like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available). To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
@@ -19,19 +19,19 @@ For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `a
In short: For `x86_64`-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
tfhe = { version = "0.4.0", features = ["x86_64-unix"] }
```
For Apple Silicon or aarch64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.3.0", features = ["aarch64-unix"] }
tfhe = { version = "0.4.0", features = ["aarch64-unix"] }
```
For `x86_64`-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
```toml
tfhe = { version = "0.3.0", features = ["x86_64"] }
tfhe = { version = "0.4.0", features = ["x86_64"] }
```
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.
@@ -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

@@ -86,7 +86,7 @@ fn main() {
// Don't consider the following line; you should follow the procedure above.
let (client_key, _) = gen_keys();
//---------------------------- SERVER SIDE
//---------------------------- CLIENT SIDE
// We use the client key to encrypt the messages:
let ct_1 = client_key.encrypt(true);
@@ -104,7 +104,8 @@ fn main() {
## Encrypting inputs using a public key
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client simply needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be utilized:
Anyone (the server or a third party) with the public key can also encrypt some (or all) of the inputs.
The public key can only be used to encrypt, not to decrypt.
```rust
use tfhe::boolean::prelude::*;
@@ -114,18 +115,18 @@ fn main() {
let (client_key, _) = gen_keys();
let public_key = PublicKey::new(&client_key);
//---------------------------- SERVER SIDE
//---------------------------- SERVER or THIRD_PARTY SIDE
// We use the public key to encrypt the messages:
let ct_1 = public_key.encrypt(true);
let ct_2 = public_key.encrypt(false);
// We serialize the ciphertexts:
// We serialize the ciphertexts (if not on the server already):
let encoded_1: Vec<u8> = bincode::serialize(&ct_1).unwrap();
let encoded_2: Vec<u8> = bincode::serialize(&ct_2).unwrap();
// ...
// And we send them to the server somehow
// And we send them to the server to be deserialized (if not on the server already)
// ...
}
```

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -46,7 +46,7 @@ fn main() {
The default configuration for x86 Unix machines:
```toml
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
tfhe = { version = "0.4.0", features = ["integer", "x86_64-unix"]}
```
Configuration options for different platforms can be found [here](../getting_started/installation.md). Other rust and homomorphic types features can be found [here](../how_to/rust_configuration.md).
@@ -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

@@ -3,6 +3,7 @@ TFHE-rs includes features to reduce the size of both keys and ciphertexts, by co
In the library, entities that can be compressed are prefixed by `Compressed`. For instance, the type of a compressed `FheUint256` is `CompressedFheUint256`.
In the following example code, we use the `bincode` crate dependency to serialize in a binary format and compare serialized sizes.
## Compressed ciphertexts
This example shows how to compress a ciphertext encypting messages over 16 bits.
@@ -19,7 +20,18 @@ fn main() {
let clear = 12_837u16;
let compressed = CompressedFheUint16::try_encrypt(clear, &client_key).unwrap();
println!(
"compressed size : {}",
bincode::serialize(&compressed).unwrap().len()
);
let decompressed = compressed.decompress();
println!(
"decompressed size: {}",
bincode::serialize(&decompressed).unwrap().len()
);
let clear_decompressed: u16 = decompressed.decrypt(&client_key);
assert_eq!(clear_decompressed, clear);
}
@@ -31,7 +43,9 @@ This example shows how to compress the server keys.
```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, CompressedServerKey, ClientKey};
use tfhe::{
generate_keys, set_server_key, ClientKey, CompressedServerKey, ConfigBuilder, FheUint8,
};
fn main() {
let config = ConfigBuilder::all_disabled()
@@ -40,8 +54,19 @@ fn main() {
let cks = ClientKey::generate(config);
let compressed_sks = CompressedServerKey::new(&cks);
println!(
"compressed size : {}",
bincode::serialize(&compressed_sks).unwrap().len()
);
let sks = compressed_sks.decompress();
println!(
"decompressed size: {}",
bincode::serialize(&sks).unwrap().len()
);
set_server_key(sks);
let clear_a = 12u8;
@@ -51,6 +76,7 @@ fn main() {
let decrypted: u8 = c.decrypt(&cks);
assert_eq!(decrypted, clear_a.wrapping_add(234));
}
```
@@ -58,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
@@ -71,7 +97,14 @@ fn main() {
.build();
let (client_key, _) = generate_keys(config);
let public_key = CompressedPublicKey::new(&client_key);
let compressed_public_key = CompressedPublicKey::new(&client_key);
println!("compressed size : {}", bincode::serialize(&compressed_public_key).unwrap().len());
let public_key = compressed_public_key.decompress();
println!("decompressed size: {}", bincode::serialize(&public_key).unwrap().len());
let a = FheUint8::try_encrypt(213u8, &public_key).unwrap();
let clear: u8 = a.decrypt(&client_key);
@@ -86,10 +119,10 @@ This example shows how to use compressed compact public keys.
```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, CompressedCompactPublicKey};
use tfhe::{generate_keys, set_server_key, CompressedCompactPublicKey, ConfigBuilder, FheUint8};
fn main() {
let config = ConfigBuilder::all_disabled()
let config = ConfigBuilder::all_disabled()
.enable_custom_integers(
tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS,
None,
@@ -98,10 +131,22 @@ fn main() {
let (client_key, _) = generate_keys(config);
let public_key_compressed = CompressedCompactPublicKey::new(&client_key);
println!(
"compressed size : {}",
bincode::serialize(&public_key_compressed).unwrap().len()
);
let public_key = public_key_compressed.decompress();
println!(
"decompressed size: {}",
bincode::serialize(&public_key).unwrap().len()
);
let a = FheUint8::try_encrypt(255u8, &public_key).unwrap();
let clear: u8 = a.decrypt(&client_key);
assert_eq!(clear, 255u8);
}
```

View File

@@ -1,9 +1,9 @@
# Parallelized Programmable Bootstrapping
The [Programmable Bootstrapping](../getting_started/security_and_cryptography.md)(PBS) is a sequential operation by nature. However, some [recent results](https://marcjoye.github.io/papers/JP22ternary.pdf) showed that parallelism could be added at the cost of having larger keys. Overall, the performance of the PBS are improved.
The [Programmable Bootstrapping](../getting_started/security_and_cryptography.md)(PBS) is a sequential operation by nature. However, some [recent results](https://marcjoye.github.io/papers/JP22ternary.pdf) showed that parallelism could be added at the cost of having larger keys. Overall, the performance of the PBS are improved. This new PBS is called a multi bit PBS.
In TFHE-rs, since integer homomorphic operations are already parallelized, activating this feature may improve performance in the case of high core count CPUs if enough cores are available, or for small input message precision.
In what follows, an example on how to use the parallelized bootstrapping:
In what follows, an example on how to use the parallelized bootstrapping by choosing multi bit PBS parameters:
```rust
use tfhe::prelude::*;
@@ -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,8 +1,12 @@
# Using TFHE-rs with nightly toolchain.
# Using the right toolchain for TFHE-rs.
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 for ARM devices)
Install the needed Rust toolchain:
```shell
# If you don't need the C API or the advanced still unstable SIMD instructions use this
rustup toolchain install stable
# Otherwise install a nightly toolchain
rustup toolchain install nightly
```
@@ -11,12 +15,23 @@ Then, you can either:
* Manually specify the toolchain to use in each of the cargo commands:
```shell
# By default the +stable should not be needed, but we add it here for completeness
cargo +stable build
cargo +stable test
# Or
cargo +nightly build
cargo +nightly test
```
* Or override the toolchain to use for the current project:
```shell
# This should not be necessary by default, but if you want to make sure your configuration is
# correct you can still set the overridden toolchain to stable
rustup override set stable
# cargo will use the `stable` toolchain.
cargo build
# Or
rustup override set nightly
# cargo will use the `nightly` toolchain.
cargo build
@@ -37,16 +52,16 @@ 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
In general, the library automatically chooses the best instruction sets available by the host. However, in the case of 'AVX-512', this has to explicitly chosen as a feature. This requires to use the [nightly toolchain](#using-tfhe-rs-with-nightly-toolchain) along with the feature `nightly-avx512`.
In general, the library automatically chooses the best instruction sets available by the host. However, in the case of 'AVX-512', this has to be explicitly chosen as a feature. This requires to use a [nightly toolchain](#using-tfhe-rs-with-nightly-toolchain) along with the feature `nightly-avx512`.
```shell
cargo +nightly build --features=nightly-avx512

View File

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

View File

@@ -0,0 +1,131 @@
# Generic Bounds
If you wish to write generic functions which use operators with mixed reference and non-reference,
it might get tricky at first to specify the trait [bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html).
This page should serve as a _cookbook_ to help you.
Operators (+, *, >>, etc) are tied to traits in `std:::ops`, e.g. `+` is `std::ops::Add`,
so to write a generic function which uses the `+` operator, you need to use add `std::ops::Add`
as a trait bound.
Then, depending on if the left hand side / right hand side is an owned value or a reference, the trait bound
is slightly different. The table below shows the possibilities.
| operation | trait bound |
| ----------- | ------------------------------------- |
| `T $op T` | `T: $Op<T, Output=T>` |
| `T $op &T` | `T: for<'a> $Op<&'a T, Output=T>` |
| `&T $op T` | `for<'a> &'a T: $Op<T, Output=T>` |
| `&T $op &T` | `for<'a> &'a T: $Op<&'a T, Output=T>` |
{% hint style="info" %}
The `for<'a>` syntax is something called [Higher-Rank Trait Bounds](https://doc.rust-lang.org/nomicon/hrtb.html), often shortened as __HRTB__
{% endhint %}
{% hint style="info" %}
Writing generic functions will also allow you to call them using clear inputs,
only allowing easier debugging.
{% endhint %}
## Example
```rust
use std::ops::{Add, Mul};
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32, FheUint64};
pub fn ex1<'a, FheType, ClearType>(ct: &'a FheType, pt: ClearType) -> FheType
where
&'a FheType: Add<ClearType, Output = FheType>,
{
ct + pt
}
pub fn ex2<'a, FheType, ClearType>(a: &'a FheType, b: &'a FheType, pt: ClearType) -> FheType
where
&'a FheType: Mul<&'a FheType, Output = FheType>,
FheType: Add<ClearType, Output = FheType>,
{
(a * b) + pt
}
pub fn ex3<FheType, ClearType>(a: FheType, b: FheType, pt: ClearType) -> FheType
where
for<'a> &'a FheType: Add<&'a FheType, Output = FheType>,
FheType: Add<FheType, Output = FheType> + Add<ClearType, Output = FheType>,
{
let tmp = (&a + &b) + (&a + &b);
tmp + pt
}
pub fn ex4<FheType, ClearType>(a: FheType, b: FheType, pt: ClearType) -> FheType
where
FheType: Clone + Add<FheType, Output = FheType> + Add<ClearType, Output = FheType>,
{
let tmp = (a.clone() + b.clone()) + (a.clone() + b.clone());
tmp + pt
}
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_keys) = generate_keys(config);
set_server_key(server_keys);
// Use FheUint32
{
let clear_a = 46546u32;
let clear_b = 6469u32;
let clear_c = 64u32;
let a = FheUint32::try_encrypt(clear_a, &client_key).unwrap();
let b = FheUint32::try_encrypt(clear_b, &client_key).unwrap();
assert_eq!(
ex1(&clear_a, clear_c),
ex1(&a, clear_c).decrypt(&client_key)
);
assert_eq!(
ex2(&clear_a, &clear_b, clear_c),
ex2(&a, &b, clear_c).decrypt(&client_key)
);
assert_eq!(
ex3(clear_a, clear_b, clear_c),
ex3(a.clone(), b.clone(), clear_c).decrypt(&client_key)
);
assert_eq!(
ex4(clear_a, clear_b, clear_c),
ex4(a, b, clear_c).decrypt(&client_key)
);
}
// Use FheUint64
{
let clear_a = 46544866u64;
let clear_b = 6469446677u64;
let clear_c = 647897u64;
let a = FheUint64::try_encrypt(clear_a, &client_key).unwrap();
let b = FheUint64::try_encrypt(clear_b, &client_key).unwrap();
assert_eq!(
ex1(&clear_a, clear_c),
ex1(&a, clear_c).decrypt(&client_key)
);
assert_eq!(
ex2(&clear_a, &clear_b, clear_c),
ex2(&a, &b, clear_c).decrypt(&client_key)
);
assert_eq!(
ex3(clear_a, clear_b, clear_c),
ex3(a.clone(), b.clone(), clear_c).decrypt(&client_key)
);
assert_eq!(
ex4(clear_a, clear_b, clear_c),
ex4(a, b, clear_c).decrypt(&client_key)
);
}
}
```

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

@@ -0,0 +1,156 @@
# A first complete example: FheAsciiString (Integer)
The goal of this tutorial is to build a data type that represents a ASCII string in FHE while implementing the `to_lower` and `to_upper` functions.
An ASCII character is stored in 7 bits.
To store an encrypted ASCII we use the `FheUint8`.
* The uppercase letters are in the range \[65, 90]
* The lowercase letters are in the range \[97, 122]
`lower_case = upper_case + UP_LOW_DISTANCE` <=> `upper_case = lower_case - UP_LOW_DISTANCE`
Where `UP_LOW_DISTANCE = 32`
## Types and methods.
This type will hold the encrypted characters as a `Vec<FheUint8>` to implement the functions that change the case.
To use the `FheUint8` type, the `integer` feature must be activated:
```toml
# Cargo.toml
[dependencies]
# Default configuration for x86 Unix machines:
tfhe = { version = "0.4.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
In the `FheAsciiString::encrypt` function, some data validation is done:
* The input string can only contain ascii characters.
It is not possible to branch on an encrypted value, however it is possible to evaluate a boolean condition and use it to get the desired result.
Checking if the 'char' is an uppercase letter to modify it to a lowercase can be done without using a branch, like this:
```rust
pub const UP_LOW_DISTANCE: u8 = 32;
fn to_lower(c: u8) -> u8 {
if c > 64 && c < 91 {
c + UP_LOW_DISTANCE
} else {
c
}
}
```
We can remove the branch this way:
```rust
pub const UP_LOW_DISTANCE: u8 = 32;
fn to_lower(c: u8) -> u8 {
c + ((c > 64) as u8 & (c < 91) as u8) * UP_LOW_DISTANCE
}
```
On an homomorphic integer, this gives
```rust
use tfhe::prelude::*;
use tfhe::FheUint8;
pub const UP_LOW_DISTANCE: u8 = 32;
fn to_lower(c: &FheUint8) -> FheUint8 {
c + (c.gt(64) & c.lt(91)) * UP_LOW_DISTANCE
}
```
The whole code is:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ClientKey, ConfigBuilder, FheUint8};
pub const UP_LOW_DISTANCE: u8 = 32;
struct FheAsciiString {
bytes: Vec<FheUint8>,
}
fn to_upper(c: &FheUint8) -> FheUint8 {
c - (c.gt(96) & c.lt(123)) * UP_LOW_DISTANCE
}
fn to_lower(c: &FheUint8) -> FheUint8 {
c + (c.gt(64) & c.lt(91)) * UP_LOW_DISTANCE
}
impl FheAsciiString {
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
assert!(
string.chars().all(|char| char.is_ascii()),
"The input string must only contain ascii letters"
);
let fhe_bytes: Vec<FheUint8> = string
.bytes()
.map(|b| FheUint8::encrypt(b, client_key))
.collect();
Self { bytes: fhe_bytes }
}
fn decrypt(&self, client_key: &ClientKey) -> String {
let ascii_bytes: Vec<u8> = self
.bytes
.iter()
.map(|fhe_b| fhe_b.decrypt(client_key))
.collect();
String::from_utf8(ascii_bytes).unwrap()
}
fn to_upper(&self) -> Self {
Self {
bytes: self.bytes.iter().map(to_upper).collect(),
}
}
fn to_lower(&self) -> Self {
Self {
bytes: self.bytes.iter().map(to_lower).collect(),
}
}
}
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let my_string = FheAsciiString::encrypt("Hello Zama, how is it going?", &client_key);
let verif_string = my_string.decrypt(&client_key);
println!("Start string: {verif_string}");
let my_string_upper = my_string.to_upper();
let verif_string = my_string_upper.decrypt(&client_key);
println!("Upper string: {verif_string}");
assert_eq!(verif_string, "HELLO ZAMA, HOW IS IT GOING?");
let my_string_lower = my_string_upper.to_lower();
let verif_string = my_string_lower.decrypt(&client_key);
println!("Lower string: {verif_string}");
assert_eq!(verif_string, "hello zama, how is it going?");
}
```

View File

@@ -1,155 +0,0 @@
# A first complete example: FheLatinString (Integer)
The goal of this tutorial is to build a data type that represents a Latin string in FHE while implementing the `to_lower` and `to_upper` functions.
The allowed characters in a Latin string are:
* Uppercase letters: `A B C D E F G H I J K L M N O P Q R S T U V W X Y Z`
* Lowercase letters: `a b c d e f g h i j k l m n o p q r s t u v w x y z`
For the code point of the letters,`ascii` codes are used:
* The uppercase letters are in the range \[65, 90]
* The lowercase letters are in the range \[97, 122]
`lower_case` = `upper_case` + 32 <=> `upper_case` = `lower_case` - 32
For this type, the `FheUint8` type is used.
## Types and methods.
This type will hold the encrypted characters as a `Vec<FheUint8>`, as well as the encrypted constant `32` to implement the functions that change the case.
In the `FheLatinString::encrypt` function, some data validation is done:
* The input string can only contain ascii letters (no digit, no special characters).
* The input string cannot mix lower and upper case letters.
These two points are to work around a limitation of FHE. It is not possible to create branches, meaning the function cannot use conditional statements. Checking if the 'char' is an uppercase letter to modify it to a lowercase one cannot be done, like in the example below.
```rust
fn to_lower(string: &String) -> String {
let mut result = String::with_capacity(string.len());
for char in string.chars() {
if char.is_uppercase() {
result.extend(char.to_lowercase().to_string().chars())
}
}
result
}
```
With these preconditions checked, implementing `to_lower` and `to_upper` is rather simple.
To use the `FheUint8` type, the `integer` feature must be activated:
```toml
# Cargo.toml
[dependencies]
# Default configuration for x86 Unix machines:
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
```rust
use tfhe::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
use tfhe::prelude::*;
struct FheLatinString{
bytes: Vec<FheUint8>,
// Constant used to switch lower case <=> upper case
cst: FheUint8,
}
impl FheLatinString {
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
assert!(
string.chars().all(|char| char.is_ascii_alphabetic()),
"The input string must only contain ascii letters"
);
let has_mixed_case = string.as_bytes().windows(2).any(|window| {
let first = char::from(*window.first().unwrap());
let second = char::from(*window.last().unwrap());
(first.is_ascii_lowercase() && second.is_ascii_uppercase())
|| (first.is_ascii_uppercase() && second.is_ascii_lowercase())
});
assert!(
!has_mixed_case,
"The input string cannot mix lower case and upper case letters"
);
let fhe_bytes = string
.bytes()
.map(|b| FheUint8::encrypt(b, client_key))
.collect::<Vec<FheUint8>>();
let cst = FheUint8::encrypt(32u8, client_key);
Self {
bytes: fhe_bytes,
cst,
}
}
fn decrypt(&self, client_key: &ClientKey) -> String {
let ascii_bytes = self
.bytes
.iter()
.map(|fhe_b| fhe_b.decrypt(client_key))
.collect::<Vec<u8>>();
String::from_utf8(ascii_bytes).unwrap()
}
fn to_upper(&self) -> Self {
Self {
bytes: self
.bytes
.iter()
.map(|b| b - &self.cst)
.collect::<Vec<FheUint8>>(),
cst: self.cst.clone(),
}
}
fn to_lower(&self) -> Self {
Self {
bytes: self
.bytes
.iter()
.map(|b| b + &self.cst)
.collect::<Vec<FheUint8>>(),
cst: self.cst.clone(),
}
}
}
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let my_string = FheLatinString::encrypt("zama", &client_key);
let verif_string = my_string.decrypt(&client_key);
println!("{}", verif_string);
let my_string_upper = my_string.to_upper();
let verif_string = my_string_upper.decrypt(&client_key);
println!("{}", verif_string);
assert_eq!(verif_string, "ZAMA");
let my_string_lower = my_string_upper.to_lower();
let verif_string = my_string_lower.decrypt(&client_key);
println!("{}", verif_string);
assert_eq!(verif_string, "zama");
}
```

View File

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

View File

@@ -1,418 +0,0 @@
use std::time::Instant;
use rayon::prelude::*;
use tfhe::integer::ciphertext::RadixCiphertext;
use tfhe::integer::keycache::IntegerKeyCache;
use tfhe::integer::ServerKey;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
/// The number of blocks to be used in the Radix.
const NUMBER_OF_BLOCKS: usize = 8;
/// Plain implementation of the volume matching algorithm.
///
/// Matches the given [sell_orders] with [buy_orders].
/// The amount of the orders that are successfully filled is written over the original order count.
fn volume_match_plain(sell_orders: &mut [u16], buy_orders: &mut [u16]) {
let total_sell_volume: u16 = sell_orders.iter().sum();
let total_buy_volume: u16 = buy_orders.iter().sum();
let total_volume = std::cmp::min(total_buy_volume, total_sell_volume);
let mut volume_left_to_transact = total_volume;
for sell_order in sell_orders.iter_mut() {
let filled_amount = std::cmp::min(volume_left_to_transact, *sell_order);
*sell_order = filled_amount;
volume_left_to_transact -= filled_amount;
}
let mut volume_left_to_transact = total_volume;
for buy_order in buy_orders.iter_mut() {
let filled_amount = std::cmp::min(volume_left_to_transact, *buy_order);
*buy_order = filled_amount;
volume_left_to_transact -= filled_amount;
}
}
/// FHE implementation of the volume matching algorithm.
///
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
/// [server_key]. The amount of the orders that are successfully filled is written over the original
/// order count.
fn volume_match_fhe(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
println!("Calculating total sell and buy volumes...");
let time = Instant::now();
let mut total_sell_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for sell_order in sell_orders.iter_mut() {
server_key.smart_add_assign(&mut total_sell_volume, sell_order);
}
let mut total_buy_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for buy_order in buy_orders.iter_mut() {
server_key.smart_add_assign(&mut total_buy_volume, buy_order);
}
println!(
"Total sell and buy volumes are calculated in {:?}",
time.elapsed()
);
println!("Calculating total volume to be matched...");
let time = Instant::now();
let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_volume);
println!(
"Calculated total volume to be matched in {:?}",
time.elapsed()
);
let fill_orders = |orders: &mut [RadixCiphertext]| {
let mut volume_left_to_transact = total_volume.clone();
for order in orders.iter_mut() {
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, order);
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
};
println!("Filling orders...");
let time = Instant::now();
fill_orders(sell_orders);
fill_orders(buy_orders);
println!("Filled orders in {:?}", time.elapsed());
}
/// FHE implementation of the volume matching algorithm.
///
/// This version of the algorithm utilizes parallelization to speed up the computation.
///
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
/// [server_key]. The amount of the orders that are successfully filled is written over the original
/// order count.
fn volume_match_fhe_parallelized(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
// Calculate the element sum of the given vector in parallel
let parallel_vector_sum = |vec: &mut [RadixCiphertext]| {
vec.to_vec().into_par_iter().reduce(
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
server_key.smart_add_parallelized(&mut acc, &mut ele)
},
)
};
println!("Calculating total sell and buy volumes...");
let time = Instant::now();
// Total sell and buy volumes can be calculated in parallel because they have no dependency on
// each other.
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|| parallel_vector_sum(sell_orders),
|| parallel_vector_sum(buy_orders),
);
println!(
"Total sell and buy volumes are calculated in {:?}",
time.elapsed()
);
println!("Calculating total volume to be matched...");
let time = Instant::now();
let total_volume =
server_key.smart_min_parallelized(&mut total_sell_volume, &mut total_buy_volume);
println!(
"Calculated total volume to be matched in {:?}",
time.elapsed()
);
let fill_orders = |orders: &mut [RadixCiphertext]| {
let mut volume_left_to_transact = total_volume.clone();
for order in orders.iter_mut() {
let mut filled_amount =
server_key.smart_min_parallelized(&mut volume_left_to_transact, order);
server_key
.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
};
println!("Filling orders...");
let time = Instant::now();
rayon::join(|| fill_orders(sell_orders), || fill_orders(buy_orders));
println!("Filled orders in {:?}", time.elapsed());
}
/// FHE implementation of the volume matching algorithm.
///
/// In this function, the implemented algorithm is modified to utilize more concurrency.
///
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
/// [server_key]. The amount of the orders that are successfully filled is written over the original
/// order count.
fn volume_match_fhe_modified(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
let compute_prefix_sum = |arr: &[RadixCiphertext]| {
if arr.is_empty() {
return arr.to_vec();
}
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
.into_par_iter()
.map(|i| {
if i < arr.len() {
arr[i].clone()
} else {
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
}
})
.collect();
for d in 0..prefix_sum.len().ilog2() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
});
}
let last = prefix_sum.last().unwrap().clone();
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for d in (0..prefix_sum.len().ilog2()).rev() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let temp = chunk.last().unwrap().clone();
let mut mid = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut mid);
chunk[(length - 1) / 2] = temp;
});
}
prefix_sum.push(last);
prefix_sum[1..=arr.len()].to_vec()
};
println!("Creating prefix sum arrays...");
let time = Instant::now();
let (prefix_sum_sell_orders, prefix_sum_buy_orders) = rayon::join(
|| compute_prefix_sum(sell_orders),
|| compute_prefix_sum(buy_orders),
);
println!("Created prefix sum arrays in {:?}", time.elapsed());
let fill_orders = |total_orders: &RadixCiphertext,
orders: &mut [RadixCiphertext],
prefix_sum_arr: &[RadixCiphertext]| {
orders
.into_par_iter()
.enumerate()
.for_each(move |(i, order)| {
server_key.smart_add_assign_parallelized(
order,
&mut server_key.smart_mul_parallelized(
&mut server_key
.smart_ge_parallelized(&mut order.clone(), &mut total_orders.clone()),
&mut server_key.smart_sub_parallelized(
&mut server_key.smart_sub_parallelized(
&mut total_orders.clone(),
&mut server_key.smart_min_parallelized(
&mut total_orders.clone(),
&mut prefix_sum_arr
.get(i - 1)
.unwrap_or(
&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
)
.clone(),
),
),
&mut order.clone(),
),
),
);
});
};
let total_buy_orders = &mut prefix_sum_buy_orders
.last()
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
.clone();
let total_sell_orders = &mut prefix_sum_sell_orders
.last()
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
.clone();
println!("Matching orders...");
let time = Instant::now();
rayon::join(
|| fill_orders(total_sell_orders, buy_orders, &prefix_sum_buy_orders),
|| fill_orders(total_buy_orders, sell_orders, &prefix_sum_sell_orders),
);
println!("Matched orders in {:?}", time.elapsed());
}
/// Runs the given [tester] function with the test cases for volume matching algorithm.
fn run_test_cases<F: Fn(&[u16], &[u16], &[u16], &[u16])>(tester: F) {
println!("Testing empty sell orders...");
tester(
&[],
&(1..11).collect::<Vec<_>>(),
&[],
&(1..11).map(|_| 0).collect::<Vec<_>>(),
);
println!();
println!("Testing empty buy orders...");
tester(
&(1..11).collect::<Vec<_>>(),
&[],
&(1..11).map(|_| 0).collect::<Vec<_>>(),
&[],
);
println!();
println!("Testing exact matching of sell and buy orders...");
tester(
&(1..11).collect::<Vec<_>>(),
&(1..11).collect::<Vec<_>>(),
&(1..11).collect::<Vec<_>>(),
&(1..11).collect::<Vec<_>>(),
);
println!();
println!("Testing the case where there are more buy orders than sell orders...");
tester(
&(1..11).map(|_| 10).collect::<Vec<_>>(),
&[200],
&(1..11).map(|_| 10).collect::<Vec<_>>(),
&[100],
);
println!();
println!("Testing the case where there are more sell orders than buy orders...");
tester(
&[200],
&(1..11).map(|_| 10).collect::<Vec<_>>(),
&[100],
&(1..11).map(|_| 10).collect::<Vec<_>>(),
);
println!();
println!("Testing maximum input size for sell and buy orders...");
tester(
&(1..=500).map(|_| 100).collect::<Vec<_>>(),
&(1..=500).map(|_| 100).collect::<Vec<_>>(),
&(1..=500).map(|_| 100).collect::<Vec<_>>(),
&(1..=500).map(|_| 100).collect::<Vec<_>>(),
);
println!();
}
/// Runs the test cases for the plain implementation of the volume matching algorithm.
fn test_volume_match_plain() {
let tester = |input_sell_orders: &[u16],
input_buy_orders: &[u16],
expected_filled_sells: &[u16],
expected_filled_buys: &[u16]| {
let mut sell_orders = input_sell_orders.to_vec();
let mut buy_orders = input_buy_orders.to_vec();
println!("Running plain implementation...");
let time = Instant::now();
volume_match_plain(&mut sell_orders, &mut buy_orders);
println!("Ran plain implementation in {:?}", time.elapsed());
assert_eq!(sell_orders, expected_filled_sells);
assert_eq!(buy_orders, expected_filled_buys);
};
println!("Running test cases for the plain implementation");
run_test_cases(tester);
}
/// Runs the test cases for the fhe implementation of the volume matching algorithm.
///
/// [parallelized] indicates whether the fhe implementation should be run in parallel.
fn test_volume_match_fhe(
fhe_function: fn(&mut [RadixCiphertext], &mut [RadixCiphertext], &ServerKey),
) {
let working_dir = std::env::current_dir().unwrap();
if working_dir.file_name().unwrap() != std::path::Path::new("tfhe") {
std::env::set_current_dir(working_dir.join("tfhe")).unwrap();
}
println!("Generating keys...");
let time = Instant::now();
let (client_key, server_key) = IntegerKeyCache.get_from_params(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
println!("Keys generated in {:?}", time.elapsed());
let tester = |input_sell_orders: &[u16],
input_buy_orders: &[u16],
expected_filled_sells: &[u16],
expected_filled_buys: &[u16]| {
let mut encrypted_sell_orders = input_sell_orders
.iter()
.cloned()
.map(|pt| client_key.encrypt_radix(pt as u64, NUMBER_OF_BLOCKS))
.collect::<Vec<RadixCiphertext>>();
let mut encrypted_buy_orders = input_buy_orders
.iter()
.cloned()
.map(|pt| client_key.encrypt_radix(pt as u64, NUMBER_OF_BLOCKS))
.collect::<Vec<RadixCiphertext>>();
println!("Running FHE implementation...");
let time = Instant::now();
fhe_function(
&mut encrypted_sell_orders,
&mut encrypted_buy_orders,
&server_key,
);
println!("Ran FHE implementation in {:?}", time.elapsed());
let decrypted_filled_sells = encrypted_sell_orders
.iter()
.map(|ct| client_key.decrypt_radix::<u64>(ct) as u16)
.collect::<Vec<u16>>();
let decrypted_filled_buys = encrypted_buy_orders
.iter()
.map(|ct| client_key.decrypt_radix::<u64>(ct) as u16)
.collect::<Vec<u16>>();
assert_eq!(decrypted_filled_sells, expected_filled_sells);
assert_eq!(decrypted_filled_buys, expected_filled_buys);
};
println!("Running test cases for the FHE implementation");
run_test_cases(tester);
}
fn main() {
for argument in std::env::args() {
if argument == "fhe-modified" {
println!("Running modified fhe version");
test_volume_match_fhe(volume_match_fhe_modified);
println!();
}
if argument == "fhe-parallel" {
println!("Running parallelized fhe version");
test_volume_match_fhe(volume_match_fhe_parallelized);
println!();
}
if argument == "plain" {
println!("Running plain version");
test_volume_match_plain();
println!();
}
if argument == "fhe" {
println!("Running fhe version");
test_volume_match_fhe(volume_match_fhe);
println!();
}
}
}

View File

@@ -0,0 +1,102 @@
use std::time::Instant;
use tfhe::integer::ciphertext::RadixCiphertext;
use tfhe::integer::{ClientKey, ServerKey};
use crate::NUMBER_OF_BLOCKS;
fn vector_sum(server_key: &ServerKey, orders: &mut [RadixCiphertext]) -> RadixCiphertext {
let mut total_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for order in orders {
server_key.smart_add_assign(&mut total_volume, order);
}
total_volume
}
fn fill_orders(
server_key: &ServerKey,
orders: &mut [RadixCiphertext],
total_volume: RadixCiphertext,
) {
let mut volume_left_to_transact = total_volume;
for order in orders {
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, order);
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
}
/// FHE implementation of the volume matching algorithm.
///
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
/// [server_key]. The amount of the orders that are successfully filled is written over the original
/// order count.
pub fn volume_match(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
println!("Calculating total sell and buy volumes...");
let time = Instant::now();
let mut total_sell_volume = vector_sum(server_key, sell_orders);
let mut total_buy_volume = vector_sum(server_key, buy_orders);
println!(
"Total sell and buy volumes are calculated in {:?}",
time.elapsed()
);
println!("Calculating total volume to be matched...");
let time = Instant::now();
let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_volume);
println!(
"Calculated total volume to be matched in {:?}",
time.elapsed()
);
println!("Filling orders...");
let time = Instant::now();
fill_orders(server_key, sell_orders, total_volume.clone());
fill_orders(server_key, buy_orders, total_volume);
println!("Filled orders in {:?}", time.elapsed());
}
pub fn tester(
client_key: &ClientKey,
server_key: &ServerKey,
input_sell_orders: &[u16],
input_buy_orders: &[u16],
expected_filled_sells: &[u16],
expected_filled_buys: &[u16],
fhe_function: fn(&mut [RadixCiphertext], &mut [RadixCiphertext], &ServerKey),
) {
let encrypt = |pt: u16| client_key.encrypt_radix(pt as u64, NUMBER_OF_BLOCKS);
let mut encrypted_sell_orders = input_sell_orders
.iter()
.cloned()
.map(encrypt)
.collect::<Vec<RadixCiphertext>>();
let mut encrypted_buy_orders = input_buy_orders
.iter()
.cloned()
.map(encrypt)
.collect::<Vec<RadixCiphertext>>();
println!("Running FHE implementation...");
let time = Instant::now();
fhe_function(
&mut encrypted_sell_orders,
&mut encrypted_buy_orders,
server_key,
);
println!("Ran FHE implementation in {:?}", time.elapsed());
let decrypt = |ct| client_key.decrypt_radix::<u64>(ct) as u16;
let decrypted_filled_sells: Vec<u16> = encrypted_sell_orders.iter().map(decrypt).collect();
let decrypted_filled_buys: Vec<u16> = encrypted_buy_orders.iter().map(decrypt).collect();
assert_eq!(decrypted_filled_sells, expected_filled_sells);
assert_eq!(decrypted_filled_buys, expected_filled_buys);
}

View File

@@ -0,0 +1,135 @@
use std::time::Instant;
use rayon::prelude::*;
use tfhe::integer::ciphertext::RadixCiphertext;
use tfhe::integer::ServerKey;
use crate::NUMBER_OF_BLOCKS;
fn compute_prefix_sum(server_key: &ServerKey, arr: &[RadixCiphertext]) -> Vec<RadixCiphertext> {
if arr.is_empty() {
return arr.to_vec();
}
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
.into_par_iter()
.map(|i| {
if i < arr.len() {
arr[i].clone()
} else {
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
}
})
.collect();
for d in 0..prefix_sum.len().ilog2() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
});
}
let last = prefix_sum.last().unwrap().clone();
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for d in (0..prefix_sum.len().ilog2()).rev() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let temp = chunk.last().unwrap().clone();
let mut mid = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut mid);
chunk[(length - 1) / 2] = temp;
});
}
prefix_sum.push(last);
prefix_sum[1..=arr.len()].to_vec()
}
fn fill_orders(
server_key: &ServerKey,
total_orders: &RadixCiphertext,
orders: &mut [RadixCiphertext],
prefix_sum_arr: &[RadixCiphertext],
) {
orders
.into_par_iter()
.enumerate()
.for_each(move |(i, order)| {
// (total_orders - previous_prefix_sum).max(0)
let mut diff = if i == 0 {
total_orders.clone()
} else {
let previous_prefix_sum = &prefix_sum_arr[i - 1];
// total_orders - previous_prefix_sum
let mut diff = server_key.smart_sub_parallelized(
&mut total_orders.clone(),
&mut previous_prefix_sum.clone(),
);
// total_orders > prefix_sum
let mut cond = server_key.smart_gt_parallelized(
&mut total_orders.clone(),
&mut previous_prefix_sum.clone(),
);
// (total_orders - previous_prefix_sum) * (total_orders > previous_prefix_sum)
// = (total_orders - previous_prefix_sum).max(0)
server_key.smart_mul_parallelized(&mut cond, &mut diff)
};
// (total_orders - previous_prefix_sum).max(0).min(*order);
*order = server_key.smart_min_parallelized(&mut diff, order);
});
}
/// FHE implementation of the volume matching algorithm.
///
/// In this function, the implemented algorithm is modified to utilize more concurrency.
///
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
/// [server_key]. The amount of the orders that are successfully filled is written over the original
/// order count.
pub fn volume_match(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
println!("Creating prefix sum arrays...");
let time = Instant::now();
let (prefix_sum_sell_orders, prefix_sum_buy_orders) = rayon::join(
|| compute_prefix_sum(server_key, sell_orders),
|| compute_prefix_sum(server_key, buy_orders),
);
println!("Created prefix sum arrays in {:?}", time.elapsed());
let zero = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
let total_buy_orders = prefix_sum_buy_orders.last().unwrap_or(&zero);
let total_sell_orders = prefix_sum_sell_orders.last().unwrap_or(&zero);
println!("Matching orders...");
let time = Instant::now();
rayon::join(
|| {
fill_orders(
server_key,
total_sell_orders,
buy_orders,
&prefix_sum_buy_orders,
)
},
|| {
fill_orders(
server_key,
total_buy_orders,
sell_orders,
&prefix_sum_sell_orders,
)
},
);
println!("Matched orders in {:?}", time.elapsed());
}

View File

@@ -0,0 +1,32 @@
fn compute_prefix_sum(arr: &[u16]) -> Vec<u16> {
let mut sum = 0;
arr.iter()
.map(|a| {
sum += a;
sum
})
.collect()
}
fn fill_orders(total_orders: u16, orders: &mut [u16], prefix_sum_arr: &[u16]) {
for (i, order) in orders.iter_mut().enumerate() {
let previous_prefix_sum = if i == 0 { 0 } else { prefix_sum_arr[i - 1] };
*order = (total_orders as i64 - previous_prefix_sum as i64)
.max(0)
.min(*order as i64) as u16;
}
}
pub fn volume_match(sell_orders: &mut [u16], buy_orders: &mut [u16]) {
let prefix_sum_sell_orders = compute_prefix_sum(sell_orders);
let prefix_sum_buy_orders = compute_prefix_sum(buy_orders);
let total_buy_orders = *prefix_sum_buy_orders.last().unwrap_or(&0);
let total_sell_orders = *prefix_sum_sell_orders.last().unwrap_or(&0);
fill_orders(total_sell_orders, buy_orders, &prefix_sum_buy_orders);
fill_orders(total_buy_orders, sell_orders, &prefix_sum_sell_orders);
}

View File

@@ -0,0 +1,111 @@
use std::time::Instant;
use tfhe::integer::ciphertext::RadixCiphertext;
use tfhe::integer::keycache::IntegerKeyCache;
use tfhe::integer::ServerKey;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
mod fhe;
mod improved_parallel_fhe;
mod improved_plain;
mod parallel_fhe;
mod plain;
/// The number of blocks to be used in the Radix.
const NUMBER_OF_BLOCKS: usize = 8;
#[allow(clippy::type_complexity)]
fn test_cases() -> Vec<(String, (Vec<u16>, Vec<u16>, Vec<u16>, Vec<u16>))> {
vec![
(
"empty sell orders".to_owned(),
(vec![], (1..11).collect::<Vec<_>>(), vec![], vec![0; 10]),
),
(
"empty buy orders".to_owned(),
((1..11).collect::<Vec<_>>(), vec![], vec![0; 10], vec![]),
),
(
"exact matching of sell and buy orders".to_owned(),
(
(1..11).collect::<Vec<_>>(),
(1..11).collect::<Vec<_>>(),
(1..11).collect::<Vec<_>>(),
(1..11).collect::<Vec<_>>(),
),
),
(
"a case where there are more buy orders than sell orders".to_owned(),
(vec![10; 10], vec![200], vec![10; 10], vec![100]),
),
(
"a case where there are more sell orders than buy orders".to_owned(),
(vec![200], vec![10; 10], vec![100], vec![10; 10]),
),
(
"maximum input size for sell and buy orders".to_owned(),
(
vec![100; 499],
vec![100; 499],
vec![100; 499],
vec![100; 499],
),
),
]
}
/// Runs the given [tester] function with the test cases for volume matching algorithm.
fn run_test_cases(tester: impl Fn(&[u16], &[u16], &[u16], &[u16])) {
for (test_name, test_case) in &test_cases() {
println!("Testing {test_name}...");
tester(&test_case.0, &test_case.1, &test_case.2, &test_case.3);
println!();
}
}
fn test_volume_match_plain(function: fn(&mut [u16], &mut [u16])) {
println!("Running test cases for the plain implementation");
run_test_cases(|a, b, c, d| plain::tester(a, b, c, d, function));
}
fn test_volume_match_fhe(
fhe_function: fn(&mut [RadixCiphertext], &mut [RadixCiphertext], &ServerKey),
) {
println!("Generating keys...");
let time = Instant::now();
let (client_key, server_key) = IntegerKeyCache.get_from_params(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
println!("Keys generated in {:?}", time.elapsed());
println!("Running test cases for the FHE implementation");
run_test_cases(|a, b, c, d| fhe::tester(&client_key, &server_key, a, b, c, d, fhe_function));
}
fn main() {
for argument in std::env::args() {
if argument == "plain" {
println!("Running plain version");
test_volume_match_plain(plain::volume_match);
println!();
}
if argument == "plain-improved" {
println!("Running plain improved version");
test_volume_match_plain(improved_plain::volume_match);
println!();
}
if argument == "fhe" {
println!("Running fhe version");
test_volume_match_fhe(fhe::volume_match);
println!();
}
if argument == "fhe-parallel" {
println!("Running parallelized fhe version");
test_volume_match_fhe(parallel_fhe::volume_match);
println!();
}
if argument == "fhe-improved" {
println!("Running improved parallelized fhe fhe version");
test_volume_match_fhe(improved_parallel_fhe::volume_match);
println!();
}
}
}

View File

@@ -0,0 +1,75 @@
use std::time::Instant;
use rayon::prelude::*;
use tfhe::integer::ciphertext::RadixCiphertext;
use tfhe::integer::ServerKey;
use crate::NUMBER_OF_BLOCKS;
// Calculate the element sum of the given vector in parallel
fn vector_sum(server_key: &ServerKey, orders: Vec<RadixCiphertext>) -> RadixCiphertext {
orders.into_par_iter().reduce(
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
server_key.smart_add_parallelized(&mut acc, &mut ele)
},
)
}
fn fill_orders(
server_key: &ServerKey,
orders: &mut [RadixCiphertext],
total_volume: RadixCiphertext,
) {
let mut volume_left_to_transact = total_volume;
for order in orders {
let mut filled_amount =
server_key.smart_min_parallelized(&mut volume_left_to_transact, order);
server_key.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
}
/// FHE implementation of the volume matching algorithm.
///
/// This version of the algorithm utilizes parallelization to speed up the computation.
///
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
/// [server_key]. The amount of the orders that are successfully filled is written over the original
/// order count.
pub fn volume_match(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
println!("Calculating total sell and buy volumes...");
let time = Instant::now();
// Total sell and buy volumes can be calculated in parallel because they have no dependency on
// each other.
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|| vector_sum(server_key, sell_orders.to_owned()),
|| vector_sum(server_key, buy_orders.to_owned()),
);
println!(
"Total sell and buy volumes are calculated in {:?}",
time.elapsed()
);
println!("Calculating total volume to be matched...");
let time = Instant::now();
let total_volume =
server_key.smart_min_parallelized(&mut total_sell_volume, &mut total_buy_volume);
println!(
"Calculated total volume to be matched in {:?}",
time.elapsed()
);
println!("Filling orders...");
let time = Instant::now();
rayon::join(
|| fill_orders(server_key, sell_orders, total_volume.clone()),
|| fill_orders(server_key, buy_orders, total_volume.clone()),
);
println!("Filled orders in {:?}", time.elapsed());
}

View File

@@ -0,0 +1,43 @@
use std::time::Instant;
fn fill_orders(orders: &mut [u16], total_volume: u16) {
let mut volume_left_to_transact = total_volume;
for order in orders {
let filled_amount = std::cmp::min(volume_left_to_transact, *order);
*order = filled_amount;
volume_left_to_transact -= filled_amount;
}
}
/// Plain implementation of the volume matching algorithm.
///
/// Matches the given [sell_orders] with [buy_orders].
/// The amount of the orders that are successfully filled is written over the original order count.
pub fn volume_match(sell_orders: &mut [u16], buy_orders: &mut [u16]) {
let total_sell_volume: u16 = sell_orders.iter().sum();
let total_buy_volume: u16 = buy_orders.iter().sum();
let total_volume = std::cmp::min(total_buy_volume, total_sell_volume);
fill_orders(sell_orders, total_volume);
fill_orders(buy_orders, total_volume);
}
pub fn tester(
input_sell_orders: &[u16],
input_buy_orders: &[u16],
expected_filled_sells: &[u16],
expected_filled_buys: &[u16],
function: fn(&mut [u16], &mut [u16]),
) {
let mut sell_orders = input_sell_orders.to_vec();
let mut buy_orders = input_buy_orders.to_vec();
println!("Running plain implementation...");
let time = Instant::now();
function(&mut sell_orders, &mut buy_orders);
println!("Ran plain implementation in {:?}", time.elapsed());
assert_eq!(sell_orders, expected_filled_sells);
assert_eq!(buy_orders, expected_filled_buys);
}

View File

@@ -15,7 +15,7 @@ fn write_result(file: &mut File, name: &str, value: usize) {
}
fn client_server_key_sizes(results_file: &Path) {
let boolean_params_vec = vec![
let boolean_params_vec = [
(DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS"),
(PARAMETERS_ERROR_PROB_2_POW_MINUS_165, "TFHE_LIB_PARAMETERS"),
];

View File

@@ -1,6 +1,18 @@
use clap::{Arg, ArgAction, Command};
use tfhe::boolean;
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, DEFAULT_PARAMETERS_KS_PBS};
use tfhe::keycache::NamedParam;
use tfhe::shortint::keycache::{KEY_CACHE, KEY_CACHE_WOPBS};
use tfhe::shortint::keycache::{KEY_CACHE, KEY_CACHE_KSK, KEY_CACHE_WOPBS};
use tfhe::shortint::parameters::key_switching::{
ShortintKeySwitchingParameters, PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
};
use tfhe::shortint::parameters::multi_bit::{
MultiBitPBSParameters, PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
};
use tfhe::shortint::parameters::parameters_compact_pk::{
PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS, PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS,
};
use tfhe::shortint::parameters::parameters_wopbs_message_carry::{
WOPBS_PARAM_MESSAGE_1_CARRY_1_KS_PBS, WOPBS_PARAM_MESSAGE_2_CARRY_2_KS_PBS,
WOPBS_PARAM_MESSAGE_3_CARRY_3_KS_PBS, WOPBS_PARAM_MESSAGE_4_CARRY_4_KS_PBS,
@@ -17,56 +29,75 @@ fn client_server_keys() {
Arg::new("multi_bit_only")
.long("multi-bit-only")
.help("Set to generate only multi bit keys, otherwise only PBS and WoPBS keys are generated")
.action(ArgAction::SetTrue),
.action(ArgAction::SetTrue)
.exclusive(true),
)
.arg(
Arg::new("coverage_only")
.long("coverage-only")
.help("Set to generate only coverage keys, a very small subset of keys")
.action(ArgAction::SetTrue)
.exclusive(true),
)
.get_matches();
// If set using the command line flag "--ladner-fischer" this algorithm will be used in
// additions
let multi_bit_only: bool = matches.get_flag("multi_bit_only");
let coverage_only: bool = matches.get_flag("coverage_only");
if multi_bit_only {
println!("Generating shortint multibit (ClientKey, ServerKey)");
for (i, params) in ALL_MULTI_BIT_PARAMETER_VEC.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}",
i + 1,
ALL_MULTI_BIT_PARAMETER_VEC.len(),
params.name()
);
generate_pbs_multi_bit_keys(&ALL_MULTI_BIT_PARAMETER_VEC);
} else if coverage_only {
println!("Generating keys (ClientKey, ServerKey) for coverage");
let start = std::time::Instant::now();
const PBS_PARAMS: [ClassicPBSParameters; 5] = [
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
PARAM_MESSAGE_3_CARRY_3_KS_PBS,
PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS,
PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS,
];
let _ = KEY_CACHE.get_from_param(params.with_non_deterministic_execution());
generate_pbs_keys(&PBS_PARAMS);
let stop = start.elapsed().as_secs();
const MULTI_BIT_PARAMS: [MultiBitPBSParameters; 2] = [
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
];
println!("Generation took {stop} seconds");
generate_pbs_multi_bit_keys(&MULTI_BIT_PARAMS);
// Clear keys as we go to avoid filling the RAM
KEY_CACHE.clear_in_memory_cache()
}
const KSK_PARAMS: [(
ClassicPBSParameters,
ClassicPBSParameters,
ShortintKeySwitchingParameters,
); 2] = [
(
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
),
(
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
PARAM_MESSAGE_3_CARRY_3_KS_PBS,
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
),
];
generate_ksk_keys(&KSK_PARAMS);
const WOPBS_PARAMS: [(ClassicPBSParameters, WopbsParameters); 1] = [(
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
WOPBS_PARAM_MESSAGE_2_CARRY_2_KS_PBS,
)];
generate_wopbs_keys(&WOPBS_PARAMS);
const BOOLEAN_PARAMS: [BooleanParameters; 2] =
[DEFAULT_PARAMETERS, DEFAULT_PARAMETERS_KS_PBS];
generate_boolean_keys(&BOOLEAN_PARAMS);
} else {
println!("Generating shortint (ClientKey, ServerKey)");
for (i, params) in ALL_PARAMETER_VEC.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}",
i + 1,
ALL_PARAMETER_VEC.len(),
params.name()
);
let start = std::time::Instant::now();
let _ = KEY_CACHE.get_from_param(params);
let stop = start.elapsed().as_secs();
println!("Generation took {stop} seconds");
// Clear keys as we go to avoid filling the RAM
KEY_CACHE.clear_in_memory_cache()
}
generate_pbs_keys(&ALL_PARAMETER_VEC);
const WOPBS_PARAMS: [(ClassicPBSParameters, WopbsParameters); 4] = [
(
@@ -87,27 +118,136 @@ fn client_server_keys() {
),
];
println!("Generating woPBS keys");
for (i, (params_shortint, params_wopbs)) in WOPBS_PARAMS.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}, {}",
i + 1,
WOPBS_PARAMS.len(),
params_shortint.name(),
params_wopbs.name(),
);
generate_wopbs_keys(&WOPBS_PARAMS);
}
}
let start = std::time::Instant::now();
fn generate_pbs_keys(params: &[ClassicPBSParameters]) {
println!("Generating shortint (ClientKey, ServerKey)");
let _ = KEY_CACHE_WOPBS.get_from_param((params_shortint, params_wopbs));
for (i, param) in params.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}",
i + 1,
params.len(),
param.name()
);
let stop = start.elapsed().as_secs();
let start = std::time::Instant::now();
println!("Generation took {stop} seconds");
let _ = KEY_CACHE.get_from_param(param);
// Clear keys as we go to avoid filling the RAM
KEY_CACHE_WOPBS.clear_in_memory_cache()
}
let stop = start.elapsed().as_secs();
println!("Generation took {stop} seconds");
// Clear keys as we go to avoid filling the RAM
KEY_CACHE.clear_in_memory_cache()
}
}
fn generate_pbs_multi_bit_keys(params: &[MultiBitPBSParameters]) {
println!("Generating shortint multibit (ClientKey, ServerKey)");
for (i, param) in params.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}",
i + 1,
params.len(),
param.name()
);
let start = std::time::Instant::now();
let _ = KEY_CACHE.get_from_param(param.with_non_deterministic_execution());
let stop = start.elapsed().as_secs();
println!("Generation took {stop} seconds");
// Clear keys as we go to avoid filling the RAM
KEY_CACHE.clear_in_memory_cache()
}
}
fn generate_ksk_keys(
params: &[(
ClassicPBSParameters,
ClassicPBSParameters,
ShortintKeySwitchingParameters,
)],
) {
println!("Generating shortint Key Switching keys (ClientKey, ServerKey)");
for (i, (param_1, param_2, param_ksk)) in params.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}__{}__{}",
i + 1,
params.len(),
param_1.name(),
param_2.name(),
param_ksk.name()
);
let start = std::time::Instant::now();
let _ = KEY_CACHE_KSK.get_from_param((param_1, param_2, param_ksk));
let stop = start.elapsed().as_secs();
println!("Generation took {stop} seconds");
// Clear keys as we go to avoid filling the RAM
KEY_CACHE_KSK.clear_in_memory_cache()
}
}
fn generate_wopbs_keys(params: &[(ClassicPBSParameters, WopbsParameters)]) {
println!("Generating woPBS keys");
for (i, (params_shortint, params_wopbs)) in params.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}, {}",
i + 1,
params.len(),
params_shortint.name(),
params_wopbs.name(),
);
let start = std::time::Instant::now();
let _ = KEY_CACHE_WOPBS.get_from_param((params_shortint, params_wopbs));
let stop = start.elapsed().as_secs();
println!("Generation took {stop} seconds");
// Clear keys as we go to avoid filling the RAM
KEY_CACHE_WOPBS.clear_in_memory_cache()
}
}
fn generate_boolean_keys(params: &[BooleanParameters]) {
println!("Generating boolean (ClientKey, ServerKey)");
for (i, param) in params.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}",
i + 1,
params.len(),
param.name()
);
let start = std::time::Instant::now();
let _ = boolean::keycache::KEY_CACHE.get_from_param(param);
let stop = start.elapsed().as_secs();
println!("Generation took {stop} seconds");
// Clear keys as we go to avoid filling the RAM
boolean::keycache::KEY_CACHE.clear_in_memory_cache()
}
}

View File

@@ -6,6 +6,7 @@ use tfhe::core_crypto::commons::dispersion::StandardDev;
use tfhe::core_crypto::commons::parameters::{GlweDimension, LweDimension, PolynomialSize};
use tfhe::keycache::NamedParam;
use tfhe::shortint::parameters::multi_bit::ALL_MULTI_BIT_PARAMETER_VEC;
use tfhe::shortint::parameters::parameters_compact_pk::ALL_PARAMETER_VEC_COMPACT_PK;
use tfhe::shortint::parameters::{
ShortintParameterSet, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1_PBS_KS,
PARAM_MESSAGE_2_CARRY_2_PBS_KS, PARAM_MESSAGE_3_CARRY_3_PBS_KS, PARAM_MESSAGE_4_CARRY_4_PBS_KS,
@@ -153,7 +154,12 @@ fn main() {
PARAM_MESSAGE_3_CARRY_3_PBS_KS,
PARAM_MESSAGE_4_CARRY_4_PBS_KS,
];
let all_classic_pbs = [ALL_PARAMETER_VEC.to_vec(), classic_params_small].concat();
let all_classic_pbs = [
ALL_PARAMETER_VEC.to_vec(),
ALL_PARAMETER_VEC_COMPACT_PK.to_vec(),
classic_params_small,
]
.concat();
let classic_pbs = all_classic_pbs
.iter()
.map(|p| ShortintParameterSet::from(*p))

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