Compare commits

...

176 Commits
0.2.5 ... 0.1.7

Author SHA1 Message Date
Arthur Meyre
f7900a2425 chore(shortint): update CI test cases 2023-01-30 18:29:22 +01:00
J-B Orfila
4faa0641ec fix(shortint): add degree management in KS-PBS 2023-01-30 18:29:22 +01:00
Arthur Meyre
2bacf4918c chore(doc): fix docstring add some links to methods in lwe_wopbs 2023-01-30 18:29:22 +01:00
Arthur Meyre
0c3e105cf3 docs(core): add blind_rotate_assign doctest 2023-01-30 18:29:22 +01:00
Arthur Meyre
bdd8fd81ce feat(core): add add_external_product_assign 2023-01-30 18:29:22 +01:00
Arthur Meyre
edb028b3b7 feat(core): expose the cmux operation 2023-01-30 18:29:22 +01:00
Arthur Meyre
b8e14f456b feat(core): add conversion functions for GgswCiphertext 2023-01-30 18:29:22 +01:00
Arthur Meyre
f5ab5ade27 chore(core): fix an import in lwe_bootstrap_key_conversion 2023-01-30 18:29:22 +01:00
Arthur Meyre
acd2f04628 refactor(fft): rename new and add an Owned alias for fourier GGSW 2023-01-30 18:29:22 +01:00
J-B Orfila
0b8aab4deb feat(core_crypto): lwe_sub 2023-01-30 18:29:22 +01:00
Arthur Meyre
a4e812475a chore(ci): update AMI 2023-01-30 18:29:22 +01:00
Arthur Meyre
841f3427c4 chore(tfhe): bump version to 0.1.7 2023-01-30 18:29:22 +01:00
J-B Orfila
c58497484b fix(shortint): fix smart_mul_lsb conditions 2023-01-30 18:29:22 +01:00
Arthur Meyre
b7f0a6610e chore(tfhe): harden github actions versions, enable dependabot for GHA 2023-01-30 18:29:22 +01:00
aquint-zama
859e5647ad chore(doc): update README twitter badge
twitter API closed to 3rd party
see https://github.com/badges/shields/issues/8837
2023-01-25 10:32:01 +01:00
aquint-zama
a2ad1bb06d chore(doc): update cover image 2023-01-13 14:43:44 +01:00
Arthur Meyre
f72fcb0490 chore(tfhe): update README 2023-01-12 14:38:24 +01:00
Arthur Meyre
941fa09ce6 feat(tfhe): add WASM and C API bindings and tests 2023-01-12 14:38:08 +01:00
Arthur Meyre
2c55284760 feat(boolean): add CompressedCiphertext 2023-01-12 14:38:08 +01:00
Arthur Meyre
9bffa7a797 feat(shortint): add CompressedCiphertext 2023-01-12 14:38:08 +01:00
Arthur Meyre
9230e5a427 feat(tfhe): add SeededLweCiphertext in core_crypto 2023-01-12 14:38:08 +01:00
J-B Orfila
eef4105d36 doc(core_crypto): gitbook 2023-01-12 14:26:19 +01:00
Arthur Meyre
76ec9adeb7 chore(tfhe): bump version to 0.1.6 2023-01-12 12:56:33 +01:00
Arthur Meyre
5694f6b8bb docs(tfhe): add user docs for JS on WASM API and limitations in a tutorial 2023-01-12 12:56:33 +01:00
Arthur Meyre
284136d91b doc(tfhe): update PBS docstring to demnonstrate seeded bsk decompression 2023-01-12 12:56:33 +01:00
Arthur Meyre
57f2cc8391 refactor(tfhe): update wopbs primitive docstring and arg order 2023-01-12 12:56:33 +01:00
Arthur Meyre
4c04cebf91 refactor(tfhe): move SeededLwePublicKey generation
- match the organization of other seeded/generation modules
- update module docstring to include Seeded entities where relevant
2023-01-12 12:56:33 +01:00
Arthur Meyre
f886a29c26 docs(core): update docstrings, add missing doctests for lwe_linear_algebra 2023-01-12 12:56:33 +01:00
Arthur Meyre
f30b23cf0e docs(tfhe): updated user documentation and API documentation 2023-01-12 12:56:33 +01:00
Arthur Meyre
d4503172b3 feat(tfhe): add CompressedServerKey to Boolean +C API +WASM API
- rename wasm functions to remove redundant boolean and shortint naming
- update C API tests for Boolean to include CompressedServerKey generation
and serde
2023-01-12 12:56:33 +01:00
Arthur Meyre
24f383e79c feat(shortint): add CompressedServerKey to shortint +C API +WASM API 2023-01-12 12:56:33 +01:00
Arthur Meyre
b23d7112ac refactor(tfhe): change new method naming for secret keys
- new -> new_empty_key so that it's obvious the key will be empty
- add static methods on secret keys to easily generate them
2023-01-12 12:56:33 +01:00
Arthur Meyre
c0b5a973db refactor(tfhe): make the seeders module more ergonomic to use 2023-01-12 12:56:33 +01:00
Arthur Meyre
f6c1188831 chore(tfhe): correct docstrings 2023-01-12 12:56:33 +01:00
Arthur Meyre
ac45312386 chore(ci): rustdoc warnings as error 2023-01-12 12:56:33 +01:00
Arthur Meyre
740a2179cb chore(ci): sync tags from public to internal repo 2023-01-12 12:56:33 +01:00
Arthur Meyre
c1e4ba39ac test(c_api): add public key serde in shortint test 2023-01-12 12:56:33 +01:00
Arthur Meyre
d5c06775fe refactor(core_crypto): add several useful structs to the prelude
- add main high level random generators as well as the underlying activated
byte random generator
- add SignedDecomposer which helps with rounding
2023-01-12 12:56:33 +01:00
Arthur Meyre
767cc122fa chore(tfhe): add doc test for new_seeder 2023-01-12 12:56:33 +01:00
Arthur Meyre
6301ae6d80 refactor(tfhe): move seeders module to core_crypto and add to prelude 2023-01-12 12:56:33 +01:00
Arthur Meyre
8e2d5e5906 chore(tfhe): rename scratch -> requirement
- renamed wopbs primitives which did not follow the naming convention
2023-01-12 12:56:33 +01:00
Arthur Meyre
bf28228d51 chore(tfhe): rename some primitives whose functionality changed 2023-01-12 12:56:33 +01:00
Arthur Meyre
beef311549 chore(tools): add .editorconfig 2023-01-12 12:56:33 +01:00
David Testé
b08c2ea77a chore(ci): measure and report key sizes used in benchmarks
Size of boostrapping and key switching keys used in benchmarks are
measured and then sent to Slab to be stored into our benchmark
database.
2023-01-12 12:56:33 +01:00
Arthur Meyre
4488d10185 chore(tfhe): fix thfe typo 2023-01-12 12:56:33 +01:00
Arthur Meyre
2904d5f9a8 feat(tfhe): add SeededLweKeyswitchKey
- add generation equivalence test
2023-01-12 12:56:33 +01:00
Arthur Meyre
f39939cc7b chore(tfhe): update check toolchain 2023-01-12 12:56:33 +01:00
Arthur Meyre
f7cba820fd feat(tfhe): add missing encryption functions for CompressedPublicKey 2023-01-12 12:56:33 +01:00
Arthur Meyre
7eed913a6a feat(tfhe): add SeededGgswCiphertextList, SeededLweBootstrapKey 2023-01-12 12:56:33 +01:00
J-B Orfila
bfd279e9c6 docs(crypto_api): add lwe_bootstrap_key gen doctest 2023-01-12 12:56:33 +01:00
Arthur Meyre
ebcfd9f9ef feat(tfhe): add SeededGgswCiphertext 2023-01-12 12:56:33 +01:00
Arthur Meyre
e1e21083e3 feat(tfhe): add SeededGlweCiphertextList 2023-01-12 12:56:33 +01:00
Arthur Meyre
d83cf42079 feat(tfhe): add SeededGlweCiphertext 2023-01-12 12:56:33 +01:00
Arthur Meyre
6c9ceb66ed feat(tfhe): add CompressedPublicKey for Shortint 2023-01-12 12:56:33 +01:00
Arthur Meyre
0674c419da feat(tfhe): js tests, remove server key requirement for shortint PK 2023-01-12 12:56:33 +01:00
Arthur Meyre
35eb927416 feat(core): add SeededLwePublicKey 2023-01-12 12:56:33 +01:00
Arthur Meyre
2c2355d7f4 feat(core): add SeededLweCiphertextList 2023-01-12 12:56:33 +01:00
Arthur Meyre
3068e71ba7 chore(docs): fix a clippy lint for docstrings 2023-01-12 12:56:33 +01:00
Arthur Meyre
f133487fc5 chore(tfhe): rename lwe_linear_algebra algorithms 2023-01-12 12:56:33 +01:00
Arthur Meyre
5c250425fa chore(ci): fix shellcheck lints in workflows 2023-01-12 12:56:33 +01:00
Arthur Meyre
9a3cda93e2 chore(ci): update m1 workflow 2023-01-12 12:56:33 +01:00
Arthur Meyre
35c3deb623 chore(ci): target to check all targets (bench, test, etc.) for clippy lints 2023-01-12 12:56:33 +01:00
Arthur Meyre
28286d6056 docs(tfhe): add various docstrings
- add docstring for lwe_keyswitch
- add docstring for lwe_keyswitch_key_generation
- add docstring for lwe_secret_key_generation
2023-01-12 12:56:33 +01:00
Arthur Meyre
cf76a3c09e chore(ci): restore boolean tests on CPU machine
- fix exit code of toolchain installation in case of failure
2023-01-12 12:56:33 +01:00
Arthur Meyre
b587d4c440 docs(tfhe): add docstrings for lwe_encryption 2023-01-12 12:56:33 +01:00
Arthur Meyre
52f328ce9e fix(tfhe): fix various docstring content and LweMask creation bug 2023-01-12 12:56:33 +01:00
Arthur Meyre
612f9917ca chore(tfhe): rename some buffers to avoid confusion about their usage 2023-01-12 12:56:33 +01:00
Arthur Meyre
e9f5248f43 docs(tfhe): add docstring for glwe_sample_extraction 2023-01-12 12:56:33 +01:00
Arthur Meyre
66050a8ce2 docs(tfhe): add PolynomialList docstrings 2023-01-12 12:56:33 +01:00
Arthur Meyre
485c885afe docs(tfhe): add docstring for Polynomial 2023-01-12 12:56:33 +01:00
Arthur Meyre
07a5815bdc fix(tfhe): make seeders module public 2023-01-12 12:56:33 +01:00
Arthur Meyre
e6f338d52a docs(tfhe): add docstring for glwe_secret_key_generation module 2023-01-12 12:56:33 +01:00
Arthur Meyre
2c910f2133 docs(tfhe): add glwe encryption formal definitions and docstrings
- correct some an -> a
2023-01-12 12:56:33 +01:00
Arthur Meyre
316f6e5a41 chore(docs): fix GGSW docstring to have actual GlweSecretKey generation 2023-01-12 12:56:33 +01:00
Arthur Meyre
71f7adb137 docs(tfhe): add link for GGSW encryption algorithm definition
- document helper function for ggsw encryption
2023-01-12 12:56:33 +01:00
Arthur Meyre
ae58ad55ab docs(tfhe): docstring for Plaintext
- add more sensible bounds for Plaintext and add PlaintextRef and
PlaintextRefMut for a more homogeneous and less confusing dev experience
2023-01-12 12:56:33 +01:00
J-B Orfila
a24ca99638 docs(crypto_api): add ggsw encryption doctest 2023-01-12 12:56:33 +01:00
Arthur Meyre
ddbf6c850a docs(tfhe): docstring for LweSecretKey 2023-01-12 12:56:33 +01:00
Arthur Meyre
07110aabf6 docs(tfhe): correct a -> an 2023-01-12 12:56:33 +01:00
Arthur Meyre
0c9d6b287d docs(tfhe): add disclaimer about parameters being toy example parameters 2023-01-12 12:56:33 +01:00
Arthur Meyre
69dbf144be docs(tfhe): add docstrings for LwePublicKey 2023-01-12 12:56:33 +01:00
Arthur Meyre
2a3bfedc59 docs(tfhe): docstring for LwePrivateFunctionalPackingKeyswitchKey 2023-01-12 12:56:33 +01:00
Arthur Meyre
c265a46df6 docs(tfhe): docstring for LwePrivateFunctionalPackingKeyswitchKeyList 2023-01-12 12:56:33 +01:00
Arthur Meyre
4d5b6f1b9c docs(tfhe): add LweKeyswitchKey docstring
- fix method naming
2023-01-12 12:56:33 +01:00
Arthur Meyre
e24d2e5c62 chore(tools): add tasks tools to escape latex equations in docs
- add all checks to pcc and run that in CI
2023-01-12 12:56:33 +01:00
Arthur Meyre
69e6a7f7cf docs(tfhe): add GswCiphertext for formal definitions 2023-01-12 12:56:33 +01:00
Arthur Meyre
a4a2e1bd38 docs(tfhe): add docstrings for LweCiphertext 2023-01-12 12:56:33 +01:00
Arthur Meyre
1fa9a567eb docs(tfhe): add LweCiphertextList docstring 2023-01-12 12:56:33 +01:00
Arthur Meyre
9ec4e3bfe4 docs(tfhe): add LweBootstrapKey docstrings
- update wording for `new` functions, the allocated vector is not empty.
2023-01-12 12:56:33 +01:00
Arthur Meyre
2cbdfffea0 docs(tfhe): add docstring for GlweSecretKey
- update docstring to indicate useful functions to fill structs
- fix GlweMask docstring
2023-01-12 12:56:33 +01:00
Arthur Meyre
32267a4023 chore(tfhe): update wording to use imperative form in docstrings 2023-01-12 12:56:33 +01:00
Arthur Meyre
09b240885b refactor(core): use from_le_bytes for gaussian RNG (see uniform RNG)
- avoids small allocations, uses std::mem::size_of for size
2023-01-12 12:56:33 +01:00
Arthur Meyre
eca1411227 docs(tfhe): add GlweCiphertext documentation 2023-01-12 12:56:33 +01:00
Arthur Meyre
4cdb429c2d chore(tfhe): finish GlweSize/PolynomialSize ordering consistency 2023-01-12 12:56:33 +01:00
Arthur Meyre
db8cee4e14 chore(ci): add test compilation checks 2023-01-12 12:56:33 +01:00
Arthur Meyre
f836b68f67 docs(tfhe): add docstring for GlweCiphertextList
- uniformize orders of GlweSize and PolynomialSize arguments for GLWE-like
entities
2023-01-12 12:56:33 +01:00
Arthur Meyre
a8727458fe chore(tfhe): change update wording for in place random noise addition 2023-01-12 12:56:33 +01:00
Arthur Meyre
e3c690acce chore(tfhe): change "in place" naming for "assign" following rust style 2023-01-12 12:56:33 +01:00
Arthur Meyre
c62fc71e1a docs(tfhe): add docstrings for GgswCiphertext, import formal definition 2023-01-12 12:56:33 +01:00
Arthur Meyre
5b8db787da chore(tfhe): misc fixes 2023-01-12 12:56:33 +01:00
Arthur Meyre
d6be51a8e0 docs(core): bring back some doc strings for random generators 2023-01-12 12:56:33 +01:00
Arthur Meyre
1307b2a1f2 feat(tfhe): add karatsuba multiplication for polynomials 2023-01-12 12:56:33 +01:00
Arthur Meyre
2fa1174af9 docs(tfhe): update polynomial and slice algorithms naming
- update docstrings to be better rendered in html.
2023-01-12 12:56:33 +01:00
Arthur Meyre
21f64c4e88 docs(tfhe): update name in module documentation 2023-01-12 12:56:33 +01:00
Arthur Meyre
6328b8baeb docs(tfhe): update entities documentation 2023-01-12 12:56:33 +01:00
Arthur Meyre
b856b8c442 docs(tfhe): update common traits docs 2023-01-12 12:56:33 +01:00
Arthur Meyre
952ef37405 docs(core): add docstring and tests for GgswCiphertextList 2023-01-12 12:56:33 +01:00
Arthur Meyre
6c3b069750 feat(core): add prelude 2023-01-12 12:56:33 +01:00
Arthur Meyre
aec4b41f5e chore(core): update Plaintext docstring 2023-01-12 12:56:33 +01:00
J-B Orfila
bcddc6b220 docs(crypto): doctests slice algorithms 2023-01-12 12:56:33 +01:00
Arthur Meyre
6e4e47e45b refactor(tfhe): rename polynomial primitives and add docstrings + tests 2023-01-12 12:56:33 +01:00
Arthur Meyre
b293614474 chore(tfhe): derive PartialEq and Eq for all entities by default 2023-01-12 12:56:33 +01:00
Arthur Meyre
c1ffe27a0c chore(tfhe): update rand to avoid deprecation warnings 2023-01-12 12:56:33 +01:00
Arthur Meyre
5e572b7e7f refactor(thfe): remove deprecation on MonomialDegree 2023-01-12 12:56:33 +01:00
Arthur Meyre
dc186bb00d refactor(tfhe): move parameters and dispersion modules 2023-01-12 12:56:33 +01:00
Arthur Meyre
74cf73ef92 refactor(tfhe): only one instance of FftBuffers, use for simple PBS algo 2023-01-12 12:56:33 +01:00
Arthur Meyre
288c8ec534 chore(doc): deny doc broken links crate-wide 2023-01-12 12:56:33 +01:00
Arthur Meyre
aa7afed90b chore(tfhe): add convenience traits to commons::traits for glob import 2023-01-12 12:56:33 +01:00
Arthur Meyre
90c6d9df30 chore(tools): add convenience pcc and conformance targets 2023-01-12 12:56:33 +01:00
Arthur Meyre
12f83a1ff2 chore(tfhe): fix refactor TODOs 2023-01-12 12:56:33 +01:00
Arthur Meyre
59aab5ca3f refactor(tfhe): unplug core and remove unused parts 2023-01-12 12:56:33 +01:00
Arthur Meyre
0f59ef9e9a refactor(boolean): unplug core engines 2023-01-12 12:56:33 +01:00
Arthur Meyre
90ad4f1c23 refactor(tfhe): unplug CUDA from boolean and remove the CUDA backend 2023-01-12 12:56:33 +01:00
Arthur Meyre
f0fde45e4b refactor(tfhe): refactor serizalization, unplug core_crypto::prelude 2023-01-12 12:56:33 +01:00
Arthur Meyre
058cf2dacd refactor(tfhe): entities Clone + Debug and default parallel + serialization 2023-01-12 12:56:33 +01:00
J-B Orfila
5cbcd48c46 feat(core): blind rotate binding 2023-01-12 12:56:33 +01:00
Arthur Meyre
1dc6bf9c88 refactor(tfhe): Change Base naming scheme 2023-01-12 12:56:33 +01:00
Arthur Meyre
4212aab403 refactor(tfhe): remove core engines from ShortintEngine 2023-01-12 12:56:33 +01:00
Arthur Meyre
ed1bb24aac refactor(tfhe): migrate PFPKSK 2023-01-12 12:56:33 +01:00
Arthur Meyre
142d9be6ac refactor(tfhe): plug woPBS primitives 2023-01-12 12:56:33 +01:00
Arthur Meyre
b633f8cdcb refactor(tfhe): plug fft backend with new primitives
- uniformize fft caches to avoid serialization problems
2023-01-12 12:56:33 +01:00
Arthur Meyre
ef8ac8679e chore(tfhe): remove binary naming 2023-01-12 12:56:33 +01:00
Arthur Meyre
f1a7af3f8e refactor(tfhe): add allocate and encrypt for BSK
- use new generation when creating ServerKey in shortint
- next step requires taking parts of the FFT backend for the refactor
2023-01-12 12:56:33 +01:00
Arthur Meyre
1f3e921d95 refactor(tfhe): add parallel bootstrap key generation
- add equivalence test between refactored sequential and parallel BSK
generation
2023-01-12 12:56:33 +01:00
Arthur Meyre
1156f0b02b chore(tfhe): update associated types name for contiguous container traits 2023-01-12 12:56:33 +01:00
Arthur Meyre
b38c3563b1 refactor(tfhe): reproduce sequential BSK generation 2023-01-12 12:56:33 +01:00
Arthur Meyre
d9bbe85722 refactor(tfhe): add GGSW encryption with coherency test between old and new 2023-01-12 12:56:33 +01:00
Arthur Meyre
fb4e349afe chore(tfhe): minor fixes 2023-01-12 12:56:33 +01:00
Arthur Meyre
6d947c835d refactor(tfhe): rewrite lwe keyswitch algorithm with new system 2023-01-12 12:56:33 +01:00
Arthur Meyre
a6beed229f chore(tfhe): make imports globs for ease of use 2023-01-12 12:56:33 +01:00
Arthur Meyre
830bac1fc3 chore(ci): fix tooling with minimum version for GATs requirements 2023-01-12 12:56:33 +01:00
Arthur Meyre
8ed019ae91 refactor(tfhe): add refactored LweKeyswitchKey generation algorithm 2023-01-12 12:56:33 +01:00
Arthur Meyre
487e8d825d refactor(tfhe): transition GlweSecretKey
- serialization work still pending
2023-01-12 12:56:33 +01:00
Arthur Meyre
8ffdfbdb47 refactor(shortint): change the LweCiphertext type 2023-01-12 12:56:33 +01:00
Arthur Meyre
2bdbb66e78 refactor(tfhe): first step of progressive refactor
- provide new structs and compatibility layers (as much as possible) to
convert between types as much as possible
- we are missing key view types in public APIs making this a bit tricky in
that particular case
2023-01-12 12:56:33 +01:00
Arthur Meyre
6a93df2b61 refactor(core): introduce new modules for progressive rework
- strategy is to have new entities for which required algorithms will be
implemented re-using existing private implementations
- when algorithms are missing at first conversion functions will be used to
be able to switch back to the old system and use existing primitives
2023-01-12 12:56:33 +01:00
Petar Ivanov
e8c23702d7 fix(tools): fix arch detection script for aarch64
On Linux with Apple M1, the output of `uname -a` is:

```
Linux ... aarch64 aarch64 aarch64 GNU/Linux
```

Therefore, recognize that output as aarch64.
2023-01-12 12:56:33 +01:00
Arthur Meyre
6f1d586edb chore(ci): sync repos on push 2023-01-12 12:56:33 +01:00
David Testé
490691485a chore(ci): do not parse report dir when walking subdirectories 2023-01-12 12:56:33 +01:00
David Testé
3acf7dbebd chore(ci): parse subdirectories for shortint benchmark results 2023-01-12 12:56:33 +01:00
Alexandre Quint
5a6acd828f chore(doc): language edits
GitBook: [#1] TFHE-rs edits - JS
2023-01-12 12:56:33 +01:00
David Testé
6d556a3e67 chore(ci): create benchmark aws profile using ec2 m6i.metal 2023-01-12 12:56:33 +01:00
David Testé
dc38a48e4c chore(ci): change benchmark parser input name
The use of "schema" was incorrect since it's meant to be used as
database name when sending data to Slab.
2023-01-12 12:56:33 +01:00
David Testé
59ecc3d000 chore(ci): fix repositories checkout
There are no submodules in tfhe-rs nor the need to authenticate
to get access to it. The right secret is used to checkout Slab.
2023-01-12 12:56:33 +01:00
David Testé
8a2c7470ca chore(ci): add workflow to trigger all benchmarks automatically 2023-01-12 12:56:33 +01:00
Arthur Meyre
d19676aff2 chore(tfhe): fix README 2023-01-12 12:56:33 +01:00
David Testé
f6ff119781 chore(ci): add benchmark workflow for boolean and shortint
These workflows are meant to be triggered by Slab CI bot server.
2023-01-12 12:56:33 +01:00
Arthur Meyre
d288dae3bd chore(ci): add clippy_all, upgrade slab workflows, change cpu instance 2023-01-12 12:56:33 +01:00
Arthur Meyre
af827456a5 chore(tfhe): fix thfe typo 2023-01-03 17:34:14 +01:00
Jeremy Bradley-Silverio Donato
52581c7df2 chore(tfhe): Update README.md 2022-12-14 17:19:59 +01:00
Arthur Meyre
5b772fc29f chore(tfhe): update version 2022-12-02 15:37:16 +01:00
J-B Orfila
0107efcda4 chore(all): update root licence 2022-12-02 15:34:44 +01:00
J-B Orfila
08981bb716 chore(all): licence updated 2022-11-30 17:59:35 +01:00
J-B Orfila
76c57c8c3c fix(doc): update pk encryption example for shortint 2022-11-30 14:14:42 +01:00
Alexandre Quint
78f94be1a1 chore(doc): language edits
GitBook: [#1] TFHE-rs edits - JS
2022-11-25 18:24:51 +01:00
Arthur Meyre
e2bcad8fe7 chore(tfhe): bump version to 0.1.4 2022-11-16 14:10:52 +01:00
Arthur Meyre
c39d6ca753 feat(wasm): add boolean server key primitives 2022-11-16 11:33:22 +01:00
Arthur Meyre
1c8f3859dd chore(wasm): fix clippy lints 2022-11-16 11:33:22 +01:00
Arthur Meyre
d62bc1cb4c chore(ci): add commit checks for all branches 2022-11-16 11:33:22 +01:00
Arthur Meyre
7a32e54469 chore(tfhe): fix Makefile typo 2022-11-16 11:33:22 +01:00
Arthur Meyre
8ebbac0dae chore(ci): update workflows 2022-11-16 11:33:22 +01:00
Arthur Meyre
45f503ae56 chore(tfhe): bump version to 0.1.3 2022-11-10 20:29:44 +01:00
Arthur Meyre
4b1e648848 chore(doc): fix docs.rs build by adding katex header 2022-11-10 20:28:36 +01:00
Arthur Meyre
94b9f62c17 chore(tfhe): update version to 0.1.2 2022-11-10 20:18:04 +01:00
J-B Orfila
62f8ecc568 fix(thfe): update public key parameters 2022-11-10 20:17:24 +01:00
Arthur Meyre
aa7129baf1 chore(crate): fix description metadata 2022-11-10 19:18:32 +01:00
455 changed files with 25005 additions and 43653 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[alias]
xtask = "run --manifest-path ./tasks/Cargo.toml --"

15
.editorconfig Normal file
View File

@@ -0,0 +1,15 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# 4 space indentation
[*.rs]
charset = utf-8
indent_style = space
indent_size = 4

9
.github/dependabot.yaml vendored Normal file
View File

@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every sunday
interval: "weekly"
day: "sunday"

View File

@@ -22,6 +22,12 @@ on:
runner_name:
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
type: string
matrix_item:
description: 'Build matrix item'
type: string
jobs:
shortint-tests:
@@ -36,15 +42,16 @@ jobs:
echo "ID: ${{ github.event.inputs.instance_id }}"
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
echo "Type: ${{ github.event.inputs.instance_type }}"
echo "Request ID: ${{ github.event.inputs.request_id }}"
- uses: actions/checkout@v2
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: actions-rs/toolchain@v1
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: stable
default: true
@@ -53,6 +60,10 @@ jobs:
run: |
make test_core_crypto
- name: Run boolean tests
run: |
make test_boolean
- name: Run C API tests
run: |
make test_c_api
@@ -61,13 +72,17 @@ jobs:
run: |
make test_user_doc
- name: Run js on wasm API tests
run: |
make test_nodejs_wasm_api_in_docker
- name: Install AWS CLI
run: |
apt update
apt install -y awscli
- name: Configure AWS credentials from Test account
uses: aws-actions/configure-aws-credentials@v1
uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838
with:
aws-access-key-id: ${{ secrets.AWS_IAM_ID }}
aws-secret-access-key: ${{ secrets.AWS_IAM_KEY }}

View File

@@ -1,113 +0,0 @@
# Compile and test project on an AWS instance
name: AWS tests on GPU
# This workflow is meant to be run via Zama CI bot Slab.
on:
workflow_dispatch:
inputs:
instance_id:
description: "AWS instance ID"
type: string
instance_image_id:
description: "AWS instance AMI ID"
type: string
instance_type:
description: "AWS EC2 instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-C target-cpu=native"
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
run-tests-linux:
concurrency:
group: ${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
cancel-in-progress: true
name: Test code in EC2
runs-on: ${{ github.event.inputs.runner_name }}
strategy:
fail-fast: false
# explicit include-based build matrix, of known valid options
matrix:
include:
- os: ubuntu-20.04
cuda: "11.8"
old_cuda: "11.1"
cuda_arch: "70"
gcc: 8
env:
CUDA_PATH: /usr/local/cuda-${{ matrix.cuda }}
OLD_CUDA_PATH: /usr/local/cuda-${{ matrix.old_cuda }}
steps:
- name: EC2 instance configuration used
run: |
echo "IDs: ${{ github.event.inputs.instance_id }}"
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
echo "Type: ${{ github.event.inputs.instance_type }}"
- uses: actions/checkout@v2
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Export CUDA variables
run: |
echo "CUDA_PATH=$CUDA_PATH" >> "${GITHUB_ENV}"
echo "$CUDA_PATH/bin" >> "${GITHUB_PATH}"
echo "LD_LIBRARY_PATH=$CUDA_PATH/lib:$LD_LIBRARY_PATH" >> "${GITHUB_ENV}"
# Specify the correct host compilers
- name: Export gcc and g++ variables
run: |
echo "CC=/usr/bin/gcc-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
echo "CXX=/usr/bin/g++-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
echo "CUDAHOSTCXX=/usr/bin/g++-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
echo "CUDACXX=$CUDA_PATH/bin/nvcc" >> "${GITHUB_ENV}"
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
- name: Cuda clippy
run: |
make clippy_cuda
- name: Run core cuda tests
run: |
make test_core_crypto_cuda
- name: Test tfhe-rs/boolean with cpu
run: |
make test_boolean
- name: Test tfhe-rs/boolean with cuda backend with CUDA 11.8
run: |
make test_boolean_cuda
- name: Export variables for CUDA 11.1
run: |
echo "CUDA_PATH=$OLD_CUDA_PATH" >> "${GITHUB_ENV}"
echo "LD_LIBRARY_PATH=$OLD_CUDA_PATH/lib:$LD_LIBRARY_PATH" >> "${GITHUB_ENV}"
echo "CUDACXX=$OLD_CUDA_PATH/bin/nvcc" >> "${GITHUB_ENV}"
- name: Test tfhe-rs/boolean with cuda backend with CUDA 11.1
run: |
cargo clean
make test_boolean_cuda
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "(Slab ci-bot beta) AWS tests GPU finished with status ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

130
.github/workflows/boolean_benchmark.yml vendored Normal file
View File

@@ -0,0 +1,130 @@
# Run boolean benchmarks on an AWS instance and return parsed results to Slab CI bot.
name: Boolean benchmarks
on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
type: string
instance_image_id:
description: 'Instance AMI ID'
type: string
instance_type:
description: 'Instance product type'
type: string
runner_name:
description: 'Action runner name'
type: string
request_id:
description: 'Slab request ID'
type: string
matrix_item:
description: 'Build matrix item'
type: string
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
jobs:
run-boolean-benchmarks:
name: Execute boolean benchmarks in EC2
runs-on: ${{ github.event.inputs.runner_name }}
if: ${{ !cancelled() }}
steps:
- name: Instance configuration used
run: |
echo "IDs: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Matrix item: ${{ inputs.matrix_item }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
with:
fetch-depth: 0
- name: Set up home
# "Install rust" step require root user to have a HOME directory which is not set.
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install rust
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: nightly
override: true
- name: Run benchmarks
run: |
make bench_boolean
- name: Parse results
run: |
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs_benchmarks \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}"
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_boolean
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--name-suffix avx512 \
--append-results
- name: Measure key sizes
run: |
make measure_boolean_key_sizes
- name: Parse key sizes results
run: |
python3 ./ci/benchmark_parser.py tfhe/boolean_key_sizes.csv ${{ env.RESULTS_FILENAME }} \
--key-sizes \
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_boolean
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on downloaded artifact"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
echo "Sending results to Slab..."
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}

View File

@@ -21,49 +21,32 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- name: Get rust toolchain to use for checks and lints
id: toolchain
run: |
echo "rs-toolchain=$(make rs_toolchain)" >> "${GITHUB_OUTPUT}"
- name: Check format
- name: Run pcc checks
run: |
make check_fmt
- name: Build doc
run: |
make doc
- name: Clippy boolean
run: |
make clippy_boolean
make pcc
- name: Build Release boolean
run: |
make build_boolean
- name: Clippy shortint
run: |
make clippy_shortint
- name: Build Release shortint
run: |
make build_shortint
- name: Clippy shortint and boolean
run: |
make clippy
- name: Build Release shortint and boolean
run: |
make build_boolean_and_shortint
- name: C API Clippy
run: |
make clippy_c_api
- name: Build Release c_api
run: |
make build_c_api
# The wasm build check is a bit annoying to set-up here and is done during the tests in
# aws_tfhe_tests.yml

View File

@@ -2,16 +2,13 @@
name: Check commit and PR compliance
on:
pull_request:
branches:
- main
- dev
jobs:
check-commit-pr:
name: Check commit and PR
runs-on: ubuntu-latest
steps:
- name: Check first line
uses: gsactions/commit-message-checker@v1
uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee
with:
pattern: '^((feat|fix|chore|refactor|style|test|docs|doc)\(\w+\)\:) .+$'
flags: "gs"
@@ -22,7 +19,7 @@ jobs:
accessToken: ${{ secrets.GITHUB_TOKEN }} # github access token is only required if checkAllCommitMessages is true
- name: Check line length
uses: gsactions/commit-message-checker@v1
uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee
with:
pattern: '(^.{0,74}$\r?\n?){0,20}'
flags: "gm"

View File

@@ -20,58 +20,42 @@ jobs:
runs-on: ["self-hosted", "m1mac"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- name: Install latest stable
uses: actions-rs/toolchain@v1
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: stable
default: true
- name: Build doc
- name: Run pcc checks
run: |
make doc
- name: Clippy boolean
run: |
make clippy_boolean
make pcc
- name: Build Release boolean
run: |
make build_boolean
- name: Clippy shortint
run: |
make clippy_shortint
- name: Build Release shortint
run: |
make build_shortint
- name: Clippy shortint and boolean
run: |
make clippy
- name: Build Release shortint and boolean
run: |
make build_boolean_and_shortint
- name: C API Clippy
run: |
make clippy_c_api
- name: Build Release c_api
run: |
make build_c_api
- name: Test tfhe-rs/boolean with cpu
run: |
make test_boolean
- name: Run core tests
run: |
make test_core_crypto
- name: Run boolean tests
run: |
make test_boolean
- name: Run C API tests
run: |
make test_c_api
@@ -80,8 +64,11 @@ jobs:
run: |
make test_user_doc
# JS tests are more easily launched in docker, we won't test that on M1 as docker is pretty
# slow on Apple machines due to the virtualization layer.
- name: Configure AWS credentials from Test account
uses: aws-actions/configure-aws-credentials@v1
uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838
with:
aws-access-key-id: ${{ secrets.AWS_IAM_ID }}
aws-secret-access-key: ${{ secrets.AWS_IAM_KEY }}
@@ -110,7 +97,7 @@ jobs:
- cargo-builds
if: ${{ always() }}
steps:
- uses: actions-ecosystem/action-remove-labels@v1
- uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
with:
labels: m1_test
github_token: ${{ secrets.GITHUB_TOKEN }}

132
.github/workflows/shortint_benchmark.yml vendored Normal file
View File

@@ -0,0 +1,132 @@
# Run shortint benchmarks on an AWS instance and return parsed results to Slab CI bot.
name: Shortint benchmarks
on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
type: string
instance_image_id:
description: 'Instance AMI ID'
type: string
instance_type:
description: 'Instance product type'
type: string
runner_name:
description: 'Action runner name'
type: string
request_id:
description: 'Slab request ID'
type: string
matrix_item:
description: 'Build matrix item'
type: string
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
jobs:
run-shortint-benchmarks:
name: Execute shortint benchmarks in EC2
runs-on: ${{ github.event.inputs.runner_name }}
if: ${{ !cancelled() }}
steps:
- name: Instance configuration used
run: |
echo "IDs: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Matrix item: ${{ inputs.matrix_item }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
with:
fetch-depth: 0
- name: Set up home
# "Install rust" step require root user to have a HOME directory which is not set.
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install rust
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: nightly
override: true
- name: Run benchmarks
run: |
make bench_shortint
- name: Parse results
run: |
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs_benchmarks \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_shortint
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--walk-subdirs \
--name-suffix avx512 \
--append-results
- name: Measure key sizes
run: |
make measure_shortint_key_sizes
- name: Parse key sizes results
run: |
python3 ./ci/benchmark_parser.py tfhe/shortint_key_sizes.csv ${{ env.RESULTS_FILENAME }} \
--key-sizes \
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_shortint
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on downloaded artifact"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
echo "Sending results to Slab..."
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}

36
.github/workflows/start_benchmarks.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
# Start all benchmark jobs on Slab CI bot.
name: Start all benchmarks
on:
push:
branches:
- 'main'
workflow_dispatch:
jobs:
start-benchmarks:
strategy:
matrix:
command: [boolean_bench, shortint_bench]
runs-on: ubuntu-latest
steps:
- name: Checkout Slab repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Start AWS job in Slab
shell: bash
# TODO: step result must be correlated to HTTP return code.
run: |
echo -n '{"command": "${{ matrix.command }}", "git_ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' > command.json
SIGNATURE="$(slab/scripts/hmac_calculator.sh command.json '${{ secrets.JOB_SECRET }}')"
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: start_aws" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @command.json \
${{ secrets.SLAB_URL }}

37
.github/workflows/sync_on_push.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# Sync repos
name: Sync repos
on:
push:
branches:
- 'main'
workflow_dispatch:
jobs:
sync-repo:
if: ${{ github.repository == 'zama-ai/tfhe-rs' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
with:
fetch-depth: 0
- name: Save repo
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: repo-archive
path: '.'
- name: git-sync
uses: wei/git-sync@55c6b63b4f21607da0e9877ca9b4d11a29fc6d83
with:
source_repo: "zama-ai/tfhe-rs"
source_branch: "main"
destination_repo: ${{ secrets.SYNC_DEST_REPO }}
destination_branch: "main"
- name: git-sync tags
uses: wei/git-sync@55c6b63b4f21607da0e9877ca9b4d11a29fc6d83
with:
source_repo: "zama-ai/tfhe-rs"
source_branch: "refs/tags/*"
destination_repo: ${{ secrets.SYNC_DEST_REPO }}
destination_branch: "refs/tags/*"

View File

@@ -0,0 +1,17 @@
# Trigger an AWS build each time commits are pushed to a pull request.
name: PR AWS build trigger
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
with:
allow-repeats: true
message: |
@slab-ci cpu_test

View File

@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["tfhe"]
members = ["tfhe", "tasks"]
[profile.bench]
lto = "fat"

61
LICENSE
View File

@@ -1,33 +1,28 @@
BSD 3-Clause Clear License
Copyright © 2022 ZAMA.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
or promote products derived from this software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE*.
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
ZAMA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*In addition to the rights carried by this license, ZAMA grants to the user a non-exclusive,
free and non-commercial license on all patents filed in its name relating to the open-source
code (the "Patents") for the sole purpose of evaluation, development, research, prototyping
and experimentation.
BSD 3-Clause Clear License
Copyright © 2022 ZAMA.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
or promote products derived from this software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
ZAMA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

156
Makefile
View File

@@ -1,14 +1,23 @@
SHELL:=$(shell /usr/bin/env which bash)
RS_CHECK_TOOLCHAIN:=$(shell cat toolchain.txt)
RS_CHECK_TOOLCHAIN:=$(shell cat toolchain.txt | tr -d '\n')
CARGO_RS_CHECK_TOOLCHAIN:=+$(RS_CHECK_TOOLCHAIN)
TARGET_ARCH_FEATURE:=$(shell ./scripts/get_arch_feature.sh)
RS_BUILD_TOOLCHAIN:=$(shell \
( (echo $(TARGET_ARCH_FEATURE) | grep -q x86) && echo stable) || echo $(RS_CHECK_TOOLCHAIN))
CARGO_RS_BUILD_TOOLCHAIN:=+$(RS_BUILD_TOOLCHAIN)
MIN_RUST_VERSION:=1.65
AVX512_SUPPORT?=OFF
WASM_RUSTFLAGS:=
# This is done to avoid forgetting it, we still precise the RUSTFLAGS in the commands to be able to
# copy paste the command in the termianl and change them if required without forgetting the flags
# copy paste the command in the terminal and change them if required without forgetting the flags
export RUSTFLAGS:=-C target-cpu=native
ifeq ($(AVX512_SUPPORT),ON)
AVX512_FEATURE=nightly-avx512
else
AVX512_FEATURE=
endif
.PHONY: rs_check_toolchain # Echo the rust toolchain used for checks
rs_check_toolchain:
@echo $(RS_CHECK_TOOLCHAIN)
@@ -21,21 +30,24 @@ rs_build_toolchain:
install_rs_check_toolchain:
@rustup toolchain list | grep -q "$(RS_CHECK_TOOLCHAIN)" || \
rustup toolchain install --profile default "$(RS_CHECK_TOOLCHAIN)" || \
echo "Unable to install $(RS_CHECK_TOOLCHAIN) toolchain, check your rustup installation. \
Rustup can be downloaded at https://rustup.rs/"
( echo "Unable to install $(RS_CHECK_TOOLCHAIN) toolchain, check your rustup installation. \
Rustup can be downloaded at https://rustup.rs/" && exit 1 )
.PHONY: install_rs_build_toolchain # Install the toolchain used for builds
install_rs_build_toolchain:
@rustup toolchain list | grep -q "$(RS_BUILD_TOOLCHAIN)" || \
@( rustup toolchain list | grep -q "$(RS_BUILD_TOOLCHAIN)" && \
./scripts/check_cargo_min_ver.sh \
--rust-toolchain "$(CARGO_RS_BUILD_TOOLCHAIN)" \
--min-rust-version "$(MIN_RUST_VERSION)" ) || \
rustup toolchain install --profile default "$(RS_BUILD_TOOLCHAIN)" || \
echo "Unable to install $(RS_BUILD_TOOLCHAIN) toolchain, check your rustup installation. \
Rustup can be downloaded at https://rustup.rs/"
( echo "Unable to install $(RS_BUILD_TOOLCHAIN) toolchain, check your rustup installation. \
Rustup can be downloaded at https://rustup.rs/" && exit 1 )
.PHONY: install_cargo_nextest # Install cargo nextest used for shortint tests
install_cargo_nextest: install_rs_build_toolchain
@cargo nextest --version > /dev/null 2>&1 || \
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-nextest --locked || \
echo "Unable to install cargo nextest, unknown error."
( echo "Unable to install cargo nextest, unknown error." && exit 1 )
.PHONY: fmt # Format rust code
fmt: install_rs_check_toolchain
@@ -69,12 +81,27 @@ clippy_c_api: install_rs_check_toolchain
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_cuda # Run clippy lints enabling the boolean, shortint, cuda and c API features
clippy_cuda: install_rs_check_toolchain
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint and the js wasm API
clippy_js_wasm_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=$(TARGET_ARCH_FEATURE),cuda,boolean-c-api,shortint-c-api \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_tasks # Run clippy lints on helper tasks crate.
clippy_tasks:
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
-p tasks -- --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,internal-keycache \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_all # Run all clippy targets
clippy_all: clippy clippy_boolean clippy_shortint clippy_all_targets clippy_c_api \
clippy_js_wasm_api clippy_tasks
.PHONY: gen_key_cache # Run the script to generate keys and cache them for shortint tests
gen_key_cache: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --release \
@@ -98,32 +125,36 @@ build_boolean_and_shortint: install_rs_build_toolchain
.PHONY: build_c_api # Build the C API for boolean and shortint
build_c_api: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api -p tfhe
.PHONY: build_web_js_api # Build the js API targeting the web browser
build_web_js_api: install_rs_build_toolchain
cd tfhe && \
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
wasm-pack build --release --target=web \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
.PHONY: build_node_js_api # Build the js API targeting nodejs
build_node_js_api: install_rs_build_toolchain
cd tfhe && \
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
wasm-pack build --release --target=nodejs \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
.PHONY: test_core_crypto # Run the tests of the core_crypto module
test_core_crypto: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
--features=$(TARGET_ARCH_FEATURE) -p tfhe -- core_crypto::
.PHONY: test_core_crypto_cuda # Run the tests of the core_crypto module with cuda enabled
test_core_crypto_cuda: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
--features=$(TARGET_ARCH_FEATURE),cuda -p tfhe -- core_crypto::backends::cuda::
.PHONY: test_boolean # Run the tests of the boolean module
test_boolean: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
.PHONY: test_boolean_cuda # Run the tests of the boolean module with cuda enabled
test_boolean_cuda: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
--features=$(TARGET_ARCH_FEATURE),boolean,cuda -p tfhe -- boolean::
.PHONY: test_c_api # Run the tests for the C API
test_c_api: install_rs_build_toolchain
./scripts/c_api_tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
./scripts/c_api_tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN)
.PHONY: test_shortint_ci # Run the tests for shortint ci
test_shortint_ci: install_rs_build_toolchain install_cargo_nextest
@@ -142,10 +173,85 @@ test_user_doc: install_rs_build_toolchain
.PHONY: doc # Build rust doc
doc: install_rs_check_toolchain
RUSTDOCFLAGS="--html-in-header katex-header.html" \
RUSTDOCFLAGS="--html-in-header katex-header.html -Dwarnings" \
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" doc \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint --no-deps
.PHONY: format_doc_latex # Format the documentation latex equations to avoid broken rendering.
format_doc_latex:
cargo xtask format_latex_doc
@"$(MAKE)" --no-print-directory fmt
@printf "\n===============================\n\n"
@printf "Please manually inspect changes made by format_latex_doc, rustfmt can break equations \
if the line length is exceeded\n"
@printf "\n===============================\n"
.PHONY: check_compile_tests # Build tests in debug without running them
check_compile_tests:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --no-run \
--features=$(TARGET_ARCH_FEATURE),shortint,boolean,internal-keycache -p tfhe && \
./scripts/c_api_tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) --build-only
.PHONY: build_nodejs_test_docker # Build a docker image with tools to run nodejs tests for wasm API
build_nodejs_test_docker:
DOCKER_BUILDKIT=1 docker build --build-arg RUST_TOOLCHAIN="$(RS_BUILD_TOOLCHAIN)" \
-f docker/Dockerfile.wasm_tests -t tfhe-wasm-tests .
.PHONY: test_nodejs_wasm_api_in_docker # Run tests for the nodejs on wasm API in a docker container
test_nodejs_wasm_api_in_docker: build_nodejs_test_docker
if [[ -t 1 ]]; then RUN_FLAGS="-it"; else RUN_FLAGS="-i"; fi && \
docker run --rm "$${RUN_FLAGS}" \
-v "$$(pwd)":/tfhe-wasm-tests/tfhe-rs \
-v tfhe-rs-root-target-cache:/root/tfhe-rs-target \
-v tfhe-rs-pkg-cache:/tfhe-wasm-tests/tfhe-rs/tfhe/pkg \
-v tfhe-rs-root-cargo-registry-cache:/root/.cargo/registry \
-v tfhe-rs-root-cache:/root/.cache \
tfhe-wasm-tests /bin/bash -i -c 'make test_nodejs_wasm_api'
.PHONY: test_nodejs_wasm_api # Run tests for the nodejs on wasm API
test_nodejs_wasm_api: build_node_js_api
cd tfhe && node --test js_on_wasm_tests
.PHONY: no_tfhe_typo # Check we did not invert the h and f in tfhe
no_tfhe_typo:
@if ! grep --exclude=Makefile --exclude-dir=.git --exclude-from=.gitignore -rniI thfe .; \
then \
exit 0; \
else \
echo "tfhe typo detected, see output log above"; \
exit 1; \
fi
.PHONY: bench_shortint # Run benchmarks for shortint
bench_shortint: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" 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 \
--bench boolean-bench \
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache,$(AVX512_FEATURE) -p tfhe
.PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint
measure_shortint_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
--example shortint_key_sizes \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache
.PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean
measure_boolean_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
--example boolean_key_sizes \
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache
.PHONY: pcc # pcc stands for pre commit checks
pcc: no_tfhe_typo check_fmt doc clippy_all check_compile_tests
.PHONY: conformance # Automatically fix problems that can be fixed
conformance: fmt
.PHONY: help # Generate list of targets with descriptions
help:
@grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1\t\2/' | expand -t30 | sort
@grep '^\.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1\t\2/' | expand -t30 | sort

View File

@@ -21,7 +21,7 @@
</a>
<!-- Follow on twitter badge using shields.io -->
<a href="https://twitter.com/zama_fhe">
<img src="https://img.shields.io/twitter/follow/zama_fhe?color=blue&style=flat-square">
<img src="https://img.shields.io/badge/follow-zama_fhe-blue?logo=twitter&style=flat-square">
</a>
</p>
@@ -33,17 +33,36 @@ arithmetics over encrypted data. It includes:
**TFHE-rs** is meant for developers and researchers who want full control over
what they can do with TFHE, while not having to worry about the low level
implementation. The goal is to have a stable, simple, high-performance and
implementation. The goal is to have a stable, simple, high-performance, and
production-ready library for all the advanced features of TFHE.
## Getting Started
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
To use the latest version of `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
+ For x86_64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.1.0", features = [ "boolean","shortint","x86_64-unix" ] }
tfhe = { version = "*", features = ["boolean", "shortint", "x86_64-unix"] }
```
+ For Apple Silicon or aarch64-based machines running Unix-like OSes:
```toml
tfhe = { version = "*", features = ["boolean", "shortint", "aarch64-unix"] }
```
Note: users with ARM devices must use `TFHE-rs` by compiling using the `nightly` toolchain.
+ For x86_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND)
running Windows:
```toml
tfhe = { version = "*", features = ["boolean", "shortint", "x86_64"] }
```
Note: aarch64-based machines are not yet supported for Windows as it's currently missing an entropy source to be able to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs
Here is a full example evaluating a Boolean circuit:
```rust
@@ -76,24 +95,32 @@ Another example of how the library can be used with shortints:
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
// Generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(Parameters::default());
let msg1 = 1;
let msg2 = 0;
let msg1 = 3;
let msg2 = 2;
let modulus = client_key.parameters.message_modulus.0;
// We use the client key to encrypt two messages:
// Encrypt two messages using the (private) client key:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
// We use the server public key to execute an integer circuit:
let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);
// Homomorphically compute an addition
let ct_add = server_key.unchecked_add(&ct_1, &ct_2);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_3);
assert_eq!(output, (msg1 + msg2) % modulus as u64);
// Define the Hamming weight function
// f: x -> sum of the bits of x
let f = |x:u64| x.count_ones() as u64;
// Generate the accumulator for the function
let acc = server_key.generate_accumulator(f);
// Compute the function over the ciphertext using the PBS
let ct_res = server_key.keyswitch_programmable_bootstrap(&ct_add, &acc);
// Decrypt the ciphertext using the (private) client key
let output = client_key.decrypt(&ct_res);
assert_eq!(output, f(msg1 + msg2));
}
```
@@ -101,7 +128,7 @@ fn main() {
There are two ways to contribute to TFHE-rs:
- you can open issues to report bugs or typos and to suggest new ideas
- you can open issues to report bugs or typos, or to suggest new ideas
- you can ask to become an official contributor by emailing [hello@zama.ai](mailto:hello@zama.ai).
(becoming an approved contributor involves signing our Contributor License Agreement (CLA))

190
ci/benchmark_parser.py Normal file
View File

@@ -0,0 +1,190 @@
"""
benchmark_parser
----------------
Parse criterion benchmark or keys size results.
"""
import argparse
import csv
import pathlib
import json
import sys
parser = argparse.ArgumentParser()
parser.add_argument('results',
help='Location of criterion benchmark results directory.'
'If the --key-size option is used, then the value would have to point to'
'a CSV file.')
parser.add_argument('output_file', help='File storing parsed results')
parser.add_argument('-d', '--database', dest='database',
help='Name of the database used to store results')
parser.add_argument('-w', '--hardware', dest='hardware',
help='Hardware reference used to perform benchmark')
parser.add_argument('-V', '--project-version', dest='project_version',
help='Commit hash reference')
parser.add_argument('-b', '--branch', dest='branch',
help='Git branch name on which benchmark was performed')
parser.add_argument('--commit-date', dest='commit_date',
help='Timestamp of commit hash used in project_version')
parser.add_argument('--bench-date', dest='bench_date',
help='Timestamp when benchmark was run')
parser.add_argument('--name-suffix', dest='name_suffix', default='',
help='Suffix to append to each of the result test names')
parser.add_argument('--append-results', dest='append_results', action='store_true',
help='Append parsed results to an existing file')
parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true',
help='Check for results in subdirectories')
parser.add_argument('--key-sizes', dest='key_sizes', action='store_true',
help='Parse only the results regarding keys size measurments')
def recursive_parse(directory, walk_subdirs=False, name_suffix=""):
"""
Parse all the benchmark results in a directory. It will attempt to parse all the files having a
.json extension at the top-level of this directory.
:param directory: path to directory that contains raw results as :class:`pathlib.Path`
:param walk_subdirs: traverse results subdirectories if parameters changes for benchmark case.
:param name_suffix: a :class:`str` suffix to apply to each test name found
:return: :class:`list` of data points
"""
excluded_directories = ["child_generate", "fork", "parent_generate", "report"]
result_values = list()
for dire in directory.iterdir():
if dire.name in excluded_directories or not dire.is_dir():
continue
for subdir in dire.iterdir():
if walk_subdirs:
subdir = subdir.joinpath("new")
if not subdir.exists():
continue
elif subdir.name != "new":
continue
test_name = parse_benchmark_file(subdir)
for stat_name, value in parse_estimate_file(subdir).items():
test_name_parts = list(filter(None, [test_name, stat_name, name_suffix]))
result_values.append({"value": value, "test": "_".join(test_name_parts)})
return result_values
def parse_benchmark_file(directory):
"""
Parse file containing details of the parameters used for a benchmark.
:param directory: directory where a benchmark case results are located as :class:`pathlib.Path`
:return: name of the test as :class:`str`
"""
raw_res = _parse_file_to_json(directory, "benchmark.json")
return raw_res["full_id"].replace(" ", "_")
def parse_estimate_file(directory):
"""
Parse file containing timing results for a benchmark.
:param directory: directory where a benchmark case results are located as :class:`pathlib.Path`
:return: :class:`dict` of data points
"""
raw_res = _parse_file_to_json(directory, "estimates.json")
return {
stat_name: raw_res[stat_name]["point_estimate"]
for stat_name in ("mean", "std_dev")
}
def parse_key_sizes(result_file):
"""
Parse file containing key sizes results. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
:return: :class:`list` of data points
"""
result_values = list()
with result_file.open() as csv_file:
reader = csv.reader(csv_file)
for (test_name, value) in reader:
result_values.append({"value": int(value), "test": test_name})
return result_values
def _parse_file_to_json(directory, filename):
result_file = directory.joinpath(filename)
return json.loads(result_file.read_text())
def dump_results(parsed_results, filename, input_args):
"""
Dump parsed results formatted as JSON to file.
:param parsed_results: :class:`list` of data points
:param filename: filename for dump file as :class:`pathlib.Path`
:param input_args: CLI input arguments
"""
if input_args.append_results:
parsed_content = json.loads(filename.read_text())
parsed_content["points"].extend(parsed_results)
filename.write_text(json.dumps(parsed_content))
else:
filename.parent.mkdir(parents=True, exist_ok=True)
series = {
"database": input_args.database,
"hardware": input_args.hardware,
"project_version": input_args.project_version,
"branch": input_args.branch,
"insert_date": input_args.bench_date,
"commit_date": input_args.commit_date,
"points": parsed_results,
}
filename.write_text(json.dumps(series))
def check_mandatory_args(input_args):
"""
Check for availability of required input arguments, the program will exit if one of them is
not present. If `append_results` flag is set, all the required arguments will be ignored.
:param input_args: CLI input arguments
"""
if input_args.append_results:
return
missing_args = list()
for arg_name in vars(input_args):
if arg_name in ["results_dir", "output_file", "name_suffix",
"append_results", "walk_subdirs", "key_sizes"]:
continue
if not getattr(input_args, arg_name):
missing_args.append(arg_name)
if missing_args:
for arg_name in missing_args:
print(f"Missing required argument: --{arg_name.replace('_', '-')}")
sys.exit(1)
if __name__ == "__main__":
args = parser.parse_args()
check_mandatory_args(args)
raw_results = pathlib.Path(args.results)
if not args.key_sizes:
print("Parsing benchmark results... ")
results = recursive_parse(raw_results, args.walk_subdirs, args.name_suffix)
else:
print("Parsing key sizes results... ")
results = parse_key_sizes(raw_results)
print("Parsing results done")
output_file = pathlib.Path(args.output_file)
print(f"Dump parsed results into '{output_file.resolve()}' ... ", end="")
dump_results(results, output_file, args)
print("Done")

View File

@@ -1,21 +1,24 @@
[profile.cpu-big]
region = "eu-west-3"
image_id = "ami-04deffe45b5b236fd"
instance_type = "c5a.8xlarge"
image_id = "ami-06f8ee10713a8f809"
instance_type = "c6i.8xlarge"
[profile.gpu]
region = "us-east-1"
image_id = "ami-0ae662beb44082155"
instance_type = "p3.2xlarge"
subnet_id = "subnet-8123c9e7"
security_group = "sg-0466d33ced960ba35"
[profile.bench]
region = "eu-west-3"
image_id = "ami-06f8ee10713a8f809"
instance_type = "m6i.metal"
[command.cpu_test]
workflow = "aws_tfhe_tests.yml"
profile = "cpu-big"
check_run_name = "Shortint CPU AWS Tests"
check_run_name = "CPU AWS Tests"
[command.gpu_test]
workflow = "aws_tfhe_tests_w_gpu.yml"
profile = "gpu"
check_run_name = "AWS tests GPU (Slab)"
[command.shortint_bench]
workflow = "shortint_benchmark.yml"
profile = "bench"
check_run_name = "Shortint CPU AWS Benchmarks"
[command.boolean_bench]
workflow = "boolean_benchmark.yml"
profile = "bench"
check_run_name = "Boolean CPU AWS Benchmarks"

View File

@@ -0,0 +1,33 @@
FROM ubuntu:22.04
ENV TZ=Europe/Paris
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV CARGO_TARGET_DIR=/root/tfhe-rs-target
ARG RUST_TOOLCHAIN="stable"
WORKDIR /tfhe-wasm-tests
RUN apt-get update && \
apt-get install -y \
build-essential \
curl \
git && \
rm -rf /var/lib/apt/lists/*
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > install-rustup.sh && \
chmod +x install-rustup.sh && \
./install-rustup.sh -y --default-toolchain "${RUST_TOOLCHAIN}" \
-c rust-src -t wasm32-unknown-unknown && \
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf > install-wasm-pack.sh && \
chmod +x install-wasm-pack.sh && \
. "$HOME/.cargo/env" && \
./install-wasm-pack.sh -y && \
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh > install-node.sh && \
chmod +x install-node.sh && \
./install-node.sh && \
. "$HOME/.nvm/nvm.sh" && \
bash -i -c 'nvm install node && nvm use node'
WORKDIR /tfhe-wasm-tests/tfhe-rs/

View File

@@ -2,6 +2,42 @@
set -e
function usage() {
echo "$0: build and/or run the C API tests"
echo
echo "--help Print this message"
echo "--rust-toolchain The toolchain to check the version for with leading"
echo "--build-only Pass to only build the tests without running them"
echo
}
BUILD_ONLY=0
while [ -n "$1" ]
do
case "$1" in
"--help" | "-h" )
usage
exit 0
;;
"--rust-toolchain" )
shift
RUST_TOOLCHAIN="$1"
;;
"--build-only" )
BUILD_ONLY=1
;;
*)
echo "Unknown param : $1"
exit 1
;;
esac
shift
done
CURR_DIR="$(dirname "$0")"
ARCH_FEATURE="$("${CURR_DIR}/get_arch_feature.sh")"
REPO_ROOT="${CURR_DIR}/.."
@@ -13,8 +49,13 @@ cd "${TFHE_BUILD_DIR}"
cmake .. -DCMAKE_BUILD_TYPE=RELEASE
RUSTFLAGS="-C target-cpu=native" cargo ${1:+"${1}"} build \
RUSTFLAGS="-C target-cpu=native" cargo ${RUST_TOOLCHAIN:+"${RUST_TOOLCHAIN}"} build \
--release --features="${ARCH_FEATURE}",boolean-c-api,shortint-c-api -p tfhe
make -j
if [[ "${BUILD_ONLY}" == "1" ]]; then
exit 0
fi
make "test"

60
scripts/check_cargo_min_ver.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -e
function usage() {
echo "$0: check minimum cargo version"
echo
echo "--help Print this message"
echo "--rust-toolchain The toolchain to check the version for with leading"
echo "--min-rust-version Check toolchain version is >= to this version, default is 1.65"
echo
}
RUST_TOOLCHAIN=""
# We set the default rust version 1.65 which is the minimum version required for stable GATs
MIN_RUST_VERSION="1.65"
while [ -n "$1" ]
do
case "$1" in
"--help" | "-h" )
usage
exit 0
;;
"--rust-toolchain" )
shift
RUST_TOOLCHAIN="$1"
;;
"--min-rust-version" )
shift
MIN_RUST_VERSION="$1"
;;
*)
echo "Unknown param : $1"
exit 1
;;
esac
shift
done
if [[ "${RUST_TOOLCHAIN::1}" != "+" ]]; then
RUST_TOOLCHAIN="+${RUST_TOOLCHAIN}"
fi
ver_string="$(cargo ${RUST_TOOLCHAIN:+"${RUST_TOOLCHAIN}"} --version | \
cut -d ' ' -f 2 | cut -d '-' -f 1)"
ver_major="$(echo "${ver_string}" | cut -d '.' -f 1)"
ver_minor="$(echo "${ver_string}" | cut -d '.' -f 2)"
min_ver_major="$(echo "${MIN_RUST_VERSION}" | cut -d '.' -f 1)"
min_ver_minor="$(echo "${MIN_RUST_VERSION}" | cut -d '.' -f 2)"
if [[ "${ver_major}" -ge "${min_ver_major}" ]] && [[ "${ver_minor}" -ge "${min_ver_minor}" ]]; then
exit 0
fi
exit 1

View File

@@ -4,7 +4,7 @@ set -e
ARCH_FEATURE=x86_64
IS_AARCH64="$( (uname -a | grep -c arm64) || true)"
IS_AARCH64="$( (uname -a | grep -c "arm64\|aarch64") || true)"
if [[ "${IS_AARCH64}" != "0" ]]; then
ARCH_FEATURE=aarch64

View File

@@ -26,15 +26,19 @@ fi
filter_expression=''\
'('\
' test(/^shortint::server_key::.*_param_message_1_carry_1$/)'\
'or test(/^shortint::server_key::.*_param_message_1_carry_2$/)'\
'or test(/^shortint::server_key::.*_param_message_1_carry_3$/)'\
'or test(/^shortint::server_key::.*_param_message_1_carry_4$/)'\
'or test(/^shortint::server_key::.*_param_message_1_carry_5$/)'\
'or test(/^shortint::server_key::.*_param_message_1_carry_6$/)'\
'or test(/^shortint::server_key::.*_param_message_2_carry_2$/)'\
'or test(/^shortint::server_key::.*_param_message_3_carry_3$/)'\
'or test(/^shortint::server_key::.*_param_message_4_carry_4$/)'\
' test(/^shortint::.*_param_message_1_carry_1$/)'\
'or test(/^shortint::.*_param_message_1_carry_2$/)'\
'or test(/^shortint::.*_param_message_1_carry_3$/)'\
'or test(/^shortint::.*_param_message_1_carry_4$/)'\
'or test(/^shortint::.*_param_message_1_carry_5$/)'\
'or test(/^shortint::.*_param_message_1_carry_6$/)'\
'or test(/^shortint::.*_param_message_2_carry_1$/)'\
'or test(/^shortint::.*_param_message_2_carry_2$/)'\
'or test(/^shortint::.*_param_message_2_carry_3$/)'\
'or test(/^shortint::.*_param_message_3_carry_1$/)'\
'or test(/^shortint::.*_param_message_3_carry_2$/)'\
'or test(/^shortint::.*_param_message_3_carry_3$/)'\
'or test(/^shortint::.*_param_message_4_carry_4$/)'\
')'\
'and not test(~smart_add_and_mul)' # This test is too slow
@@ -50,6 +54,18 @@ cargo ${1:+"${1}"} nextest run \
--test-threads "${n_threads}" \
-E "${filter_expression}"
filter_expression_wopbs='test(/^shortint::wopbs::.*$/)'
# Run tests only no examples or benches for wopbs
cargo ${1:+"${1}"} nextest run \
--tests \
--release \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--test-threads "${n_threads}" \
-E "${filter_expression_wopbs}"
cargo ${1:+"${1}"} test \
--release \
--package tfhe \

12
tasks/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "tasks"
version = "0.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "3.1"
lazy_static = "1.4"
log = "0.4"
simplelog = "0.12"

View File

@@ -0,0 +1,453 @@
use crate::utils::project_root;
use std::io::{Error, ErrorKind};
use std::{fmt, fs};
fn recurse_find_rs_files(
root_dir: std::path::PathBuf,
rs_files: &mut Vec<std::path::PathBuf>,
at_root: bool,
) {
for curr_entry in root_dir.read_dir().unwrap() {
let curr_path = curr_entry.unwrap().path().canonicalize().unwrap();
if curr_path.is_file() {
if let Some(extension) = curr_path.extension() {
if extension == "rs" {
rs_files.push(curr_path);
}
}
} else if curr_path.is_dir() {
if at_root {
// Hardcoded ignores for root .git and target
match curr_path.file_name().unwrap().to_str().unwrap() {
".git" => continue,
"target" => continue,
_ => recurse_find_rs_files(curr_path.to_path_buf(), rs_files, false),
};
} else {
recurse_find_rs_files(curr_path.to_path_buf(), rs_files, false);
}
}
}
}
#[derive(Debug)]
struct LatexEscapeToolError {
details: String,
}
impl LatexEscapeToolError {
fn new(msg: &str) -> LatexEscapeToolError {
LatexEscapeToolError {
details: msg.to_string(),
}
}
}
impl fmt::Display for LatexEscapeToolError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.details)
}
}
impl std::error::Error for LatexEscapeToolError {}
const DOC_TEST_START: &str = "///";
const DOC_COMMENT_START: &str = "//!";
const BACKSLASH_UTF8_LEN: usize = '\\'.len_utf8();
enum LineType {
DocTest { code_block_limit: bool },
DocComment { code_block_limit: bool },
EmptyLine,
Other,
}
fn get_line_type_and_trimmed_line(line: &str) -> (LineType, &str) {
let mut trimmed_line = line.trim_start();
let line_type = if trimmed_line.starts_with(DOC_COMMENT_START) {
trimmed_line = trimmed_line
.strip_prefix(DOC_COMMENT_START)
.unwrap()
.trim_start();
let has_code_block_limit = trimmed_line.starts_with("```");
LineType::DocComment {
code_block_limit: has_code_block_limit,
}
} else if trimmed_line.starts_with(DOC_TEST_START) {
trimmed_line = trimmed_line
.strip_prefix(DOC_TEST_START)
.unwrap()
.trim_start();
let has_code_block_limit = trimmed_line.starts_with("```");
LineType::DocTest {
code_block_limit: has_code_block_limit,
}
} else if trimmed_line.is_empty() {
LineType::EmptyLine
} else {
LineType::Other
};
(line_type, trimmed_line)
}
struct CommentContent<'a> {
is_in_code_block: bool,
line_start: &'a str,
line_content: &'a str,
}
fn find_contiguous_doc_comment<'a>(
lines: &[&'a str],
start_line_idx: usize,
) -> (Vec<CommentContent<'a>>, usize) {
let mut doc_comment_end_line_idx = start_line_idx + 1;
let mut is_in_code_block = false;
let mut contiguous_doc_comment = Vec::<CommentContent>::new();
for (line_idx, line) in lines.iter().enumerate().skip(start_line_idx) {
let (line_type, line_content) = get_line_type_and_trimmed_line(line);
let line_start = &line[..line.len() - line_content.len()];
// If there is an empty line we are still in the DocComment
let line_type = if let LineType::EmptyLine = line_type {
LineType::DocComment {
code_block_limit: false,
}
} else {
line_type
};
match line_type {
LineType::DocComment { code_block_limit } => {
if code_block_limit {
// We have found a code block limit, either starting or ending, toggle the
// flag
is_in_code_block = !is_in_code_block;
};
contiguous_doc_comment.push(CommentContent {
is_in_code_block,
line_start,
line_content,
});
// For now the only thing we know is that the next line is potentially the end of
// the comment block, required if a file is a giant comment block to have the proper
// bound
doc_comment_end_line_idx = line_idx + 1;
}
_ => {
// We are sure that the current line is the end of the comment block
doc_comment_end_line_idx = line_idx;
break;
}
};
}
(contiguous_doc_comment, doc_comment_end_line_idx)
}
fn find_contiguous_doc_test<'a>(
lines: &[&'a str],
start_line_idx: usize,
) -> (Vec<CommentContent<'a>>, usize) {
let mut doc_test_end_line_idx = start_line_idx + 1;
let mut is_in_code_block = false;
let mut contiguous_doc_test = Vec::<CommentContent>::new();
for (line_idx, line) in lines.iter().enumerate().skip(start_line_idx) {
let (line_type, line_content) = get_line_type_and_trimmed_line(line);
let line_start = &line[..line.len() - line_content.len()];
// If there is an empty line we are still in the DocTest
let line_type = if let LineType::EmptyLine = line_type {
LineType::DocTest {
code_block_limit: false,
}
} else {
line_type
};
match line_type {
LineType::DocTest { code_block_limit } => {
if code_block_limit {
// We have found a code block limit, either starting or ending, toggle the
// flag
is_in_code_block = !is_in_code_block;
};
contiguous_doc_test.push(CommentContent {
is_in_code_block,
line_start,
line_content,
});
// For now the only thing we know is that the next line is potentially the end of
// the comment block, required if a file is a giant comment block to have the proper
// bound
doc_test_end_line_idx = line_idx + 1;
}
_ => {
// We are sure that the current line is the end of the comment block
doc_test_end_line_idx = line_idx;
break;
}
};
}
(contiguous_doc_test, doc_test_end_line_idx)
}
fn find_contiguous_part_in_doc_test_or_comment(
part_is_code_block: bool,
full_doc_comment_content: &Vec<CommentContent>,
part_start_idx: usize,
) -> (usize, usize) {
let mut next_line_idx = part_start_idx + 1;
loop {
// We have exhausted the doc comment content, break
if next_line_idx == full_doc_comment_content.len() {
break;
}
let CommentContent {
is_in_code_block: next_line_is_in_code_block,
line_start: _,
line_content: _,
} = full_doc_comment_content[next_line_idx];
// We check if the next line is in a different part, if so we break
if next_line_is_in_code_block != part_is_code_block {
break;
}
next_line_idx += 1;
}
// next_line_idx points to the end of the part and is therefore returned as the part_stop_idx
(part_start_idx, next_line_idx)
}
enum LatexEquationKind {
Inline,
Multiline,
NotAnEquation,
}
fn escape_underscores_rewrite_equations(
comment_to_rewrite: &[CommentContent],
rewritten_content: &mut String,
) -> Result<(), LatexEscapeToolError> {
let mut latex_equation_kind = LatexEquationKind::NotAnEquation;
for CommentContent {
is_in_code_block: _,
line_start,
line_content,
} in comment_to_rewrite.iter()
{
rewritten_content.push_str(line_start);
let mut previous_char = '\0';
let mut chars = line_content.chars().peekable();
while let Some(current_char) = chars.next() {
match (previous_char, current_char) {
('$', '$') => {
match latex_equation_kind {
LatexEquationKind::Inline => {
// Problem we find an opening $$ after an opening $, return an error
return Err(LatexEscapeToolError::new(
"Found an opening '$' without a corresponding closing '$'",
));
}
LatexEquationKind::Multiline => {
// Closing $$, no more in a latex equation
latex_equation_kind = LatexEquationKind::NotAnEquation
}
LatexEquationKind::NotAnEquation => {
// Opening $$, in a multiline latex equation
latex_equation_kind = LatexEquationKind::Multiline
}
};
}
(_, '$') => {
let is_inline_marker = chars.peek() != Some(&'$');
if is_inline_marker {
match latex_equation_kind {
LatexEquationKind::Multiline => {
// Problem we find an opening $ after an opening $$, return an error
return Err(LatexEscapeToolError::new(
"Found an opening '$$' without a corresponding closing '$$'",
));
}
LatexEquationKind::Inline => {
// Closing $, no more in a latex equation
latex_equation_kind = LatexEquationKind::NotAnEquation
}
LatexEquationKind::NotAnEquation => {
// Opening $, in an inline latex equation
latex_equation_kind = LatexEquationKind::Inline
}
};
}
// If the marker is not an inline marker but a multiline marker let the other
// case manage it at the next iteration
}
// If the _ is not escaped and we are in an equation we need to escape it
(prev, '_') if prev != '\\' => match latex_equation_kind {
LatexEquationKind::NotAnEquation => (),
_ => rewritten_content.push('\\'),
},
_ => (),
}
rewritten_content.push(current_char);
previous_char = current_char;
}
}
Ok(())
}
fn process_doc_lines_until_impossible<'a>(
lines: &[&'a str],
rewritten_content: &'a mut String,
comment_search_fn: fn(&[&'a str], usize) -> (Vec<CommentContent<'a>>, usize),
start_line_idx: usize,
) -> Result<usize, LatexEscapeToolError> {
let (full_doc_content, doc_end_line_idx) = comment_search_fn(lines, start_line_idx);
// Now we find code blocks parts OR pure comments parts
let mut current_line_in_doc_idx = 0;
while current_line_in_doc_idx < full_doc_content.len() {
let CommentContent {
is_in_code_block,
line_start: _,
line_content: _,
} = full_doc_content[current_line_in_doc_idx];
let (current_part_start_idx, current_part_stop_idx) =
find_contiguous_part_in_doc_test_or_comment(
is_in_code_block,
&full_doc_content,
current_line_in_doc_idx,
);
let current_part_content = &full_doc_content[current_part_start_idx..current_part_stop_idx];
// The current part is a code block
if is_in_code_block {
for CommentContent {
is_in_code_block: _,
line_start,
line_content,
} in current_part_content.iter()
{
// We can just push the content unmodified
rewritten_content.push_str(line_start);
rewritten_content.push_str(line_content);
}
} else {
// The part is a pure comment, we need to rewrite equations
escape_underscores_rewrite_equations(current_part_content, rewritten_content)?;
}
current_line_in_doc_idx += current_part_content.len();
}
Ok(doc_end_line_idx)
}
fn process_non_doc_lines_until_impossible(
lines: &Vec<&str>,
rewritten_content: &mut String,
mut line_idx: usize,
) -> usize {
while line_idx < lines.len() {
let line = lines[line_idx];
match get_line_type_and_trimmed_line(line) {
(LineType::Other, _) => {
rewritten_content.push_str(line);
line_idx += 1;
}
_ => break,
};
}
line_idx
}
fn escape_underscore_in_latex_doc_in_file(
file_path: &std::path::Path,
) -> Result<(), LatexEscapeToolError> {
let file_name = file_path.to_str().unwrap();
let content = std::fs::read_to_string(file_name).unwrap();
let number_of_underscores = content.matches('_').count();
let potential_additional_capacity_required = number_of_underscores * BACKSLASH_UTF8_LEN;
// Enough for the length of the original string + the length if we had to escape *all* `_`
// which won't happen but avoids reallocations
let mut rewritten_content =
String::with_capacity(content.len() + potential_additional_capacity_required);
let content_by_lines: Vec<&str> = content.split_inclusive('\n').collect();
let mut line_idx = 0_usize;
while line_idx < content_by_lines.len() {
let line = content_by_lines[line_idx];
let (line_type, _) = get_line_type_and_trimmed_line(line);
line_idx = match line_type {
LineType::DocComment {
code_block_limit: _,
} => process_doc_lines_until_impossible(
&content_by_lines,
&mut rewritten_content,
find_contiguous_doc_comment,
line_idx,
)?,
LineType::DocTest {
code_block_limit: _,
} => process_doc_lines_until_impossible(
&content_by_lines,
&mut rewritten_content,
find_contiguous_doc_test,
line_idx,
)?,
LineType::Other => process_non_doc_lines_until_impossible(
&content_by_lines,
&mut rewritten_content,
line_idx,
),
LineType::EmptyLine => {
rewritten_content.push_str(line);
line_idx + 1
}
};
}
fs::write(file_name, rewritten_content).unwrap();
Ok(())
}
pub fn escape_underscore_in_latex_doc() -> Result<(), Error> {
let project_root = project_root();
let mut src_files: Vec<std::path::PathBuf> = Vec::new();
recurse_find_rs_files(project_root, &mut src_files, true);
println!("Found {} files to process.", src_files.len());
let mut files_with_problems: Vec<(std::path::PathBuf, LatexEscapeToolError)> = Vec::new();
println!("Processing...");
for file in src_files.into_iter() {
if let Err(err) = escape_underscore_in_latex_doc_in_file(&file) {
files_with_problems.push((file, err));
}
}
println!("Done!");
if !files_with_problems.is_empty() {
for (file_with_problem, error) in files_with_problems.iter() {
println!(
"File: {}, has error: {}",
file_with_problem.display(),
error
);
}
return Err(Error::new(
ErrorKind::InvalidInput,
"Issues while processing files, check log.",
));
}
Ok(())
}

88
tasks/src/main.rs Normal file
View File

@@ -0,0 +1,88 @@
#[macro_use]
extern crate lazy_static;
use clap::{Arg, Command};
use log::LevelFilter;
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
mod format_latex_doc;
mod utils;
// -------------------------------------------------------------------------------------------------
// CONSTANTS
// -------------------------------------------------------------------------------------------------
lazy_static! {
static ref DRY_RUN: AtomicBool = AtomicBool::new(false);
static ref ROOT_DIR: PathBuf = utils::project_root();
static ref ENV_TARGET_NATIVE: utils::Environment = {
let mut env = HashMap::new();
env.insert("RUSTFLAGS", "-Ctarget-cpu=native");
env
};
}
// -------------------------------------------------------------------------------------------------
// MACROS
// -------------------------------------------------------------------------------------------------
#[macro_export]
macro_rules! cmd {
(<$env: ident> $cmd: expr) => {
$crate::utils::execute($cmd, Some(&*$env), Some(&*$crate::ROOT_DIR))
};
($cmd: expr) => {
$crate::utils::execute($cmd, None, Some(&*$crate::ROOT_DIR))
};
}
// -------------------------------------------------------------------------------------------------
// MAIN
// -------------------------------------------------------------------------------------------------
fn main() -> Result<(), std::io::Error> {
// We parse the input args
let matches = Command::new("tasks")
.about("Rust scripts runner")
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.help("Prints debug messages"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.help("Do not execute the commands"),
)
.subcommand(Command::new("format_latex_doc").about("Escape underscores in latex equations"))
.arg_required_else_help(true)
.get_matches();
// We initialize the logger with proper verbosity
let verb = if matches.contains_id("verbose") {
LevelFilter::Debug
} else {
LevelFilter::Info
};
CombinedLogger::init(vec![TermLogger::new(
verb,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
)])
.unwrap();
// We set the dry-run mode if present
if matches.contains_id("dry-run") {
DRY_RUN.store(true, Relaxed);
}
if matches.subcommand_matches("format_latex_doc").is_some() {
format_latex_doc::escape_underscore_in_latex_doc()?;
}
Ok(())
}

50
tasks/src/utils.rs Normal file
View File

@@ -0,0 +1,50 @@
use log::{debug, info};
use std::collections::HashMap;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::Ordering::Relaxed;
pub type Environment = HashMap<&'static str, &'static str>;
#[allow(dead_code)]
pub fn execute(cmd: &str, env: Option<&Environment>, cwd: Option<&PathBuf>) -> Result<(), Error> {
info!("Executing {}", cmd);
debug!("Env {:?}", env);
debug!("Cwd {:?}", cwd);
if crate::DRY_RUN.load(Relaxed) {
info!("Skipping execution because of --dry-run mode");
return Ok(());
}
let mut command = Command::new("sh");
command
.arg("-c")
.arg(cmd)
.stderr(Stdio::inherit())
.stdout(Stdio::inherit());
if let Some(env) = env {
for (key, val) in env.iter() {
command.env(key, val);
}
}
if let Some(cwd) = cwd {
command.current_dir(cwd);
}
let output = command.output()?;
if !output.status.success() {
Err(Error::new(
ErrorKind::Other,
"Command exited with nonzero status.",
))
} else {
Ok(())
}
}
pub fn project_root() -> PathBuf {
Path::new(&env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(1)
.unwrap()
.to_path_buf()
}

View File

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "0.1.0"
version = "0.1.7"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
@@ -8,14 +8,15 @@ homepage = "https://zama.ai/"
documentation = "https://docs.zama.ai/tfhe-rs"
repository = "https://github.com/zama-ai/tfhe-rs"
license = "BSD-3-Clause-Clear"
description = "Concrete is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
build = "build.rs"
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt"]
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt", "/js_on_wasm_tests/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
rand = "0.7"
rand = "0.8.5"
rand_distr = "0.4.3"
kolmogorov_smirnov = "1.1.0"
paste = "1.0.7"
lazy_static = { version = "1.4.0" }
@@ -23,24 +24,28 @@ criterion = "0.3.5"
doc-comment = "0.3.3"
# Used in user documentation
bincode = "1.3.3"
fs2 = { version = "0.4.3"}
fs2 = { version = "0.4.3" }
[build-dependencies]
cbindgen = { version = "0.24.3", optional = true }
[dependencies]
concrete-csprng = { version = "0.2.1" }
concrete-cuda = { version = "0.1.1", optional = true }
concrete-csprng = { version = "0.2.1", features = [
"generator_soft",
"parallel",
] }
lazy_static = { version = "1.4.0", optional = true }
serde = { version = "1.0", optional = true }
rayon = { version = "1.5.0", optional = true }
bincode = { version = "1.3.3", optional = true }
concrete-fft = { version = "0.1", optional = true }
aligned-vec = "0.5"
dyn-stack = { version = "0.8", optional = true }
serde = { version = "1.0", features = ["derive"] }
rayon = { version = "1.5.0" }
bincode = { version = "1.3.3" }
concrete-fft = { version = "0.1", features = ["serde"] }
aligned-vec = { version = "0.5", features = ["serde"] }
dyn-stack = { version = "0.8" }
once_cell = "1.13"
paste = "1.0.7"
fs2 = { version = "0.4.3", optional = true }
# While we wait for repeat_n in rust standard library
itertools = "0.10.5"
# wasm deps
wasm-bindgen = { version = "0.2.63", features = [
@@ -52,11 +57,11 @@ serde-wasm-bindgen = { version = "0.4", optional = true }
getrandom = { version = "0.2.8", optional = true }
[features]
boolean = ["minimal_core_crypto_features"]
shortint = ["minimal_core_crypto_features"]
boolean = []
shortint = []
internal-keycache = ["lazy_static", "fs2"]
__c_api = ["cbindgen", "minimal_core_crypto_features"]
__c_api = ["cbindgen"]
boolean-c-api = ["boolean", "__c_api"]
shortint-c-api = ["shortint", "__c_api"]
@@ -71,78 +76,30 @@ __wasm_api = [
boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
cuda = ["backend_cuda"]
nightly-avx512 = ["backend_fft_nightly_avx512"]
# A pure-rust CPU backend.
backend_default = ["concrete-csprng/generator_soft"]
# An accelerated backend, using the `concrete-fft` library.
backend_fft = ["concrete-fft", "dyn-stack"]
backend_fft_serialization = [
"bincode",
"concrete-fft/serde",
"aligned-vec/serde",
"__commons_serialization",
]
backend_fft_nightly_avx512 = ["concrete-fft/nightly"]
# Enables the parallel engine in default backend.
backend_default_parallel = ["__commons_parallel"]
nightly-avx512 = ["concrete-fft/nightly"]
# Enable the x86_64 specific accelerated implementation of the random generator for the default
# backend
backend_default_generator_x86_64_aesni = [
"concrete-csprng/generator_x86_64_aesni",
]
generator_x86_64_aesni = ["concrete-csprng/generator_x86_64_aesni"]
# Enable the aarch64 specific accelerated implementation of the random generator for the default
# backend
backend_default_generator_aarch64_aes = [
"concrete-csprng/generator_aarch64_aes",
]
# Enable the serialization engine in the default backend.
backend_default_serialization = ["bincode", "__commons_serialization"]
# A GPU backend, relying on Cuda acceleration
backend_cuda = ["concrete-cuda"]
generator_aarch64_aes = ["concrete-csprng/generator_aarch64_aes"]
# Private features
__profiling = []
__private_docs = []
__commons_parallel = ["rayon", "concrete-csprng/parallel"]
__commons_serialization = ["serde", "serde/derive"]
seeder_unix = ["concrete-csprng/seeder_unix"]
seeder_x86_64_rdseed = ["concrete-csprng/seeder_x86_64_rdseed"]
minimal_core_crypto_features = [
"backend_default",
"backend_default_parallel",
"backend_default_serialization",
"backend_fft",
"backend_fft_serialization",
]
# These target_arch features enable a set of public features for concrete-core if users want a known
# good/working configuration for concrete-core.
# These target_arch features enable a set of public features for tfhe if users want a known
# good/working configuration for tfhe.
# For a target_arch that does not yet have such a feature, one can still enable features manually or
# create a feature for said target_arch to make its use simpler.
x86_64 = [
"minimal_core_crypto_features",
"backend_default_generator_x86_64_aesni",
"seeder_x86_64_rdseed",
]
x86_64 = ["generator_x86_64_aesni", "seeder_x86_64_rdseed"]
x86_64-unix = ["x86_64", "seeder_unix"]
# CUDA builds are Unix only at the moment
x86_64-unix-cuda = ["x86_64-unix", "cuda"]
aarch64 = [
"minimal_core_crypto_features",
"backend_default_generator_aarch64_aes",
]
aarch64 = ["generator_aarch64_aes"]
aarch64-unix = ["aarch64", "seeder_unix"]
[package.metadata.docs.rs]
@@ -172,6 +129,14 @@ required-features = ["shortint", "internal-keycache"]
name = "generates_test_keys"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "boolean_key_sizes"
required-features = ["boolean", "internal-keycache"]
[[example]]
name = "shortint_key_sizes"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "micro_bench_and"
required-features = ["boolean"]

View File

@@ -15,7 +15,8 @@ materials provided with the distribution.
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
or promote products derived from this software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE*.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@@ -25,8 +26,3 @@ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CA
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*In addition to the rights carried by this license, ZAMA grants to the user a non-exclusive,
free and non-commercial license on all patents filed in its name relating to the open-source
code (the "Patents") for the sole purpose of evaluation, development, research, prototyping
and experimentation.

View File

@@ -22,39 +22,32 @@ fn bench_gates(c: &mut Criterion, params: BooleanParameters, parameter_name: &st
let ct2 = cks.encrypt(false);
let ct3 = cks.encrypt(true);
let id = format!("AND gate {}", parameter_name);
let id = format!("AND gate {parameter_name}");
c.bench_function(&id, |b| b.iter(|| black_box(sks.and(&ct1, &ct2))));
let id = format!("NAND gate {}", parameter_name);
let id = format!("NAND gate {parameter_name}");
c.bench_function(&id, |b| b.iter(|| black_box(sks.nand(&ct1, &ct2))));
let id = format!("OR gate {}", parameter_name);
let id = format!("OR gate {parameter_name}");
c.bench_function(&id, |b| b.iter(|| black_box(sks.or(&ct1, &ct2))));
let id = format!("XOR gate {}", parameter_name);
let id = format!("XOR gate {parameter_name}");
c.bench_function(&id, |b| b.iter(|| black_box(sks.xor(&ct1, &ct2))));
let id = format!("XNOR gate {}", parameter_name);
let id = format!("XNOR gate {parameter_name}");
c.bench_function(&id, |b| b.iter(|| black_box(sks.xnor(&ct1, &ct2))));
let id = format!("NOT gate {}", parameter_name);
let id = format!("NOT gate {parameter_name}");
c.bench_function(&id, |b| b.iter(|| black_box(sks.not(&ct1))));
let id = format!("MUX gate {}", parameter_name);
let id = format!("MUX gate {parameter_name}");
c.bench_function(&id, |b| b.iter(|| black_box(sks.mux(&ct1, &ct2, &ct3))));
}
#[cfg(not(feature = "cuda"))]
fn bench_default_parameters(c: &mut Criterion) {
bench_gates(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
}
#[cfg(feature = "cuda")]
fn bench_default_parameters(_: &mut Criterion) {
let _ = DEFAULT_PARAMETERS; // to avoid unused import warnings
println!("DEFAULT_PARAMETERS not benched as they are not compatible with the cuda feature.");
}
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
bench_gates(c, TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS");
}

View File

@@ -41,7 +41,7 @@ where
let mut ct_0 = cks.encrypt(clear_0);
let mut ct_1 = cks.encrypt(clear_1);
let bench_id = format!("{}::{}", bench_name, param_name);
let bench_id = format!("{bench_name}::{param_name}");
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
binary_op(sks, &mut ct_0, &mut ct_1);
@@ -71,7 +71,7 @@ where
let mut ct_0 = cks.encrypt(clear_0);
let bench_id = format!("{}::{}", bench_name, param_name);
let bench_id = format!("{bench_name}::{param_name}");
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
binary_op(sks, &mut ct_0, clear_1 as u8);
@@ -97,7 +97,7 @@ fn carry_extract(c: &mut Criterion) {
let ct_0 = cks.encrypt(clear_0);
let bench_id = format!("ServerKey::carry_extract::{}", param_name);
let bench_id = format!("ServerKey::carry_extract::{param_name}");
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
sks.carry_extract(&ct_0);
@@ -125,7 +125,7 @@ fn programmable_bootstrapping(c: &mut Criterion) {
let ctxt = cks.encrypt(clear_0);
let id = format!("ServerKey::programmable_bootstrap::{}", param_name);
let id = format!("ServerKey::programmable_bootstrap::{param_name}");
bench_group.bench_function(&id, |b| {
b.iter(|| {
@@ -151,7 +151,7 @@ fn bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
let mut ct = cks.encrypt_without_padding(clear as u64);
let vec_lut = wopbs_key.generate_lut_native_crt(&ct, |x| x);
let id = format!("Shortint WOPBS: {:?}", param);
let id = format!("Shortint WOPBS: {param:?}");
bench_group.bench_function(&id, |b| {
b.iter(|| {

View File

@@ -11,6 +11,9 @@ void test_default_keygen_w_serde(void) {
BooleanCiphertext *ct = NULL;
Buffer ct_ser_buffer = {.pointer = NULL, .length = 0};
BooleanCiphertext *deser_ct = NULL;
BooleanCompressedCiphertext *cct = NULL;
BooleanCompressedCiphertext *deser_cct = NULL;
BooleanCiphertext *decompressed_ct = NULL;
int gen_keys_ok = boolean_gen_keys_with_default_parameters(&cks, &sks);
assert(gen_keys_ok == 0);
@@ -37,10 +40,34 @@ void test_default_keygen_w_serde(void) {
assert(result == true);
int c_encrypt_ok = boolean_client_key_encrypt_compressed(cks, true, &cct);
assert(c_encrypt_ok == 0);
int c_ser_ok = boolean_serialize_compressed_ciphertext(cct, &ct_ser_buffer);
assert(c_ser_ok == 0);
deser_view.pointer = ct_ser_buffer.pointer;
deser_view.length = ct_ser_buffer.length;
int c_deser_ok = boolean_deserialize_compressed_ciphertext(deser_view, &deser_cct);
assert(c_deser_ok == 0);
int decomp_ok = boolean_decompress_ciphertext(cct, &decompressed_ct);
assert(decomp_ok == 0);
bool c_result = false;
int c_decrypt_ok = boolean_client_key_decrypt(cks, decompressed_ct, &c_result);
assert(c_decrypt_ok == 0);
assert(c_result == true);
destroy_boolean_client_key(cks);
destroy_boolean_server_key(sks);
destroy_boolean_ciphertext(ct);
destroy_boolean_ciphertext(deser_ct);
destroy_boolean_compressed_ciphertext(cct);
destroy_boolean_compressed_ciphertext(deser_cct);
destroy_boolean_ciphertext(decompressed_ct);
destroy_buffer(&ct_ser_buffer);
}
@@ -57,7 +84,7 @@ void test_predefined_keygen_w_serde(void) {
destroy_boolean_server_key(sks);
gen_keys_ok = boolean_gen_keys_with_predefined_parameters_set(
BOOLEAN_PARAMETERS_SET_THFE_LIB_PARAMETERS, &cks, &sks);
BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS, &cks, &sks);
assert(gen_keys_ok == 0);

View File

@@ -326,19 +326,41 @@ bool c_xnor(bool left, bool right) { return !c_xor(left, right); }
void test_server_key(void) {
BooleanClientKey *cks = NULL;
BooleanCompressedServerKey *csks = NULL;
BooleanServerKey *sks = NULL;
Buffer cks_ser_buffer = {.pointer = NULL, .length = 0};
BooleanClientKey *deser_cks = NULL;
Buffer csks_ser_buffer = {.pointer = NULL, .length = 0};
BooleanCompressedServerKey *deser_csks = NULL;
Buffer sks_ser_buffer = {.pointer = NULL, .length = 0};
BooleanServerKey *deser_sks = NULL;
BooleanParameters *params = NULL;
int gen_keys_ok = boolean_gen_keys_with_default_parameters(&cks, &sks);
assert(gen_keys_ok == 0);
int get_params_ok = boolean_get_parameters(BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS, &params);
assert(get_params_ok == 0);
int gen_cks_ok = boolean_gen_client_key(params, &cks);
assert(gen_cks_ok == 0);
int gen_csks_ok = boolean_gen_compressed_server_key(cks, &csks);
assert(gen_csks_ok == 0);
int ser_csks_ok = boolean_serialize_compressed_server_key(csks, &csks_ser_buffer);
assert(ser_csks_ok == 0);
BufferView deser_view = {.pointer = csks_ser_buffer.pointer, .length = csks_ser_buffer.length};
int deser_csks_ok = boolean_deserialize_compressed_server_key(deser_view, &deser_csks);
assert(deser_csks_ok == 0);
int decompress_csks_ok = boolean_decompress_server_key(deser_csks, &sks);
assert(decompress_csks_ok == 0);
int ser_cks_ok = boolean_serialize_client_key(cks, &cks_ser_buffer);
assert(ser_cks_ok == 0);
BufferView deser_view = {.pointer = cks_ser_buffer.pointer, .length = cks_ser_buffer.length};
deser_view.pointer = cks_ser_buffer.pointer;
deser_view.length = cks_ser_buffer.length;
int deser_cks_ok = boolean_deserialize_client_key(deser_view, &deser_cks);
assert(deser_cks_ok == 0);
@@ -390,10 +412,14 @@ void test_server_key(void) {
boolean_server_key_xnor_scalar_assign);
destroy_boolean_client_key(cks);
destroy_boolean_compressed_server_key(csks);
destroy_boolean_server_key(sks);
destroy_boolean_client_key(deser_cks);
destroy_boolean_compressed_server_key(deser_csks);
destroy_boolean_server_key(deser_sks);
destroy_boolean_parameters(params);
destroy_buffer(&cks_ser_buffer);
destroy_buffer(&csks_ser_buffer);
destroy_buffer(&sks_ser_buffer);
}

View File

@@ -14,7 +14,7 @@ void micro_bench_and() {
// assert(gen_keys_ok == 0);
int gen_keys_ok = boolean_gen_keys_with_predefined_parameters_set(
BOOLEAN_PARAMETERS_SET_THFE_LIB_PARAMETERS, &cks, &sks);
BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS, &cks, &sks);
assert(gen_keys_ok == 0);
int num_loops = 10000;

View File

@@ -12,6 +12,9 @@ void test_predefined_keygen_w_serde(void) {
ShortintCiphertext *ct = NULL;
Buffer ct_ser_buffer = {.pointer = NULL, .length = 0};
ShortintCiphertext *deser_ct = NULL;
ShortintCompressedCiphertext *cct = NULL;
ShortintCompressedCiphertext *deser_cct = NULL;
ShortintCiphertext *decompressed_ct = NULL;
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
@@ -41,11 +44,35 @@ void test_predefined_keygen_w_serde(void) {
assert(result == 3);
int c_encrypt_ok = shortint_client_key_encrypt_compressed(cks, 3, &cct);
assert(c_encrypt_ok == 0);
int c_ser_ok = shortint_serialize_compressed_ciphertext(cct, &ct_ser_buffer);
assert(c_ser_ok == 0);
deser_view.pointer = ct_ser_buffer.pointer;
deser_view.length = ct_ser_buffer.length;
int c_deser_ok = shortint_deserialize_compressed_ciphertext(deser_view, &deser_cct);
assert(c_deser_ok == 0);
int decomp_ok = shortint_decompress_ciphertext(cct, &decompressed_ct);
assert(decomp_ok == 0);
uint64_t c_result = -1;
int c_decrypt_ok = shortint_client_key_decrypt(cks, decompressed_ct, &c_result);
assert(c_decrypt_ok == 0);
assert(c_result == 3);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
destroy_shortint_parameters(params);
destroy_shortint_ciphertext(ct);
destroy_shortint_ciphertext(deser_ct);
destroy_shortint_compressed_ciphertext(cct);
destroy_shortint_compressed_ciphertext(deser_cct);
destroy_shortint_ciphertext(decompressed_ct);
destroy_buffer(&ct_ser_buffer);
}
@@ -69,10 +96,11 @@ void test_custom_keygen(void) {
void test_public_keygen(void) {
ShortintClientKey *cks = NULL;
ShortintServerKey *sks = NULL;
ShortintPublicKey *pks = NULL;
ShortintPublicKey *pks_deser = NULL;
ShortintParameters *params = NULL;
ShortintCiphertext *ct = NULL;
Buffer pks_ser_buff = {.pointer = NULL, .length = 0};
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
@@ -83,12 +111,16 @@ void test_public_keygen(void) {
int gen_pks = shortint_gen_public_key(cks, &pks);
assert(gen_pks == 0);
int gen_sks = shortint_gen_server_key(cks, &sks);
assert(gen_sks == 0);
int pks_ser = shortint_serialize_public_key(pks, &pks_ser_buff);
assert(pks_ser == 0);
BufferView pks_ser_buff_view = {.pointer = pks_ser_buff.pointer, .length = pks_ser_buff.length};
int pks_deser_ok = shortint_deserialize_public_key(pks_ser_buff_view, &pks_deser);
assert(pks_deser_ok == 0);
uint64_t msg = 2;
int encrypt_ok = shortint_public_key_encrypt(pks, sks, msg, &ct);
int encrypt_ok = shortint_public_key_encrypt(pks_deser, msg, &ct);
assert(encrypt_ok == 0);
uint64_t result = -1;
@@ -99,7 +131,54 @@ void test_public_keygen(void) {
destroy_shortint_parameters(params);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
destroy_shortint_public_key(pks);
destroy_shortint_public_key(pks_deser);
destroy_buffer(&pks_ser_buff);
destroy_shortint_ciphertext(ct);
}
void test_compressed_public_keygen(void) {
ShortintClientKey *cks = NULL;
ShortintCompressedPublicKey *cpks = NULL;
ShortintPublicKey *pks = NULL;
ShortintParameters *params = NULL;
ShortintCiphertext *ct = NULL;
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
int gen_keys_ok = shortint_gen_client_key(params, &cks);
assert(gen_keys_ok == 0);
int gen_cpks = shortint_gen_compressed_public_key(cks, &cpks);
assert(gen_cpks == 0);
uint64_t msg = 2;
int encrypt_compressed_ok = shortint_compressed_public_key_encrypt(cpks, msg, &ct);
assert(encrypt_compressed_ok == 0);
uint64_t result_compressed = -1;
int decrypt_compressed_ok = shortint_client_key_decrypt(cks, ct, &result_compressed);
assert(decrypt_compressed_ok == 0);
assert(result_compressed == 2);
int decompress_ok = shortint_decompress_public_key(cpks, &pks);
assert(decompress_ok == 0);
int encrypt_ok = shortint_public_key_encrypt(pks, msg, &ct);
assert(encrypt_ok == 0);
uint64_t result = -1;
int decrypt_ok = shortint_client_key_decrypt(cks, ct, &result);
assert(decrypt_ok == 0);
assert(result == 2);
destroy_shortint_parameters(params);
destroy_shortint_client_key(cks);
destroy_shortint_compressed_public_key(cpks);
destroy_shortint_public_key(pks);
destroy_shortint_ciphertext(ct);
}
@@ -108,5 +187,6 @@ int main(void) {
test_predefined_keygen_w_serde();
test_custom_keygen();
test_public_keygen();
test_compressed_public_keygen();
return EXIT_SUCCESS;
}

View File

@@ -296,9 +296,12 @@ uint64_t right_shift(uint64_t left, uint8_t right) { return left >> right; }
void test_server_key(void) {
ShortintClientKey *cks = NULL;
ShortintCompressedServerKey *csks = NULL;
ShortintServerKey *sks = NULL;
Buffer cks_ser_buffer = {.pointer = NULL, .length = 0};
ShortintClientKey *deser_cks = NULL;
Buffer csks_ser_buffer = {.pointer = NULL, .length = 0};
ShortintCompressedServerKey *deser_csks = NULL;
Buffer sks_ser_buffer = {.pointer = NULL, .length = 0};
ShortintServerKey *deser_sks = NULL;
ShortintParameters *params = NULL;
@@ -309,13 +312,28 @@ void test_server_key(void) {
int get_params_ok = shortint_get_parameters(message_bits, carry_bits, &params);
assert(get_params_ok == 0);
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
int gen_cks_ok = shortint_gen_client_key(params, &cks);
assert(gen_cks_ok == 0);
int gen_csks_ok = shortint_gen_compressed_server_key(cks, &csks);
assert(gen_csks_ok == 0);
int ser_csks_ok = shortint_serialize_compressed_server_key(csks, &csks_ser_buffer);
assert(ser_csks_ok == 0);
BufferView deser_view = {.pointer = csks_ser_buffer.pointer, .length = csks_ser_buffer.length};
int deser_csks_ok = shortint_deserialize_compressed_server_key(deser_view, &deser_csks);
assert(deser_csks_ok == 0);
int decompress_csks_ok = shortint_decompress_server_key(deser_csks, &sks);
assert(decompress_csks_ok == 0);
int ser_cks_ok = shortint_serialize_client_key(cks, &cks_ser_buffer);
assert(ser_cks_ok == 0);
BufferView deser_view = {.pointer = cks_ser_buffer.pointer, .length = cks_ser_buffer.length};
deser_view.pointer = cks_ser_buffer.pointer;
deser_view.length = cks_ser_buffer.length;
int deser_cks_ok = shortint_deserialize_client_key(deser_view, &deser_cks);
assert(deser_cks_ok == 0);
@@ -543,11 +561,14 @@ void test_server_key(void) {
forbidden_scalar_mod_values, 1);
destroy_shortint_client_key(cks);
destroy_shortint_compressed_server_key(csks);
destroy_shortint_server_key(sks);
destroy_shortint_client_key(deser_cks);
destroy_shortint_compressed_server_key(deser_csks);
destroy_shortint_server_key(deser_sks);
destroy_shortint_parameters(params);
destroy_buffer(&cks_ser_buffer);
destroy_buffer(&csks_ser_buffer);
destroy_buffer(&sks_ser_buffer);
}

View File

@@ -1,9 +1,6 @@
# Operations and Examples
# Operations
In thfe::boolean, the available operations are mainly related to their equivalent Boolean gates,
i.e., AND, OR,... In what follows, an example of a unary gate (NOT) and one about a binary gate
(XOR). The last one is about the ternary MUX gate are detailed, which gives the possibility to
homomorphically compute conditional statements of the form ``If..Then..Else``.
In tfhe::boolean, the available operations are mainly related to their equivalent Boolean gates (i.e., AND, OR... etc). What follows is an example of a unary gate (NOT) and one about a binary gate (XOR). The last one is about the ternary MUX gate, which gives the possibility to homomorphically compute conditional statements of the form `If..Then..Else`.
## The NOT unary gate
@@ -26,7 +23,6 @@ fn main() {
}
```
## Binary gates
```rust
@@ -49,10 +45,10 @@ fn main() {
}
```
## The MUX ternary gate
Let ``ct_1, ct_2, ct_3`` be three Boolean
ciphertexts. Then, the MUX gate (abbreviation of MUtipleXer) is equivalent to the operation:
Let `ct_1, ct_2, ct_3` be three Boolean ciphertexts. Then, the MUX gate (abbreviation of MUtipleXer) is equivalent to the operation:
```r
if ct_1 {
return ct_2
@@ -61,7 +57,7 @@ if ct_1 {
}
```
This example show how to use the MUX ternary gate.
This example shows how to use the MUX ternary gate:
```rust
use tfhe::boolean::prelude::*;

View File

@@ -0,0 +1,44 @@
# Cryptographic Parameters
## Default parameters
The TFHE cryptographic scheme relies on a variant of [Regev cryptosystem](https://cims.nyu.edu/\~regev/papers/lwesurvey.pdf), and is based on a problem so hard to solve that it is even post-quantum resistant.
In practice, you need to tune some cryptographic parameters in order to ensure both the correctness of the result and the security of the computation.
To make it simpler, **we provide two sets of parameters**, which ensure correct computations for a certain probability with the standard security of 128 bits. There exists an error probability due to the probabilistic nature of the encryption, which requires adding randomness (called noise) following a Gaussian distribution. If this noise is too large, the decryption will not give a correct result. There is a trade-off between efficiency and correctness: generally, using a less efficient parameter set (in terms of computation time) leads to a smaller risk of having an error during homomorphic evaluation.
In the two proposed sets of parameters, the only difference lies in this probability error. The default parameter set ensures a probability error of at most $$2^{-40}$$ when computing a programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error probability claimed into the original [TFHE paper](https://eprint.iacr.org/2018/421), namely $$2^{-165}$$, but it is up-to-date regarding security requirements.
The following array summarizes this:
| Parameter set | Error probability |
| :-------------------: | :---------------: |
| DEFAULT\_PARAMETERS | $$2^{-40}$$ |
| TFHE\_LIB\_PARAMETERS | $$2^{-165}$$ |
## User-defined parameters
Note that, if you desire, you can also create your own set of parameters. This is an `unsafe` operation as failing to properly fix the parameters will potentially result in an incorrect and/or insecure computation:
```rust
use tfhe::boolean::prelude::*;
fn main() {
// WARNING: might be insecure and/or incorrect
// You can create your own set of parameters
let parameters = unsafe {
BooleanParameters::new(
LweDimension(586),
GlweDimension(2),
PolynomialSize(512),
StandardDev(0.00008976167396834998),
StandardDev(0.00000002989040792967434),
DecompositionBaseLog(8),
DecompositionLevelCount(2),
DecompositionBaseLog(2),
DecompositionLevelCount(5),
)
};
}
```

View File

@@ -1,22 +1,20 @@
# Tutorial: a first boolean circuit
# Tutorial
This library is meant to be used both on the **server side** and on the **client side**.
The usual use case would follow those steps:
This library is meant to be used both on the **server side** and on the **client side**. The typical use case should follow the subsequent steps:
1. On the **client side**, generate the `client` and `server keys`.
2. Send the `server key` to the **server**.
3. Then any number of times:
+ On the **client side**, *encryption* of the input data with the `client key`.
+ Transmit the encrypted input to the **server**.
+ On the **server side**, *homomorphic computation* with the `server key`.
+ Transmit the encrypted output to the **client**.
+ On the **client side**, *decryption* of the output data with `client key`.
* On the **client side**, _encryption_ of the input data with the `client key`.
* Transmit the encrypted input to the **server**.
* On the **server side**, _homomorphic computation_ with the `server key`.
* Transmit the encrypted output to the **client**.
* On the **client side**, _decryption_ of the output data with the `client key`.
## 1. Setup
## Setup
In the first step, the client creates two keys: the `client key` and the `server key`, with the `concrete_boolean::gen_keys` function:
In the first step, the client creates two keys: the `client key` and the `server key`,
with the
`concrete_boolean::gen_keys` function:
```rust
use tfhe::boolean::prelude::*;
@@ -28,17 +26,11 @@ fn main() {
}
```
In more details:
* The `client_key` is of type `ClientKey`. It is **secret**, and must **never** be transmitted. This key will only be used to encrypt and decrypt data.
* The `server_key` is of type `ServerKey`. It is a **public key**, and can be shared with any party. This key has to be sent to the server because it is required for homomorphic computation.
+ The `client_key` is of type `ClientKey`. It is **secret**, and must **never** be transmitted.
This key will only be used to encrypt and decrypt data.
+ The `server_key` is of type `ServerKey`. It is a **public key**, and can be shared with any
party.
This key has to be sent to the server because it is required for the homomorphic computation.
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits. This way you can use any compatible serializer to store/send the data. For instance, to store the `server_key` in a binary file, you can use the `bincode` library:
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits.
This way you can use any compatible serializer to store/send the data. For instance, to store
the `server_key` in a binary file, you can use the `bincode` library:
```rust
use std::fs::File;
use std::io::{Write, Read};
@@ -80,15 +72,10 @@ fn main() {
}
```
## 2. Encrypting Inputs
## Encrypting Inputs
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 employed:
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
used:
```rust
use tfhe::boolean::prelude::*;
@@ -112,15 +99,10 @@ fn main() {
}
```
## 2bis. Encrypting Inputs using public key
## 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:
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
used:
```rust
use tfhe::boolean::prelude::*;
@@ -145,11 +127,9 @@ fn main() {
}
```
## Executing a Boolean circuit
## Executing a Boolean Circuit
Once the encrypted inputs are on the **server side**, the `server_key` can be used to
homomorphically execute the desired boolean circuit:
Once the encrypted inputs are on the **server side**, the `server_key` can be used to homomorphically execute the desired Boolean circuit:
```rust
use std::fs::File;
@@ -191,8 +171,7 @@ fn main() {
## Decrypting the output
Once the encrypted output is on the client side, the `client_key` can be used to
decrypt it:
Once the encrypted output is on the client side, the `client_key` can be used to decrypt it:
```rust
use std::fs::File;
@@ -218,29 +197,3 @@ fn main() {
assert_eq!(output, true);
}
```

View File

@@ -1,54 +0,0 @@
# Cryptographic parameters
## Default parameters
The TFHE cryptographic scheme relies on a variant of [Regev cryptosystem](https://cims.nyu.edu/~regev/papers/lwesurvey.pdf), and is based on a problem so hard to solve, that is even post-quantum resistant.
In practice, you need to tune some cryptographic parameters, in order to ensure the correctness of the result, and the security of the computation.
To make it simpler, **we provide two sets of parameters**, which ensure correct computations for a certain probability with the standard security of 128 bits. There exists an error probability due the probabilistic nature of the encryption, which requires adding randomness (called noise) following a Gaussian distribution. If this noise is too large, the decryption will not give a correct result. There is a trade-off between efficiency and correctness: generally, using a less efficient parameter set (in terms of computation time) leads to a smaller risk of having an error during homomorphic evaluation.
In the two proposed sets of parameters, the only difference lies into this probability error.
The default parameter set ensures a probability error of at most $$2^{-40}$$ when computing a
programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error
probability claimed into the original [TFHE paper](https://eprint.iacr.org/2018/421),
namely $$2^{-165}$$, but up to date regarding security requirements.
The following array summarizes this:
| Parameter set | Error probability |
|:-------------------:|:-----------------:|
| DEFAULT_PARAMETERS | $$ 2^{-40} $$ |
| TFHE_LIB_PARAMETERS | $$ 2^{-165} $$ |
## User-defined parameters
Note that if you desire, you can also create your own set of parameters.
This is an `unsafe` operation as failing to properly fix the parameters will potentially result with an incorrect and/or insecure computation:
```rust
use tfhe::boolean::prelude::*;
fn main() {
// WARNING: might be insecure and/or incorrect
// You can create your own set of parameters
let parameters = unsafe {
BooleanParameters::new(
LweDimension(586),
GlweDimension(2),
PolynomialSize(512),
StandardDev(0.00008976167396834998),
StandardDev(0.00000002989040792967434),
DecompositionBaseLog(8),
DecompositionLevelCount(2),
DecompositionBaseLog(2),
DecompositionLevelCount(5),
)
};
}
```

View File

@@ -1,8 +1,8 @@
# What is TFHE-rs?
<mark style="background-color:yellow;">⭐️</mark> [<mark style="background-color:yellow;">Star the repo on Github</mark>](https://github.com/zama-ai/tfhe-rs) <mark style="background-color:yellow;">| 🗣</mark> [<mark style="background-color:yellow;">Community support forum</mark> ](https://community.zama.ai)<mark style="background-color:yellow;">| 📁</mark> [<mark style="background-color:yellow;">Contribute to the project</mark>](https://docs.zama.ai/tfhe-rs/developers/contributing)<mark style="background-color:yellow;"></mark>
<mark style="background-color:yellow;">⭐️</mark> [<mark style="background-color:yellow;">Star the repo on Github</mark>](https://github.com/zama-ai/tfhe-rs) <mark style="background-color:yellow;">| 🗣</mark> [<mark style="background-color:yellow;">Community support forum</mark> ](https://community.zama.ai)<mark style="background-color:yellow;">| 📁</mark> [<mark style="background-color:yellow;">Contribute to the project</mark>](https://docs.zama.ai/tfhe-rs/developers/contributing)
![](_static/docs\_home.jpg)
![](\_static/tfhe-rs-doc-home.png)
TFHE-rs is a pure Rust implementation of TFHE for boolean and small integer arithmetics over encrypted data. It includes a Rust and C API, as well as a client-side WASM API.
@@ -10,14 +10,13 @@ TFHE-rs is meant for developers and researchers who want full control over what
The goal is to have a stable, simple, high-performance, and production-ready library for all the advanced features of TFHE.
### Key Cryptographic concepts
## Key cryptographic concepts
TFHE-rs library implements Zamas variant of Fully Homomorphic Encryption over the Torus (TFHE). TFHE is based on Learning With Errors (LWE), a well studied cryptographic primitive believed to be secure even against quantum computers.
The TFHE-rs library implements Zamas variant of Fully Homomorphic Encryption over the Torus (TFHE). TFHE is based on Learning With Errors (LWE), a well-studied cryptographic primitive believed to be secure even against quantum computers.
In cryptography, a raw value is called a message (also sometimes called a cleartext), an encoded message is called a plaintext and an encrypted plaintext is called a ciphertext.
In cryptography, a raw value is called a message (also sometimes called a cleartext), while an encoded message is called a plaintext and an encrypted plaintext is called a ciphertext.
The idea of homomorphic encryption is that you can compute on ciphertexts while not knowing messages encrypted in them. A scheme is said to be _fully homomorphic_, meaning any program can be evaluated with it, if at least two of the following operations are supported \($$x$$is a plaintext and $$E[x]$$ is the
corresponding ciphertext\):
The idea of homomorphic encryption is that you can compute on ciphertexts while not knowing messages encrypted within them. A scheme is said to be _fully homomorphic_, meaning any program can be evaluated with it, if at least two of the following operations are supported ($$x$$is a plaintext and $$E[x]$$ is the corresponding ciphertext):
* homomorphic univariate function evaluation: $$f(E[x]) = E[f(x)]$$
* homomorphic addition: $$E[x] + E[y] = E[x + y]$$
@@ -28,9 +27,8 @@ Zama's variant of TFHE is fully homomorphic and deals with fixed-precision numbe
Using FHE in a Rust program with TFHE-rs consists in:
* generating a client key and a server key using secure parameters:
* client key encrypts/decrypts data and must be kept secret
* server key is used to perform operations on encrypted data and could be
public (also called evaluation key)
* a client key encrypts/decrypts data and must be kept secret
* a server key is used to perform operations on encrypted data and could be public (also called an evaluation key)
* encrypting plaintexts using the client key to produce ciphertexts
* operating homomorphically on ciphertexts with the server key
* decrypting the resulting ciphertexts into plaintexts using the client key

View File

@@ -10,11 +10,11 @@
* [Benchmarks](getting\_started/benchmarks.md)
* [Security and Cryptography](getting\_started/security\_and\_cryptography.md)
## Booleans
* [Tutorial](Booleans/tutorial.md)
* [Operations](Booleans/operations.md)
* [Cryptographic Parameters](Booleans/parameters.md)
* [Serialization/Deserialization](Booleans/serialization.md)
## Boolean
* [Tutorial](Boolean/tutorial.md)
* [Operations](Boolean/operations.md)
* [Cryptographic Parameters](Boolean/parameters.md)
* [Serialization/Deserialization](Boolean/serialization.md)
## Shortint
* [Tutorial](shortint/tutorial.md)
@@ -25,6 +25,13 @@
## C API
* [Tutorial](c_api/tutorial.md)
## JS on WASM API
* [Tutorial](js_on_wasm_api/tutorial.md)
## Low-Level Core Cryptography
* [Quick Start](core_crypto/presentation.md)
* [Tutorial](core_crypto/tutorial.md)
## Developers
* [Contributing](dev/contributing.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,16 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 424 173" width="424" height="173">
<!-- svg-source:excalidraw -->
<defs>
<style>
@font-face {
font-family: "Virgil";
src: url("https://excalidraw.com/Virgil.woff2");
}
@font-face {
font-family: "Cascadia";
src: url("https://excalidraw.com/Cascadia.woff2");
}
</style>
</defs>
<g stroke-linecap="round" transform="translate(26 44) rotate(0 194 38.60598503740641)"><path d="M-0.72 -1.78 C148.22 -1.79, 298.89 -0.4, 389.35 -0.66 M0.64 -0.29 C96.57 -1.4, 195.57 -0.99, 387.19 -1.07 M388.14 2.03 C392.43 23.61, 391.39 49, 385.98 75.21 M389.42 -0.67 C389.81 14.73, 390.33 33.43, 388.78 76.49 M387.8 79.39 C277.29 77.61, 166.8 75.17, -1.18 76.59 M387.01 76.05 C246.87 81.15, 103.96 81.92, 0.98 78.37 M-3.84 75.76 C-2.18 57.07, 4.75 34.74, -0.59 -2.81 M1 77.36 C-0.79 48.54, -1.41 22.74, 1.4 1.83" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(43.27283572239912 63.29950568870447) rotate(0 35.31670822942641 19.35162094763092)"><path d="M0.11 -2.85 C28.93 -4.68, 48.62 2.97, 73.79 0.71 M0.92 1.49 C21.03 0.46, 41.01 -1.23, 70.4 0.64 M70.88 3.15 C68.43 11.93, 69.14 26.71, 68.06 39.17 M69.56 -1.68 C71.59 8.67, 68.48 17.57, 70.35 40.23 M68.74 39.32 C48.28 40.85, 30.28 35.73, -0.92 40.56 M69.59 37.89 C48.04 38.14, 23.44 38.86, -0.31 39.89 M2.24 36.95 C-1.92 26.09, -1.01 13.79, -3.25 -2.11 M0.55 39.03 C1.24 29.93, -1.56 22.42, 0.25 1.64" stroke="#0b7285" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(244.47880299251847 58.546134663341206) rotate(0 77.40648379052368 25.15710723192018)"><path d="M0.5 3.27 C41.25 1.83, 85.12 4.03, 158.28 2.64 M1.74 -0.75 C60.33 -2.64, 122.13 -1.19, 153.85 -0.7 M155.49 -2.28 C158.86 11.5, 154.4 20.06, 153.49 51.17 M153.03 -0.55 C152.94 16.04, 156.21 35.59, 154.41 49.32 M157.56 51.63 C96.68 46.86, 29.51 52.23, -2.5 49.76 M155.17 52.19 C121.59 51.66, 87.99 54.57, -0.92 50.35 M-1.24 51.37 C-0.7 37.8, 0.93 29.69, -2.96 2.02 M-1.48 52.03 C0.44 36.29, -0.71 20.9, 1.27 0.34" stroke="#a61e4d" stroke-width="1" fill="none"></path></g><g transform="translate(249.47880299251847 71.70324189526139) rotate(0 72.40648379052368 12)"><text x="72.40648379052374" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.30839567747301px" fill="#a61e4d" text-anchor="middle" style="white-space: pre;" direction="ltr">noise</text></g><g stroke-linecap="round" transform="translate(35.610972568578745 52.67581047381532) rotate(0 89.501246882793 30.962593516209466)"><path d="M3.48 2.12 C42.75 -4.16, 86.77 -1.56, 177.5 3.84 M-1.92 1.02 C66.55 2.87, 130.16 1.88, 179.45 -1.63 M179.35 -3.41 C181.95 11.53, 178.24 25.72, 176.46 63.54 M180.52 0.51 C178.89 22.76, 177.57 49.34, 177.3 60.97 M175.68 59.6 C140.66 62.47, 97.79 56.72, 3.94 58.21 M177.14 61.73 C134.25 62.89, 91.18 61.47, 1.89 60.4 M-0.84 60.36 C-0.67 48.08, 3.19 30.78, -2.25 -3.82 M0.14 61.15 C-2.52 41.13, 0.16 19.4, -0.06 -0.75" stroke="#087f5b" stroke-width="1" fill="none"></path></g><g transform="translate(48.27283572239912 70.65112663633539) rotate(0 30.31670822942641 12)"><text x="30.316708229426418" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.248703637731044px" fill="#087f5b" text-anchor="middle" style="white-space: pre;" direction="ltr">carry</text></g><g stroke-linecap="round" transform="translate(123.17705735660911 63.319201995012975) rotate(0 43.5411471321695 21.286783042394035)"><path d="M3.78 -1.95 C21.85 2.27, 33.36 -1.31, 85.7 0.15 M0.95 -1.99 C30.47 0.73, 63.7 1.15, 87.61 0.81 M83.5 1.56 C89.47 15.22, 83.3 24.69, 84.55 46.54 M85.31 -0.87 C84.54 12.96, 85.46 26.61, 85.69 43.87 M85.54 45.92 C71.62 40.51, 50.01 40.74, -2.16 41.74 M85.45 44.45 C55.35 43.91, 24.36 42.43, 0.99 42.77 M-3.26 44.49 C1.26 31.11, -1.14 19.41, -1.89 2.33 M-1.27 43.89 C-1.22 30.47, 1.35 21.91, -0.8 -1.4" stroke="#0b7285" stroke-width="1" fill="none"></path></g><g transform="translate(128.1770573566091 72.60598503740698) rotate(0 38.5411471321695 12)"><text x="38.54114713216951" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.27057356608477px" fill="#087f5b" text-anchor="middle" style="white-space: pre;" direction="ltr">message</text></g><g transform="translate(371 138) rotate(0 20 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">LSB</text></g><g transform="translate(10 135) rotate(0 21.5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">MSB</text></g><g transform="translate(162 10) rotate(0 51 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">Ciphertext</text></g></svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

BIN
tfhe/docs/_static/tfhe-rs-doc-home.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

@@ -1,25 +1,32 @@
# Tutorial: using the C API
# Tutorial
Welcome to this `TFHE-rs` C API tutorial!
## Using the C API
This library exposes a C binding to the `TFHE-rs` primitives to implement _Fully Homomorphic Encryption_ (FHE) programs.
Welcome to this TFHE-rs C API tutorial!
# First steps using `TFHE-rs` C API
This library exposes a C binding to the TFHE-rs primitives to implement _Fully Homomorphic Encryption_ (FHE) programs.
## Setting-up `TFHE-rs` C API for use in a C program.
## First steps using TFHE-rs C API
`TFHE-rs` C API can be built on a Unix x86_64 machine using the following command:
### Setting-up TFHE-rs C API for use in a C program.
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo build --release --features=x86_64-unix,boolean-c-api,shortint-c-api -p tfhe
```
All features are opt-in, but for simplicity here, the C API is enabled for booleans and shortints.
or on a Unix aarch64 machine using the following command
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO_ROOT}/target/release/"
```shell
RUSTFLAGS="-C target-cpu=native" cargo build --release --features=aarch64-unix,boolean-c-api,shortint-c-api -p tfhe
```
The build system needs to be set-up so that the C or C++ program links against `TFHE-rs` C API
binaries.
All features are opt-in, but for simplicity here, the C API is enabled for boolean and shortint.
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO\_ROOT}/target/release/"
The build system needs to be set up so that the C or C++ program links against TFHE-rs C API binaries.
Here is a minimal CMakeLists.txt allowing to do just that:
@@ -51,17 +58,14 @@ endif()
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
```
## Commented code of a PBS doubling a 2 bits encrypted message using `TFHE-rs C API`
### Commented code of a PBS doubling a 2 bits encrypted message using `TFHE-rs C API`.
The steps required to perform the mutiplication by 2 of a 2 bits ciphertext
using a PBS are detailed.
This is NOT the most efficient way of doing this operation,
but it allows to show the management required to run a PBS manually using the C API.
The steps required to perform the mutiplication by 2 of a 2 bits ciphertext using a PBS are detailed. This is NOT the most efficient way of doing this operation, but it can help to show the management required to run a PBS manually using the C API.
WARNING: The following example does not have proper memory management in the error case to make it easier to fit the code on this page.
To run the example below, the above CMakeLists.txt and main.c files need to be in the same
directory. The commands to run are:
To run the example below, the above CMakeLists.txt and main.c files need to be in the same directory. The commands to run are:
```shell
# /!\ Be sure to update CMakeLists.txt to give the absolute path to the compiled tfhe library
$ ls
@@ -169,6 +173,6 @@ int main(void)
}
```
# Audience
## Audience
Programmers wishing to use `TFHE-rs` but unable to use Rust (for various reasons) can use these bindings in their language of choice as long as it can interface with C code to bring `TFHE-rs` functionalities to said language.
Programmers wishing to use TFHE-rs but who are unable to use Rust (for various reasons) can use these bindings in their language of choice, as long as it can interface with C code to bring TFHE-rs functionalities to said language.

View File

@@ -0,0 +1,62 @@
# Overview of the `core_crypto` Module
The `core_crypto` module from TFHE-rs is dedicated to the implementation of the cryptographic tools related to TFHE. To construct an FHE application, the [shortint](../shortint/tutorial.md) and/or [Boolean](../Boolean/tutorial.md) modules (based on this one) are recommended.
The `core_crypto` module offers an API to low-level cryptographic primitives and objects, like `lwe_encryption` or `rlwe_ciphertext`. Its goal is to propose an easy-to-use API for cryptographers.
The overall code architecture is split in two parts: one for the entity definitions, and another one focused on the algorithms. For instance, the entities contain the definition of useful types, like LWE ciphertext or bootstrapping keys. The algorithms are then naturally defined to work using these entities.
The API is convenient to easily add or modify existing algorithms or to have direct access to the raw data. For instance, even if the LWE ciphertext object is defined along with functions giving access to he body, this is also possible to bypass these to get directly the $$i^{th}$$ element of LWE mask.
For instance, the code to encrypt and then decrypt a message looks like:
```rust
use tfhe::core_crypto::prelude::*;
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
// computations
// Define parameters for LweCiphertext creation
let lwe_dimension = LweDimension(742);
let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
// Create the PRNG
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
// Create the LweSecretKey
let lwe_secret_key =
allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
// Create the plaintext
let msg = 3u64;
let plaintext = Plaintext(msg << 60);
// Create a new LweCiphertext
let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
encrypt_lwe_ciphertext(
&lwe_secret_key,
&mut lwe,
plaintext,
lwe_modular_std_dev,
&mut encryption_generator,
);
let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe);
// Round and remove encoding
// First create a decomposer working on the high 4 bits corresponding to our encoding.
let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
let rounded = decomposer.closest_representable(decrypted_plaintext.0);
// Remove the encoding
let cleartext = rounded >> 60;
// Check we recovered the original message
assert_eq!(cleartext, msg);
```

View File

@@ -0,0 +1,244 @@
# Tutorial
## Using the `core_crypto` primitives
Welcome to this tutorial about TFHE-rs `core_crypto` module!
### Setting-up TFHE-rs to use the `core_crypto` module
To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
```toml
tfhe = { version = "0.1.7", features = [ "x86_64-unix" ] }
```
Here, 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 actived 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.
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. Note that `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs.
In short:
For x86_64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.1.7", features = ["x86_64-unix"] }
```
For Apple Silicon or aarch64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.1.7", features = ["aarch64-unix"] }
```
For x86_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
```toml
tfhe = { version = "0.1.7", features = ["x86_64"] }
```
### Commented code to double a 2 bits message in a leveled fashion and using a PBS with the `core_crypto` module.
As a complete example showing the usage of some common primitives of the `core_crypto` APIs, the following Rust code homomorphically computes 2 * 3 using two different methods. First using a cleartext multiplication and second using a PBS.
```rust
use tfhe::core_crypto::prelude::*;
pub fn main() {
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
// computations
// Define the parameters for a 4 bits message able to hold the doubled 2 bits message
let small_lwe_dimension = LweDimension(742);
let glwe_dimension = GlweDimension(1);
let polynomial_size = PolynomialSize(2048);
let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
let pbs_base_log = DecompositionBaseLog(23);
let pbs_level = DecompositionLevelCount(1);
// Request the best seeder possible, starting with hardware entropy sources and falling back to
// /dev/random on Unix systems if enabled via cargo features
let mut boxed_seeder = new_seeder();
// Get a mutable reference to the seeder as a trait object from the Box returned by new_seeder
let seeder = boxed_seeder.as_mut();
// Create a generator which uses a CSPRNG to generate secret keys
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
// Create a generator which uses two CSPRNGs to generate public masks and secret encryption
// noise
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
println!("Generating keys...");
// Generate an LweSecretKey with binary coefficients
let small_lwe_sk =
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
// Generate a GlweSecretKey with binary coefficients
let glwe_sk =
GlweSecretKey::generate_new_binary(glwe_dimension, polynomial_size, &mut secret_generator);
// Create a copy of the GlweSecretKey re-interpreted as an LweSecretKey
let big_lwe_sk = glwe_sk.clone().into_lwe_secret_key();
// Generate the bootstrapping key, we use the parallel variant for performance reason
let std_bootstrapping_key = par_allocate_and_generate_new_lwe_bootstrap_key(
&small_lwe_sk,
&glwe_sk,
pbs_base_log,
pbs_level,
glwe_modular_std_dev,
&mut encryption_generator,
);
// Create the empty bootstrapping key in the Fourier domain
let mut fourier_bsk = FourierLweBootstrapKey::new(
std_bootstrapping_key.input_lwe_dimension(),
std_bootstrapping_key.glwe_size(),
std_bootstrapping_key.polynomial_size(),
std_bootstrapping_key.decomposition_base_log(),
std_bootstrapping_key.decomposition_level_count(),
);
// Use the conversion function (a memory optimized version also exists but is more complicated
// to use) to convert the standard bootstrapping key to the Fourier domain
convert_standard_lwe_bootstrap_key_to_fourier(&std_bootstrapping_key, &mut fourier_bsk);
// We don't need the standard bootstrapping key anymore
drop(std_bootstrapping_key);
// Our 4 bits message space
let message_modulus = 1u64 << 4;
// Our input message
let input_message = 3u64;
// Delta used to encode 4 bits of message + a bit of padding on u64
let delta = (1_u64 << 63) / message_modulus;
// Apply our encoding
let plaintext = Plaintext(input_message * delta);
// Allocate a new LweCiphertext and encrypt our plaintext
let lwe_ciphertext_in: LweCiphertextOwned<u64> = allocate_and_encrypt_new_lwe_ciphertext(
&small_lwe_sk,
plaintext,
lwe_modular_std_dev,
&mut encryption_generator,
);
// Compute a cleartext multiplication by 2
let mut cleartext_multiplication_ct = lwe_ciphertext_in.clone();
println!("Performing cleartext multiplication...");
lwe_ciphertext_cleartext_mul(
&mut cleartext_multiplication_ct,
&lwe_ciphertext_in,
Cleartext(2),
);
// Decrypt the cleartext multiplication result
let cleartext_multipliation_plaintext: Plaintext<u64> =
decrypt_lwe_ciphertext(&small_lwe_sk, &cleartext_multiplication_ct);
// Create a SignedDecomposer to perform the rounding of the decrypted plaintext
// We pass a DecompositionBaseLog of 5 and a DecompositionLevelCount of 1 indicating we want to
// round the 5 MSB, 1 bit of padding plus our 4 bits of message
let signed_decomposer =
SignedDecomposer::new(DecompositionBaseLog(5), DecompositionLevelCount(1));
// Round and remove our encoding
let cleartext_multiplication_result: u64 =
signed_decomposer.closest_representable(cleartext_multipliation_plaintext.0) / delta;
println!("Checking result...");
assert_eq!(6, cleartext_multiplication_result);
println!(
"Cleartext multiplication result is correct! \
Expected 6, got {cleartext_multiplication_result}"
);
// Now we will use a PBS to compute the same multiplication, it is NOT the recommended way of
// doing this operation in terms of performance as it's much more costly than a multiplication
// with a cleartext, however it resets the noise in a ciphertext to a nominal level and allows
// to evaluate arbitrary functions so depending on your use case it can be a better fit.
// Here we will define a helper function to generate an accumulator for a PBS
fn generate_accumulator<F>(
polynomial_size: PolynomialSize,
glwe_size: GlweSize,
message_modulus: usize,
delta: u64,
f: F,
) -> GlweCiphertextOwned<u64>
where
F: Fn(u64) -> u64,
{
// N/(p/2) = size of each block, to correct noise from the input we introduce the notion of
// box, which manages redundancy to yield a denoised value for several noisy values around
// a true input value.
let box_size = polynomial_size.0 / message_modulus;
// Create the accumulator
let mut accumulator_u64 = vec![0_u64; polynomial_size.0];
// Fill each box with the encoded denoised value
for i in 0..message_modulus {
let index = i * box_size;
accumulator_u64[index..index + box_size]
.iter_mut()
.for_each(|a| *a = f(i as u64) * delta);
}
let half_box_size = box_size / 2;
// Negate the first half_box_size coefficients to manage negacyclicity and rotate
for a_i in accumulator_u64[0..half_box_size].iter_mut() {
*a_i = (*a_i).wrapping_neg();
}
// Rotate the accumulator
accumulator_u64.rotate_left(half_box_size);
let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
let accumulator =
allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &accumulator_plaintext);
accumulator
}
// Generate the accumulator for our multiplication by 2 using a simple closure
let accumulator: GlweCiphertextOwned<u64> = generate_accumulator(
polynomial_size,
glwe_dimension.to_glwe_size(),
message_modulus as usize,
delta,
|x: u64| 2 * x,
);
// Allocate the LweCiphertext to store the result of the PBS
let mut pbs_multiplication_ct =
LweCiphertext::new(0u64, big_lwe_sk.lwe_dimension().to_lwe_size());
println!("Computing PBS...");
programmable_bootstrap_lwe_ciphertext(
&lwe_ciphertext_in,
&mut pbs_multiplication_ct,
&accumulator,
&fourier_bsk,
);
// Decrypt the PBS multiplication result
let pbs_multipliation_plaintext: Plaintext<u64> =
decrypt_lwe_ciphertext(&big_lwe_sk, &pbs_multiplication_ct);
// Round and remove our encoding
let pbs_multiplication_result: u64 =
signed_decomposer.closest_representable(pbs_multipliation_plaintext.0) / delta;
println!("Checking result...");
assert_eq!(6, pbs_multiplication_result);
println!(
"Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
);
}
```

View File

@@ -1,6 +1,6 @@
# Contribute
# Contributing
There are two ways to contribute to **TFHE-rs**:
* you can open issues to report bugs and typos and to suggest ideas
* you can ask to become an official contributor by emailing hello@zama.ai. Only approved contributors can end pull requests, so please make sure to get in touch before you do!
* you can ask to become an official contributor by emailing hello@zama.ai. Only approved contributors can end pull requests, so please make sure to get in touch before you do!

View File

@@ -1,44 +1,44 @@
# Benchmarks
Due to their nature, homomorphic operations are obviously slower than their clear equivalent. In what follows, some timings are exposed for the basic operations. For completeness, some benchmarks of other libraries are also given.
Due to their nature, homomorphic operations are obviously slower than their clear equivalent. In what follows, some timings are exposed for basic operations. For completeness, some benchmarks of other libraries are also given.
All the benchmarks had been launched on an AWS m6i.metal with the following specifications:
Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
All the benchmarks had been launched on an AWS m6i.metal with the following specifications: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
## Booleans
## Boolean
This measures the execution time of a single binary boolean gate.
### thfe.rs::booleans
### tfhe.rs::boolean.
| Parameter set | concrete-fft | concrete-fft + avx512 |
| --- | --- | --- |
| DEFAULT_PARAMETERS | 8.8ms | 6.8ms |
| TFHE_LIB_PARAMETERS | 13.6ms | 10.9ms |
| Parameter set | concrete-fft | concrete-fft + avx512 |
| --------------------- | ------------ | --------------------- |
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
### tfhe-lib
### tfhe-lib.
| Parameter set | fftw | spqlios-fma|
| --- | --- | --- |
| default_128bit_gate_bootstrapping_parameters | 28.9ms | 15.7ms |
| Parameter set | fftw | spqlios-fma |
| ------------------------------------------------ | ------ | ----------- |
| default\_128bit\_gate\_bootstrapping\_parameters | 28.9ms | 15.7ms |
### OpenFHE
### OpenFHE.
| Parameter set | GINX | GINX (Intel HEXL) |
| --- | --- | --- |
| STD_128 | 172ms | 78ms |
| MEDIUM | 113ms | 50.2ms |
| Parameter set | GINX | GINX (Intel HEXL) |
| ------------- | ----- | ----------------- |
| STD\_128 | 172ms | 78ms |
| MEDIUM | 113ms | 50.2ms |
## Shortints
This measures the execution time for some operations and some parameter sets of shortints.
## Shortint
This measures the execution time for some operations and some parameter sets of shortint.
### tfhe.rs::shortint.
### thfe.rs::shortint
This uses the concrete-fft + avx512 configuration.
| Parameter set | unchecked_add | unchecked_mul_lsb | keyswitch_programmable_bootstrap |
| --- | --- | --- | --- |
| PARAM_MESSAGE_1_CARRY_1 | 337 ns | 10.1 ms | 9.91 ms |
| PARAM_MESSAGE_2_CARRY_2 | 407 ns | 21.7 ms | 21.4 ms |
| PARAM_MESSAGE_3_CARRY_3 | 3.06 µs | 161 ms | 159 ms |
| PARAM_MESSAGE_4_CARRY_4 | 11.7 µs | 1.03 s | 956 ms |
| 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 | 134 ms |
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 945 ms |

View File

@@ -4,9 +4,8 @@
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
```toml
tfhe = { version = "0.1.0", features = [ "boolean", "shortint", "x86_64-unix" ] }
tfhe = { version = "0.1.7", features = [ "boolean", "shortint", "x86_64-unix" ] }
```
## Choosing your features
@@ -17,36 +16,32 @@ tfhe = { version = "0.1.0", features = [ "boolean", "shortint", "x86_64-unix" ]
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 |
| Kind | Features | Type(s) |
| --------- | ---------- | ----------------------- |
| Booleans | `boolean` | Booleans |
| ShortInts | `shortint` | Short unsigned integers |
### Serialization.
The different data types and keys exposed by the crate can be serialized / deserialized.
More information can be found [here](../Booleans/serialization.md) for Booleans and [here](../shortint/serialization.md) for shortint.
More information can be found [here](../Boolean/serialization.md) for boolean and [here](../shortint/serialization.md) for shortint.
## Supported platforms
TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (x86 with `RDSEED`
instruction).
TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (x86 with `RDSEED` instruction).
| OS | x86 | aarch64 |
| --------- | ------------- |------------------|
| Linux | `x86_64-unix` | `aarch64-unix`* |
| macOS | `x86_64-unix` | `aarch64-unix`* |
| Windows | `x86_64` | Unsupported |
| OS | x86 | aarch64 |
| ------- | ------------- | ---------------- |
| Linux | `x86_64-unix` | `aarch64-unix`\* |
| macOS | `x86_64-unix` | `aarch64-unix`\* |
| Windows | `x86_64` | Unsupported |
{% hint style="info" %}
Users who have ARM devices can use `TFHE-rs` by compiling using the
`nightly` toolchain.
Users who have ARM devices can use `TFHE-rs` by compiling using the `nightly` toolchain.
{% endhint %}
### Using TFHE-rs with nightly toolchain
### Using TFHE-rs with nightly toolchain.
First, install the needed Rust toolchain:

View File

@@ -2,52 +2,43 @@
## Boolean
The list of supported operations by the homomorphic booleans is:
The list of supported operations by the homomorphic Booleans is:
|Operation Name | type |
| ------ | ------ |
| `not` | Unary |
| `and` | Binary |
| `or` | Binary |
| `xor` | Binary |
| `nor` | Binary |
| `xnor` | Binary |
| `cmux` | Ternary |
A walk-through using homomorphic Booleans can be found [here](../Booleans/tutorial.md).
| Operation Name | type |
| -------------- | ------- |
| `not` | Unary |
| `and` | Binary |
| `or` | Binary |
| `xor` | Binary |
| `nor` | Binary |
| `xnor` | Binary |
| `cmux` | Ternary |
A walk-through using homomorphic Booleans can be found [here](../Boolean/tutorial.md).
## ShortInt
In TFHE-rs, the shortints represent short unsigned integers encoded over 8 bits maximum. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
In TFHE-rs, shortint represents short unsigned integers encoded over a maximum of 8 bits. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
The list of supported operations is:
| Operation name | Type |
|--------------- | ------ |
| Negation | Unary |
| Addition | Binary |
| Subtraction | Binary |
| Multiplication | Binary |
| Division* | Binary |
| Modular reduction | Binary |
| Comparisons | Binary |
| Left/Right Shift | Binary |
| And | Binary |
| Or | Binary |
| Xor | Binary |
| Exact Function Evaluation | Unary/Binary |
| Operation name | Type |
| ------------------------- | ------------ |
| Negation | Unary |
| Addition | Binary |
| Subtraction | Binary |
| Multiplication | Binary |
| Division\* | Binary |
| Modular reduction | Binary |
| Comparisons | Binary |
| Left/Right Shift | Binary |
| And | Binary |
| Or | Binary |
| Xor | Binary |
| Exact Function Evaluation | Unary/Binary |
{% hint style="info" %}
\* The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns 0.
{% endhint %}
A walk-through example can be found [here](../shortint/tutorial.md) and more examples and
explanations can be found [here](../shortint/operations.md)
A walk-through example can be found [here](../shortint/tutorial.md), and more examples and explanations can be found [here](../shortint/operations.md).[ ](../shortint/operations.md)

View File

@@ -1,25 +1,21 @@
# Quick start
# Quick Start
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans or short integers (named shortints in the rest of this documentation).
It allows one to execute a circuit on an **untrusted server** because both circuit inputs and outputs are kept **private**.
Data are indeed encrypted on the client side, before being sent to the server. On the server side every computation is performed on ciphertexts.
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans or short integers (named shortint in the rest of this documentation). It allows one to execute a circuit on an **untrusted server** because both circuit inputs and outputs are kept **private**. Data are indeed encrypted on the client side, before being sent to the server. On the server side, every computation is performed on ciphertexts.
The server however has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. She can then decrypt it with her `secret key`.
The server, however, has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. She can then decrypt it with her `secret key`.
## General method to write an homomorphic circuit program
## General method to write homomorphic circuit program
The overall process to write an homomorphic program is the same for both Boolean and shortint types. In a nutshell, the basic steps for using the TFHE-rs library are the following:
The overall process to write an homomorphic program is the same for both Boolean and short integers types.
In a nutshell, the basic steps for using the TFHE-rs library are the following:
- Choose a data type (Boolean or shortint)
- Import the library
- Create client and server keys
- Encrypt data with the client key
- Compute over encrypted data using the server key
- Decrypt data with the client key
* Choose a data type (Boolean or shortint)
* Import the library
* Create client and server keys
* Encrypt data with the client key
* Compute over encrypted data using the server key
* Decrypt data with the client key
### Boolean example
### Boolean example.
Here is an example to illustrate how the library can be used to evaluate a Boolean circuit:
@@ -47,9 +43,9 @@ fn main() {
}
```
### Shortint example
### Shortint example.
and here is a full example using shortints:
And here is a full example using shortint:
```rust
use tfhe::shortint::prelude::*;
@@ -76,5 +72,4 @@ fn main() {
}
```
The library is pretty simple to use, and can evaluate **homomorphic circuits of arbitrary length**. The description of the algorithms can be found in the [TFHE](https://doi.org/10.1007/s00145-019-09319-x) paper (also available as [ePrint 2018/421](https://ia.cr/2018/421)).

View File

@@ -1,74 +1,49 @@
# Cryptography & Security
# Security and Cryptography
# TFHE
## TFHE
TFHE-rs is a cryptographic library dedicated to Fully Homomorphic Encryption. As its name
suggests, it is based on the TFHE scheme.
TFHE-rs is a cryptographic library dedicated to Fully Homomorphic Encryption. As its name suggests, it is based on the TFHE scheme.
It is interesting to understand some basics about TFHE,
in order to apprehend where the limitations are coming from both
in terms of precision (number of bits used to represent the plaintext values)
and execution time (why TFHE operations are slower than native operations).
It is interesting to understand some basics about TFHE in order to comprehend where the limitations are coming from, both in terms of precision (number of bits used to represent the plaintext values) and execution time (why TFHE operations are slower than native operations).
# LWE Ciphertexts
## LWE ciphertexts
Although there are many kinds of ciphertexts in TFHE,
all the encrypted values in TFHE-rs are mainly stored as LWE ciphertexts.
Although there are many kinds of ciphertexts in TFHE, all the encrypted values in TFHE-rs are mainly stored as LWE ciphertexts.
The security of TFHE relies on the LWE problem which stands for Learning With Errors.
The problem is believed to be secure against quantum attacks.
The security of TFHE relies on the LWE problem, which stands for Learning With Errors. The problem is believed to be secure against quantum attacks.
An LWE Ciphertext is a collection of 32-bits or 64-bits unsigned integers.
Before encrypting a message in an LWE ciphertext, one needs to first encode it as a plaintext.
This is done by shifting the message to the most significant bits of the unsigned integer type used.
An LWE Ciphertext is a collection of 32-bit or 64-bit unsigned integers. Before encrypting a message in an LWE ciphertext, one must first encode it as a plaintext. This is done by shifting the message to the most significant bits of the unsigned integer type used.
Then, a little random value called noise is added to the least significant bits.
This noise (also called error for Learning With Errors) is crucial to the security of the ciphertext.
Then, a little random value called noise is added to the least significant bits. This noise (also called error for Learning With Errors) is crucial to the security of the ciphertext.
$$ plaintext = (\Delta * m) + e $$
$$plaintext = (\Delta * m) + e$$
![](../_static/lwe.png)
![](../\_static/lwe.png)
To go from a **plaintext** to a **ciphertext** one needs to encrypt the plaintext using a secret key.
To go from a **plaintext** to a **ciphertext,** one must encrypt the plaintext using a secret key.
An LWE secret key is a list of `n` random integers: $$S = (s_0, ..., s_n)$$.
$$n$$ is called the $$LweDimension$$
An LWE secret key is a list of `n` random integers: $$S = (s_0, ..., s_n)$$. $$n$$ is called the $$LweDimension$$
A LWE ciphertext, is composed of two parts:
- The mask $$(a_0, ..., a_{n-1})$$
- The body $$b$$
The mask of a _fresh_ ciphertext (one that is the result of an encryption
and not an operation such as ciphertext addition) is a list of `n` uniformly random values.
* The mask $$(a_0, ..., a_{n-1})$$
* The body $$b$$
The mask of a _fresh_ ciphertext (one that is the result of an encryption and not an operation, such as ciphertext addition) is a list of `n` uniformly random values.
The body is computed as follows:
$$ b = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext $$
$$b = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext$$
Now that the encryption scheme is defined, to illustrate why it is slower to compute over encrypted data,
let us show the example of the addition between ciphertexts.
Now that the encryption scheme is defined, to illustrate why it is slower to compute over encrypted data, let us show the example of the addition between ciphertexts.
To add two ciphertexts, we must add their $mask$ and $body$ as done below.
To add two ciphertexts, we must add their $mask$ and $body$, as is done below.
$$
ct_0 = (a_{0}, ..., a_{n}, b) \\
ct_1 = (a_{1}^{'}, ..., a_{n}^{'}, b^{'}) \\
ct_{2} = ct_0 + ct_1 \\
ct_{2} = (a_{0} + a_{0}^{'}, ..., a_{n} + a_{n}^{'}, b + b^{'})\\
b + b^{'} = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext + (\sum_{i = 0}^{n-1}{a_i^{'} * s_i}) + plaintext^{'}\\
b + b^{'} = (\sum_{i = 0}^{n-1}{(a_i + a_i^{'})* s_i}) + \Delta m + \Delta m^{'} + e + e^{'}\\
ct_0 = (a_{0}, ..., a_{n}, b) \\ ct_1 = (a_{1}^{'}, ..., a_{n}^{'}, b^{'}) \\ ct_{2} = ct_0 + ct_1 \\ ct_{2} = (a_{0} + a_{0}^{'}, ..., a_{n} + a_{n}^{'}, b + b^{'})\\ b + b^{'} = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext + (\sum_{i = 0}^{n-1}{a_i^{'} * s_i}) + plaintext^{'}\\ b + b^{'} = (\sum_{i = 0}^{n-1}{(a_i + a_i^{'})* s_i}) + \Delta m + \Delta m^{'} + e + e^{'}\\
$$
To add ciphertexts, it is sufficient to add their masks and bodies.
Instead of just adding 2 integers, one needs to add $$n + 1$$ elements.
The addition is an intuitive example to show the slowdown of FHE computation compared to plaintext
computation but other operations are far more expensive
(e.g., the computation of a lookup table using the Programmable Bootstrapping)
# Ciphertexts Operations
To add ciphertexts, it is sufficient to add their masks and bodies. Instead of just adding 2 integers, one needs to add $$n + 1$$ elements. The addition is an intuitive example to show the slowdown of FHE computation compared to plaintext computation, but other operations are far more expensive (e.g., the computation of a lookup table using the Programmable Bootstrapping).
## Understanding noise and padding
@@ -79,43 +54,40 @@ In FHE, there are two types of operations that can be applied to ciphertexts:
In FHE, the noise must be tracked and managed in order to guarantee the correctness of the computation.
Bootstrapping operations are used across the computation to decrease the noise in the ciphertexts, preventing it from tampering the message. The rest of the operations are called leveled because they do not need bootstrapping operations and thus are most of the time really fast.
Bootstrapping operations are used across the computation to decrease the noise in the ciphertexts, preventing it from tampering the message. The rest of the operations are called leveled because they do not need bootstrapping operations and, thus, are usually really fast.
The following sections explain the concept of noise and padding in ciphertexts.
### Noise
### Noise.
For it to be secure, LWE requires random noise to be added to the message at encryption time.
In TFHE, this random noise is drawn from a Centered Normal Distribution parameterized by a standard deviation. This standard deviation is a security parameter.
With all other security parameters set, the larger the standard deviation is, the more secure the encryption is.
In TFHE, this random noise is drawn from a Centered Normal Distribution parameterized by a standard deviation. This standard deviation is a security parameter. With all other security parameters set, the larger the standard deviation is, the more secure the encryption is.
In `TFHE-rs`, the noise is encoded in the least significant bits of the plaintexts. Each leveled computation will increase the noise, and thus if too many computations are done, the noise will eventually overflow onto the significant data bits of the message and lead to an incorrect result.
In `TFHE-rs`, the noise is encoded in the least significant bits of the plaintexts. Each leveled computation will increase the noise. Thus, if too many computations are performed, the noise will eventually overflow onto the significant data bits of the message and lead to an incorrect result.
The figure below illustrates this problem in case of an addition, where an extra bit of noise is incurred as a result.
![Noise overtaking on the plaintexts after homomorphic addition. Most Significant bits are on the left.](../_static/fig7.png)
![Noise overtaking the plaintexts after homomorphic addition. Most significant bits are on the left.](../\_static/fig7.png)
TFHE-rs offers the possibility to automatically manage the noise, by performing bootstrapping operations to reset the noise when needed.
TFHE-rs offers the ability to automatically manage noise by performing bootstrapping operations to reset the noise when needed.
### Padding.
### Padding
Since encoded values have a fixed precision, operating on them can sometimes produce results that are outside the original interval. To avoid losing precision or wrapping around the interval, TFHE-rs uses additional bits by defining bits of **padding** on the most significant bits.
Since encoded values have a fixed precision, operating on them can sometime produce results that are outside the original interval. To avoid losing precision or wrapping around the interval, TFHE-rs uses additional bits by defining bits of **padding** on the most significant bits.
As an example, consider adding two ciphertexts. Adding two values could end up outside the range of either ciphertext, and thus necessitate a carry, which would then be carried onto the first padding bit. In the figure below, each plaintext over 32 bits has one bit of padding on its left (i.e., the most significant bit). After the addition, the padding bit is no longer available, as it has been used in order for the carry. This is referred to as **consuming** bits of padding. Since no padding is left, there is no guarantee that further additions would yield correct results.
As an example, consider adding two ciphertexts. Adding two values could en up outside the range of either ciphertexts, and thus necessitate a carry, which would then be carried onto the first padding bit. In the figure below, each plaintext over 32 bits has one bit of padding on its left \(i.e., the most significant bit\). After the addition, the padding bit is no longer available, as it has been used in order for the carry. This is referred to as **consuming** bits of padding. Since no padding is left, there is no guarantee that additional additions would yield correct results.
![](../_static/fig6.png)
![](../\_static/fig6.png)
If you would like to know more about TFHE, you can find more information in our [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
## Security
### Security.
By default, the cryptographic parameters provided by `TFHE-rs` ensure at least 128 bits of security.
The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
By default, the cryptographic parameters provided by `TFHE-rs` ensure at least 128 bits of security. The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
For all sets of parameters, the error probability when computing a univariate function over one ciphertext is $$2^{-40}$$. Note that univariate functions might be performed when arithmetic functions are computed (for instance, the multiplication of two ciphertexts).
## Public key encryption
### Public key encryption.
In public key encryption, the public key consists in providing a given number of message encrypting the value 0. By setting the number of encryptions of 0 in the public key at $$m = \lceil (n+1) \log(q) \rceil + \lambda$$, where $$n$$ is the LWE dimension, $$q$$ is the ciphertext modulus and $$\lambda$$ is the number of security bits. In a nutshell, this construction is secure due to the left-over-hash lemma, which is essentially related to the impossibility of breaking the underlying multiple subset sum problem. By using this formula, this guarantees both a high density subset sum and an exponentially large number of possible associated random vectors per LWE sample (a,b)
In public key encryption, the public key contains a given number of ciphertexts all encrypting the value 0. By setting the number of encryptions of 0 in the public key at $$m = \lceil (n+1) \log(q) \rceil + \lambda$$, where $$n$$ is the LWE dimension, $$q$$ is the ciphertext modulus, and $$\lambda$$ is the number of security bits. In a nutshell, this construction is secure due to the leftover hash lemma, which is essentially related to the impossibility of breaking the underlying multiple subset sum problem. By using this formula, this guarantees both a high-density subset sum and an exponentially large number of possible associated random vectors per LWE sample (a,b).

View File

@@ -0,0 +1,115 @@
# Tutorial
## Using the JS on WASM API
Welcome to this TFHE-rs JS on WASM API tutorial!
TFHE-rs uses WASM to expose a JS binding to the client-side primitives, like key generation and encryption, of the Boolean and shortint modules.
There are several limitations at this time. Due to a lack of threading support in WASM, key
generation can be too slow to be practical for bigger parameter sets.
Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory space of WASM.
This means that some parameters sets are virtually unusable.
## First steps using TFHE-rs JS on WASM API
### Setting-up TFHE-rs JS on WASM API for use in nodejs programs.
To build the JS on WASM bindings for TFHE-rs, you will first need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.65) [`rust toolchain`](https://rustup.rs/).
Then, in a shell run the following to clone the TFHE-rs repo (one may want to checkout a
specific tag, here the default branch is used for the build):
```shell
$ git clone https://github.com/zama-ai/tfhe-rs.git
Cloning into 'tfhe-rs'...
...
Resolving deltas: 100% (3866/3866), done.
$ cd tfhe-rs
$ cd tfhe
$ rustup run wasm-pack build --release --target=nodejs --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
[INFO]: Compiling to Wasm...
...
[INFO]: :-) Your wasm pkg is ready to publish at ...
```
The command above targets nodejs. A binding for a web browser can be generated as well using
`--target=web`. This use case will not be discussed in this tutorial.
Both Boolean and shortint features are enabled here but it's possible to use one without the other.
After the build, a new directory ***pkg*** is present in the `tfhe` directory.
```shell
$ ls pkg
LICENSE index.html package.json tfhe.d.ts tfhe.js tfhe_bg.txt tfhe_bg.wasm tfhe_bg.wasm.d.ts
$
```
### Commented code to generate keys for shortint and encrypt a ciphertext
{% hint style=“warning” %}
Be sure to update the path of the required clause in the example below for the TFHE
package that was just built.
{% endhint %}
```javascript
// Here import assert to check the decryption went well and panic otherwise
const assert = require('node:assert').strict;
// Import the Shortint module from the TFHE-rs package generated earlier
const { Shortint } = require("/path/to/built/tfhe/pkg");
function shortint_example() {
// Get pre-defined parameters from the shortint module to manage messages with 4 bits of useful
// information in total (2 bits of "message" and 2 bits of "carry")
let params = Shortint.get_parameters(2, 2);
// Create a new secret ClientKey, this must not be shared
console.log("Generating client keys...")
let cks = Shortint.new_client_key(params);
// Encrypt 3 in a ciphertext
console.log("Encrypting 3...")
let ct = Shortint.encrypt(cks, BigInt(3));
// Demonstrate ClientKey serialization (for example saving it on disk on the user device)
let serialized_cks = Shortint.serialize_client_key(cks);
// Deserialization
let deserialized_cks = Shortint.deserialize_client_key(serialized_cks);
// Demonstrate ciphertext serialization to send over the network
let serialized_ct = Shortint.serialize_ciphertext(ct);
// Deserialize a ciphertext received over the network for example
let deserialized_ct = Shortint.deserialize_ciphertext(serialized_ct);
// Decrypt with the deserialized objects
console.log("Decrypting ciphertext...")
let decrypted = Shortint.decrypt(deserialized_cks, deserialized_ct);
// Check decryption works as expected
assert.deepStrictEqual(decrypted, BigInt(3));
console.log("Decryption successful!")
// Generate public evaluation keys, also called ServerKey
console.log("Generating compressed ServerKey...")
let sks = Shortint.new_compressed_server_key(cks);
// Can be serialized to send over the network to the machine doing the evaluation
let serialized_sks = Shortint.serialize_compressed_server_key(sks);
let deserialized_sks = Shortint.deserialize_compressed_server_key(serialized_sks);
console.log("All done!")
}
shortint_example();
```
The `example.js` script can then be run using [`node`](https://nodejs.org/) like so:
```shell
$ node example.js
Generating client keys...
Encrypting 3...
Decrypting ciphertext...
Decryption successful!
Generating compressed ServerKey...
All done!
$
```

View File

@@ -1,54 +1,44 @@
# How Shortint are represented
# Operations
## How shortint is represented
In `shortint`, the encrypted data is stored in an LWE ciphertext.
Conceptually, the message stored in an LWE ciphertext, is divided into
a **carry buffer** and a **message buffer**.
Conceptually, the message stored in an LWE ciphertext is divided into a **carry buffer** and a **message buffer**.
![](../\_static/ciphertext-representation.svg)
![](../\_static/ciphertext-representation.png)
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus: the exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in incorrect outputs.
In order to ensure the computation correctness, we keep track of the maximum value encrypted in a
ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to resume safely the computations. Therefore, in `shortint` the carry modulus is mainly considered as a means to do more computations.
In order to ensure the correctness of the computation, we keep track of the maximum value encrypted in a ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to safely resume the computations. Therefore, in `shortint` the carry modulus is mainly considered as a means to do more computations.
# Types of operations
## Types of operations
The operations available via a `ServerKey` may come in different variants:
- operations that take their inputs as encrypted values.
- scalar operations take at least one non-encrypted value as input.
* operations that take their inputs as encrypted values.
* scalar operations that take at least one non-encrypted value as input.
For example, the addition has both variants:
- `ServerKey::unchecked_add` which takes two encrypted values and adds them.
- `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the
so-called scalar) and adds them.
* `ServerKey::unchecked_add` which takes two encrypted values and adds them.
* `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the so-called scalar) and adds them.
Each operation may come in different 'flavors':
- `unchecked`: Always does the operation, without checking if the result may exceed the capacity of
the plaintext space. Using this operations might have an impact on the correctness of the
following operations;
- `checked`: Checks are done before computing the operation, returning an error if operation
cannot be done safely;
- `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation
will clear the carry modulus to make the operation possible.
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely;
* `smart`: Always does the operation - if the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible.
Not all operations have these 3 flavors, as some of them are implemented in a way
that the operation is always possible without ever exceeding the plaintext space capacity.
Not all operations have these 3 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
## How to use operation types
# How to use operation types
Let's try to do a circuit evaluation using the different flavours of operations we already introduced.
For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly.
Otherwise, the `checked` and `smart` are the best options.
As an example, let's do a scalar multiplication, a subtraction and a multiplication.
Let's try to do a circuit evaluation using the different flavours of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise, the `checked` and `smart` are the best options.
As an example, let's do a scalar multiplication, a subtraction, and a multiplication.
```rust
use tfhe::shortint::prelude::*;
@@ -78,10 +68,9 @@ fn main() {
}
```
During this computation the carry buffer has been overflowed and as all the operations were `unchecked` the output
may be incorrect.
During this computation, the carry buffer has been overflowed and, as all the operations were `unchecked`, the output may be incorrect.
If we redo this same circuit but using the `checked` flavour, a panic will occur.
If we redo this same circuit with the `checked` flavour, a panic will occur.
```rust
use tfhe::shortint::prelude::*;
@@ -123,12 +112,9 @@ fn main() {
}
```
Therefore, the `checked` flavour permits to manually manage the overflow of the carry buffer
by raising an error if the correctness is not guaranteed.
Therefore, the `checked` flavour permits manual management of the overflow of the carry buffer by raising an error if the correctness is not guaranteed.
Lastly, using the `smart` flavour will output the correct result all the time. However, the
computation may be slower
as the carry buffer may be cleaned during the computations.
Lastly, using the `smart` flavour will output the correct result all the time. However, the computation may be slower as the carry buffer may be cleaned during the computations.
```rust
use tfhe::shortint::prelude::*;
@@ -157,61 +143,60 @@ fn main() {
assert_eq!(output, ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64);
}
```
#List of available operations
\#List of available operations
{% hint style="warning" %}
Currently, certain operations can only be used if the parameter set chosen is compatible with the
bivariate programmable bootstrapping, meaning the carry buffer is larger or equal than the
message buffer. These operations are marked with a star (*).
Currently, certain operations can only be used if the parameter set chosen is compatible with the bivariate programmable bootstrapping, meaning the carry buffer is larger than or equal to the message buffer. These operations are marked with a star (\*).
{% endhint %}
The list of implemented operations for shortint is:
The list of implemented operations for shortints is:
- addition between two ciphertexts
- addition between a ciphertext and an unencrypted scalar
- comparisons `<`, `<=`, `>`, `>=`, `==` between a ciphertext and an unencrypted scalar
- division of a ciphertext by an unencrypted scalar
- LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
- multiplication of a ciphertext by an unencrypted scalar
- bitwise shift `<<`, `>>`
- subtraction of a ciphertext by another ciphertext
- subtraction of a ciphertext by an unencrypted scalar
- negation of a ciphertext
- bitwise and, or and xor (*)
- comparisons `<`, `<=`, `>`, `>=`, `==` between two ciphertexts (*)
- division between two ciphertexts (*)
- MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (*)
* addition between two ciphertexts
* addition between a ciphertext and an unencrypted scalar
* comparisons `<`, `<=`, `>`, `>=`, `==` between a ciphertext and an unencrypted scalar
* division of a ciphertext by an unencrypted scalar
* LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
* multiplication of a ciphertext by an unencrypted scalar
* bitwise shift `<<`, `>>`
* subtraction of a ciphertext by another ciphertext
* subtraction of a ciphertext by an unencrypted scalar
* negation of a ciphertext
* bitwise and, or and xor (\*)
* comparisons `<`, `<=`, `>`, `>=`, `==` between two ciphertexts (\*)
* division between two ciphertexts (\*)
* MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (\*)
In what follows, some simple code examples are given.
## Public key encryption
TFHE-rs supports both private and public key encryption methods. Note that the only difference
between both lies into the encryption step: in this case, the encryption method is called using
`public_key` instead of `client_key`.
### Public key encryption.
TFHE-rs supports both private and public key encryption methods. Note that the only difference between both lies into the encryption step: in this case, the encryption method is called using `public_key` instead of `client_key`.
Here is a small example on how to use public encryption:
Here a small example on how to use public encryption:
```rust
use tfhe::boolean::prelude::*;
use tfhe::shortint::prelude::*;
fn main() {
// Generate the client key and the server key:
let (cks, mut sks) = gen_keys();
let (cks, _) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let pks = PublicKey::new(&cks);
let msg = 2;
// Encryption of one message:
let ct = pks.encrypt(true);
let ct = pks.encrypt(msg);
// Decryption:
let dec = cks.decrypt(&ct);
assert_eq!(true, dec);
assert_eq!(dec, msg);
}
```
In what follows, all examples are related to private key encryption.
## Arithmetic operations
Classical arithmetic operations are supported by shortints:
### Arithmetic operations.
Classical arithmetic operations are supported by shortint:
```rust
use tfhe::shortint::prelude::*;
@@ -238,15 +223,13 @@ fn main() {
}
```
### Bitwise operations
#### bitwise operations
Short homomorphic integer types support some bitwise operations.
A simple example on how to use these operations:
```rust
```rust
use tfhe::shortint::prelude::*;
fn main() {
@@ -271,14 +254,13 @@ fn main() {
}
```
### Comparisons
#### comparisons
Short homomorphic integer types support comparison operations.
A simple example on how to use these operations:
```rust
use tfhe::shortint::prelude::*;
fn main() {
@@ -303,14 +285,11 @@ fn main() {
}
```
### Univariate function evaluations
#### univariate function evaluations
A simple example on how to use this operation to homomorphically compute
the hamming weight (i.e., the number of bit equals to one) of an encrypted
number.
A simple example on how to use this operation to homomorphically compute the hamming weight (i.e., the number of bits equal to one) of an encrypted number.
```rust
use tfhe::shortint::prelude::*;
fn main() {
@@ -337,17 +316,13 @@ fn main() {
}
```
### Bi-variate function evaluations
#### bi-variate function evaluations
Using the shortint types offers the possibility to evaluate bi-variate functions, i.e.,
functions that takes two ciphertexts as input. This requires to choose a parameter set
such that the carry buffer size is at least as large as the message one i.e.,
PARAM_MESSAGE_X_CARRY_Y with X <= Y.
Using the shortint types offers the possibility to evaluate bi-variate functions, i.e., functions that takes two ciphertexts as input. This requires choosing a parameter set such that the carry buffer size is at least as large as the message one i.e., PARAM\_MESSAGE\_X\_CARRY\_Y with X <= Y.
In what follows, a simple code example:
Here is a simple code example:
```rust
use tfhe::shortint::prelude::*;
fn main() {
@@ -374,5 +349,3 @@ fn main() {
assert_eq!(output, (msg1.count_ones() as u64 + msg2.count_ones() as u64) % modulus);
}
```

View File

@@ -1,20 +1,17 @@
# Cryptographic parameters
All parameter sets provides at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equals to $$2^{-40}$$ when computing a programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting_started/security_and_cryptography.md) for more details about the encryption process).
# Cryptographic Parameters
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing a programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting\_started/security\_and\_cryptography.md) for more details about the encryption process).
## Parameters and message precision
`shortint` comes with sets of parameters that permit to use the functionalities of the library securely and efficiently. Each parameter sets is associated to the message and carry precisions. Thus, each key pair is entangled to precision.
`shortint` comes with sets of parameters that permit the use of the library functionalities securely and efficiently. Each parameter set is associated to the message and carry precisions. Thus, each key pair is entangled to precision.
The user is allowed to choose which set of parameters to use when creating the pair of keys.
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is:
`PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
In what follows, there is an example where keys are generated to have messages encoded over 3 bits i.e., computations are done modulus $$2^3 = 8$$), with 3 bits of carry.
```rust
use tfhe::shortint::prelude::*;
@@ -33,21 +30,19 @@ fn main() {
## Impact of parameters on the operations
As shown [here](../getting_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
As shown [here](../getting\_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
### Generic bi-variate functions
### Generic bi-variate functions.
The computations of bi-variate functions is based on a trick *concatenating* two ciphertexts into one. In the case where the carry buffer is not at least as large as the message one, this trick is not working anymore. Then, many bi-variate operations, such as comparisons cannot be correctly computed anymore. The only exception concerns the multiplication.
The computations of bi-variate functions is based on a trick, _concatenating_ two ciphertexts into one. In the case where the carry buffer is not at least as large as the message one, this trick no longer works. Then, many bi-variate operations, such as comparisons cannot be correctly computed. The only exception concerns the multiplication.
### Multiplication
### Multiplication.
In the case of the multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication_algorithm#Quarter_square_multiplication). In order to correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM_MESSAGE_X_CARRY_Y with Y>=1). This method is in general slower than using the other one. Note that using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
In the case of the multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication\_algorithm#Quarter\_square\_multiplication). In order to correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM\_MESSAGE\_X\_CARRY\_Y with Y>=1). This method is, in general, slower than using the other one. Note that using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
## User-defined parameter sets
Beyond the predefined parameter sets, this is possible to define new parameter sets.
To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fulfill the
`Parameter` structure fields.
Beyond the predefined parameter sets, it is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `Parameter` structure fields.
For instance:
@@ -77,12 +72,3 @@ fn main() {
};
}
```

View File

@@ -1,11 +1,10 @@
# Serialization / Deserialization
# Serialization/Deserialization
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that does the computations.
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that performs the computations.
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework, serde's Serialize and Deserialize are implemented on tfhe::shortint's types.
To be able to serialize our data, we need to pick a [data format](https://serde.rs/#data-formats), for our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework. Serde's Serialize and Deserialize are then implemented on tfhe::shortint's types.
To be able to serialize our data, we need to pick a [data format](https://serde.rs/#data-formats). For our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
```toml
# Cargo.toml
@@ -15,7 +14,6 @@ To be able to serialize our data, we need to pick a [data format](https://serde.
bincode = "1.3.3"
```
```rust
// main.rs

View File

@@ -1,10 +1,13 @@
# Tutorial: Writing an homomorphic circuit using shortints
# Tutorial
# 1. Key Generation
## Writing an homomorphic circuit using shortint
## Key Generation
`tfhe::shortint` provides 2 key types:
- `ClientKey`
- `ServerKey`
* `ClientKey`
* `ServerKey`
The `ClientKey` is the key that encrypts and decrypts messages (integer values up to 8 bits here), thus this key is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
@@ -12,7 +15,6 @@ The `ServerKey` is the key that is used to actually do the FHE computations. It
To reflect that, computation/operation methods are tied to the `ServerKey` type.
```rust
use tfhe::shortint::prelude::*;
@@ -22,8 +24,7 @@ fn main() {
}
```
# 2. Encrypting values
## Encrypting values
Once the keys have been generated, the client key is used to encrypt data:
@@ -43,7 +44,7 @@ fn main() {
}
```
# 2 bis. Encrypting values using a public key
## Encrypting values using a public key
Once the keys have been generated, the client key is used to encrypt data:
@@ -52,23 +53,21 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(Parameters::default());
let (client_key, _) = gen_keys(Parameters::default());
let public_key = PublicKey::new(&client_key);
let msg1 = 1;
let msg2 = 0;
// We use the client key to encrypt two messages:
let ct_1 = public_key.encrypt(&server_key, msg1);
let ct_2 = public_key.encrypt(&server_key, msg2);
let ct_1 = public_key.encrypt(msg1);
let ct_2 = public_key.encrypt(msg2);
}
```
## Computing and decrypting
# 3. Computing and decrypting
With our `server_key`, and encrypted values, we can now do an addition
and then decrypt the result.
With our `server_key` and encrypted values, we can now do an addition and then decrypt the result.
```rust
use tfhe::shortint::prelude::*;

View File

@@ -0,0 +1,63 @@
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use tfhe::boolean::parameters::{DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
use tfhe::boolean::{client_key, server_key};
fn write_result(file: &mut File, name: &str, value: usize) {
let line = format!("{name},{value}\n");
let error_message = format!("cannot write {name} result into file");
file.write_all(line.as_bytes()).expect(&error_message);
}
fn client_server_key_sizes(results_file: &Path) {
let boolean_params_vec = vec![
(DEFAULT_PARAMETERS, "default"),
(TFHE_LIB_PARAMETERS, "tfhe_lib"),
];
File::create(results_file).expect("create results file failed");
let mut file = OpenOptions::new()
.append(true)
.open(results_file)
.expect("cannot open results file");
println!("Generating boolean (ClientKey, ServerKey)");
for (i, (params, name)) in boolean_params_vec.iter().enumerate() {
println!(
"Generating [{} / {}] : {}",
i + 1,
boolean_params_vec.len(),
name
);
let cks = client_key::ClientKey::new(params);
let sks = server_key::ServerKey::new(&cks);
let ksk_size = sks.key_switching_key_size_bytes();
write_result(&mut file, &format!("boolean_{}_{}", name, "ksk"), ksk_size);
println!(
"Element in KSK: {}, size in bytes: {}",
sks.key_switching_key_size_elements(),
ksk_size,
);
let bsk_size = sks.bootstrapping_key_size_bytes();
write_result(&mut file, &format!("boolean_{}_{}", name, "bsk"), bsk_size);
println!(
"Element in BSK: {}, size in bytes: {}",
sks.bootstrapping_key_size_elements(),
bsk_size,
);
}
}
fn main() {
let work_dir = std::env::current_dir().unwrap();
println!("work_dir: {}", std::env::current_dir().unwrap().display());
// Change workdir so that the location of the keycache matches the one for tests
let mut new_work_dir = work_dir;
new_work_dir.push("tfhe");
std::env::set_current_dir(new_work_dir).unwrap();
let results_file = Path::new("boolean_key_sizes.csv");
client_server_key_sizes(results_file)
}

View File

@@ -1,12 +1,15 @@
use tfhe::shortint::keycache::{FileStorage, NamedParam, PersistentStorage};
use tfhe::shortint::parameters::ALL_PARAMETER_VEC;
use tfhe::shortint::{gen_keys, ClientKey, ServerKey};
use tfhe::shortint::keycache::{NamedParam, KEY_CACHE, KEY_CACHE_WOPBS};
use tfhe::shortint::parameters::parameters_wopbs_message_carry::{
WOPBS_PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_3_CARRY_3,
WOPBS_PARAM_MESSAGE_4_CARRY_4,
};
use tfhe::shortint::parameters::{
Parameters, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_4_CARRY_4,
};
fn client_server_keys() {
let file_storage = FileStorage::new("keys/shortint/client_server".to_string());
println!("Generating (ClientKey, ServerKey)");
println!("Generating shortint (ClientKey, ServerKey)");
for (i, params) in ALL_PARAMETER_VEC.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}",
@@ -15,17 +18,41 @@ fn client_server_keys() {
params.name()
);
let keys: Option<(ClientKey, ServerKey)> = file_storage.load(params);
let _ = KEY_CACHE.get_from_param(params);
// Clear keys as we go to avoid filling the RAM
KEY_CACHE.clear_in_memory_cache()
}
if keys.is_some() {
continue;
}
const WOPBS_PARAMS: [(Parameters, Parameters); 4] = [
(PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_1_CARRY_1),
(PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_2_CARRY_2),
(PARAM_MESSAGE_3_CARRY_3, WOPBS_PARAM_MESSAGE_3_CARRY_3),
(PARAM_MESSAGE_4_CARRY_4, WOPBS_PARAM_MESSAGE_4_CARRY_4),
];
let client_server_keys = gen_keys(params);
file_storage.store(params, &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(),
);
let _ = KEY_CACHE_WOPBS.get_from_param((params_shortint, params_wopbs));
// Clear keys as we go to avoid filling the RAM
KEY_CACHE_WOPBS.clear_in_memory_cache()
}
}
fn main() {
let work_dir = std::env::current_dir().unwrap();
println!("work_dir: {}", std::env::current_dir().unwrap().display());
// Change workdir so that the location of the keycache matches the one for tests
let mut new_work_dir = work_dir;
new_work_dir.push("tfhe");
std::env::set_current_dir(new_work_dir).unwrap();
client_server_keys()
}

View File

@@ -0,0 +1,82 @@
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use tfhe::shortint::keycache::{NamedParam, KEY_CACHE};
use tfhe::shortint::parameters::{
PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2, PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
};
fn write_result(file: &mut File, name: &str, value: usize) {
let line = format!("{name},{value}\n");
let error_message = format!("cannot write {name} result into file");
file.write_all(line.as_bytes()).expect(&error_message);
}
fn client_server_key_sizes(results_file: &Path) {
let shortint_params_vec = vec![
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
];
File::create(results_file).expect("create results file failed");
let mut file = OpenOptions::new()
.append(true)
.open(results_file)
.expect("cannot open results file");
println!("Generating shortint (ClientKey, ServerKey)");
for (i, params) in shortint_params_vec.iter().enumerate() {
println!(
"Generating [{} / {}] : {}",
i + 1,
shortint_params_vec.len(),
params.name()
);
let keys = KEY_CACHE.get_from_param(*params);
// Client keys don't have public access to members, but the keys in there are small anyways
// let cks = keys.client_key();
let sks = keys.server_key();
let ksk_size = sks.key_switching_key_size_bytes();
write_result(
&mut file,
&format!("shortint_{}_ksk", params.name().to_lowercase()),
ksk_size,
);
println!(
"Element in KSK: {}, size in bytes: {}",
sks.key_switching_key_size_elements(),
ksk_size,
);
let bsk_size = sks.bootstrapping_key_size_bytes();
write_result(
&mut file,
&format!("shortint_{}_bsk", params.name().to_lowercase()),
bsk_size,
);
println!(
"Element in BSK: {}, size in bytes: {}",
sks.bootstrapping_key_size_elements(),
bsk_size,
);
// Clear keys as we go to avoid filling the RAM
KEY_CACHE.clear_in_memory_cache()
}
}
fn main() {
let work_dir = std::env::current_dir().unwrap();
println!("work_dir: {}", std::env::current_dir().unwrap().display());
// Change workdir so that the location of the keycache matches the one for tests
let mut new_work_dir = work_dir;
new_work_dir.push("tfhe");
std::env::set_current_dir(new_work_dir).unwrap();
let results_file = Path::new("shortint_key_sizes.csv");
client_server_key_sizes(results_file)
}

View File

@@ -0,0 +1,176 @@
const crypto = require('crypto');
const test = require('node:test');
const assert = require('node:assert').strict;
const { Boolean, Shortint, BooleanParameterSet } = require("../pkg");
function genRandomBigIntWithBytes(byteCount) {
return BigInt('0x' + crypto.randomBytes(byteCount).toString('hex'))
}
// Boolean tests
test('boolean_encrypt_decrypt', (t) => {
let params = Boolean.get_parameters(BooleanParameterSet.Default);
let cks = Boolean.new_client_key(params);
let ct = Boolean.encrypt(cks, true);
let serialized_cks = Boolean.serialize_client_key(cks);
let deserialized_cks = Boolean.deserialize_client_key(serialized_cks);
let serialized_ct = Boolean.serialize_ciphertext(ct);
let deserialized_ct = Boolean.deserialize_ciphertext(serialized_ct);
let decrypted = Boolean.decrypt(deserialized_cks, deserialized_ct);
assert.deepStrictEqual(decrypted, true);
let sks = Boolean.new_compressed_server_key(cks);
let serialized_sks = Boolean.serialize_compressed_server_key(sks);
let deserialized_sks = Boolean.deserialize_compressed_server_key(serialized_sks);
// No equality tests here, as wasm stores pointers which will always differ
});
test('boolean_compressed_encrypt_decrypt', (t) => {
let params = Boolean.get_parameters(BooleanParameterSet.Default);
let cks = Boolean.new_client_key(params);
let ct = Boolean.encrypt_compressed(cks, true);
let serialized_cks = Boolean.serialize_client_key(cks);
let deserialized_cks = Boolean.deserialize_client_key(serialized_cks);
let serialized_ct = Boolean.serialize_compressed_ciphertext(ct);
let deserialized_ct = Boolean.deserialize_compressed_ciphertext(serialized_ct);
let decompressed_ct = Boolean.decompress_ciphertext(deserialized_ct);
let decrypted = Boolean.decrypt(deserialized_cks, decompressed_ct);
assert.deepStrictEqual(decrypted, true);
});
test('boolean_public_encrypt_decrypt', (t) => {
let params = Boolean.get_parameters(BooleanParameterSet.Default);
let cks = Boolean.new_client_key(params);
let pk = Boolean.new_public_key(cks);
let serialized_pk = Boolean.serialize_public_key(pk);
let deserialized_pk = Boolean.deserialize_public_key(serialized_pk);
let ct = Boolean.encrypt_with_public_key(deserialized_pk, true);
let serialized_ct = Boolean.serialize_ciphertext(ct);
let deserialized_ct = Boolean.deserialize_ciphertext(serialized_ct);
let decrypted = Boolean.decrypt(cks, deserialized_ct);
assert.deepStrictEqual(decrypted, true);
});
test('boolean_deterministic_keygen', (t) => {
const TEST_LOOP_COUNT = 128;
let seed_high_bytes = genRandomBigIntWithBytes(8);
let seed_low_bytes = genRandomBigIntWithBytes(8);
let params = Boolean.get_parameters(BooleanParameterSet.Default);
let cks = Boolean.new_client_key_from_seed_and_parameters(seed_high_bytes, seed_low_bytes, params);
let other_cks = Boolean.new_client_key_from_seed_and_parameters(seed_high_bytes, seed_low_bytes, params);
for (let i = 0; i < TEST_LOOP_COUNT; i++) {
let ct_true = Boolean.encrypt(cks, true);
let decrypt_true_other = Boolean.decrypt(other_cks, ct_true);
assert.deepStrictEqual(decrypt_true_other, true);
let ct_false = Boolean.encrypt(cks, false);
let decrypt_false_other = Boolean.decrypt(other_cks, ct_false);
assert.deepStrictEqual(decrypt_false_other, false);
}
});
// Shortint tests
test('shortint_encrypt_decrypt', (t) => {
let params = Shortint.get_parameters(2, 2);
let cks = Shortint.new_client_key(params);
let ct = Shortint.encrypt(cks, BigInt(3));
let serialized_cks = Shortint.serialize_client_key(cks);
let deserialized_cks = Shortint.deserialize_client_key(serialized_cks);
let serialized_ct = Shortint.serialize_ciphertext(ct);
let deserialized_ct = Shortint.deserialize_ciphertext(serialized_ct);
let decrypted = Shortint.decrypt(deserialized_cks, deserialized_ct);
assert.deepStrictEqual(decrypted, BigInt(3));
let sks = Shortint.new_compressed_server_key(cks);
let serialized_sks = Shortint.serialize_compressed_server_key(sks);
let deserialized_sks = Shortint.deserialize_compressed_server_key(serialized_sks);
// No equality tests here, as wasm stores pointers which will always differ
});
test('shortint_compressed_encrypt_decrypt', (t) => {
let params = Shortint.get_parameters(2, 2);
let cks = Shortint.new_client_key(params);
let ct = Shortint.encrypt_compressed(cks, BigInt(3));
let serialized_cks = Shortint.serialize_client_key(cks);
let deserialized_cks = Shortint.deserialize_client_key(serialized_cks);
let serialized_ct = Shortint.serialize_compressed_ciphertext(ct);
let deserialized_ct = Shortint.deserialize_compressed_ciphertext(serialized_ct);
let decompressed_ct = Shortint.decompress_ciphertext(deserialized_ct);
let decrypted = Shortint.decrypt(deserialized_cks, decompressed_ct);
assert.deepStrictEqual(decrypted, BigInt(3));
});
test('shortint_public_encrypt_decrypt', (t) => {
let params = Shortint.get_parameters(2, 0);
let cks = Shortint.new_client_key(params);
let pk = Shortint.new_public_key(cks);
let ct = Shortint.encrypt_with_public_key(pk, BigInt(3));
let serialized_ct = Shortint.serialize_ciphertext(ct);
let deserialized_ct = Shortint.deserialize_ciphertext(serialized_ct);
let decrypted = Shortint.decrypt(cks, deserialized_ct);
assert.deepStrictEqual(decrypted, BigInt(3));
});
test('shortint_compressed_public_encrypt_decrypt', (t) => {
let params = Shortint.get_parameters(1, 1);
let cks = Shortint.new_client_key(params);
let pk = Shortint.new_compressed_public_key(cks);
let serialized_pk = Shortint.serialize_compressed_public_key(pk);
let deserialized_pk = Shortint.deserialize_compressed_public_key(serialized_pk);
let ct = Shortint.encrypt_with_compressed_public_key(deserialized_pk, BigInt(1));
let serialized_ct = Shortint.serialize_ciphertext(ct);
let deserialized_ct = Shortint.deserialize_ciphertext(serialized_ct);
let decrypted = Shortint.decrypt(cks, deserialized_ct);
assert.deepStrictEqual(decrypted, BigInt(1));
});
test('shortint_deterministic_keygen', (t) => {
const TEST_LOOP_COUNT = 128;
let seed_high_bytes = genRandomBigIntWithBytes(8);
let seed_low_bytes = genRandomBigIntWithBytes(8);
let params = Shortint.get_parameters(2, 2);
let cks = Shortint.new_client_key_from_seed_and_parameters(seed_high_bytes, seed_low_bytes, params);
let other_cks = Shortint.new_client_key_from_seed_and_parameters(seed_high_bytes, seed_low_bytes, params);
for (let i = 0; i < TEST_LOOP_COUNT; i++) {
let random_message = genRandomBigIntWithBytes(4) % BigInt(4);
let ct = Shortint.encrypt(cks, random_message);
let decrypt_other = Shortint.decrypt(other_cks, ct);
assert.deepStrictEqual(decrypt_other, random_message);
}
});

15
tfhe/katex-header.html Normal file
View File

@@ -0,0 +1,15 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/katex.min.css" integrity="sha384-KiWOvVjnN8qwAZbuQyWDIbfCLFhLXNETzBQjA/92pIowpC0d2O3nppDGQVgwd2nB" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/katex.min.js" integrity="sha384-0fdwu/T/EQMsQlrHCCHoH10pkPLlKA1jL5dFyUOvB3lfeT2540/2g6YgSi2BL14p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/contrib/auto-render.min.js" integrity="sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR" crossorigin="anonymous"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.body, {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "\\(", right: "\\)", display: false},
{left: "$", right: "$", display: false},
{left: "\\[", right: "\\]", display: true}
]
});
});
</script>

View File

@@ -2,7 +2,7 @@
//!
//! This module implements the ciphertext structure containing an encryption of a Boolean message.
use crate::core_crypto::prelude::*;
use crate::core_crypto::entities::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// A structure containing a ciphertext, meant to encrypt a Boolean message.
@@ -10,7 +10,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// It is used to evaluate a Boolean circuits homomorphically.
#[derive(Clone, Debug)]
pub enum Ciphertext {
Encrypted(LweCiphertext32),
Encrypted(LweCiphertextOwned<u32>),
Trivial(bool),
}
@@ -25,11 +25,9 @@ impl Serialize for Ciphertext {
where
S: Serializer,
{
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
match self {
Ciphertext::Encrypted(lwe) => {
let ciphertext = ser_eng.serialize(lwe).map_err(serde::ser::Error::custom)?;
let ciphertext = bincode::serialize(lwe).map_err(serde::ser::Error::custom)?;
SerializableCiphertext::Encrypted(ciphertext)
}
Ciphertext::Trivial(b) => SerializableCiphertext::Trivial(*b),
@@ -45,16 +43,55 @@ impl<'de> Deserialize<'de> for Ciphertext {
{
let thing = SerializableCiphertext::deserialize(deserializer)?;
let mut de_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
Ok(match thing {
SerializableCiphertext::Encrypted(data) => {
let lwe = de_eng
.deserialize(data.as_slice())
.map_err(serde::de::Error::custom)?;
let lwe =
bincode::deserialize(data.as_slice()).map_err(serde::de::Error::custom)?;
Self::Encrypted(lwe)
}
SerializableCiphertext::Trivial(b) => Self::Trivial(b),
})
}
}
/// A structure containing a compressed ciphertext, meant to encrypt a Boolean message.
///
/// It has to be decompressed before evaluating a Boolean circuit.
#[derive(Clone, Debug)]
pub struct CompressedCiphertext {
pub(crate) ciphertext: SeededLweCiphertext<u32>,
}
#[derive(Serialize, Deserialize)]
struct SerializableCompressedCiphertext {
data: Vec<u8>,
}
impl Serialize for CompressedCiphertext {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let data = bincode::serialize(&self.ciphertext).map_err(serde::ser::Error::custom)?;
let ser_ct = SerializableCompressedCiphertext { data };
ser_ct.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for CompressedCiphertext {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let thing = SerializableCompressedCiphertext::deserialize(deserializer)?;
let lwe = bincode::deserialize(&thing.data).map_err(serde::de::Error::custom)?;
Ok(Self { ciphertext: lwe })
}
}
impl From<CompressedCiphertext> for Ciphertext {
fn from(value: CompressedCiphertext) -> Self {
Self::Encrypted(value.ciphertext.decompress_into_lwe_ciphertext())
}
}

View File

@@ -3,10 +3,10 @@
//! This module implements the generation of the client' secret keys, together with the
//! encryption and decryption methods.
use crate::boolean::ciphertext::Ciphertext;
use crate::boolean::engine::{CpuBooleanEngine, WithThreadLocalEngine};
use crate::boolean::ciphertext::{Ciphertext, CompressedCiphertext};
use crate::boolean::engine::{BooleanEngine, WithThreadLocalEngine};
use crate::boolean::parameters::BooleanParameters;
use crate::core_crypto::prelude::*;
use crate::core_crypto::entities::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Debug, Formatter};
@@ -20,8 +20,8 @@ use std::fmt::{Debug, Formatter};
/// * `parameters` - the cryptographic parameter set.
#[derive(Clone)]
pub struct ClientKey {
pub(crate) lwe_secret_key: LweSecretKey32,
pub(crate) glwe_secret_key: GlweSecretKey32,
pub(crate) lwe_secret_key: LweSecretKeyOwned<u32>,
pub(crate) glwe_secret_key: GlweSecretKeyOwned<u32>,
pub(crate) parameters: BooleanParameters,
}
@@ -46,12 +46,11 @@ impl Debug for ClientKey {
}
impl ClientKey {
/// Encrypts a Boolean message using the client key.
/// Encrypt a Boolean message using the client key.
///
/// # Example
///
/// ```rust
/// # #[cfg(not(feature = "cuda"))]
/// # fn main() {
/// use tfhe::boolean::prelude::*;
///
@@ -65,19 +64,41 @@ impl ClientKey {
/// let dec = cks.decrypt(&ct);
/// assert_eq!(true, dec);
/// # }
/// # #[cfg(feature = "cuda")]
/// # fn main() {}
/// ```
pub fn encrypt(&self, message: bool) -> Ciphertext {
CpuBooleanEngine::with_thread_local_mut(|engine| engine.encrypt(message, self))
BooleanEngine::with_thread_local_mut(|engine| engine.encrypt(message, self))
}
/// Decrypts a ciphertext encrypting a Boolean message using the client key.
/// Encrypt a Boolean message using the client key returning a compressed ciphertext.
///
/// # Example
///
/// ```rust
/// # fn main() {
/// use tfhe::boolean::prelude::*;
///
/// // Generate the client key and the server key:
/// let (cks, mut sks) = gen_keys();
///
/// // Encryption of one message:
/// let ct = cks.encrypt_compressed(true);
///
/// let ct: Ciphertext = ct.into();
///
/// // Decryption:
/// let dec = cks.decrypt(&ct);
/// assert_eq!(true, dec);
/// # }
/// ```
pub fn encrypt_compressed(&self, message: bool) -> CompressedCiphertext {
BooleanEngine::with_thread_local_mut(|engine| engine.encrypt_compressed(message, self))
}
/// Decrypt a ciphertext encrypting a Boolean message using the client key.
///
/// # Example
///
/// ```rust
/// # #[cfg(not(feature = "cuda"))]
/// # fn main() {
/// use tfhe::boolean::prelude::*;
///
@@ -91,24 +112,16 @@ impl ClientKey {
/// let dec = cks.decrypt(&ct);
/// assert_eq!(true, dec);
/// # }
/// # #[cfg(feature = "cuda")]
/// # fn main() {}
/// ```
pub fn decrypt(&self, ct: &Ciphertext) -> bool {
CpuBooleanEngine::with_thread_local_mut(|engine| engine.decrypt(ct, self))
BooleanEngine::with_thread_local_mut(|engine| engine.decrypt(ct, self))
}
/// Allocates and generates a client key.
///
/// # Panic
///
/// This will panic when the "cuda" feature is enabled and the parameters
/// uses a GlweDimension > 1 (as it is not yet supported by the cuda backend).
/// Allocate and generate a client key.
///
/// # Example
///
/// ```rust
/// # #[cfg(not(feature = "cuda"))]
/// # fn main() {
/// use tfhe::boolean::client_key::ClientKey;
/// use tfhe::boolean::parameters::TFHE_LIB_PARAMETERS;
@@ -117,23 +130,9 @@ impl ClientKey {
/// // Generate the client key:
/// let cks = ClientKey::new(&TFHE_LIB_PARAMETERS);
/// # }
/// # #[cfg(feature = "cuda")]
/// # fn main() {
/// use tfhe::boolean::client_key::ClientKey;
/// use tfhe::boolean::parameters::GPU_DEFAULT_PARAMETERS;
/// use tfhe::boolean::prelude::*;
///
/// // Generate the client key:
/// let cks = ClientKey::new(&GPU_DEFAULT_PARAMETERS);}
/// ```
pub fn new(parameter_set: &BooleanParameters) -> ClientKey {
#[cfg(feature = "cuda")]
{
if parameter_set.glwe_dimension.0 > 1 {
panic!("the cuda backend does not support support GlweSize greater than one");
}
}
CpuBooleanEngine::with_thread_local_mut(|engine| engine.create_client_key(*parameter_set))
BooleanEngine::with_thread_local_mut(|engine| engine.create_client_key(*parameter_set))
}
}
@@ -149,14 +148,10 @@ impl Serialize for ClientKey {
where
S: Serializer,
{
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
let lwe_secret_key = ser_eng
.serialize(&self.lwe_secret_key)
.map_err(serde::ser::Error::custom)?;
let glwe_secret_key = ser_eng
.serialize(&self.glwe_secret_key)
.map_err(serde::ser::Error::custom)?;
let lwe_secret_key =
bincode::serialize(&self.lwe_secret_key).map_err(serde::ser::Error::custom)?;
let glwe_secret_key =
bincode::serialize(&self.glwe_secret_key).map_err(serde::ser::Error::custom)?;
SerializableClientKey {
lwe_secret_key,
@@ -174,14 +169,11 @@ impl<'de> Deserialize<'de> for ClientKey {
{
let thing =
SerializableClientKey::deserialize(deserializer).map_err(serde::de::Error::custom)?;
let mut de_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
Ok(Self {
lwe_secret_key: de_eng
.deserialize(thing.lwe_secret_key.as_slice())
lwe_secret_key: bincode::deserialize(thing.lwe_secret_key.as_slice())
.map_err(serde::de::Error::custom)?,
glwe_secret_key: de_eng
.deserialize(thing.glwe_secret_key.as_slice())
glwe_secret_key: bincode::deserialize(thing.glwe_secret_key.as_slice())
.map_err(serde::de::Error::custom)?,
parameters: thing.parameters,
})

View File

@@ -0,0 +1,462 @@
use crate::boolean::ciphertext::Ciphertext;
use crate::boolean::{ClientKey, PLAINTEXT_TRUE};
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
use crate::core_crypto::commons::generators::{DeterministicSeeder, EncryptionRandomGenerator};
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
use crate::core_crypto::entities::*;
use crate::core_crypto::fft_impl::math::fft::Fft;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::error::Error;
/// Memory used as buffer for the bootstrap
///
/// It contains contiguous chunk which is then sliced and converted
/// into core's View types.
#[derive(Default)]
struct Memory {
buffer: Vec<u32>,
}
impl Memory {
/// Return a tuple with buffers that matches the server key.
///
/// - The first element is the accumulator for bootstrap step.
/// - The second element is a lwe buffer where the result of the of the bootstrap should be
/// written
fn as_buffers(
&mut self,
server_key: &ServerKey,
) -> (GlweCiphertextView<'_, u32>, LweCiphertextMutView<'_, u32>) {
let num_elem_in_accumulator = server_key.bootstrapping_key.glwe_size().0
* server_key.bootstrapping_key.polynomial_size().0;
let num_elem_in_lwe = server_key
.bootstrapping_key
.output_lwe_dimension()
.to_lwe_size()
.0;
let total_elem_needed = num_elem_in_accumulator + num_elem_in_lwe;
let all_elements = if self.buffer.len() < total_elem_needed {
self.buffer.resize(total_elem_needed, 0u32);
self.buffer.as_mut_slice()
} else {
&mut self.buffer[..total_elem_needed]
};
let (accumulator_elements, lwe_elements) =
all_elements.split_at_mut(num_elem_in_accumulator);
{
let mut accumulator = GlweCiphertextMutView::from_container(
accumulator_elements,
server_key.bootstrapping_key.polynomial_size(),
);
accumulator.get_mut_mask().as_mut().fill(0u32);
accumulator.get_mut_body().as_mut().fill(PLAINTEXT_TRUE);
}
let accumulator = GlweCiphertextView::from_container(
accumulator_elements,
server_key.bootstrapping_key.polynomial_size(),
);
let lwe = LweCiphertextMutView::from_container(lwe_elements);
(accumulator, lwe)
}
}
/// A structure containing the server public key.
///
/// This server key data lives on the CPU.
///
/// The server key is generated by the client and is meant to be published: the client
/// sends it to the server so it can compute homomorphic Boolean circuits.
///
/// In more details, it contains:
/// * `bootstrapping_key` - a public key, used to perform the bootstrapping operation.
/// * `key_switching_key` - a public key, used to perform the key-switching operation.
#[derive(Clone)]
pub struct ServerKey {
pub(crate) bootstrapping_key: FourierLweBootstrapKeyOwned,
pub(crate) key_switching_key: LweKeyswitchKeyOwned<u32>,
}
impl ServerKey {
pub fn bootstrapping_key_size_elements(&self) -> usize {
self.bootstrapping_key.as_view().data().as_ref().len()
}
pub fn bootstrapping_key_size_bytes(&self) -> usize {
self.bootstrapping_key_size_elements() * std::mem::size_of::<concrete_fft::c64>()
}
pub fn key_switching_key_size_elements(&self) -> usize {
self.key_switching_key.as_ref().len()
}
pub fn key_switching_key_size_bytes(&self) -> usize {
self.key_switching_key_size_elements() * std::mem::size_of::<u64>()
}
}
/// A structure containing the compressed server public key.
///
/// This server key data lives on the CPU.
///
/// The server key is generated by the client and is meant to be published: the client
/// sends it to the server so it can compute homomorphic Boolean circuits.
///
/// In more details, it contains:
/// * `bootstrapping_key` - a public key, used to perform the bootstrapping operation.
/// * `key_switching_key` - a public key, used to perform the key-switching operation.
#[derive(Clone)]
pub struct CompressedServerKey {
pub(crate) bootstrapping_key: SeededLweBootstrapKeyOwned<u32>,
pub(crate) key_switching_key: SeededLweKeyswitchKeyOwned<u32>,
}
/// Perform ciphertext bootstraps on the CPU
pub(crate) struct Bootstrapper {
memory: Memory,
/// A structure containing two CSPRNGs to generate material for encryption like public masks
/// and secret errors.
///
/// The [`EncryptionRandomGenerator`] contains two CSPRNGs, one publicly seeded used to
/// generate mask coefficients and one privately seeded used to generate errors during
/// encryption.
pub(crate) encryption_generator: EncryptionRandomGenerator<ActivatedRandomGenerator>,
pub(crate) computation_buffers: ComputationBuffers,
pub(crate) seeder: DeterministicSeeder<ActivatedRandomGenerator>,
}
impl Bootstrapper {
pub fn new(seeder: &mut dyn Seeder) -> Self {
Bootstrapper {
memory: Default::default(),
encryption_generator: EncryptionRandomGenerator::<_>::new(seeder.seed(), seeder),
computation_buffers: Default::default(),
seeder: DeterministicSeeder::<_>::new(seeder.seed()),
}
}
pub(crate) fn new_server_key(
&mut self,
cks: &ClientKey,
) -> Result<ServerKey, Box<dyn std::error::Error>> {
let standard_bootstraping_key: LweBootstrapKeyOwned<u32> =
par_allocate_and_generate_new_lwe_bootstrap_key(
&cks.lwe_secret_key,
&cks.glwe_secret_key,
cks.parameters.pbs_base_log,
cks.parameters.pbs_level,
cks.parameters.glwe_modular_std_dev,
&mut self.encryption_generator,
);
// creation of the bootstrapping key in the Fourier domain
let mut fourier_bsk = FourierLweBootstrapKey::new(
standard_bootstraping_key.input_lwe_dimension(),
standard_bootstraping_key.glwe_size(),
standard_bootstraping_key.polynomial_size(),
standard_bootstraping_key.decomposition_base_log(),
standard_bootstraping_key.decomposition_level_count(),
);
let fft = Fft::new(standard_bootstraping_key.polynomial_size());
let fft = fft.as_view();
self.computation_buffers.resize(
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized_requirement(fft)
.unwrap()
.unaligned_bytes_required(),
);
let stack = self.computation_buffers.stack();
// Conversion to fourier domain
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized(
&standard_bootstraping_key,
&mut fourier_bsk,
fft,
stack,
);
// Convert the GLWE secret key into an LWE secret key:
let big_lwe_secret_key = cks.glwe_secret_key.clone().into_lwe_secret_key();
// creation of the key switching key
let ksk = allocate_and_generate_new_lwe_keyswitch_key(
&big_lwe_secret_key,
&cks.lwe_secret_key,
cks.parameters.ks_base_log,
cks.parameters.ks_level,
cks.parameters.lwe_modular_std_dev,
&mut self.encryption_generator,
);
Ok(ServerKey {
bootstrapping_key: fourier_bsk,
key_switching_key: ksk,
})
}
pub(crate) fn new_compressed_server_key(
&mut self,
cks: &ClientKey,
) -> Result<CompressedServerKey, Box<dyn std::error::Error>> {
#[cfg(not(feature = "__wasm_api"))]
let bootstrapping_key = par_allocate_and_generate_new_seeded_lwe_bootstrap_key(
&cks.lwe_secret_key,
&cks.glwe_secret_key,
cks.parameters.pbs_base_log,
cks.parameters.pbs_level,
cks.parameters.glwe_modular_std_dev,
&mut self.seeder,
);
#[cfg(feature = "__wasm_api")]
let bootstrapping_key = allocate_and_generate_new_seeded_lwe_bootstrap_key(
&cks.lwe_secret_key,
&cks.glwe_secret_key,
cks.parameters.pbs_base_log,
cks.parameters.pbs_level,
cks.parameters.glwe_modular_std_dev,
&mut self.seeder,
);
let big_lwe_secret_key = cks.glwe_secret_key.clone().into_lwe_secret_key();
// creation of the key switching key
let key_switching_key = allocate_and_generate_new_seeded_lwe_keyswitch_key(
&big_lwe_secret_key,
&cks.lwe_secret_key,
cks.parameters.ks_base_log,
cks.parameters.ks_level,
cks.parameters.lwe_modular_std_dev,
&mut self.seeder,
);
Ok(CompressedServerKey {
bootstrapping_key,
key_switching_key,
})
}
pub(crate) fn bootstrap(
&mut self,
input: &LweCiphertextOwned<u32>,
server_key: &ServerKey,
) -> Result<LweCiphertextOwned<u32>, Box<dyn Error>> {
let (accumulator, mut buffer_after_pbs) = self.memory.as_buffers(server_key);
let fourier_bsk = &server_key.bootstrapping_key;
let fft = Fft::new(fourier_bsk.polynomial_size());
let fft = fft.as_view();
self.computation_buffers.resize(
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<u64>(
fourier_bsk.glwe_size(),
fourier_bsk.polynomial_size(),
fft,
)
.unwrap()
.unaligned_bytes_required(),
);
let stack = self.computation_buffers.stack();
programmable_bootstrap_lwe_ciphertext_mem_optimized(
input,
&mut buffer_after_pbs,
&accumulator,
fourier_bsk,
fft,
stack,
);
Ok(LweCiphertext::from_container(
buffer_after_pbs.as_ref().to_owned(),
))
}
pub(crate) fn keyswitch(
&mut self,
input: &LweCiphertextOwned<u32>,
server_key: &ServerKey,
) -> Result<LweCiphertextOwned<u32>, Box<dyn Error>> {
// Allocate the output of the KS
let mut output = LweCiphertext::new(
0u32,
server_key
.bootstrapping_key
.input_lwe_dimension()
.to_lwe_size(),
);
keyswitch_lwe_ciphertext(&server_key.key_switching_key, input, &mut output);
Ok(output)
}
pub(crate) fn bootstrap_keyswitch(
&mut self,
mut ciphertext: LweCiphertextOwned<u32>,
server_key: &ServerKey,
) -> Result<Ciphertext, Box<dyn Error>> {
let (accumulator, mut buffer_lwe_after_pbs) = self.memory.as_buffers(server_key);
let fourier_bsk = &server_key.bootstrapping_key;
let fft = Fft::new(fourier_bsk.polynomial_size());
let fft = fft.as_view();
self.computation_buffers.resize(
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<u64>(
fourier_bsk.glwe_size(),
fourier_bsk.polynomial_size(),
fft,
)
.unwrap()
.unaligned_bytes_required(),
);
let stack = self.computation_buffers.stack();
// Compute a bootstrap
programmable_bootstrap_lwe_ciphertext_mem_optimized(
&ciphertext,
&mut buffer_lwe_after_pbs,
&accumulator,
fourier_bsk,
fft,
stack,
);
// Compute a key switch to get back to input key
keyswitch_lwe_ciphertext(
&server_key.key_switching_key,
&buffer_lwe_after_pbs,
&mut ciphertext,
);
Ok(Ciphertext::Encrypted(ciphertext))
}
}
impl From<CompressedServerKey> for ServerKey {
fn from(compressed_server_key: CompressedServerKey) -> Self {
let CompressedServerKey {
key_switching_key,
bootstrapping_key,
} = compressed_server_key;
let key_switching_key = key_switching_key.decompress_into_lwe_keyswitch_key();
let standard_bootstrapping_key = bootstrapping_key.decompress_into_lwe_bootstrap_key();
let mut bootstrapping_key = FourierLweBootstrapKeyOwned::new(
standard_bootstrapping_key.input_lwe_dimension(),
standard_bootstrapping_key.glwe_size(),
standard_bootstrapping_key.polynomial_size(),
standard_bootstrapping_key.decomposition_base_log(),
standard_bootstrapping_key.decomposition_level_count(),
);
convert_standard_lwe_bootstrap_key_to_fourier(
&standard_bootstrapping_key,
&mut bootstrapping_key,
);
Self {
key_switching_key,
bootstrapping_key,
}
}
}
#[derive(Serialize, Deserialize)]
struct SerializableServerKey {
pub bootstrapping_key: Vec<u8>,
pub key_switching_key: Vec<u8>,
}
impl Serialize for ServerKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let key_switching_key =
bincode::serialize(&self.key_switching_key).map_err(serde::ser::Error::custom)?;
let bootstrapping_key =
bincode::serialize(&self.bootstrapping_key).map_err(serde::ser::Error::custom)?;
SerializableServerKey {
key_switching_key,
bootstrapping_key,
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ServerKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let thing =
SerializableServerKey::deserialize(deserializer).map_err(serde::de::Error::custom)?;
let key_switching_key = bincode::deserialize(thing.key_switching_key.as_slice())
.map_err(serde::de::Error::custom)?;
let bootstrapping_key = bincode::deserialize(thing.bootstrapping_key.as_slice())
.map_err(serde::de::Error::custom)?;
Ok(Self {
bootstrapping_key,
key_switching_key,
})
}
}
#[derive(Serialize, Deserialize)]
struct SerializableCompressedServerKey {
pub bootstrapping_key: Vec<u8>,
pub key_switching_key: Vec<u8>,
}
impl Serialize for CompressedServerKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let key_switching_key =
bincode::serialize(&self.key_switching_key).map_err(serde::ser::Error::custom)?;
let bootstrapping_key =
bincode::serialize(&self.bootstrapping_key).map_err(serde::ser::Error::custom)?;
SerializableCompressedServerKey {
key_switching_key,
bootstrapping_key,
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for CompressedServerKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let thing = SerializableCompressedServerKey::deserialize(deserializer)
.map_err(serde::de::Error::custom)?;
let key_switching_key = bincode::deserialize(thing.key_switching_key.as_slice())
.map_err(serde::de::Error::custom)?;
let bootstrapping_key = bincode::deserialize(thing.bootstrapping_key.as_slice())
.map_err(serde::de::Error::custom)?;
Ok(Self {
bootstrapping_key,
key_switching_key,
})
}
}

View File

@@ -1,317 +0,0 @@
use crate::boolean::ciphertext::Ciphertext;
use crate::boolean::{ClientKey, PLAINTEXT_TRUE};
use crate::core_crypto::prelude::*;
use crate::seeders::new_seeder;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::error::Error;
use super::{BooleanServerKey, Bootstrapper};
/// Memory used as buffer for the bootstrap
///
/// It contains contiguous chunk which is then sliced and converted
/// into core's View types.
#[derive(Default)]
struct Memory {
elements: Vec<u32>,
}
impl Memory {
/// Returns a tuple with buffers that matches the server key.
///
/// - The first element is the accumulator for bootstrap step.
/// - The second element is a lwe buffer where the result of the of the bootstrap should be
/// written
fn as_buffers(
&mut self,
engine: &mut DefaultEngine,
server_key: &CpuBootstrapKey,
) -> (GlweCiphertextView32, LweCiphertextMutView32) {
let num_elem_in_accumulator = server_key
.bootstrapping_key
.glwe_dimension()
.to_glwe_size()
.0
* server_key.bootstrapping_key.polynomial_size().0;
let num_elem_in_lwe = server_key
.bootstrapping_key
.output_lwe_dimension()
.to_lwe_size()
.0;
let total_elem_needed = num_elem_in_accumulator + num_elem_in_lwe;
let all_elements = if self.elements.len() < total_elem_needed {
self.elements.resize(total_elem_needed, 0u32);
self.elements.as_mut_slice()
} else {
&mut self.elements[..total_elem_needed]
};
let (accumulator_elements, lwe_elements) =
all_elements.split_at_mut(num_elem_in_accumulator);
accumulator_elements
[num_elem_in_accumulator - server_key.bootstrapping_key.polynomial_size().0..]
.fill(PLAINTEXT_TRUE);
let accumulator = engine
.create_glwe_ciphertext_from(
&*accumulator_elements,
server_key.bootstrapping_key.polynomial_size(),
)
.unwrap();
let lwe = engine.create_lwe_ciphertext_from(lwe_elements).unwrap();
(accumulator, lwe)
}
}
/// A structure containing the server public key.
///
/// This server key data lives on the CPU.
///
/// The server key is generated by the client and is meant to be published: the client
/// sends it to the server so it can compute homomorphic Boolean circuits.
///
/// In more details, it contains:
/// * `bootstrapping_key` - a public key, used to perform the bootstrapping operation.
/// * `key_switching_key` - a public key, used to perform the key-switching operation.
#[derive(Clone)]
pub struct CpuBootstrapKey {
pub(super) standard_bootstraping_key: LweBootstrapKey32,
pub(super) bootstrapping_key: FftFourierLweBootstrapKey32,
pub(super) key_switching_key: LweKeyswitchKey32,
}
impl CpuBootstrapKey {}
impl BooleanServerKey for CpuBootstrapKey {
fn lwe_size(&self) -> LweSize {
self.bootstrapping_key.input_lwe_dimension().to_lwe_size()
}
}
/// Performs ciphertext bootstraps on the CPU
pub(crate) struct CpuBootstrapper {
memory: Memory,
engine: DefaultEngine,
fourier_engine: FftEngine,
}
impl CpuBootstrapper {
pub(crate) fn new_server_key(
&mut self,
cks: &ClientKey,
) -> Result<CpuBootstrapKey, Box<dyn std::error::Error>> {
// convert into a variance for rlwe context
let var_rlwe = Variance(cks.parameters.glwe_modular_std_dev.get_variance());
// creation of the bootstrapping key in the Fourier domain
let standard_bootstraping_key: LweBootstrapKey32 =
self.engine.generate_new_lwe_bootstrap_key(
&cks.lwe_secret_key,
&cks.glwe_secret_key,
cks.parameters.pbs_base_log,
cks.parameters.pbs_level,
var_rlwe,
)?;
let fourier_bsk = self
.fourier_engine
.convert_lwe_bootstrap_key(&standard_bootstraping_key)?;
// Convert the GLWE secret key into an LWE secret key:
let big_lwe_secret_key = self
.engine
.transform_glwe_secret_key_to_lwe_secret_key(cks.glwe_secret_key.clone())?;
// convert into a variance for lwe context
let var_lwe = Variance(cks.parameters.lwe_modular_std_dev.get_variance());
// creation of the key switching key
let ksk = self.engine.generate_new_lwe_keyswitch_key(
&big_lwe_secret_key,
&cks.lwe_secret_key,
cks.parameters.ks_level,
cks.parameters.ks_base_log,
var_lwe,
)?;
Ok(CpuBootstrapKey {
standard_bootstraping_key,
bootstrapping_key: fourier_bsk,
key_switching_key: ksk,
})
}
}
impl Default for CpuBootstrapper {
fn default() -> Self {
let engine =
DefaultEngine::new(new_seeder()).expect("Unexpectedly failed to create a core engine");
let fourier_engine = FftEngine::new(()).unwrap();
Self {
memory: Default::default(),
engine,
fourier_engine,
}
}
}
impl Bootstrapper for CpuBootstrapper {
type ServerKey = CpuBootstrapKey;
fn bootstrap(
&mut self,
input: &LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<LweCiphertext32, Box<dyn Error>> {
let (accumulator, mut buffer_lwe_after_pbs) =
self.memory.as_buffers(&mut self.engine, server_key);
// Need to be able to get view from &Lwe
let inner_data = self
.engine
.consume_retrieve_lwe_ciphertext(input.clone())
.unwrap();
let input = self
.engine
.create_lwe_ciphertext_from(inner_data.as_slice())
.unwrap();
self.fourier_engine.discard_bootstrap_lwe_ciphertext(
&mut buffer_lwe_after_pbs,
&input,
&accumulator,
&server_key.bootstrapping_key,
)?;
let data = self
.engine
.consume_retrieve_lwe_ciphertext(buffer_lwe_after_pbs)
.unwrap()
.to_vec();
let ct = self.engine.create_lwe_ciphertext_from(data)?;
Ok(ct)
}
fn keyswitch(
&mut self,
input: &LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<LweCiphertext32, Box<dyn Error>> {
// Allocate the output of the KS
let mut ct_ks = self
.engine
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])?;
self.engine.discard_keyswitch_lwe_ciphertext(
&mut ct_ks,
input,
&server_key.key_switching_key,
)?;
Ok(ct_ks)
}
fn bootstrap_keyswitch(
&mut self,
ciphertext: LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<Ciphertext, Box<dyn Error>> {
let (accumulator, mut buffer_lwe_after_pbs) =
self.memory.as_buffers(&mut self.engine, server_key);
let mut inner_data = self
.engine
.consume_retrieve_lwe_ciphertext(ciphertext)
.unwrap();
let input_view = self
.engine
.create_lwe_ciphertext_from(inner_data.as_slice())?;
self.fourier_engine.discard_bootstrap_lwe_ciphertext(
&mut buffer_lwe_after_pbs,
&input_view,
&accumulator,
&server_key.bootstrapping_key,
)?;
// Convert from a mut view to a view
let slice = self
.engine
.consume_retrieve_lwe_ciphertext(buffer_lwe_after_pbs)
.unwrap();
let buffer_lwe_after_pbs = self.engine.create_lwe_ciphertext_from(&*slice)?;
let mut output_view = self
.engine
.create_lwe_ciphertext_from(inner_data.as_mut_slice())?;
// Compute a key switch to get back to input key
self.engine.discard_keyswitch_lwe_ciphertext(
&mut output_view,
&buffer_lwe_after_pbs,
&server_key.key_switching_key,
)?;
let ciphertext = self.engine.create_lwe_ciphertext_from(inner_data)?;
Ok(Ciphertext::Encrypted(ciphertext))
}
}
#[derive(Serialize, Deserialize)]
struct SerializableCpuServerKey {
pub standard_bootstraping_key: Vec<u8>,
pub key_switching_key: Vec<u8>,
}
impl Serialize for CpuBootstrapKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
let key_switching_key = ser_eng
.serialize(&self.key_switching_key)
.map_err(serde::ser::Error::custom)?;
let standard_bootstraping_key = ser_eng
.serialize(&self.standard_bootstraping_key)
.map_err(serde::ser::Error::custom)?;
SerializableCpuServerKey {
key_switching_key,
standard_bootstraping_key,
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for CpuBootstrapKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let thing = SerializableCpuServerKey::deserialize(deserializer)
.map_err(serde::de::Error::custom)?;
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
let key_switching_key = ser_eng
.deserialize(thing.key_switching_key.as_slice())
.map_err(serde::de::Error::custom)?;
let standard_bootstraping_key = ser_eng
.deserialize(thing.standard_bootstraping_key.as_slice())
.map_err(serde::de::Error::custom)?;
let bootstrapping_key = FftEngine::new(())
.unwrap()
.convert_lwe_bootstrap_key(&standard_bootstraping_key)
.map_err(serde::de::Error::custom)?;
Ok(Self {
standard_bootstraping_key,
key_switching_key,
bootstrapping_key,
})
}
}

View File

@@ -1,240 +0,0 @@
use super::{BooleanServerKey, Bootstrapper, CpuBootstrapKey};
use crate::boolean::PLAINTEXT_TRUE;
use crate::core_crypto::prelude::*;
use crate::seeders::new_seeder;
use std::collections::BTreeMap;
use crate::boolean::ciphertext::Ciphertext;
pub(crate) struct CudaBootstrapKey {
bootstrapping_key: CudaFourierLweBootstrapKey32,
key_switching_key: CudaLweKeyswitchKey32,
}
impl BooleanServerKey for CudaBootstrapKey {
fn lwe_size(&self) -> LweSize {
self.bootstrapping_key.input_lwe_dimension().to_lwe_size()
}
}
#[derive(PartialOrd, PartialEq, Ord, Eq)]
struct KeyId {
// Both of these are for the accumulator
glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
lwe_dimension_after_bootstrap: LweDimension,
}
#[derive(Default)]
struct CudaMemory {
cuda_buffers: BTreeMap<KeyId, CudaBuffers>,
}
/// All the buffers needed to do a bootstrap or a keyswitch or bootstrap + keyswitch
struct CudaBuffers {
accumulator: CudaGlweCiphertext32,
// Its size is the one of a ciphertext after pbs
lwe_after_bootstrap: CudaLweCiphertext32,
// Its size is the one of a ciphertext after a keyswitch
// ie the size of a ciphertext before the bootstrap
lwe_after_keyswitch: CudaLweCiphertext32,
}
impl CudaMemory {
/// Returns the buffers that matches the given key.
fn as_buffers_for_key(
&mut self,
cpu_engine: &mut DefaultEngine,
cuda_engine: &mut CudaEngine,
server_key: &CudaBootstrapKey,
) -> &mut CudaBuffers {
let key_id = KeyId {
glwe_dimension: server_key.bootstrapping_key.glwe_dimension(),
polynomial_size: server_key.bootstrapping_key.polynomial_size(),
lwe_dimension_after_bootstrap: server_key.bootstrapping_key.output_lwe_dimension(),
};
self.cuda_buffers.entry(key_id).or_insert_with(|| {
let output_lwe_size = server_key
.bootstrapping_key
.output_lwe_dimension()
.to_lwe_size();
let output_ciphertext = cpu_engine
.create_lwe_ciphertext_from(vec![0u32; output_lwe_size.0])
.unwrap();
let cuda_lwe_after_bootstrap = cuda_engine
.convert_lwe_ciphertext(&output_ciphertext)
.unwrap();
let num_elements = server_key
.bootstrapping_key
.glwe_dimension()
.to_glwe_size()
.0
* server_key.bootstrapping_key.polynomial_size().0;
let mut acc = vec![0u32; num_elements];
acc[num_elements - server_key.bootstrapping_key.polynomial_size().0..]
.fill(PLAINTEXT_TRUE);
let accumulator = cpu_engine
.create_glwe_ciphertext_from(acc, server_key.bootstrapping_key.polynomial_size())
.unwrap();
let cuda_accumulator = cuda_engine.convert_glwe_ciphertext(&accumulator).unwrap();
let lwe_size_after_keyswitch = server_key
.key_switching_key
.output_lwe_dimension()
.to_lwe_size();
let output_ciphertext = cpu_engine
.create_lwe_ciphertext_from(vec![0u32; lwe_size_after_keyswitch.0])
.unwrap();
let cuda_lwe_after_keyswitch = cuda_engine
.convert_lwe_ciphertext(&output_ciphertext)
.unwrap();
CudaBuffers {
accumulator: cuda_accumulator,
lwe_after_bootstrap: cuda_lwe_after_bootstrap,
lwe_after_keyswitch: cuda_lwe_after_keyswitch,
}
})
}
}
pub(crate) struct CudaBootstrapper {
cuda_engine: CudaEngine,
cpu_engine: DefaultEngine,
memory: CudaMemory,
}
impl Default for CudaBootstrapper {
fn default() -> Self {
Self {
cuda_engine: CudaEngine::new(()).unwrap(),
// Secret does not matter, we won't generate keys or ciphertext.
cpu_engine: DefaultEngine::new(new_seeder()).unwrap(),
memory: Default::default(),
}
}
}
impl CudaBootstrapper {
pub(crate) fn new_serverk_key(
&mut self,
server_key: &CpuBootstrapKey,
) -> Result<CudaBootstrapKey, Box<dyn std::error::Error>> {
let bootstrapping_key = self
.cuda_engine
.convert_lwe_bootstrap_key(&server_key.standard_bootstraping_key)?;
let key_switching_key = self
.cuda_engine
.convert_lwe_keyswitch_key(&server_key.key_switching_key)?;
Ok(CudaBootstrapKey {
bootstrapping_key,
key_switching_key,
})
}
}
impl Bootstrapper for CudaBootstrapper {
type ServerKey = CudaBootstrapKey;
fn bootstrap(
&mut self,
input: &LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<LweCiphertext32, Box<dyn std::error::Error>> {
let cuda_buffers =
self.memory
.as_buffers_for_key(&mut self.cpu_engine, &mut self.cuda_engine, server_key);
// The output size of keyswitch is the one of regular boolean ciphertext
// so we can use lwe_after_keyswitch
self.cuda_engine
.discard_convert_lwe_ciphertext(&mut cuda_buffers.lwe_after_keyswitch, input)?;
self.cuda_engine.discard_bootstrap_lwe_ciphertext(
&mut cuda_buffers.lwe_after_bootstrap,
&cuda_buffers.lwe_after_keyswitch,
&cuda_buffers.accumulator,
&server_key.bootstrapping_key,
)?;
let output_ciphertext = self
.cuda_engine
.convert_lwe_ciphertext(&cuda_buffers.lwe_after_bootstrap)?;
Ok(output_ciphertext)
}
fn keyswitch(
&mut self,
input: &LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<LweCiphertext32, Box<dyn std::error::Error>> {
let cuda_buffers =
self.memory
.as_buffers_for_key(&mut self.cpu_engine, &mut self.cuda_engine, server_key);
// The input of the function we implement must be a ciphertext that result of a bootstrap
// so we can discard convert in the lwe ciphertext after bootstrap
self.cuda_engine
.discard_convert_lwe_ciphertext(&mut cuda_buffers.lwe_after_bootstrap, input)?;
self.cuda_engine.discard_keyswitch_lwe_ciphertext(
&mut cuda_buffers.lwe_after_keyswitch,
&cuda_buffers.lwe_after_bootstrap,
&server_key.key_switching_key,
)?;
let output_ciphertext = self
.cuda_engine
.convert_lwe_ciphertext(&cuda_buffers.lwe_after_keyswitch)?;
Ok(output_ciphertext)
}
fn bootstrap_keyswitch(
&mut self,
ciphertext: LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<Ciphertext, Box<dyn std::error::Error>> {
// We re-implement instead of calling our bootstrap and then keyswitch fn
// to avoid one extra conversion / copy cpu <-> gpu
let cuda_buffers =
self.memory
.as_buffers_for_key(&mut self.cpu_engine, &mut self.cuda_engine, server_key);
// The output size of keyswitch is the one of regular boolean ciphertext
// so we can use it
self.cuda_engine
.discard_convert_lwe_ciphertext(&mut cuda_buffers.lwe_after_keyswitch, &ciphertext)?;
self.cuda_engine.discard_bootstrap_lwe_ciphertext(
&mut cuda_buffers.lwe_after_bootstrap,
&cuda_buffers.lwe_after_keyswitch,
&cuda_buffers.accumulator,
&server_key.bootstrapping_key,
)?;
self.cuda_engine.discard_keyswitch_lwe_ciphertext(
&mut cuda_buffers.lwe_after_keyswitch,
&cuda_buffers.lwe_after_bootstrap,
&server_key.key_switching_key,
)?;
// We write the result from gpu to cpu avoiding an extra allocation
let mut data = self
.cpu_engine
.consume_retrieve_lwe_ciphertext(ciphertext)?;
let mut view = self
.cpu_engine
.create_lwe_ciphertext_from(data.as_mut_slice())?;
self.cuda_engine
.discard_convert_lwe_ciphertext(&mut view, &cuda_buffers.lwe_after_keyswitch)?;
let output_ciphertext = self.cpu_engine.create_lwe_ciphertext_from(data)?;
Ok(Ciphertext::Encrypted(output_ciphertext))
}
}

View File

@@ -1,48 +0,0 @@
use crate::boolean::ciphertext::Ciphertext;
use crate::core_crypto::prelude::{LweCiphertext32, LweSize};
mod cpu;
#[cfg(feature = "cuda")]
mod cuda;
#[cfg(feature = "cuda")]
pub(crate) use cuda::{CudaBootstrapKey, CudaBootstrapper};
pub(crate) use cpu::{CpuBootstrapKey, CpuBootstrapper};
pub trait BooleanServerKey {
/// The LweSize of the Ciphertexts that this key can bootstrap
fn lwe_size(&self) -> LweSize;
}
/// Trait for types which implement the bootstrapping + key switching
/// of a ciphertext.
///
/// Meant to be implemented for different hardware (CPU, GPU) or for other bootstrapping
/// technics.
pub(crate) trait Bootstrapper: Default {
type ServerKey: BooleanServerKey;
/// Shall return the result of the bootstrapping of the
/// input ciphertext or an error if any
fn bootstrap(
&mut self,
input: &LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<LweCiphertext32, Box<dyn std::error::Error>>;
/// Shall return the result of the key switching of the
/// input ciphertext or an error if any
fn keyswitch(
&mut self,
input: &LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<LweCiphertext32, Box<dyn std::error::Error>>;
/// Shall do the bootstrapping and key switching of the ciphertext.
/// The result is returned as a new value.
fn bootstrap_keyswitch(
&mut self,
ciphertext: LweCiphertext32,
server_key: &Self::ServerKey,
) -> Result<Ciphertext, Box<dyn std::error::Error>>;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,3 @@
#![deny(rustdoc::broken_intra_doc_links)]
//! Welcome the the tfhe.rs `boolean` module documentation!
//!
//! # Description
//! This library makes it possible to execute boolean gates over encrypted bits.
//! It allows to execute a boolean circuit on an untrusted server because both circuit inputs and
@@ -18,7 +15,6 @@
//! homomorphically.
//!
//! ```rust
//! # #[cfg(not(feature = "cuda"))]
//! # fn main() {
//!
//! use tfhe::boolean::gen_keys;
@@ -54,9 +50,6 @@
//! let output_3 = client_key.decrypt(&ct_9);
//! assert_eq!(output_3, true);
//! # }
//!
//! # #[cfg(feature = "cuda")]
//! # fn main() {}
//! ```
use crate::boolean::client_key::ClientKey;
@@ -114,15 +107,12 @@ pub(crate) fn random_integer() -> u32 {
/// meant to be published (the client sends it to the server).
///
/// ```rust
/// # #[cfg(not(feature = "cuda"))]
/// # fn main() {
/// use tfhe::boolean::gen_keys;
/// use tfhe::boolean::prelude::*;
/// // generate the client key and the server key:
/// let (cks, sks) = gen_keys();
/// # }
/// # #[cfg(feature = "cuda")]
/// # fn main() {}
/// ```
pub fn gen_keys() -> (ClientKey, ServerKey) {
// generate the client key

View File

@@ -18,9 +18,9 @@
//! This is an unsafe operation as failing to properly fix the parameters will potentially result
//! with an incorrect and/or insecure computation.
pub use crate::core_crypto::prelude::{
pub use crate::core_crypto::commons::dispersion::StandardDev;
pub use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
StandardDev,
};
use serde::{Deserialize, Serialize};
@@ -107,18 +107,3 @@ pub const TFHE_LIB_PARAMETERS: BooleanParameters = BooleanParameters {
ks_base_log: DecompositionBaseLog(5),
ks_level: DecompositionLevelCount(3),
};
/// This parameter set ensures a probability of error upper-bounded by $2^{-40}$ and 128-bit
/// security.
/// They are GPU-compliant.
pub const GPU_DEFAULT_PARAMETERS: BooleanParameters = BooleanParameters {
lwe_dimension: LweDimension(686),
glwe_dimension: GlweDimension(1),
polynomial_size: PolynomialSize(1024),
lwe_modular_std_dev: StandardDev(0.000019703241702943194),
glwe_modular_std_dev: StandardDev(0.00000004053919869756513),
pbs_base_log: DecompositionBaseLog(6),
pbs_level: DecompositionLevelCount(3),
ks_base_log: DecompositionBaseLog(2),
ks_level: DecompositionLevelCount(6),
};

View File

@@ -1,5 +1,9 @@
#![doc(hidden)]
pub use super::ciphertext::Ciphertext;
//! Module with the definition of the prelude.
//!
//! The TFHE-rs preludes include convenient imports.
//! Having `tfhe::boolean::prelude::*;` should be enough to start using the lib.
pub use super::ciphertext::{Ciphertext, CompressedCiphertext};
pub use super::client_key::ClientKey;
pub use super::gen_keys;
pub use super::parameters::*;

View File

@@ -1,24 +1,25 @@
//! Module with the definition of the encryption PublicKey.
use crate::boolean::ciphertext::Ciphertext;
use crate::boolean::client_key::ClientKey;
use crate::boolean::engine::{CpuBooleanEngine, WithThreadLocalEngine};
use crate::boolean::engine::{BooleanEngine, WithThreadLocalEngine};
use crate::boolean::parameters::BooleanParameters;
use crate::core_crypto::prelude::*;
use crate::core_crypto::entities::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// A structure containing a public key.
#[derive(Clone)]
pub struct PublicKey {
pub(crate) lwe_public_key: LwePublicKey32,
pub(crate) lwe_public_key: LwePublicKeyOwned<u32>,
pub(crate) parameters: BooleanParameters,
}
impl PublicKey {
/// Encrypts a Boolean message using the client key.
/// Encrypt a Boolean message using the client key.
///
/// # Example
///
/// ```rust
/// # #[cfg(not(feature = "cuda"))]
/// # fn main() {
/// use tfhe::boolean::prelude::*;
///
@@ -36,26 +37,16 @@ impl PublicKey {
/// let dec = cks.decrypt(&ct_res);
/// assert_eq!(false, dec);
/// # }
/// # #[cfg(feature = "cuda")]
/// # fn main() {}
/// ```
pub fn encrypt(&self, message: bool) -> Ciphertext {
CpuBooleanEngine::with_thread_local_mut(|engine| {
engine.encrypt_with_public_key(message, self)
})
BooleanEngine::with_thread_local_mut(|engine| engine.encrypt_with_public_key(message, self))
}
/// Allocates and generates a client key.
///
/// # Panic
///
/// This will panic when the "cuda" feature is enabled and the parameters
/// uses a GlweDimension > 1 (as it is not yet supported by the cuda backend).
/// Allocate and generate a client key.
///
/// # Example
///
/// ```rust
/// # #[cfg(not(feature = "cuda"))]
/// # fn main() {
/// use tfhe::boolean::prelude::*;
///
@@ -64,11 +55,9 @@ impl PublicKey {
///
/// let pks = PublicKey::new(&cks);
/// # }
/// # #[cfg(feature = "cuda")]
/// # fn main() {}
/// ```
pub fn new(client_key: &ClientKey) -> PublicKey {
CpuBooleanEngine::with_thread_local_mut(|engine| engine.create_public_key(client_key))
BooleanEngine::with_thread_local_mut(|engine| engine.create_public_key(client_key))
}
}
@@ -83,11 +72,8 @@ impl Serialize for PublicKey {
where
S: Serializer,
{
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
let lwe_public_key = ser_eng
.serialize(&self.lwe_public_key)
.map_err(serde::ser::Error::custom)?;
let lwe_public_key =
bincode::serialize(&self.lwe_public_key).map_err(serde::ser::Error::custom)?;
SerializablePublicKey {
lwe_public_key,
@@ -104,11 +90,9 @@ impl<'de> Deserialize<'de> for PublicKey {
{
let thing =
SerializablePublicKey::deserialize(deserializer).map_err(serde::de::Error::custom)?;
let mut de_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
Ok(Self {
lwe_public_key: de_eng
.deserialize(thing.lwe_public_key.as_slice())
lwe_public_key: bincode::deserialize(thing.lwe_public_key.as_slice())
.map_err(serde::de::Error::custom)?,
parameters: thing.parameters,
})

View File

@@ -8,18 +8,12 @@
#[cfg(test)]
mod tests;
use serde::{Deserialize, Serialize};
use crate::boolean::ciphertext::Ciphertext;
use crate::boolean::client_key::ClientKey;
use crate::boolean::engine::bootstrapping::CpuBootstrapKey;
#[cfg(feature = "cuda")]
use crate::boolean::engine::{bootstrapping::CudaBootstrapKey, CudaBooleanEngine};
pub use crate::boolean::engine::bootstrapping::{CompressedServerKey, ServerKey};
use crate::boolean::engine::{
BinaryGatesAssignEngine, BinaryGatesEngine, CpuBooleanEngine, WithThreadLocalEngine,
BinaryGatesAssignEngine, BinaryGatesEngine, BooleanEngine, WithThreadLocalEngine,
};
#[cfg(feature = "cuda")]
use std::sync::Arc;
pub trait BinaryBooleanGates<L, R> {
fn and(&self, ct_left: L, ct_right: R) -> Ciphertext;
@@ -39,156 +33,103 @@ pub trait BinaryBooleanGatesAssign<L, R> {
fn xnor_assign(&self, ct_left: L, ct_right: R);
}
trait RefFromServerKey {
fn get_ref(server_key: &ServerKey) -> &Self;
}
trait DefaultImplementation {
type Engine: WithThreadLocalEngine;
type BootsrapKey: RefFromServerKey;
}
#[derive(Clone)]
pub struct ServerKey {
cpu_key: CpuBootstrapKey,
#[cfg(feature = "cuda")]
cuda_key: Arc<CudaBootstrapKey>,
}
#[cfg(not(feature = "cuda"))]
mod implementation {
use super::*;
impl RefFromServerKey for CpuBootstrapKey {
fn get_ref(server_key: &ServerKey) -> &Self {
&server_key.cpu_key
}
}
impl DefaultImplementation for ServerKey {
type Engine = CpuBooleanEngine;
type BootsrapKey = CpuBootstrapKey;
}
}
#[cfg(feature = "cuda")]
mod implementation {
use super::*;
impl RefFromServerKey for CudaBootstrapKey {
fn get_ref(server_key: &ServerKey) -> &Self {
&server_key.cuda_key
}
}
impl DefaultImplementation for ServerKey {
type Engine = CudaBooleanEngine;
type BootsrapKey = CudaBootstrapKey;
type Engine = BooleanEngine;
}
}
impl<Lhs, Rhs> BinaryBooleanGates<Lhs, Rhs> for ServerKey
where
<ServerKey as DefaultImplementation>::Engine:
BinaryGatesEngine<Lhs, Rhs, <ServerKey as DefaultImplementation>::BootsrapKey>,
<ServerKey as DefaultImplementation>::Engine: BinaryGatesEngine<Lhs, Rhs, ServerKey>,
{
fn and(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.and(ct_left, ct_right, bootstrap_key)
engine.and(ct_left, ct_right, self)
})
}
fn nand(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.nand(ct_left, ct_right, bootstrap_key)
engine.nand(ct_left, ct_right, self)
})
}
fn nor(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.nor(ct_left, ct_right, bootstrap_key)
engine.nor(ct_left, ct_right, self)
})
}
fn or(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.or(ct_left, ct_right, bootstrap_key)
engine.or(ct_left, ct_right, self)
})
}
fn xor(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.xor(ct_left, ct_right, bootstrap_key)
engine.xor(ct_left, ct_right, self)
})
}
fn xnor(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.xnor(ct_left, ct_right, bootstrap_key)
engine.xnor(ct_left, ct_right, self)
})
}
}
impl<Lhs, Rhs> BinaryBooleanGatesAssign<Lhs, Rhs> for ServerKey
where
<ServerKey as DefaultImplementation>::Engine:
BinaryGatesAssignEngine<Lhs, Rhs, <ServerKey as DefaultImplementation>::BootsrapKey>,
<ServerKey as DefaultImplementation>::Engine: BinaryGatesAssignEngine<Lhs, Rhs, ServerKey>,
{
fn and_assign(&self, ct_left: Lhs, ct_right: Rhs) {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.and_assign(ct_left, ct_right, bootstrap_key)
engine.and_assign(ct_left, ct_right, self)
})
}
fn nand_assign(&self, ct_left: Lhs, ct_right: Rhs) {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.nand_assign(ct_left, ct_right, bootstrap_key)
engine.nand_assign(ct_left, ct_right, self)
})
}
fn nor_assign(&self, ct_left: Lhs, ct_right: Rhs) {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.nor_assign(ct_left, ct_right, bootstrap_key)
engine.nor_assign(ct_left, ct_right, self)
})
}
fn or_assign(&self, ct_left: Lhs, ct_right: Rhs) {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.or_assign(ct_left, ct_right, bootstrap_key)
engine.or_assign(ct_left, ct_right, self)
})
}
fn xor_assign(&self, ct_left: Lhs, ct_right: Rhs) {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.xor_assign(ct_left, ct_right, bootstrap_key)
engine.xor_assign(ct_left, ct_right, self)
})
}
fn xnor_assign(&self, ct_left: Lhs, ct_right: Rhs) {
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
engine.xnor_assign(ct_left, ct_right, bootstrap_key)
engine.xnor_assign(ct_left, ct_right, self)
})
}
}
impl ServerKey {
pub fn new(cks: &ClientKey) -> Self {
let cpu_key =
CpuBooleanEngine::with_thread_local_mut(|engine| engine.create_server_key(cks));
Self::from(cpu_key)
BooleanEngine::with_thread_local_mut(|engine| engine.create_server_key(cks))
}
pub fn trivial_encrypt(&self, message: bool) -> Ciphertext {
@@ -196,11 +137,11 @@ impl ServerKey {
}
pub fn not(&self, ct: &Ciphertext) -> Ciphertext {
CpuBooleanEngine::with_thread_local_mut(|engine| engine.not(ct))
BooleanEngine::with_thread_local_mut(|engine| engine.not(ct))
}
pub fn not_assign(&self, ct: &mut Ciphertext) {
CpuBooleanEngine::with_thread_local_mut(|engine| engine.not_assign(ct))
BooleanEngine::with_thread_local_mut(|engine| engine.not_assign(ct))
}
pub fn mux(
@@ -209,56 +150,14 @@ impl ServerKey {
ct_then: &Ciphertext,
ct_else: &Ciphertext,
) -> Ciphertext {
#[cfg(feature = "cuda")]
{
CudaBooleanEngine::with_thread_local_mut(|engine| {
engine.mux(ct_condition, ct_then, ct_else, &self.cuda_key)
})
}
#[cfg(not(feature = "cuda"))]
{
CpuBooleanEngine::with_thread_local_mut(|engine| {
engine.mux(ct_condition, ct_then, ct_else, &self.cpu_key)
})
}
BooleanEngine::with_thread_local_mut(|engine| {
engine.mux(ct_condition, ct_then, ct_else, self)
})
}
}
impl From<CpuBootstrapKey> for ServerKey {
fn from(cpu_key: CpuBootstrapKey) -> Self {
#[cfg(feature = "cuda")]
{
let cuda_key = CudaBooleanEngine::with_thread_local_mut(|engine| {
engine.create_server_key(&cpu_key)
});
let cuda_key = Arc::new(cuda_key);
Self { cpu_key, cuda_key }
}
#[cfg(not(feature = "cuda"))]
{
Self { cpu_key }
}
}
}
impl Serialize for ServerKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.cpu_key.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ServerKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let cpu_key = CpuBootstrapKey::deserialize(deserializer)?;
Ok(Self::from(cpu_key))
impl CompressedServerKey {
pub fn new(cks: &ClientKey) -> Self {
BooleanEngine::with_thread_local_mut(|engine| engine.create_compressed_server_key(cks))
}
}

View File

@@ -13,7 +13,6 @@ const NB_CT: usize = 8;
/// Number of gates computed in the deep circuit test
const NB_GATE: usize = 1 << 11;
#[cfg(not(feature = "cuda"))]
mod default_parameters_tests {
use super::*;
use crate::boolean::parameters::DEFAULT_PARAMETERS;
@@ -60,7 +59,6 @@ mod default_parameters_tests {
}
}
#[cfg(not(feature = "cuda"))]
mod tfhe_lib_parameters_tests {
use super::*;
use crate::boolean::parameters::TFHE_LIB_PARAMETERS;
@@ -107,52 +105,6 @@ mod tfhe_lib_parameters_tests {
}
}
mod gpu_default_parameters_tests {
use super::*;
use crate::boolean::parameters::GPU_DEFAULT_PARAMETERS;
#[test]
fn test_encrypt_decrypt_lwe_secret_key_default_parameters() {
test_encrypt_decrypt_lwe_secret_key(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_and_gate_default_parameters() {
test_and_gate(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_nand_gate_default_parameters() {
test_nand_gate(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_or_gate_default_parameters() {
test_or_gate(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_nor_gate_default_parameters() {
test_nor_gate(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_xor_gate_default_parameters() {
test_xor_gate(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_xnor_gate_default_parameters() {
test_xnor_gate(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_not_gate_default_parameters() {
test_not_gate(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_mux_gate_default_parameters() {
test_mux_gate(GPU_DEFAULT_PARAMETERS);
}
#[test]
fn test_deep_circuit_default_parameters() {
test_deep_circuit(GPU_DEFAULT_PARAMETERS);
}
}
/// test encryption and decryption with the LWE secret key
fn test_encrypt_decrypt_lwe_secret_key(parameters: BooleanParameters) {
// generate the client key set
@@ -232,11 +184,7 @@ fn test_and_gate(parameters: BooleanParameters) {
let dec_and = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_and,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_and, "left: {ct1:?}, right: {ct2:?}");
// AND gate -> left: Ciphertext, right: bool
let ct_res = sks.and(&ct1, b2);
@@ -245,7 +193,7 @@ fn test_and_gate(parameters: BooleanParameters) {
let dec_and = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_and, "left: {:?}, right: {:?}", ct1, b2);
assert_eq!(expected_result, dec_and, "left: {ct1:?}, right: {b2:?}");
// AND gate -> left: bool, right: Ciphertext
let ct_res = sks.and(b1, &ct2);
@@ -254,7 +202,7 @@ fn test_and_gate(parameters: BooleanParameters) {
let dec_and = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_and, "left: {:?}, right: {:?}", b1, ct2);
assert_eq!(expected_result, dec_and, "left: {b1:?}, right: {ct2:?}");
// AND gate -> "left: {:?}, right: {:?}",ct1, ct2
let mut ct_res = ct1.clone();
@@ -264,11 +212,7 @@ fn test_and_gate(parameters: BooleanParameters) {
let dec_and = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_and,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_and, "left: {ct1:?}, right: {ct2:?}");
// AND gate -> left: Ciphertext, right: bool
let mut ct_res = ct1.clone();
@@ -278,7 +222,7 @@ fn test_and_gate(parameters: BooleanParameters) {
let dec_and = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_and, "left: {:?}, right: {:?}", ct1, b2);
assert_eq!(expected_result, dec_and, "left: {ct1:?}, right: {b2:?}");
// AND gate -> left: bool, right: Ciphertext
let mut ct_res = ct2.clone();
@@ -288,7 +232,7 @@ fn test_and_gate(parameters: BooleanParameters) {
let dec_and = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_and, "left: {:?}, right: {:?}", b1, ct2);
assert_eq!(expected_result, dec_and, "left: {b1:?}, right: {ct2:?}");
}
}
@@ -324,8 +268,7 @@ fn test_mux_gate(parameters: BooleanParameters) {
// assert
assert_eq!(
expected_result, dec_mux,
"cond: {:?}, then: {:?}, else: {:?}",
ct1, ct2, ct3
"cond: {ct1:?}, then: {ct2:?}, else: {ct3:?}"
);
}
}
@@ -356,11 +299,7 @@ fn test_nand_gate(parameters: BooleanParameters) {
let dec_nand = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_nand,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_nand, "left: {ct1:?}, right: {ct2:?}");
// NAND gate -> left: Ciphertext, right: bool
let ct_res = sks.nand(&ct1, b2);
@@ -369,11 +308,7 @@ fn test_nand_gate(parameters: BooleanParameters) {
let dec_nand = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_nand,
"left: {:?}, right: {:?}",
ct1, b2
);
assert_eq!(expected_result, dec_nand, "left: {ct1:?}, right: {b2:?}");
// NAND gate -> left: bool, right: Ciphertext
let ct_res = sks.nand(b1, &ct2);
@@ -382,11 +317,7 @@ fn test_nand_gate(parameters: BooleanParameters) {
let dec_nand = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_nand,
"left: {:?}, right: {:?}",
b1, ct2
);
assert_eq!(expected_result, dec_nand, "left: {b1:?}, right: {ct2:?}");
// NAND gate -> "left: {:?}, right: {:?}",ct1, ct2
let mut ct_res = ct1.clone();
@@ -396,11 +327,7 @@ fn test_nand_gate(parameters: BooleanParameters) {
let dec_nand = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_nand,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_nand, "left: {ct1:?}, right: {ct2:?}");
// NAND gate -> left: Ciphertext, right: bool
let mut ct_res = ct1.clone();
@@ -410,11 +337,7 @@ fn test_nand_gate(parameters: BooleanParameters) {
let dec_nand = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_nand,
"left: {:?}, right: {:?}",
ct1, b2
);
assert_eq!(expected_result, dec_nand, "left: {ct1:?}, right: {b2:?}");
// NAND gate -> left: bool, right: Ciphertext
let mut ct_res = ct2.clone();
@@ -424,11 +347,7 @@ fn test_nand_gate(parameters: BooleanParameters) {
let dec_nand = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_nand,
"left: {:?}, right: {:?}",
b1, ct2
);
assert_eq!(expected_result, dec_nand, "left: {b1:?}, right: {ct2:?}");
}
}
@@ -458,11 +377,7 @@ fn test_nor_gate(parameters: BooleanParameters) {
let dec_nor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_nor,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_nor, "left: {ct1:?}, right: {ct2:?}");
// NOR gate -> left: Ciphertext, right: bool
let ct_res = sks.nor(&ct1, b2);
@@ -471,7 +386,7 @@ fn test_nor_gate(parameters: BooleanParameters) {
let dec_nor = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_nor, "left: {:?}, right: {:?}", ct1, b2);
assert_eq!(expected_result, dec_nor, "left: {ct1:?}, right: {b2:?}");
// NOR gate -> left: bool, right: Ciphertext
let ct_res = sks.nor(b1, &ct2);
@@ -480,7 +395,7 @@ fn test_nor_gate(parameters: BooleanParameters) {
let dec_nor = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_nor, "left: {:?}, right: {:?}", b1, ct2);
assert_eq!(expected_result, dec_nor, "left: {b1:?}, right: {ct2:?}");
// NOR gate -> "left: {:?}, right: {:?}",ct1, ct2
let mut ct_res = ct1.clone();
@@ -490,11 +405,7 @@ fn test_nor_gate(parameters: BooleanParameters) {
let dec_nor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_nor,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_nor, "left: {ct1:?}, right: {ct2:?}");
// NOR gate -> left: Ciphertext, right: bool
let mut ct_res = ct1.clone();
@@ -504,7 +415,7 @@ fn test_nor_gate(parameters: BooleanParameters) {
let dec_nor = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_nor, "left: {:?}, right: {:?}", ct1, b2);
assert_eq!(expected_result, dec_nor, "left: {ct1:?}, right: {b2:?}");
// NOR gate -> left: bool, right: Ciphertext
let mut ct_res = ct2.clone();
@@ -514,7 +425,7 @@ fn test_nor_gate(parameters: BooleanParameters) {
let dec_nor = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_nor, "left: {:?}, right: {:?}", b1, ct2);
assert_eq!(expected_result, dec_nor, "left: {b1:?}, right: {ct2:?}");
}
}
@@ -580,7 +491,7 @@ fn test_or_gate(parameters: BooleanParameters) {
let dec_or = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_or, "left: {:?}, right: {:?}", ct1, ct2);
assert_eq!(expected_result, dec_or, "left: {ct1:?}, right: {ct2:?}");
// OR gate -> left: Ciphertext, right: bool
let ct_res = sks.or(&ct1, b2);
@@ -589,7 +500,7 @@ fn test_or_gate(parameters: BooleanParameters) {
let dec_or = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_or, "left: {:?}, right: {:?}", ct1, b2);
assert_eq!(expected_result, dec_or, "left: {ct1:?}, right: {b2:?}");
// OR gate -> left: bool, right: Ciphertext
let ct_res = sks.or(b1, &ct2);
@@ -598,7 +509,7 @@ fn test_or_gate(parameters: BooleanParameters) {
let dec_or = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_or, "left: {:?}, right: {:?}", b1, ct2);
assert_eq!(expected_result, dec_or, "left: {b1:?}, right: {ct2:?}");
// OR gate -> "left: {:?}, right: {:?}",ct1, ct2
let mut ct_res = ct1.clone();
@@ -608,7 +519,7 @@ fn test_or_gate(parameters: BooleanParameters) {
let dec_or = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_or, "left: {:?}, right: {:?}", ct1, ct2);
assert_eq!(expected_result, dec_or, "left: {ct1:?}, right: {ct2:?}");
// OR gate -> left: Ciphertext, right: bool
let mut ct_res = ct1.clone();
@@ -618,7 +529,7 @@ fn test_or_gate(parameters: BooleanParameters) {
let dec_or = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_or, "left: {:?}, right: {:?}", ct1, b2);
assert_eq!(expected_result, dec_or, "left: {ct1:?}, right: {b2:?}");
// OR gate -> left: bool, right: Ciphertext
let mut ct_res = ct2.clone();
@@ -628,7 +539,7 @@ fn test_or_gate(parameters: BooleanParameters) {
let dec_or = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_or, "left: {:?}, right: {:?}", b1, ct2);
assert_eq!(expected_result, dec_or, "left: {b1:?}, right: {ct2:?}");
}
}
@@ -658,11 +569,7 @@ fn test_xnor_gate(parameters: BooleanParameters) {
let dec_xnor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_xnor,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_xnor, "left: {ct1:?}, right: {ct2:?}");
// XNOR gate -> left: Ciphertext, right: bool
let ct_res = sks.xnor(&ct1, b2);
@@ -671,11 +578,7 @@ fn test_xnor_gate(parameters: BooleanParameters) {
let dec_xnor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_xnor,
"left: {:?}, right: {:?}",
ct1, b2
);
assert_eq!(expected_result, dec_xnor, "left: {ct1:?}, right: {b2:?}");
// XNOR gate -> left: bool, right: Ciphertext
let ct_res = sks.xnor(b1, &ct2);
@@ -684,11 +587,7 @@ fn test_xnor_gate(parameters: BooleanParameters) {
let dec_xnor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_xnor,
"left: {:?}, right: {:?}",
b1, ct2
);
assert_eq!(expected_result, dec_xnor, "left: {b1:?}, right: {ct2:?}");
// XNOR gate -> "left: {:?}, right: {:?}",ct1, ct2
let mut ct_res = ct1.clone();
@@ -698,11 +597,7 @@ fn test_xnor_gate(parameters: BooleanParameters) {
let dec_xnor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_xnor,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_xnor, "left: {ct1:?}, right: {ct2:?}");
// XNOR gate -> left: Ciphertext, right: bool
let mut ct_res = ct1.clone();
@@ -712,11 +607,7 @@ fn test_xnor_gate(parameters: BooleanParameters) {
let dec_xnor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_xnor,
"left: {:?}, right: {:?}",
ct1, b2
);
assert_eq!(expected_result, dec_xnor, "left: {ct1:?}, right: {b2:?}");
// XNOR gate -> left: bool, right: Ciphertext
let mut ct_res = ct2.clone();
@@ -726,11 +617,7 @@ fn test_xnor_gate(parameters: BooleanParameters) {
let dec_xnor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_xnor,
"left: {:?}, right: {:?}",
b1, ct2
);
assert_eq!(expected_result, dec_xnor, "left: {b1:?}, right: {ct2:?}");
}
}
@@ -760,11 +647,7 @@ fn test_xor_gate(parameters: BooleanParameters) {
let dec_xor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_xor,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_xor, "left: {ct1:?}, right: {ct2:?}");
// XOR gate -> left: Ciphertext, right: bool
let ct_res = sks.xor(&ct1, b2);
@@ -773,7 +656,7 @@ fn test_xor_gate(parameters: BooleanParameters) {
let dec_xor = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_xor, "left: {:?}, right: {:?}", ct1, b2);
assert_eq!(expected_result, dec_xor, "left: {ct1:?}, right: {b2:?}");
// XOR gate -> left: bool, right: Ciphertext
let ct_res = sks.xor(b1, &ct2);
@@ -782,7 +665,7 @@ fn test_xor_gate(parameters: BooleanParameters) {
let dec_xor = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_xor, "left: {:?}, right: {:?}", b1, ct2);
assert_eq!(expected_result, dec_xor, "left: {b1:?}, right: {ct2:?}");
// XOR gate -> "left: {:?}, right: {:?}",ct1, ct2
let mut ct_res = ct1.clone();
@@ -792,11 +675,7 @@ fn test_xor_gate(parameters: BooleanParameters) {
let dec_xor = cks.decrypt(&ct_res);
// assert
assert_eq!(
expected_result, dec_xor,
"left: {:?}, right: {:?}",
ct1, ct2
);
assert_eq!(expected_result, dec_xor, "left: {ct1:?}, right: {ct2:?}");
// XOR gate -> left: Ciphertext, right: bool
let mut ct_res = ct1.clone();
@@ -806,7 +685,7 @@ fn test_xor_gate(parameters: BooleanParameters) {
let dec_xor = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_xor, "left: {:?}, right: {:?}", ct1, b2);
assert_eq!(expected_result, dec_xor, "left: {ct1:?}, right: {b2:?}");
// XOR gate -> left: bool, right: Ciphertext
let mut ct_res = ct2.clone();
@@ -816,7 +695,7 @@ fn test_xor_gate(parameters: BooleanParameters) {
let dec_xor = cks.decrypt(&ct_res);
// assert
assert_eq!(expected_result, dec_xor, "left: {:?}, right: {:?}", b1, ct2);
assert_eq!(expected_result, dec_xor, "left: {b1:?}, right: {ct2:?}");
}
}

View File

@@ -6,6 +6,10 @@ use crate::boolean;
pub struct BooleanCiphertext(pub(in crate::c_api) boolean::ciphertext::Ciphertext);
pub struct BooleanCompressedCiphertext(
pub(in crate::c_api) boolean::ciphertext::CompressedCiphertext,
);
#[no_mangle]
pub unsafe extern "C" fn boolean_serialize_ciphertext(
ciphertext: *const BooleanCiphertext,
@@ -42,3 +46,62 @@ pub unsafe extern "C" fn boolean_deserialize_ciphertext(
*result = Box::into_raw(heap_allocated_ciphertext);
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_decompress_ciphertext(
compressed_ciphertext: *mut BooleanCompressedCiphertext,
result: *mut *mut BooleanCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let compressed_ciphertext = get_mut_checked(compressed_ciphertext).unwrap();
let ciphertext = compressed_ciphertext.0.clone().into();
let heap_allocated_ciphertext = Box::new(BooleanCiphertext(ciphertext));
*result = Box::into_raw(heap_allocated_ciphertext);
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_serialize_compressed_ciphertext(
ciphertext: *const BooleanCompressedCiphertext,
result: *mut Buffer,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
let ciphertext = get_ref_checked(ciphertext).unwrap();
let buffer: Buffer = bincode::serialize(&ciphertext.0).unwrap().into();
*result = buffer;
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_deserialize_compressed_ciphertext(
buffer_view: BufferView,
result: *mut *mut BooleanCompressedCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let ciphertext: boolean::ciphertext::CompressedCiphertext =
bincode::deserialize(buffer_view.into()).unwrap();
let heap_allocated_ciphertext = Box::new(BooleanCompressedCiphertext(ciphertext));
*result = Box::into_raw(heap_allocated_ciphertext);
})
}

View File

@@ -5,7 +5,7 @@ use std::os::raw::c_int;
use crate::boolean;
use super::BooleanCiphertext;
use super::{BooleanCiphertext, BooleanCompressedCiphertext};
pub struct BooleanClientKey(pub(in crate::c_api) boolean::client_key::ClientKey);
#[no_mangle]
@@ -52,6 +52,29 @@ pub unsafe extern "C" fn boolean_client_key_encrypt(
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_client_key_encrypt_compressed(
client_key: *const BooleanClientKey,
value_to_encrypt: bool,
result: *mut *mut BooleanCompressedCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let client_key = get_ref_checked(client_key).unwrap();
let heap_allocated_ciphertext = Box::new(BooleanCompressedCiphertext(
client_key.0.encrypt_compressed(value_to_encrypt),
));
*result = Box::into_raw(heap_allocated_ciphertext);
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_client_key_decrypt(
client_key: *const BooleanClientKey,

View File

@@ -2,7 +2,10 @@ use crate::c_api::utils::*;
use std::os::raw::c_int;
use super::parameters::BooleanParameters;
use super::{BooleanCiphertext, BooleanClientKey, BooleanPublicKey, BooleanServerKey};
use super::{
BooleanCiphertext, BooleanClientKey, BooleanCompressedCiphertext, BooleanCompressedServerKey,
BooleanPublicKey, BooleanServerKey,
};
#[no_mangle]
pub unsafe extern "C" fn destroy_boolean_client_key(client_key: *mut BooleanClientKey) -> c_int {
@@ -22,6 +25,17 @@ pub unsafe extern "C" fn destroy_boolean_server_key(server_key: *mut BooleanServ
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_boolean_compressed_server_key(
server_key: *mut BooleanCompressedServerKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(server_key).unwrap();
drop(Box::from_raw(server_key));
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_boolean_public_key(public_key: *mut BooleanPublicKey) -> c_int {
catch_panic(|| {
@@ -52,3 +66,14 @@ pub unsafe extern "C" fn destroy_boolean_ciphertext(
drop(Box::from_raw(boolean_ciphertext));
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_boolean_compressed_ciphertext(
boolean_ciphertext: *mut BooleanCompressedCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(boolean_ciphertext).unwrap();
drop(Box::from_raw(boolean_ciphertext));
})
}

View File

@@ -10,10 +10,10 @@ use std::os::raw::c_int;
use crate::boolean;
pub use ciphertext::BooleanCiphertext;
pub use ciphertext::{BooleanCiphertext, BooleanCompressedCiphertext};
pub use client_key::BooleanClientKey;
pub use public_key::BooleanPublicKey;
pub use server_key::BooleanServerKey;
pub use server_key::{BooleanCompressedServerKey, BooleanServerKey};
#[no_mangle]
pub unsafe extern "C" fn boolean_gen_keys_with_default_parameters(
@@ -109,7 +109,7 @@ pub unsafe extern "C" fn boolean_trivial_encrypt(
check_ptr_is_non_null_and_aligned(result).unwrap();
let heap_allocated_result = Box::new(BooleanCiphertext(
boolean::engine::CpuBooleanEngine::with_thread_local_mut(|engine| {
boolean::engine::BooleanEngine::with_thread_local_mut(|engine| {
engine.trivial_encrypt(message)
}),
));

View File

@@ -1,7 +1,7 @@
use crate::c_api::utils::*;
use crate::core_crypto::prelude::{
use crate::core_crypto::commons::dispersion::StandardDev;
use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
StandardDev,
};
use std::os::raw::c_int;
@@ -69,7 +69,7 @@ pub(in crate::c_api) enum BooleanParametersSet {
}
pub const BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS: c_int = 0;
pub const BOOLEAN_PARAMETERS_SET_THFE_LIB_PARAMETERS: c_int = 1;
pub const BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS: c_int = 1;
impl TryFrom<c_int> for BooleanParametersSet {
type Error = String;
@@ -79,7 +79,7 @@ impl TryFrom<c_int> for BooleanParametersSet {
BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS => {
Ok(BooleanParametersSet::DefaultParameters)
}
BOOLEAN_PARAMETERS_SET_THFE_LIB_PARAMETERS => {
BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS => {
Ok(BooleanParametersSet::TfheLibParameters)
}
_ => Err(format!(

View File

@@ -8,6 +8,9 @@ use crate::boolean::server_key::{BinaryBooleanGates, BinaryBooleanGatesAssign};
use super::BooleanCiphertext;
pub struct BooleanServerKey(pub(in crate::c_api) boolean::server_key::ServerKey);
pub struct BooleanCompressedServerKey(
pub(in crate::c_api) boolean::server_key::CompressedServerKey,
);
#[no_mangle]
pub unsafe extern "C" fn boolean_gen_server_key(
@@ -31,6 +34,28 @@ pub unsafe extern "C" fn boolean_gen_server_key(
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_gen_compressed_server_key(
client_key: *const super::BooleanClientKey,
result_server_key: *mut *mut BooleanCompressedServerKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result_server_key).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result_server_key = std::ptr::null_mut();
let client_key = get_ref_checked(client_key).unwrap();
let server_key = boolean::server_key::CompressedServerKey::new(&client_key.0);
let heap_allocated_server_key = Box::new(BooleanCompressedServerKey(server_key));
*result_server_key = Box::into_raw(heap_allocated_server_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_server_key_and(
server_key: *const BooleanServerKey,
@@ -602,3 +627,62 @@ pub unsafe extern "C" fn boolean_deserialize_server_key(
*result = Box::into_raw(heap_allocated_server_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_serialize_compressed_server_key(
server_key: *const BooleanCompressedServerKey,
result: *mut Buffer,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
let server_key = get_ref_checked(server_key).unwrap();
let buffer: Buffer = bincode::serialize(&server_key.0).unwrap().into();
*result = buffer;
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_deserialize_compressed_server_key(
buffer_view: BufferView,
result: *mut *mut BooleanCompressedServerKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
// *result = std::ptr::null_mut();
let server_key: boolean::server_key::CompressedServerKey =
bincode::deserialize(buffer_view.into()).unwrap();
let heap_allocated_server_key = Box::new(BooleanCompressedServerKey(server_key));
*result = Box::into_raw(heap_allocated_server_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn boolean_decompress_server_key(
compressed_server_key: *const BooleanCompressedServerKey,
result: *mut *mut BooleanServerKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let compressed_server_key = get_ref_checked(compressed_server_key).unwrap();
let heap_allocated_public_key = Box::new(BooleanServerKey(
boolean::server_key::ServerKey::from(compressed_server_key.0.clone()),
));
*result = Box::into_raw(heap_allocated_public_key);
})
}

View File

@@ -1,4 +1,3 @@
#![deny(rustdoc::broken_intra_doc_links)]
#![allow(clippy::missing_safety_doc)]
#[cfg(feature = "boolean-c-api")]
pub mod boolean;

View File

@@ -5,6 +5,9 @@ use std::os::raw::c_int;
use crate::shortint;
pub struct ShortintCiphertext(pub(in crate::c_api) shortint::ciphertext::Ciphertext);
pub struct ShortintCompressedCiphertext(
pub(in crate::c_api) shortint::ciphertext::CompressedCiphertext,
);
#[no_mangle]
pub unsafe extern "C" fn shortint_ciphertext_set_degree(
@@ -68,3 +71,62 @@ pub unsafe extern "C" fn shortint_deserialize_ciphertext(
*result = Box::into_raw(heap_allocated_ciphertext);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_decompress_ciphertext(
compressed_ciphertext: *mut ShortintCompressedCiphertext,
result: *mut *mut ShortintCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let compressed_ciphertext = get_mut_checked(compressed_ciphertext).unwrap();
let ciphertext = compressed_ciphertext.0.clone().into();
let heap_allocated_ciphertext = Box::new(ShortintCiphertext(ciphertext));
*result = Box::into_raw(heap_allocated_ciphertext);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_serialize_compressed_ciphertext(
ciphertext: *const ShortintCompressedCiphertext,
result: *mut Buffer,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
let ciphertext = get_ref_checked(ciphertext).unwrap();
let buffer: Buffer = bincode::serialize(&ciphertext.0).unwrap().into();
*result = buffer;
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_deserialize_compressed_ciphertext(
buffer_view: BufferView,
result: *mut *mut ShortintCompressedCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let ciphertext: shortint::ciphertext::CompressedCiphertext =
bincode::deserialize(buffer_view.into()).unwrap();
let heap_allocated_ciphertext = Box::new(ShortintCompressedCiphertext(ciphertext));
*result = Box::into_raw(heap_allocated_ciphertext);
})
}

View File

@@ -5,7 +5,7 @@ use std::os::raw::c_int;
use crate::shortint;
use super::ShortintCiphertext;
use super::{ShortintCiphertext, ShortintCompressedCiphertext};
pub struct ShortintClientKey(pub(in crate::c_api) shortint::client_key::ClientKey);
#[no_mangle]
@@ -52,6 +52,29 @@ pub unsafe extern "C" fn shortint_client_key_encrypt(
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_client_key_encrypt_compressed(
client_key: *const ShortintClientKey,
value_to_encrypt: u64,
result: *mut *mut ShortintCompressedCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let client_key = get_ref_checked(client_key).unwrap();
let heap_allocated_ciphertext = Box::new(ShortintCompressedCiphertext(
client_key.0.encrypt_compressed(value_to_encrypt),
));
*result = Box::into_raw(heap_allocated_ciphertext);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_client_key_decrypt(
client_key: *const ShortintClientKey,

View File

@@ -3,8 +3,9 @@ use std::os::raw::c_int;
use super::parameters::ShortintParameters;
use super::{
ShortintBivariatePBSAccumulator, ShortintCiphertext, ShortintClientKey, ShortintPBSAccumulator,
ShortintPublicKey, ShortintServerKey,
ShortintBivariatePBSAccumulator, ShortintCiphertext, ShortintClientKey,
ShortintCompressedCiphertext, ShortintCompressedPublicKey, ShortintCompressedServerKey,
ShortintPBSAccumulator, ShortintPublicKey, ShortintServerKey,
};
#[no_mangle]
@@ -25,6 +26,17 @@ pub unsafe extern "C" fn destroy_shortint_server_key(server_key: *mut ShortintSe
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_shortint_compressed_server_key(
server_key: *mut ShortintCompressedServerKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(server_key).unwrap();
drop(Box::from_raw(server_key));
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_shortint_public_key(public_key: *mut ShortintPublicKey) -> c_int {
catch_panic(|| {
@@ -34,6 +46,17 @@ pub unsafe extern "C" fn destroy_shortint_public_key(public_key: *mut ShortintPu
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_shortint_compressed_public_key(
compressed_public_key: *mut ShortintCompressedPublicKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(compressed_public_key).unwrap();
drop(Box::from_raw(compressed_public_key));
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_shortint_parameters(
shortint_parameters: *mut ShortintParameters,
@@ -56,6 +79,17 @@ pub unsafe extern "C" fn destroy_shortint_ciphertext(
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_shortint_compressed_ciphertext(
shortint_ciphertext: *mut ShortintCompressedCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(shortint_ciphertext).unwrap();
drop(Box::from_raw(shortint_ciphertext));
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_shortint_pbs_accumulator(
pbs_accumulator: *mut ShortintPBSAccumulator,

View File

@@ -10,11 +10,11 @@ use std::os::raw::c_int;
use crate::shortint;
pub use ciphertext::ShortintCiphertext;
pub use ciphertext::{ShortintCiphertext, ShortintCompressedCiphertext};
pub use client_key::ShortintClientKey;
pub use public_key::ShortintPublicKey;
pub use public_key::{ShortintCompressedPublicKey, ShortintPublicKey};
pub use server_key::pbs::{ShortintBivariatePBSAccumulator, ShortintPBSAccumulator};
pub use server_key::ShortintServerKey;
pub use server_key::{ShortintCompressedServerKey, ShortintServerKey};
#[no_mangle]
pub unsafe extern "C" fn shortint_gen_keys_with_parameters(

View File

@@ -1,7 +1,7 @@
use crate::c_api::utils::*;
use crate::core_crypto::prelude::{
pub use crate::core_crypto::commons::dispersion::StandardDev;
pub use crate::core_crypto::commons::parameters::{
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
StandardDev,
};
use std::os::raw::c_int;

View File

@@ -5,9 +5,12 @@ use std::os::raw::c_int;
use crate::shortint;
use super::{ShortintCiphertext, ShortintClientKey, ShortintServerKey};
use super::{ShortintCiphertext, ShortintClientKey};
pub struct ShortintPublicKey(pub(in crate::c_api) shortint::public_key::PublicKey);
pub struct ShortintCompressedPublicKey(
pub(in crate::c_api) shortint::public_key::CompressedPublicKey,
);
#[no_mangle]
pub unsafe extern "C" fn shortint_gen_public_key(
@@ -34,7 +37,6 @@ pub unsafe extern "C" fn shortint_gen_public_key(
#[no_mangle]
pub unsafe extern "C" fn shortint_public_key_encrypt(
public_key: *const ShortintPublicKey,
server_key: *const ShortintServerKey,
value_to_encrypt: u64,
result: *mut *mut ShortintCiphertext,
) -> c_int {
@@ -46,11 +48,9 @@ pub unsafe extern "C" fn shortint_public_key_encrypt(
*result = std::ptr::null_mut();
let public_key = get_ref_checked(public_key).unwrap();
let server_key = get_ref_checked(server_key).unwrap();
let heap_allocated_ciphertext = Box::new(ShortintCiphertext(
public_key.0.encrypt(&server_key.0, value_to_encrypt),
));
let heap_allocated_ciphertext =
Box::new(ShortintCiphertext(public_key.0.encrypt(value_to_encrypt)));
*result = Box::into_raw(heap_allocated_ciphertext);
})
@@ -92,3 +92,108 @@ pub unsafe extern "C" fn shortint_deserialize_public_key(
*result = Box::into_raw(heap_allocated_public_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_gen_compressed_public_key(
client_key: *const ShortintClientKey,
result: *mut *mut ShortintCompressedPublicKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let client_key = get_ref_checked(client_key).unwrap();
let heap_allocated_compressed_public_key = Box::new(ShortintCompressedPublicKey(
shortint::public_key::CompressedPublicKey::new(&client_key.0),
));
*result = Box::into_raw(heap_allocated_compressed_public_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_compressed_public_key_encrypt(
compressed_public_key: *const ShortintCompressedPublicKey,
value_to_encrypt: u64,
result: *mut *mut ShortintCiphertext,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let compressed_public_key = get_ref_checked(compressed_public_key).unwrap();
let heap_allocated_ciphertext = Box::new(ShortintCiphertext(
compressed_public_key.0.encrypt(value_to_encrypt),
));
*result = Box::into_raw(heap_allocated_ciphertext);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_serialize_compressed_public_key(
compressed_public_key: *const ShortintCompressedPublicKey,
result: *mut Buffer,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
let compressed_public_key = get_ref_checked(compressed_public_key).unwrap();
let buffer: Buffer = bincode::serialize(&compressed_public_key.0).unwrap().into();
*result = buffer;
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_deserialize_compressed_public_key(
buffer_view: BufferView,
result: *mut *mut ShortintCompressedPublicKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let compressed_public_key: shortint::public_key::CompressedPublicKey =
bincode::deserialize(buffer_view.into()).unwrap();
let heap_allocated_public_key =
Box::new(ShortintCompressedPublicKey(compressed_public_key));
*result = Box::into_raw(heap_allocated_public_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_decompress_public_key(
compressed_public_key: *const ShortintCompressedPublicKey,
result: *mut *mut ShortintPublicKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let compressed_public_key = get_ref_checked(compressed_public_key).unwrap();
let heap_allocated_public_key = Box::new(ShortintPublicKey(
shortint::public_key::PublicKey::from(compressed_public_key.0.clone()),
));
*result = Box::into_raw(heap_allocated_public_key);
})
}

View File

@@ -20,6 +20,9 @@ pub mod shift;
pub mod sub;
pub struct ShortintServerKey(pub(in crate::c_api) shortint::server_key::ServerKey);
pub struct ShortintCompressedServerKey(
pub(in crate::c_api) shortint::server_key::CompressedServerKey,
);
#[no_mangle]
pub unsafe extern "C" fn shortint_gen_server_key(
@@ -79,3 +82,84 @@ pub unsafe extern "C" fn shortint_deserialize_server_key(
*result = Box::into_raw(heap_allocated_server_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_gen_compressed_server_key(
client_key: *const super::ShortintClientKey,
result_server_key: *mut *mut ShortintCompressedServerKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result_server_key).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result_server_key = std::ptr::null_mut();
let client_key = get_ref_checked(client_key).unwrap();
let server_key = shortint::server_key::CompressedServerKey::new(&client_key.0);
let heap_allocated_server_key = Box::new(ShortintCompressedServerKey(server_key));
*result_server_key = Box::into_raw(heap_allocated_server_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_serialize_compressed_server_key(
server_key: *const ShortintCompressedServerKey,
result: *mut Buffer,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
let server_key = get_ref_checked(server_key).unwrap();
let buffer: Buffer = bincode::serialize(&server_key.0).unwrap().into();
*result = buffer;
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_deserialize_compressed_server_key(
buffer_view: BufferView,
result: *mut *mut ShortintCompressedServerKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
// *result = std::ptr::null_mut();
let server_key: shortint::server_key::CompressedServerKey =
bincode::deserialize(buffer_view.into()).unwrap();
let heap_allocated_server_key = Box::new(ShortintCompressedServerKey(server_key));
*result = Box::into_raw(heap_allocated_server_key);
})
}
#[no_mangle]
pub unsafe extern "C" fn shortint_decompress_server_key(
compressed_server_key: *const ShortintCompressedServerKey,
result: *mut *mut ShortintServerKey,
) -> c_int {
catch_panic(|| {
check_ptr_is_non_null_and_aligned(result).unwrap();
// First fill the result with a null ptr so that if we fail and the return code is not
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
*result = std::ptr::null_mut();
let compressed_server_key = get_ref_checked(compressed_server_key).unwrap();
let heap_allocated_public_key = Box::new(ShortintServerKey(
shortint::server_key::ServerKey::from(compressed_server_key.0.clone()),
));
*result = Box::into_raw(heap_allocated_public_key);
})
}

View File

@@ -7,11 +7,9 @@ use super::{ShortintCiphertext, ShortintServerKey};
pub type AccumulatorCallback = Option<extern "C" fn(u64) -> u64>;
pub type BivariateAccumulatorCallback = Option<extern "C" fn(u64, u64) -> u64>;
pub struct ShortintPBSAccumulator(
pub(in crate::c_api) crate::core_crypto::prelude::GlweCiphertext64,
);
pub struct ShortintPBSAccumulator(pub(in crate::c_api) crate::shortint::server_key::Accumulator);
pub struct ShortintBivariatePBSAccumulator(
pub(in crate::c_api) crate::core_crypto::prelude::GlweCiphertext64,
pub(in crate::c_api) crate::shortint::server_key::Accumulator,
);
#[no_mangle]

View File

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

View File

@@ -0,0 +1,70 @@
//! Module containing primitives pertaining to the conversion of
//! [`standard GGSW ciphertexts`](`GgswCiphertext`) to various representations/numerical domains
//! like the Fourier domain.
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::fft_impl::crypto::ggsw::{
fill_with_forward_fourier_scratch, FourierGgswCiphertext,
};
use crate::core_crypto::fft_impl::math::fft::{Fft, FftView};
use concrete_fft::c64;
use dyn_stack::{DynStack, SizeOverflow, StackReq};
/// Convert a [`GGSW ciphertext`](`GgswCiphertext`) with standard coefficients to the Fourier
/// domain.
///
/// If you want to manage the computation memory manually you can use
/// [`convert_standard_ggsw_ciphertext_to_fourier_mem_optimized`].
pub fn convert_standard_ggsw_ciphertext_to_fourier<Scalar, InputCont, OutputCont>(
input_ggsw: &GgswCiphertext<InputCont>,
output_ggsw: &mut FourierGgswCiphertext<OutputCont>,
) where
Scalar: UnsignedTorus,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = c64>,
{
let fft = Fft::new(output_ggsw.polynomial_size());
let fft = fft.as_view();
let mut buffers = ComputationBuffers::new();
buffers.resize(
convert_standard_ggsw_ciphertext_to_fourier_mem_optimized_requirement(fft)
.unwrap()
.unaligned_bytes_required(),
);
convert_standard_ggsw_ciphertext_to_fourier_mem_optimized(
input_ggsw,
output_ggsw,
fft,
buffers.stack(),
);
}
/// Memory optimized version of [`convert_standard_ggsw_ciphertext_to_fourier`].
///
/// See [`cmux_assign_mem_optimized`](`crate::core_crypto::algorithms::cmux_assign_mem_optimized`)
/// for usage.
pub fn convert_standard_ggsw_ciphertext_to_fourier_mem_optimized<Scalar, InputCont, OutputCont>(
input_ggsw: &GgswCiphertext<InputCont>,
output_ggsw: &mut FourierGgswCiphertext<OutputCont>,
fft: FftView<'_>,
stack: DynStack<'_>,
) where
Scalar: UnsignedTorus,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = c64>,
{
output_ggsw
.as_mut_view()
.fill_with_forward_fourier(input_ggsw.as_view(), fft, stack);
}
/// Return the required memory for [`convert_standard_ggsw_ciphertext_to_fourier_mem_optimized`].
pub fn convert_standard_ggsw_ciphertext_to_fourier_mem_optimized_requirement(
fft: FftView<'_>,
) -> Result<StackReq, SizeOverflow> {
fill_with_forward_fourier_scratch(fft)
}

View File

@@ -0,0 +1,854 @@
//! Module containing primitives pertaining to [`GGSW ciphertext
//! encryption`](`GgswCiphertext#ggsw-encryption`).
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::dispersion::DispersionParameter;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::decomposition::DecompositionLevel;
use crate::core_crypto::commons::math::random::ActivatedRandomGenerator;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use rayon::prelude::*;
/// Encrypt a plaintext in a [`GGSW ciphertext`](`GgswCiphertext`).
///
/// See the [`GGSW ciphertext formal definition`](`GgswCiphertext#ggsw-encryption`) for the
/// definition of the encryption algorithm.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GgswCiphertext creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// // Create the plaintext
/// let encoded_msg = 3u64 << 60;
/// let plaintext = Plaintext(encoded_msg);
///
/// // Create a new GgswCiphertext
/// let mut ggsw = GgswCiphertext::new(
/// 0u64,
/// glwe_size,
/// polynomial_size,
/// decomp_base_log,
/// decomp_level_count,
/// );
///
/// encrypt_ggsw_ciphertext(
/// &glwe_secret_key,
/// &mut ggsw,
/// plaintext,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
/// ```
pub fn encrypt_ggsw_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
output: &mut GgswCiphertext<OutputCont>,
encoded: Plaintext<Scalar>,
noise_parameters: impl DispersionParameter,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert!(
output.polynomial_size() == glwe_secret_key.polynomial_size(),
"Mismatch between polynomial sizes of output cipertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.polynomial_size(),
glwe_secret_key.polynomial_size()
);
assert!(
output.glwe_size().to_glwe_dimension() == glwe_secret_key.glwe_dimension(),
"Mismatch between GlweDimension of output cipertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.glwe_size().to_glwe_dimension(),
glwe_secret_key.glwe_dimension()
);
// Generators used to have same sequential and parallel key generation
let gen_iter = generator
.fork_ggsw_to_ggsw_levels::<Scalar>(
output.decomposition_level_count(),
output.glwe_size(),
output.polynomial_size(),
)
.expect("Failed to split generator into ggsw levels");
let output_glwe_size = output.glwe_size();
let output_polynomial_size = output.polynomial_size();
let decomp_base_log = output.decomposition_base_log();
for (level_index, (mut level_matrix, mut generator)) in
output.iter_mut().zip(gen_iter).enumerate()
{
let decomp_level = DecompositionLevel(level_index + 1);
let factor = encoded
.0
.wrapping_neg()
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
// We iterate over the rows of the level matrix, the last row needs special treatment
let gen_iter = generator
.fork_ggsw_level_to_glwe::<Scalar>(output_glwe_size, output_polynomial_size)
.expect("Failed to split generator into glwe");
let last_row_index = level_matrix.glwe_size().0 - 1;
for ((row_index, mut row_as_glwe), mut generator) in level_matrix
.as_mut_glwe_list()
.iter_mut()
.enumerate()
.zip(gen_iter)
{
encrypt_ggsw_level_matrix_row(
glwe_secret_key,
(row_index, last_row_index),
factor,
&mut row_as_glwe,
noise_parameters,
&mut generator,
);
}
}
}
/// Parallel variant of [`encrypt_ggsw_ciphertext`].
///
/// See the [`formal definition`](`GgswCiphertext#ggsw-encryption`) for the definition of the
/// encryption algorithm.
///
/// New tasks are created per level matrix and per row of each level matrix.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GgswCiphertext creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// // Create the plaintext
/// let encoded_msg = 3u64 << 60;
/// let plaintext = Plaintext(encoded_msg);
///
/// // Create a new GgswCiphertext
/// let mut ggsw = GgswCiphertext::new(
/// 0u64,
/// glwe_size,
/// polynomial_size,
/// decomp_base_log,
/// decomp_level_count,
/// );
///
/// par_encrypt_ggsw_ciphertext(
/// &glwe_secret_key,
/// &mut ggsw,
/// plaintext,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
/// ```
pub fn par_encrypt_ggsw_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
output: &mut GgswCiphertext<OutputCont>,
encoded: Plaintext<Scalar>,
noise_parameters: impl DispersionParameter + Sync,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus + Sync + Send,
KeyCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ParallelByteRandomGenerator,
{
assert!(
output.polynomial_size() == glwe_secret_key.polynomial_size(),
"Mismatch between polynomial sizes of output cipertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.polynomial_size(),
glwe_secret_key.polynomial_size()
);
assert!(
output.glwe_size().to_glwe_dimension() == glwe_secret_key.glwe_dimension(),
"Mismatch between GlweDimension of output cipertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.glwe_size().to_glwe_dimension(),
glwe_secret_key.glwe_dimension()
);
// Generators used to have same sequential and parallel key generation
let gen_iter = generator
.par_fork_ggsw_to_ggsw_levels::<Scalar>(
output.decomposition_level_count(),
output.glwe_size(),
output.polynomial_size(),
)
.expect("Failed to split generator into ggsw levels");
let output_glwe_size = output.glwe_size();
let output_polynomial_size = output.polynomial_size();
let decomp_base_log = output.decomposition_base_log();
output.par_iter_mut().zip(gen_iter).enumerate().for_each(
|(level_index, (mut level_matrix, mut generator))| {
let decomp_level = DecompositionLevel(level_index + 1);
let factor = encoded
.0
.wrapping_neg()
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
// We iterate over the rows of the level matrix, the last row needs special treatment
let gen_iter = generator
.par_fork_ggsw_level_to_glwe::<Scalar>(output_glwe_size, output_polynomial_size)
.expect("Failed to split generator into glwe");
let last_row_index = level_matrix.glwe_size().0 - 1;
level_matrix
.as_mut_glwe_list()
.par_iter_mut()
.enumerate()
.zip(gen_iter)
.for_each(|((row_index, mut row_as_glwe), mut generator)| {
encrypt_ggsw_level_matrix_row(
glwe_secret_key,
(row_index, last_row_index),
factor,
&mut row_as_glwe,
noise_parameters,
&mut generator,
);
});
},
);
}
/// Convenience function to encrypt a row of a [`GgswLevelMatrix`] irrespective of the current row
/// being encrypted. Allows to share code between sequential ([`encrypt_ggsw_ciphertext`]) and
/// parallel ([`par_encrypt_ggsw_ciphertext`]) variants of the GGSW ciphertext encryption.
///
/// You probably don't want to use this function directly.
fn encrypt_ggsw_level_matrix_row<Scalar, KeyCont, OutputCont, Gen>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
(row_index, last_row_index): (usize, usize),
factor: Scalar,
row_as_glwe: &mut GlweCiphertext<OutputCont>,
noise_parameters: impl DispersionParameter,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
if row_index < last_row_index {
// Not the last row
let sk_poly_list = glwe_secret_key.as_polynomial_list();
let sk_poly = sk_poly_list.get(row_index);
// Copy the key polynomial to the output body, to avoid allocating a temporary buffer
let mut body = row_as_glwe.get_mut_body();
body.as_mut().copy_from_slice(sk_poly.as_ref());
slice_wrapping_scalar_mul_assign(body.as_mut(), factor);
encrypt_glwe_ciphertext_assign(glwe_secret_key, row_as_glwe, noise_parameters, generator);
} else {
// The last row needs a slightly different treatment
let mut body = row_as_glwe.get_mut_body();
body.as_mut().fill(Scalar::ZERO);
body.as_mut()[0] = factor.wrapping_neg();
encrypt_glwe_ciphertext_assign(glwe_secret_key, row_as_glwe, noise_parameters, generator);
}
}
/// Convenience function to share the core logic of the seeded GGSW encryption between all
/// functions needing it.
///
/// Allows to efficiently encrypt lists of seeded GGSW.
///
/// WARNING: this assumes the caller manages the coherency of calls to the generator to make sure
/// the right bytes are generated at the right time.
pub fn encrypt_seeded_ggsw_ciphertext_with_existing_generator<Scalar, KeyCont, OutputCont, Gen>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
output: &mut SeededGgswCiphertext<OutputCont>,
encoded: Plaintext<Scalar>,
noise_parameters: impl DispersionParameter,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar> + std::fmt::Debug,
Gen: ByteRandomGenerator,
{
// Generators used to have same sequential and parallel key generation
let gen_iter = generator
.fork_ggsw_to_ggsw_levels::<Scalar>(
output.decomposition_level_count(),
output.glwe_size(),
output.polynomial_size(),
)
.expect("Failed to split generator into ggsw levels");
let output_glwe_size = output.glwe_size();
let output_polynomial_size = output.polynomial_size();
let decomp_base_log = output.decomposition_base_log();
for (level_index, (mut level_matrix, mut loop_generator)) in
output.iter_mut().zip(gen_iter).enumerate()
{
let decomp_level = DecompositionLevel(level_index + 1);
let factor = encoded
.0
.wrapping_neg()
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
// We iterate over the rows of the level matrix, the last row needs special treatment
let gen_iter = loop_generator
.fork_ggsw_level_to_glwe::<Scalar>(output_glwe_size, output_polynomial_size)
.expect("Failed to split generator into glwe");
let last_row_index = level_matrix.glwe_size().0 - 1;
for ((row_index, mut row_as_glwe), mut loop_generator) in level_matrix
.as_mut_seeded_glwe_list()
.iter_mut()
.enumerate()
.zip(gen_iter)
{
encrypt_seeded_ggsw_level_matrix_row(
glwe_secret_key,
(row_index, last_row_index),
factor,
&mut row_as_glwe,
noise_parameters,
&mut loop_generator,
);
}
}
}
/// Encrypt a plaintext in a [`seeded GGSW ciphertext`](`SeededGgswCiphertext`).
///
/// See the [`formal definition`](`GgswCiphertext#ggsw-encryption`) for the definition of the
/// encryption algorithm.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GgswCiphertext creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// // Create the plaintext
/// let encoded_msg = 3u64 << 60;
/// let plaintext = Plaintext(encoded_msg);
///
/// // Create a new GgswCiphertext
/// let mut ggsw = SeededGgswCiphertext::new(
/// 0u64,
/// glwe_size,
/// polynomial_size,
/// decomp_base_log,
/// decomp_level_count,
/// seeder.seed().into(),
/// );
///
/// encrypt_seeded_ggsw_ciphertext(
/// &glwe_secret_key,
/// &mut ggsw,
/// plaintext,
/// glwe_modular_std_dev,
/// seeder,
/// );
/// ```
pub fn encrypt_seeded_ggsw_ciphertext<Scalar, KeyCont, OutputCont, NoiseSeeder>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
output: &mut SeededGgswCiphertext<OutputCont>,
encoded: Plaintext<Scalar>,
noise_parameters: impl DispersionParameter,
noise_seeder: &mut NoiseSeeder,
) where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar> + std::fmt::Debug,
// Maybe Sized allows to pass Box<dyn Seeder>.
NoiseSeeder: Seeder + ?Sized,
{
assert!(
output.polynomial_size() == glwe_secret_key.polynomial_size(),
"Mismatch between polynomial sizes of output cipertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.polynomial_size(),
glwe_secret_key.polynomial_size()
);
assert!(
output.glwe_size().to_glwe_dimension() == glwe_secret_key.glwe_dimension(),
"Mismatch between GlweDimension of output cipertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.glwe_size().to_glwe_dimension(),
glwe_secret_key.glwe_dimension()
);
let mut generator = EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
output.compression_seed().seed,
noise_seeder,
);
encrypt_seeded_ggsw_ciphertext_with_existing_generator(
glwe_secret_key,
output,
encoded,
noise_parameters,
&mut generator,
)
}
/// Convenience function to share the core logic of the parallele seeded GGSW encryption between all
/// functions needing it.
///
/// Allows to efficiently encrypt lists of seeded GGSW.
///
/// WARNING: this assumes the caller manages the coherency of calls to the generator to make sure
/// the right bytes are generated at the right time.
pub fn par_encrypt_seeded_ggsw_ciphertext_with_existing_generator<
Scalar,
KeyCont,
OutputCont,
Gen,
>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
output: &mut SeededGgswCiphertext<OutputCont>,
encoded: Plaintext<Scalar>,
noise_parameters: impl DispersionParameter + Sync,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus + Sync + Send,
KeyCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ParallelByteRandomGenerator,
{
// Generators used to have same sequential and parallel key generation
let gen_iter = generator
.par_fork_ggsw_to_ggsw_levels::<Scalar>(
output.decomposition_level_count(),
output.glwe_size(),
output.polynomial_size(),
)
.expect("Failed to split generator into ggsw levels");
let output_glwe_size = output.glwe_size();
let output_polynomial_size = output.polynomial_size();
let decomp_base_log = output.decomposition_base_log();
output.par_iter_mut().zip(gen_iter).enumerate().for_each(
|(level_index, (mut level_matrix, mut generator))| {
let decomp_level = DecompositionLevel(level_index + 1);
let factor = encoded
.0
.wrapping_neg()
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
// We iterate over the rows of the level matrix, the last row needs special treatment
let gen_iter = generator
.par_fork_ggsw_level_to_glwe::<Scalar>(output_glwe_size, output_polynomial_size)
.expect("Failed to split generator into glwe");
let last_row_index = level_matrix.glwe_size().0 - 1;
level_matrix
.as_mut_seeded_glwe_list()
.par_iter_mut()
.enumerate()
.zip(gen_iter)
.for_each(|((row_index, mut row_as_glwe), mut generator)| {
encrypt_seeded_ggsw_level_matrix_row(
glwe_secret_key,
(row_index, last_row_index),
factor,
&mut row_as_glwe,
noise_parameters,
&mut generator,
);
});
},
);
}
/// Parallel variant of [`encrypt_ggsw_ciphertext`].
///
/// See the [`formal definition`](`GgswCiphertext#ggsw-encryption`) for the definition of the
/// encryption algorithm.
///
/// New tasks are created per level matrix and per row of each level matrix.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GgswCiphertext creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// // Create the plaintext
/// let encoded_msg = 3u64 << 60;
/// let plaintext = Plaintext(encoded_msg);
///
/// // Create a new GgswCiphertext
/// let mut ggsw = SeededGgswCiphertext::new(
/// 0u64,
/// glwe_size,
/// polynomial_size,
/// decomp_base_log,
/// decomp_level_count,
/// seeder.seed().into(),
/// );
///
/// par_encrypt_seeded_ggsw_ciphertext(
/// &glwe_secret_key,
/// &mut ggsw,
/// plaintext,
/// glwe_modular_std_dev,
/// seeder,
/// );
/// ```
pub fn par_encrypt_seeded_ggsw_ciphertext<Scalar, KeyCont, OutputCont, NoiseSeeder>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
output: &mut SeededGgswCiphertext<OutputCont>,
encoded: Plaintext<Scalar>,
noise_parameters: impl DispersionParameter + Sync,
noise_seeder: &mut NoiseSeeder,
) where
Scalar: UnsignedTorus + Sync + Send,
KeyCont: Container<Element = Scalar> + Sync,
OutputCont: ContainerMut<Element = Scalar>,
// Maybe Sized allows to pass Box<dyn Seeder>.
NoiseSeeder: Seeder + ?Sized,
{
assert!(
output.polynomial_size() == glwe_secret_key.polynomial_size(),
"Mismatch between polynomial sizes of output cipertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.polynomial_size(),
glwe_secret_key.polynomial_size()
);
assert!(
output.glwe_size().to_glwe_dimension() == glwe_secret_key.glwe_dimension(),
"Mismatch between GlweDimension of output cipertexts and input secret key. \
Got {:?} in output, and {:?} in secret key.",
output.glwe_size().to_glwe_dimension(),
glwe_secret_key.glwe_dimension()
);
let mut generator = EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
output.compression_seed().seed,
noise_seeder,
);
par_encrypt_seeded_ggsw_ciphertext_with_existing_generator(
glwe_secret_key,
output,
encoded,
noise_parameters,
&mut generator,
);
}
/// Convenience function to encrypt a row of a [`GgswLevelMatrix`] irrespective of the current row
/// being encrypted. Allows to share code between sequential ([`encrypt_seeded_ggsw_ciphertext`])
/// and parallel ([`par_encrypt_seeded_ggsw_ciphertext`]) variants of the GGSW ciphertext
/// encryption.
///
/// You probably don't want to use this function directly.
fn encrypt_seeded_ggsw_level_matrix_row<Scalar, KeyCont, OutputCont, Gen>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
(row_index, last_row_index): (usize, usize),
factor: Scalar,
row_as_glwe: &mut SeededGlweCiphertext<OutputCont>,
noise_parameters: impl DispersionParameter,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
if row_index < last_row_index {
// Not the last row
let sk_poly_list = glwe_secret_key.as_polynomial_list();
let sk_poly = sk_poly_list.get(row_index);
// Copy the key polynomial to the output body, to avoid allocating a temporary buffer
let mut body = row_as_glwe.get_mut_body();
body.as_mut().copy_from_slice(sk_poly.as_ref());
slice_wrapping_scalar_mul_assign(body.as_mut(), factor);
encrypt_seeded_glwe_ciphertext_assign_with_existing_generator(
glwe_secret_key,
row_as_glwe,
noise_parameters,
generator,
);
} else {
// The last row needs a slightly different treatment
let mut body = row_as_glwe.get_mut_body();
body.as_mut().fill(Scalar::ZERO);
body.as_mut()[0] = factor.wrapping_neg();
encrypt_seeded_glwe_ciphertext_assign_with_existing_generator(
glwe_secret_key,
row_as_glwe,
noise_parameters,
generator,
);
}
}
#[cfg(test)]
mod test {
use crate::core_crypto::commons::generators::{
DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator,
};
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, CompressionSeed};
use crate::core_crypto::commons::test_tools;
use crate::core_crypto::prelude::*;
fn test_parallel_and_seeded_ggsw_encryption_equivalence<Scalar>()
where
Scalar: UnsignedTorus + Sync + Send,
{
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
// computations
// Define parameters for GgswCiphertext creation
let glwe_size = GlweSize(2);
let polynomial_size = PolynomialSize(1024);
let decomp_base_log = DecompositionBaseLog(8);
let decomp_level_count = DecompositionLevelCount(3);
let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
// Create the PRNG
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let main_seed = seeder.seed();
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
const NB_TESTS: usize = 10;
for _ in 0..NB_TESTS {
// Create the GlweSecretKey
let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
glwe_size.to_glwe_dimension(),
polynomial_size,
&mut secret_generator,
);
// Create the plaintext
let encoded_msg: Scalar =
test_tools::random_uint_between(Scalar::ZERO..Scalar::TWO.shl(2));
let plaintext = Plaintext(encoded_msg);
let compression_seed: CompressionSeed = seeder.seed().into();
let mut ser_ggsw = GgswCiphertext::new(
Scalar::ZERO,
glwe_size,
polynomial_size,
decomp_base_log,
decomp_level_count,
);
let mut deterministic_seeder =
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
compression_seed.seed,
&mut deterministic_seeder,
);
encrypt_ggsw_ciphertext(
&glwe_secret_key,
&mut ser_ggsw,
plaintext,
glwe_modular_std_dev,
&mut encryption_generator,
);
let mut par_ggsw = GgswCiphertext::new(
Scalar::ZERO,
glwe_size,
polynomial_size,
decomp_base_log,
decomp_level_count,
);
let mut deterministic_seeder =
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
compression_seed.seed,
&mut deterministic_seeder,
);
par_encrypt_ggsw_ciphertext(
&glwe_secret_key,
&mut par_ggsw,
plaintext,
glwe_modular_std_dev,
&mut encryption_generator,
);
assert_eq!(ser_ggsw, par_ggsw);
// Create a new GgswCiphertext
let mut ser_seeded_ggsw = SeededGgswCiphertext::new(
Scalar::ZERO,
glwe_size,
polynomial_size,
decomp_base_log,
decomp_level_count,
compression_seed,
);
let mut deterministic_seeder =
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
encrypt_seeded_ggsw_ciphertext(
&glwe_secret_key,
&mut ser_seeded_ggsw,
plaintext,
glwe_modular_std_dev,
&mut deterministic_seeder,
);
let mut par_seeded_ggsw = SeededGgswCiphertext::new(
Scalar::ZERO,
glwe_size,
polynomial_size,
decomp_base_log,
decomp_level_count,
compression_seed,
);
let mut deterministic_seeder =
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
par_encrypt_seeded_ggsw_ciphertext(
&glwe_secret_key,
&mut par_seeded_ggsw,
plaintext,
glwe_modular_std_dev,
&mut deterministic_seeder,
);
assert_eq!(ser_seeded_ggsw, par_seeded_ggsw);
let decompressed_ggsw = par_seeded_ggsw.decompress_into_ggsw_ciphertext();
assert_eq!(ser_ggsw, decompressed_ggsw);
}
}
#[test]
fn test_parallel_and_seeded_ggsw_encryption_equivalence_u32() {
test_parallel_and_seeded_ggsw_encryption_equivalence::<u32>();
}
#[test]
fn test_parallel_and_seeded_ggsw_encryption_equivalence_u64() {
test_parallel_and_seeded_ggsw_encryption_equivalence::<u64>();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
//! Module containing primitives pertaining to the operation usually referred to as a
//! _sample extract_ in the literature. Allowing to extract a single
//! [`LWE Ciphertext`](`LweCiphertext`) from a given [`GLWE ciphertext`](`GlweCiphertext`).
use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::commons::numeric::UnsignedInteger;
use crate::core_crypto::commons::parameters::{MonomialDegree, *};
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// Extract the nth coefficient from the body of a [`GLWE Ciphertext`](`GlweCiphertext`) as an
/// [`LWE ciphertext`](`LweCiphertext`).
///
/// # Formal definition
///
/// This operation is usually referred to as a _sample extract_ in the literature.
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GlweCiphertext creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// // Create the plaintext
/// let msg = 3u64;
/// let encoded_msg = msg << 60;
/// let mut plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(polynomial_size.0));
///
/// let special_value = 15;
/// *plaintext_list.get_mut(42).0 = 15 << 60;
///
/// // Create a new GlweCiphertext
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
///
/// encrypt_glwe_ciphertext(
/// &glwe_secret_key,
/// &mut glwe,
/// &plaintext_list,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// // Now we get the equivalent LweSecretKey from the GlweSecretKey
/// let equivalent_lwe_sk = glwe_secret_key.clone().into_lwe_secret_key();
///
/// let mut extracted_sample =
/// LweCiphertext::new(0u64, equivalent_lwe_sk.lwe_dimension().to_lwe_size());
///
/// // Here we chose to extract sample at index 42 (corresponding to the MonomialDegree(42))
/// extract_lwe_sample_from_glwe_ciphertext(&glwe, &mut extracted_sample, MonomialDegree(42));
///
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&equivalent_lwe_sk, &extracted_sample);
///
/// // Round and remove encoding
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
///
/// let recovered_message = decomposer.closest_representable(decrypted_plaintext.0) >> 60;
///
/// // We check we recover our special value instead of the 3 stored in all other slots of the
/// // GlweCiphertext
/// assert_eq!(special_value, recovered_message);
/// ```
pub fn extract_lwe_sample_from_glwe_ciphertext<Scalar, InputCont, OutputCont>(
input_glwe: &GlweCiphertext<InputCont>,
output_lwe: &mut LweCiphertext<OutputCont>,
nth: MonomialDegree,
) where
Scalar: UnsignedInteger,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert!(
input_glwe.glwe_size().to_glwe_dimension().0 * input_glwe.polynomial_size().0
== output_lwe.lwe_size().to_lwe_dimension().0,
"Mismatch between equivalent LweDimension of input ciphertext and output ciphertext. \
Got {:?} for input and {:?} for output.",
LweDimension(input_glwe.glwe_size().to_glwe_dimension().0 * input_glwe.polynomial_size().0),
output_lwe.lwe_size().to_lwe_dimension(),
);
// We retrieve the bodies and masks of the two ciphertexts.
let (mut lwe_mask, lwe_body) = output_lwe.get_mut_mask_and_body();
let (glwe_mask, glwe_body) = input_glwe.get_mask_and_body();
// We copy the body
*lwe_body.0 = glwe_body.as_ref()[nth.0];
// We copy the mask (each polynomial is in the wrong order)
lwe_mask.as_mut().copy_from_slice(glwe_mask.as_ref());
// We compute the number of elements which must be
// turned into their opposite
let opposite_count = input_glwe.polynomial_size().0 - nth.0 - 1;
// We loop through the polynomials
for lwe_mask_poly in lwe_mask.as_mut().chunks_mut(input_glwe.polynomial_size().0) {
// We reverse the polynomial
lwe_mask_poly.reverse();
// We compute the opposite of the proper coefficients
slice_wrapping_opposite_assign(&mut lwe_mask_poly[0..opposite_count]);
// We rotate the polynomial properly
lwe_mask_poly.rotate_left(opposite_count);
}
}

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