Compare commits

...

159 Commits
tmp_ccs ... cm

Author SHA1 Message Date
J-B Orfila
d220008757 (artifact_tches): Cleaning 2025-05-14 11:44:32 +02:00
Loris
b1b55b6426 Typos 2025-02-03 18:20:02 +01:00
Loris
77bea74ac9 (float) Tches Artifact 2025
Co-authored-by: Loris Bergerat <loris.bergerat@zama.ai>
2025-01-31 20:04:58 +01:00
dependabot[bot]
a8b6c72910 chore(deps): bump tj-actions/changed-files from 40.2.0 to 40.2.1
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 40.2.0 to 40.2.1.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](da093c1609...1c938490c8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 10:21:53 +01:00
Arthur Meyre
c2b21ed709 chore(integer): remove unused RadixDecomposition struct and code 2023-12-08 10:38:21 +01:00
tmontaigu
ad41fdf5a5 feat(integer): expose default and smart ciphertext sum
This expose the ciphertext sum default and smart variants

This also removes the par_seq_op functions as they are less optimal
2023-12-06 10:50:45 +01:00
Arthur Meyre
eeae19f35f chore(core_crypto): disable seeded entities for non power of 2 moduli
- as random uniform generation has rejection sampling for non native moduli
the seeded decompression currently does not work as it allocates just
enough bytes for a native integer and not for the various retries which may
be needed
- follow-up issue: https://github.com/zama-ai/tfhe-rs-internal/issues/358
2023-12-05 15:25:30 +01:00
Mayeul@Zama
798572e58c style(shortint): rename MaxNoiseLevel::valid validate 2023-12-04 18:16:46 +01:00
Mayeul@Zama
36d375943c refactor(shortint): encapsulate Degree and MaxDegree 2023-12-04 18:16:46 +01:00
Mayeul@Zama
a1488b10d5 style(shortint): move MaxDegree 2023-12-04 18:16:46 +01:00
Mayeul@Zama
b153641280 style(shortint): scalar_sub use scalar_add 2023-12-04 18:16:46 +01:00
tmontaigu
48405959a4 feat(integer): add decrypt_trivial
This adds a decrypt_trivial method to all ciphertext types of
shortint and integer.

This functions tries to "decrypt" the ciphertext if it is a
trivial one, otherwise it return an error.

This is meant to be a debugging 'tool':

To debug a function / circuit, users can call the function
on trivial ciphertexts intead of real ciphertext, that way,
computations are faster _and_ they will now be able to see intermediate
values via these decrypt_trivial, to do some print-debugging or use a
debugger.
2023-12-04 15:50:53 +01:00
Arthur Meyre
d39e73be91 chore(core): freshen up native decomposition tests
- the classic native decomposer results are exact and there aren't that
many cases to test so tests are changed to be exhaustive
- non native currently still has some work in progress parts so won't be
made exhaustive right away, additionally the next PR for the prime Q effort
will make some changes to it, so this is why the current code has not been
touched for the non native decomposer
2023-12-04 14:56:17 +01:00
dependabot[bot]
71447d845f chore(deps): bump tj-actions/changed-files from 40.1.1 to 40.2.0
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 40.1.1 to 40.2.0.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](25ef3926d1...da093c1609)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 09:22:17 +01:00
Arthur Meyre
837df59b44 fix(shortint): programmable_bootstrapping_native_crt could alter its input
- the alteration is a trick to be able to perform the wopbs in the native
crt mode but it modifies the input ciphertext and leaves it modified
meaning that the input no longer represents the same data/encryption
- applying the functon several times likely would lead to incorrect results
starting from the second call with increasing error/divergence from the
original encrypted data
- clone locally instead, should be negligible given wopbs runtime

BREAKING CHANGE:
programmable_bootstrapping_native_crt signature has changed
2023-12-01 14:46:09 +01:00
Arthur Meyre
303cac2092 chore(ci): re-enable release profile for doctest
- following merge of 17.0.4 in rust stable the bug uncovered by lto on
aarch64 has been fixed https://github.com/rust-lang/rust/issues/116941 so
we remove the hard coded override
2023-12-01 12:34:26 +01:00
Arthur Meyre
bb1a969c34 chore(ci): update nightly toolchain to have fixed LLVM as well
- fix lints linked to latest nightly
2023-12-01 12:34:26 +01:00
tmontaigu
9dac9242be feat(integer): add boolean_ops to work on BooleanBlock
This adds boolean_bitand/or/xor/not to work on BooleanBlock
This is meant to improve API of BooleanBlock.
2023-12-01 11:13:31 +01:00
tmontaigu
e97ac815eb feat(shortint): add trivial pbs
When we detect that the ciphertext to be bootstrapped
is a trivial ciphertext, we can apply the lookup table
in clear thus saving computation time.

This allows shortint,integer,hlapi to have
way faster computations when all inputs are trivial
allowing to more rapidly check and debug a circuit
2023-11-30 22:01:28 +01:00
Arthur Meyre
62feb59722 chore(ci): fix doctest by using parameters with enough precision 2023-11-30 14:57:07 +01:00
Mayeul@Zama
ac8916a30f refactor(shortint): define woppbs on server key instead of engine 2023-11-30 14:54:33 +01:00
Mayeul@Zama
069ea98ad6 style(shortint): fix remaining unused_self 2023-11-30 14:54:33 +01:00
Mayeul@Zama
e587d1835e refactor(shortint): define operations on server key instead of engine 2023-11-30 14:54:33 +01:00
Mayeul@Zama
16121e7487 style(core): fix some unused self 2023-11-30 14:54:33 +01:00
Mayeul@Zama
3ab566de7b style(boolean): fix some unused self 2023-11-30 14:54:33 +01:00
David Testé
2309b07703 test(integer): add smart tests for radix_parallel min/max
This also refactor the code to use macro to parametrize tests to
make code smaller.
2023-11-30 14:44:17 +01:00
David Testé
8755094c38 test(integer): fix tests for unsigned scalar min/max operations 2023-11-30 14:44:17 +01:00
David Testé
4da10e9dd5 chore(ci): add aws ec2 fallback profile for cpu tests
This is done to mitigate resource shortages in our base AWS region
(eu-west-3) due to the high number of instances that are launched
in parallel in our Pull Requests.
2023-11-30 13:02:42 +01:00
Arthur Meyre
cdda260063 chore(ci): .gitignore was ignoring all files/directories names keys
- this was hiding some source files in vscode search and could likely have
been very annoying when commiting stuff
2023-11-29 18:14:33 +01:00
Arthur Meyre
be413fff50 chore(shortint): remove doctest as tests as it is confirmed they fail
- doctest were also failing as tests and so it is not linked to doctests
- still unclear what is causing the issue, the results are sometimes way
off
- the concentration of failed tests can indicate a miscompile as those
tests never fail on the M1 CI, some alignment is causing issues from time
to time
2023-11-29 17:43:25 +01:00
Arthur Meyre
3ed960d255 chore(ci): fix a command naming issue for the CI 2023-11-29 16:07:31 +01:00
Arthur Meyre
bdadd39a34 chore(ci): add some missing spec indicators for cargo commands 2023-11-29 15:29:06 +01:00
Arthur Meyre
f03f2f9c6d chore(ci): fix clippy trivium target which also triggered for tfhe 2023-11-29 15:29:06 +01:00
Arthur Meyre
f03ec9bbed chore(tfhe): re-allow semicolon_if_nothing_returned
- create a section for lints that have been considered to be disallowed but
are kept allowed, either because they bring too little value or because
they don't help with readability in certain circumstances
2023-11-29 15:28:43 +01:00
Arthur Meyre
5137751dd2 chore(ci): fix pedantic lint with missing trailing ; on () return 2023-11-29 15:28:43 +01:00
tmontaigu
edc3449dbf feat(integer): add signed overflowing_add 2023-11-29 13:02:58 +01:00
Arthur Meyre
6068c509de feat(tfhe): add LWE encryption and linalg with non power of 2 moduli 2023-11-29 09:54:55 +01:00
David Testé
30a4348e3a test(integer): use keycache for comparisons and wopbs 2023-11-28 14:11:53 +01:00
Mayeul@Zama
3013e02d90 fix(core): fix typo in comment 2023-11-28 13:10:23 +01:00
Mayeul@Zama
c029917c5c style(all): rename NB_TEST NB_TESTS 2023-11-28 13:10:23 +01:00
Mayeul@Zama
d23d04021b style(core): fix clippy::inconsistent_struct_constructor 2023-11-28 13:10:23 +01:00
Mayeul@Zama
5a5e9e0ac1 style(core): fix clippy::ptr_as_ptr 2023-11-28 13:10:23 +01:00
Mayeul@Zama
cc0a3bad8d style(core): fix clippy::iter_without_into_iter 2023-11-28 13:10:23 +01:00
Mayeul@Zama
1d12f60849 style(core): fix clippy::default_trait_access 2023-11-28 13:10:23 +01:00
Mayeul@Zama
a0db39c86e style(core): fix clippy::redundant_closure_for_method_calls 2023-11-28 13:10:23 +01:00
Mayeul@Zama
ef4558ac13 style(core): fix clippy::trivially_copy_pass_by_ref 2023-11-28 13:10:23 +01:00
Mayeul@Zama
bfb22b4531 style(core): fix clippy::needless_pass_by_value 2023-11-28 13:10:23 +01:00
Mayeul@Zama
88025010e1 style(core): fix clippy::unnecessary_wraps 2023-11-28 13:10:23 +01:00
Mayeul@Zama
b1f4f3b330 style(core): fix clippy::semicolon_if_nothing_returned 2023-11-28 13:10:23 +01:00
Mayeul@Zama
7575a426ab style(core): fix clippy::used_underscore_binding 2023-11-28 13:10:23 +01:00
Mayeul@Zama
1ac57218b1 style(core): fix clippy::manual_let_else 2023-11-28 13:10:23 +01:00
Mayeul@Zama
b7c3f16e24 style(core): fix clippy::implicit_clone 2023-11-28 13:10:23 +01:00
Mayeul@Zama
bf4f9198fb style(core): fix clippy::if_not_else 2023-11-28 13:10:23 +01:00
Mayeul@Zama
e618e1d05d style(core): fix clippy::uninlined_format_args 2023-11-28 13:10:23 +01:00
Mayeul@Zama
000428d688 style(core): replace assert by unwrap 2023-11-28 13:10:23 +01:00
Arthur Meyre
937c90666b chore(core): change the test LUT to apply the identity function
- the identity function more easily detects errors in the PBS as each mega
case contains a different value compared to its neighbours
2023-11-28 11:27:45 +01:00
sarah el kazdadi
b6a6f1b098 feat(core): specialize keyswitch implementation for small scalar values 2023-11-27 09:57:37 +01:00
David Testé
c2d7f1748c chore(ci): add core_crypto layer to code coverage 2023-11-22 10:21:17 +01:00
Mayeul@Zama
e8cd55dee6 feat(shortint): add degree information in CheckError::CarryFull 2023-11-21 19:52:26 +01:00
Mayeul@Zama
95aea9dbe8 feat(shortint): add noise checks 2023-11-21 19:52:26 +01:00
Mayeul@Zama
89f701d307 refactor(shortint): refactor CheckError 2023-11-21 19:52:26 +01:00
Mayeul@Zama
224146686f feat(shortint): add max_noise_level 2023-11-21 19:52:26 +01:00
Mayeul@Zama
b6b5f92220 fix(integer): update noise_level manually in direct calls to core_crypto 2023-11-21 19:52:26 +01:00
David Testé
0fec9e252b chore(ci): change benchmark aws ec2 machine type
This instance type hpc7a.96xlarge yields better performances for
nearly the same hourly cost.
2023-11-21 14:34:08 +01:00
Arthur Meyre
53c9b82824 chore(shortint): fix typo for NoiseLevel variant UNKNOWN 2023-11-20 18:54:14 +01:00
tmontaigu
f670a950d6 fix(integer): fix inner index computation in sum
In the function that sums a vec of ciphertexts,
we track trivial zeros to avoid un-needed PBSes.

One of this tracker is `last_block_where_addition_happened`
however it was not properly computed.
It was initialized to `num_blocks - 1`, and then got applied
a bunch of `max(current, new)` where 0 <= new <= num_block - 1
which means last_block_where_addition_happened was always num_blocks - 1.

The correct initial value is 0.
2023-11-20 16:40:21 +01:00
tmontaigu
a44970a9a3 feat(integer): avoid un-necessary computations in mul params 1_X
When the parameters have 1 bit of message (message modulus == 2)
then the multiplication of 2 blocks does not create a result that can
go into the carry space.

We use that fact to avoid doig un-necessary computations
when multiplying integers encrypted under parameters with 1 bit.
2023-11-20 15:21:59 +01:00
Arthur Meyre
55775b8e02 fix(shortint): fix overflow behavior of NoiseLevel
- we will need to use a MAX/UNKNOWN level for forward compatibility with
old serialized ciphertexts, this patch ensures the add/mul behavior
saturates properly to usize::MAX to force a refresh in operations which
do it automatically
2023-11-17 18:34:02 +01:00
Arthur Meyre
523d561de6 chore(ci): add _ci_run_filter to standalone tests in shortint
- those tests were likely ignored, this is no longer the case
2023-11-17 18:34:02 +01:00
tmontaigu
61a50d0bcc chore(integer): make oveflowing_add/sub return BooleanBlock 2023-11-17 16:22:20 +01:00
Arthur Meyre
ee57f5658b chore(ci): refactor integer script and skip div and rem preferring div_rem 2023-11-17 15:00:50 +01:00
tmontaigu
9362965f50 feat(integer): add accessors to inner shortint sks
Users can access blocks from an integer but they don't have
the ability to use the inner shortint server key to process
individual blocks.

This adds an AsRef impl on integer ServerKey to allow that.

This also adds shortcuts to the integer ServerKey to get
the MessageModulus/CarryModulus (these are shorticuts
because users could do `integer_key.as_ref().message_modulus`.
2023-11-16 16:25:27 +01:00
Arthur Meyre
00fb60451d chore(ci): group signed and unsigned integer for better runtime homogeneity 2023-11-16 14:18:30 +01:00
Arthur Meyre
18b9fd4464 chore(ci): re-enable mistakenly disabled AVX512 for integers 2023-11-16 14:18:30 +01:00
Arthur Meyre
eace0bfb85 chore(ci): spread tests between two CI machines/workflow for faster runtime 2023-11-16 14:18:30 +01:00
Arthur Meyre
af1be5ebca chore(core): fix noise generation which could overflow the custom modulus
- updated some function name (for modulus checking) to be clearer on what
they do and when to use them
2023-11-16 08:58:40 +01:00
tmontaigu
916bd8a09f feat(hlapi): move if_then_else/cmux to FheBool
- This makes FheBool use integer::BooleanBlock internally.
- It makes comparisons (eq, ne, le, etc) return a FheBool instead of
  FheUint/FheInt.
- It also moves the if_then_else and cmux methods to FheBool.
- Adds casting from FheBool to FheUint/FheInt (but not from
  FheUint/FheInt to FheBool as we expect users to do `a.ne(0)`
  as its matches Rust)

BREAKING CHANGE:
    - Comparisons now return FheBool
    - if_then_else/cmux are now methods of FheBool.
2023-11-15 23:22:30 +01:00
tmontaigu
20cb0642ce refactor(hlapi): implement CastFrom for GenericInteger
And add the trait to the prelude so that users can use
it.
2023-11-15 23:22:30 +01:00
Arthur Meyre
151f9f6d82 chore(ci): fix build on main following several big merges 2023-11-15 13:29:08 +01:00
Arthur Meyre
8db8cb49e4 chore(shortint): add some flaky/failing doctests as actual tests
- check that those are actually failing or that they are a doctest bug
- add _ci_run_filter so that we can easily make sure tests run in CI even
if they don't have the "parameter format"
2023-11-15 11:10:44 +01:00
Arthur Meyre
b4583976a2 chore(tfhe): fix .gitignore for key cache
- this was not properly ignoring the keycache if a file had a specific
extension
2023-11-15 11:10:30 +01:00
Arthur Meyre
b450375da1 chore(integer): restore assert after using 3_3 params for CRT doctests
- fix max degree for CRT keys which don't need to propagate carries

BREAKING CHANGE:
pub API removed from pub interface
2023-11-15 11:10:30 +01:00
tmontaigu
f02f1fb297 feat(integer): add unsigned_oveflowing_add 2023-11-14 18:57:09 +01:00
Mayeul@Zama
17642fa703 refactor(shortint): remove unused EngineResult 2023-11-14 16:30:09 +01:00
Mayeul@Zama
23fa9b24bd refactor(shortint): separate lut generation from ShortintEngine 2023-11-14 16:30:09 +01:00
tmontaigu
0453b9bd60 fix(integer): fix signed_overflowing_sub using trivial 0 2023-11-13 15:43:33 +01:00
Arthur Meyre
9b2cf67911 chore(tfhe): fix required features for the generate_test_keys util 2023-11-13 10:05:17 +01:00
dependabot[bot]
36a7656048 chore(deps): bump tj-actions/changed-files from 40.1.0 to 40.1.1
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 40.1.0 to 40.1.1.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](18c8a4eceb...25ef3926d1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 09:58:27 +01:00
Arthur Meyre
61c8eadd58 chore(ci): update Makefile for semver trick compatibility
- adding the tfhe package as a dependency is currently causing issues with
Cargo because of unified feature resolution it seems, it needs an
additional version specifier to disambiguate which package we are referring
to, an issue exists on their end but I don't think a fix is to be expected
soon https://github.com/rust-lang/cargo/issues/12891
- commiting this to main and then backporting the relevant pieces to 0.4.x
2023-11-10 15:35:38 +01:00
Arthur Meyre
fdd4d9d1cc chore(c_api): add more comments in the build.rs file and cbindgen.toml 2023-11-10 15:35:38 +01:00
Arthur Meyre
62700ab853 chore(tfhe): clarify dependency vs feature selection 2023-11-10 15:35:38 +01:00
Arthur Meyre
27445645e7 chore(c_api): have a way to skip cbindgen in a semver trick setting 2023-11-10 15:35:38 +01:00
tmontaigu
ea0cd26c0b chore(tfhe): fix builds on main 2023-11-10 15:15:31 +01:00
David Testé
ff48582679 test(core_crypto): silence dead code warnings on test utils 2023-11-10 09:35:16 +01:00
tmontaigu
a77c87ff12 refactor(hlapi): make GenericInteger generic over the Id 2023-11-09 20:33:53 +01:00
tmontaigu
6d143f1edc refactor(hlapi): remove unused FromParameters trait 2023-11-09 20:33:53 +01:00
Arthur Meyre
216e6b443a chore(tfhe): fix pedantic lints 2023-11-09 17:12:00 +01:00
Arthur Meyre
1400ae946c test(tfhe): add uniform random test
- use DKW test, it is e.g. used in
https://github.com/wch/r-source/blob/trunk/tests/p-r-random-tests.R

See Wikipedia DKW inequality
2023-11-09 17:12:00 +01:00
Arthur Meyre
c332902a05 feat(core): add support for non power of 2 moduli for random generation
- add convenience function to get truncated f64 value of an integer modulus
- update trait bounds for random generation for clearer diagnostics
2023-11-09 17:12:00 +01:00
Arthur Meyre
cf7a7f132d chore(doc): update a slightly wrong docstring 2023-11-09 14:38:43 +01:00
tmontaigu
6e0a3b9ad7 feat(integer): add BooleanBlock wrapper type
The BooleanBlock wrapper type is meant to convey the fact that
the ciphertext encrypts a 0 or 1.

Since its meant to be a simple wrapper, the goal for is to be flexible
and not add more burden than usefulness.

Hopefully this implementation somehow achieves that

Breaking Changes:
 - This changes the return type of comparisons from a T to
   a BooleanBlock. Requiring existing code to explicitely convert
   using `.into_radix`.
 - This makes the cmux/if_then_else functions take a BooleanValue
   as the input type  Requiring existing code to wrap their condition
   ciphertext in a new BooleanValue
2023-11-08 19:40:21 +01:00
Arthur Meyre
1f825dde08 chore(tfhe): bump version to 0.5.0 2023-11-08 15:55:22 +01:00
tmontaigu
f9222de47c feat(integer): add signed_overflowing_sub 2023-11-08 15:11:05 +01:00
Mayeul@Zama
5732e8dd7a test(hlapi): test base and compressed integer conformance 2023-11-08 09:25:55 +01:00
Mayeul@Zama
9db35c5474 chore(clippy): remove useless #[allow(warning)] 2023-11-07 16:47:04 +01:00
Mayeul@Zama
b69f73e8e6 chore(clippy): fix use_self warnings 2023-11-07 16:47:04 +01:00
Mayeul@Zama
90bdf75147 chore(clippy): enable nursery lints 2023-11-07 16:47:04 +01:00
Mayeul@Zama
233ea17adf chore(clippy): enable pedantic lints 2023-11-07 16:47:04 +01:00
David Testé
df6ee79841 chore(ci): test examples and apps in the ci 2023-11-07 10:58:03 +01:00
Mayeul@Zama
6497fb9a15 feat(shortint): update noise level in operations 2023-11-06 11:33:24 +01:00
Mayeul@Zama
d8894e3b69 feat(shortint): add noise level to ciphertexts 2023-11-06 11:33:24 +01:00
dependabot[bot]
42636bab13 chore(deps): bump tj-actions/changed-files from 40.0.0 to 40.1.0
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 40.0.0 to 40.1.0.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](af292f1e84...18c8a4eceb)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 09:42:04 +01:00
tmontaigu
ec27d3dc6f refactor(hlapi): remove wrapping of booleans
This commit removes the wrapping of the `tfhe::boolean`
that was done in the HLAPI, effectively making the HLAPI
only wrapping `tfhe::integer`.

FheBool is now reused to be a single shortint block
compatible with other type FheUint8,16,etc (previously they were not).

In the future, `tfhe::boolean` could be re-wrapped in hlapi, but
this time, to be used as a base for all integers and not just
FheBool.

BREAKING CHANGE:
- hlapi no longer wraps tfhe::boolean API.
- tfhe::ConfigBuilder::enable_bool/disable_bool/all_disabled/all_enabled
  removed. Now default configuration should be done using
  `tfhe::ConfigBuilder::default()`.
- `tfhe::ConfigBuilder::use_default_small_integer` removed
  use `tfhe::CondifBuilder::default_with_small_encryption()`
- Uninitialied{ClientKey, PublicKey, CompressedPublicKey} error types
  removed as these erros are no longer possible
2023-11-04 00:18:16 +01:00
Mayeul@Zama
5272c95de4 fix(shortint): fix modulus on LUT output in test 2023-11-03 09:45:22 +01:00
Mayeul@Zama
27d7ace3ef feat(shortint): fix keyswitching wrapping behavior 2023-11-03 09:45:22 +01:00
Mayeul@Zama
d80ab231a8 fix(shortint): add LUT generation without carry 2023-11-03 09:45:22 +01:00
tmontaigu
fe3fa531f9 refactor(hlapi): Remove shortint support from HLAPI
This removes the wrapping of shortints from the HLAPI,
the reasons are:

Contrary to integers for which we have different bit size
by combining different number of blocks from the _same_ key.
shortints had different bit size, but also different keys
which lead to:

- Not being able to cast between 2 different shortint type
  and between 1 shortint and 1 integer. Technically these casts
  are possible, but requires a keyswitch (and likely a PBS).
  But the keyswitch requires parameters, which may not always exists.

- Due to each shortint having different keys, the internal code to
  manage that made heavy use of macros to avoid having thousands of
  repeated lines. However, this made the code harder to follow / modify
  especially for people that were not familiar with that.

- In practive to really benefit from shortints, proper management of
  carry space is needed, however the HLAPI completely hides that,
  resulting in less optimal performances. In short, shortints
  are better used as a low level construct.

- Building a FheUint4 with two block of message_2_carry_2
  is likely to be faster the one message_4_carry_4 for most use
  cases.

So removing the wrapping of shortints will simplify the code, and
allow for more simplification later.
Also, it will allow us to expose Fhe{Ui/I}nt{2, 4, 6} types
which are compatible (cast_from/into) with Fhe{Ui/I}nt{8, 16, 32, etc}.

BREAKING CHANGE:
    - FheUint{2,3,4} removed from HLAPI
    - All HLAPI functions thied to shortints are removed
2023-10-31 09:32:05 +01:00
tmontaigu
5c1573c266 fix(integer): fix worst case noise growth in encrypted shifts
In encrypted shifts we pack 3 bits from 3 different blocks into the same
blocks by doing `b0 * 4 + 2 * b1 + b2`, and then do a PBS to simulate a
hardware mux gate.

If the inputs of shift (ie, in lhs << rhs, lhs != rhs, ie we don't do
lhs << lhs) this is fine regarding the norm2 noise.

However if we do things like `a << a` or `a >> a`, which is probably a
very rare thing but not impossible, the norm2 noise would go above the
limit that guarantees our error probability.

To fix that, we extract the bits that tells shift amount, so that they
are already properly aligned to their mux input position.
The packing becomes `b0 + 2 * b1 + b2` and so,
the noise growth is ok even in the worst case of doind `a << a`.
2023-10-30 15:02:02 +01:00
dependabot[bot]
7772e8112d chore(deps): bump tj-actions/changed-files from 39.2.3 to 40.0.0
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 39.2.3 to 40.0.0.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](95690f9ece...af292f1e84)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 12:07:44 +01:00
dependabot[bot]
5e92cb1475 chore(deps): bump JS-DevTools/npm-publish from 3.0.0 to 3.0.1
Bumps [JS-DevTools/npm-publish](https://github.com/js-devtools/npm-publish) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/js-devtools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](6fd3bc8dad...4b07b26a2f)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 12:07:24 +01:00
dependabot[bot]
f51e19b071 chore(deps): bump actions/checkout from 4.1.0 to 4.1.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.0...b4ffde65f46336ab88eb53be808477a3936bae11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 12:06:53 +01:00
tmontaigu
aeb00ae584 chore(integer): use Arc<ServerKey> for executor
The goal is to avoid holding the key twice in memory
when both the executor and the test case needs the key
2023-10-27 18:01:55 +02:00
Arthur Meyre
ce5e9c1bdb chore(integer): more CRT tests and related fixes
- add remaining tests
- fix unchecked scalar mul for small carries
2023-10-27 11:30:00 +02:00
Arthur Meyre
4d4e124e94 chore(integer): add crt 32 bits tests with 5_1 params
- remove buggy unchecked_scalar_add_assign and replace by the proper
implementation which had a different name

BREAKING CHANGE:
removed an API entry point which was not required
2023-10-27 11:30:00 +02:00
Arthur Meyre
ca6d37e06f feat(integer): better handle trivial 0 blocks from LHS
- currently the filter only applied to the RHS but LHS can also benefit
from the filter
2023-10-27 10:31:24 +02:00
Mayeul@Zama
e3143315f3 fix(integer): disable broken assert in smart_crt_sub_assign 2023-10-27 09:43:51 +02:00
Mayeul@Zama
f8636fe814 feat(integer): add asserts in smart ops 2023-10-27 09:43:51 +02:00
tmontaigu
7e72400321 chore(doc): replace some ^ which could be interpreted as xor not pow 2023-10-26 23:42:58 +02:00
tmontaigu
728b409256 chore(integer): move comparator test out of it
Move the comparisons test (eq, ne, ge, gt, etc)
that were in the comparator module out of the comparator module.

This is so that in later commits will create test cases out
of these tests so they can, like other unsigned tests be
used to test other implementations of ServerKey
2023-10-25 10:31:55 +02:00
Arthur Meyre
d91404e567 chore(integer): remove empty where clause 2023-10-25 09:41:37 +02:00
David Testé
e11c3d7b7c chore(ci): add signed integer benchmarks to the CI 2023-10-25 09:14:00 +02:00
David Testé
6f8eeb043c chore(bench): add default ops for singed integers benchmarks 2023-10-25 09:14:00 +02:00
Arthur Meyre
00d55182b4 chore(ci): update examples to have a tmp dir to avoid rights issues in /tmp
- on machines where multiple users can log in, some files used for
serialization doctests would cause rights access issues and crash doctests
2023-10-23 15:03:18 +02:00
dependabot[bot]
6f6ce106c3 chore(deps): bump tj-actions/changed-files from 39.2.2 to 39.2.3
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 39.2.2 to 39.2.3.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](408093d9ff...95690f9ece)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 10:28:00 +02:00
dependabot[bot]
68fcbb5280 chore(deps): bump JS-DevTools/npm-publish from 2.2.2 to 3.0.0
Bumps [JS-DevTools/npm-publish](https://github.com/js-devtools/npm-publish) from 2.2.2 to 3.0.0.
- [Release notes](https://github.com/js-devtools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](fe72237be0...6fd3bc8dad)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 10:27:35 +02:00
dependabot[bot]
3f46389cc8 chore(deps): bump actions/checkout from 4.1.0 to 4.1.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8ade135a41...b4ffde65f4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 10:27:10 +02:00
Arthur Meyre
9e8dd01cb9 chore(ci): enable integer multi bit tests on M1 2023-10-20 17:55:38 +02:00
tmontaigu
0085ceb97b chore(ci): set node version 2023-10-20 10:24:50 +02:00
tmontaigu
be9a4d2d9c chore(wasm): update dependencies of wasm tests 2023-10-20 10:24:50 +02:00
Arthur Meyre
87421e8307 chore(ci): update M1 workflow to not explode the 6h GitHub limit
- run doc tests for CI with LTO off following M1 investigation
- LTO fat may be a cause of the wopbs flaky tests, disabling to check
2023-10-19 14:18:05 +02:00
Arthur Meyre
0c3919628f refactor(core): use avx512 intrinsics when available for data conversions
- we use inline assembly for now as rust does not propose those in the std
or core arch crates at the moment
- add tests for avx512 conversion
2023-10-19 13:21:19 +02:00
Arthur Meyre
f1c21888a7 chore(doc): encourage users to use dedicated keys to Radix or CRT 2023-10-19 09:52:22 +02:00
tmontaigu
2624beb7fa fix(integer): fix unsigned_overflowing_sub on trivials
unsigned_overflowing_sub does an independant subtraction
on each blocks with a correcting term being added to avoid
trashing the padding bit (lhs - rhs + correction).

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

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

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

- add multiplication
- add leading_zeros
- add neg
2023-10-17 13:31:35 +02:00
Arthur Meyre
ecee305340 chore(core): change prelude algorithms imports 2023-10-17 13:31:35 +02:00
Mayeul@Zama
f08ea8cf85 fix(integer): fix max_degree formula 2023-10-17 11:35:08 +02:00
Mayeul@Zama
096e320b97 fix(crt): use 3_3 parameters for crt tests 2023-10-17 11:35:08 +02:00
Mayeul@Zama
95aac64c1c style(crt): compute modulus from base in tests 2023-10-17 11:35:08 +02:00
Mayeul@Zama
76aaa56691 fix(integer): fix small mul test 2023-10-17 11:35:08 +02:00
Mayeul@Zama
a40489bdd2 style(shortint): do not use assign ops on a cloned input 2023-10-17 11:35:08 +02:00
Mayeul@Zama
4bf617eb10 feat(shortint): cleanup input if necessary in ops 2023-10-17 11:35:08 +02:00
Mayeul@Zama
070073d229 feat(shortint): cleanup input if necessary in apply_lookup_table_bivariate 2023-10-17 11:35:08 +02:00
Arthur Meyre
6c1ca8e32b chore(core): use modular_distance instead of abs_diff in fft tests
- we are doing backwards conversions to the torus, so values could wrap
around near 0 or u64::MAX, take the modular distance which represents the
distance on the torus
2023-10-17 10:29:24 +02:00
Arthur Meyre
6523610ca4 refactor(core): refactor conversion code from f64 to i64
- observed that the subnormal case is already handled by the shift logic so
the special handling was not required
- add test for avx512 conversion
2023-10-17 10:29:24 +02:00
Arthur Meyre
41c20e22f5 chore(ci): enable AVX512 for integer and multi bit integer tests 2023-10-17 10:28:14 +02:00
466 changed files with 28720 additions and 19141 deletions

View File

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

View File

@@ -1,4 +1,4 @@
name: AWS Integer Tests on CPU
name: AWS Unsigned Integer Tests on CPU
env:
CARGO_TERM_COLOR: always
@@ -23,13 +23,13 @@ on:
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
fork_repo:
description: 'Name of forked repo as user/repo'
description: "Name of forked repo as user/repo"
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
description: "Git SHA to checkout from fork"
type: string
jobs:
@@ -50,7 +50,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
@@ -65,13 +65,21 @@ jobs:
toolchain: stable
default: true
- name: Gen Keys if required
run: |
make GEN_KEY_CACHE_MULTI_BIT_ONLY=TRUE gen_key_cache
- name: Run unsigned integer multi-bit tests
run: |
AVX512_SUPPORT=ON make test_unsigned_integer_multi_bit_ci
- name: Gen Keys if required
run: |
make gen_key_cache
- name: Run integer tests
- name: Run unsigned integer tests
run: |
BIG_TESTS_INSTANCE=TRUE make test_integer_ci
AVX512_SUPPORT=ON BIG_TESTS_INSTANCE=TRUE make test_unsigned_integer_ci
- name: Slack Notification
if: ${{ always() }}

View File

@@ -1,4 +1,4 @@
name: AWS Multi Bit Tests on CPU
name: AWS Signed Integer Tests on CPU
env:
CARGO_TERM_COLOR: always
@@ -23,13 +23,13 @@ on:
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
fork_repo:
description: 'Name of forked repo as user/repo'
description: "Name of forked repo as user/repo"
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
description: "Git SHA to checkout from fork"
type: string
jobs:
@@ -50,7 +50,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
@@ -73,9 +73,17 @@ jobs:
run: |
make test_shortint_multi_bit_ci
- name: Run integer multi-bit tests
- name: Run signed integer multi-bit tests
run: |
make test_integer_multi_bit_ci
AVX512_SUPPORT=ON make test_signed_integer_multi_bit_ci
- name: Gen Keys if required
run: |
make gen_key_cache
- name: Run signed integer tests
run: |
AVX512_SUPPORT=ON BIG_TESTS_INSTANCE=TRUE make test_signed_integer_ci
- name: Slack Notification
if: ${{ always() }}

View File

@@ -50,7 +50,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
@@ -100,6 +100,12 @@ jobs:
- name: Run example tests
run: |
make test_examples
make dark_market
- name: Run apps tests
run: |
make test_trivium
make test_kreyvium
- name: Slack Notification
if: ${{ always() }}

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Install and run newline linter checks
if: matrix.os == 'ubuntu-latest'

View File

@@ -38,6 +38,7 @@ jobs:
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ inputs.runner_name }}
timeout-minutes: 1080
steps:
# Step used for log purpose.
- name: Instance configuration used
@@ -50,7 +51,7 @@ jobs:
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
@@ -67,7 +68,7 @@ jobs:
- name: Check for file changes
id: changed-files
uses: tj-actions/changed-files@408093d9ff9c134c33b974e0722ce06b9d6e8263
uses: tj-actions/changed-files@1c938490c880156b746568a518594309cfb3f66b
with:
files_yaml: |
tfhe:
@@ -79,6 +80,12 @@ jobs:
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
run: |
make GEN_KEY_CACHE_COVERAGE_ONLY=TRUE gen_key_cache
make gen_key_cache_core_crypto
- name: Run coverage for core_crypto
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
run: |
make test_core_crypto_cov AVX512_SUPPORT=ON
- name: Run coverage for boolean
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
@@ -97,7 +104,7 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./coverage/
fail_ci_if_error: true
files: shortint/cobertura.xml,boolean/cobertura.xml
files: shortint/cobertura.xml,boolean/cobertura.xml,core_crypto/cobertura.xml,core_crypto_avx512/cobertura.xml
- name: Slack Notification
if: ${{ failure() }}

View File

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

View File

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

View File

@@ -74,7 +74,7 @@ jobs:
echo "Request ID: ${{ inputs.request_id }}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
fetch-depth: 0
@@ -96,7 +96,7 @@ jobs:
override: true
- name: Checkout Slab repo
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: zama-ai/slab
path: slab

View File

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

View File

@@ -15,7 +15,6 @@ env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-C target-cpu=native"
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
CARGO_PROFILE: release_lto_off
FAST_TESTS: "TRUE"
concurrency:
@@ -28,7 +27,7 @@ jobs:
runs-on: ["self-hosted", "m1mac"]
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
@@ -111,10 +110,9 @@ jobs:
run: |
make test_shortint_multi_bit_ci
# # These multi bit integer tests are too slow on M1 with low core count and low RAM
# - name: Run integer multi bit tests
# run: |
# make test_integer_multi_bit_ci
- name: Run integer multi bit tests
run: |
make test_integer_multi_bit_ci
remove_label:
name: Remove m1_test label

View File

@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
fetch-depth: 0
@@ -49,7 +49,7 @@ jobs:
- name: Publish web package
if: ${{ inputs.push_web_package }}
uses: JS-DevTools/npm-publish@fe72237be0920f7a0cafd6a966c9b929c9466e9b
uses: JS-DevTools/npm-publish@4b07b26a2f6e0a51846e1870223e545bae91c552
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json
@@ -65,7 +65,7 @@ jobs:
- name: Publish Node package
if: ${{ inputs.push_node_package }}
uses: JS-DevTools/npm-publish@fe72237be0920f7a0cafd6a966c9b929c9466e9b
uses: JS-DevTools/npm-publish@4b07b26a2f6e0a51846e1870223e545bae91c552
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json

View File

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

View File

@@ -17,10 +17,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Checkout lattice-estimator
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: malb/lattice-estimator
path: lattice_estimator

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ jobs:
echo "Request ID: ${{ inputs.request_id }}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
fetch-depth: 0
@@ -73,7 +73,7 @@ jobs:
override: true
- name: Checkout Slab repo
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: zama-ai/slab
path: slab

View File

@@ -0,0 +1,129 @@
# Run signed integer benchmarks on an AWS instance and return parsed results to Slab CI bot.
name: Signed Integer 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
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
PARSE_INTEGER_BENCH_CSV_FILE: tfhe_rs_integer_benches_${{ github.sha }}.csv
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
run-integer-benchmarks:
name: Execute signed integer 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 }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
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 with AVX512
run: |
make AVX512_SUPPORT=ON FAST_BENCH=TRUE bench_signed_integer
- name: Parse benchmarks to csv
run: |
make PARSE_INTEGER_BENCH_CSV_FILE=${{ env.PARSE_INTEGER_BENCH_CSV_FILE }} \
parse_integer_benches
- name: Upload csv results artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_csv_integer
path: ${{ env.PARSE_INTEGER_BENCH_CSV_FILE }}
- 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 \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--name-suffix avx512 \
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_integer
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on results file"
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_v2" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@b24d75fe0e728a4bf9fc42ee217caa686d141ee8
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Signed integer benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -0,0 +1,133 @@
# Run all signed integer benchmarks on an AWS instance and return parsed results to Slab CI bot.
name: Signed Integer full 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
user_inputs:
description: "Type of benchmarks to run"
type: string
default: "weekly_benchmarks"
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
integer-benchmarks:
name: Execute signed integer benchmarks for all operations flavor
runs-on: ${{ github.event.inputs.runner_name }}
if: ${{ !cancelled() }}
continue-on-error: true
strategy:
max-parallel: 1
matrix:
command: [ integer, integer_multi_bit ]
op_flavor: [ default, default_comp, default_scalar, default_scalar_comp,
unchecked, unchecked_comp, unchecked_scalar, unchecked_scalar_comp ]
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 }}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
fetch-depth: 0
- name: Get benchmark details
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
echo "COMMIT_DATE=$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})" >> "${GITHUB_ENV}"
echo "COMMIT_HASH=$(git describe --tags --dirty)" >> "${GITHUB_ENV}"
- 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: Checkout Slab repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON BENCH_OP_FLAVOR=${{ matrix.op_flavor }} bench_signed_${{ matrix.command }}
- name: Parse results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs \
--hardware ${{ inputs.instance_type }} \
--project-version "${{ env.COMMIT_HASH }}" \
--branch ${{ github.ref_name }} \
--commit-date "${{ env.COMMIT_DATE }}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--name-suffix avx512 \
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_${{ matrix.command }}_${{ matrix.op_flavor }}
path: ${{ env.RESULTS_FILENAME }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on results file"
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_v2" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
slack-notification:
name: Slack Notification
runs-on: ${{ github.event.inputs.runner_name }}
if: ${{ failure() }}
needs: integer-benchmarks
steps:
- name: Notify
continue-on-error: true
uses: rtCamp/action-slack-notify@b24d75fe0e728a4bf9fc42ee217caa686d141ee8
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Signed integer full benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -0,0 +1,129 @@
# Run signed integer benchmarks with multi-bit cryptographic parameters on an AWS instance and return parsed results to Slab CI bot.
name: Signed Integer Multi-bit 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
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
PARSE_INTEGER_BENCH_CSV_FILE: tfhe_rs_integer_benches_${{ github.sha }}.csv
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
run-integer-benchmarks:
name: Execute signed integer multi-bit 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 }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
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 multi-bit benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON FAST_BENCH=TRUE bench_signed_integer_multi_bit
- name: Parse benchmarks to csv
run: |
make PARSE_INTEGER_BENCH_CSV_FILE=${{ env.PARSE_INTEGER_BENCH_CSV_FILE }} \
parse_integer_benches
- name: Upload csv results artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_csv_integer
path: ${{ env.PARSE_INTEGER_BENCH_CSV_FILE }}
- 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 \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--name-suffix avx512 \
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: ${{ github.sha }}_integer
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on results file"
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_v2" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@b24d75fe0e728a4bf9fc42ee217caa686d141ee8
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Signed integer benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -20,10 +20,18 @@ on:
description: "Run integer benches"
type: boolean
default: true
signed_integer_bench:
description: "Run signed integer benches"
type: boolean
default: true
integer_multi_bit_bench:
description: "Run integer multi bit benches"
type: boolean
default: true
signed_integer_multi_bit_bench:
description: "Run signed integer multi bit benches"
type: boolean
default: true
pbs_bench:
description: "Run PBS benches"
type: boolean
@@ -38,17 +46,20 @@ jobs:
if: ${{ (github.event_name == 'push' && github.repository == 'zama-ai/tfhe-rs') || github.event_name == 'workflow_dispatch' }}
strategy:
matrix:
command: [boolean_bench, shortint_bench, integer_bench, integer_multi_bit_bench, pbs_bench, wasm_client_bench]
command: [ boolean_bench, shortint_bench,
integer_bench, integer_multi_bit_bench,
signed_integer_bench, signed_integer_multi_bit_bench,
pbs_bench, wasm_client_bench ]
runs-on: ubuntu-latest
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
fetch-depth: 0
- name: Check for file changes
id: changed-files
uses: tj-actions/changed-files@408093d9ff9c134c33b974e0722ce06b9d6e8263
uses: tj-actions/changed-files@1c938490c880156b746568a518594309cfb3f66b
with:
files_yaml: |
common_benches:
@@ -69,13 +80,23 @@ jobs:
integer_bench:
- tfhe/src/shortint/**
- tfhe/src/integer/**
- tfhe/benches/integer/**
- tfhe/benches/integer/bench.rs
- .github/workflows/integer_benchmark.yml
integer_multi_bit_bench:
- tfhe/src/shortint/**
- tfhe/src/integer/**
- tfhe/benches/integer/**
- .github/workflows/integer_benchmark.yml
- tfhe/benches/integer/bench.rs
- .github/workflows/integer_multi_bit_benchmark.yml
signed_integer_bench:
- tfhe/src/shortint/**
- tfhe/src/integer/**
- tfhe/benches/integer/signed_bench.rs
- .github/workflows/signed_integer_benchmark.yml
signed_integer_multi_bit_bench:
- tfhe/src/shortint/**
- tfhe/src/integer/**
- tfhe/benches/integer/signed_bench.rs
- .github/workflows/signed_integer_multi_bit_benchmark.yml
pbs_bench:
- tfhe/src/core_crypto/**
- tfhe/benches/core_crypto/**
@@ -85,7 +106,7 @@ jobs:
- .github/workflows/wasm_client_benchmark.yml
- name: Checkout Slab repo
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: zama-ai/slab
path: slab

View File

@@ -24,16 +24,17 @@ jobs:
if: ${{ (github.event_name == 'schedule' && github.repository == 'zama-ai/tfhe-rs') || github.event_name == 'workflow_dispatch' }}
strategy:
matrix:
command: [ boolean_bench, shortint_full_bench, integer_full_bench, pbs_bench, wasm_client_bench ]
command: [ boolean_bench, shortint_full_bench, integer_full_bench,
signed_integer_full_bench, pbs_bench, wasm_client_bench ]
runs-on: ubuntu-latest
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
fetch-depth: 0
- name: Checkout Slab repo
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
repository: zama-ai/slab
path: slab

View File

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

View File

@@ -48,7 +48,7 @@ jobs:
Pull Request has been approved :tada:
Launching full test suite...
@slab-ci cpu_test
@slab-ci cpu_integer_test
@slab-ci cpu_multi_bit_test
@slab-ci cpu_unsigned_integer_test
@slab-ci cpu_signed_integer_test
@slab-ci cpu_wasm_test
@slab-ci csprng_randomness_testing

View File

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

6
.gitignore vendored
View File

@@ -3,9 +3,9 @@ target/
.vscode/
# Path we use for internal-keycache during tests
./keys/
/keys/
# In case of symlinked keys
./keys
/keys
**/Cargo.lock
**/*.bin
@@ -18,4 +18,4 @@ target/
dieharder_run.log
# Coverage reports
./coverage/
/coverage/

View File

@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["tfhe", "tasks", "apps/trivium", "concrete-csprng"]
members = ["tfhe", "tasks", "apps/trivium", "concrete-csprng", "concrete-float"]
[profile.bench]
lto = "fat"

290
Makefile
View File

@@ -6,7 +6,7 @@ TARGET_ARCH_FEATURE:=$(shell ./scripts/get_arch_feature.sh)
RS_BUILD_TOOLCHAIN:=stable
CARGO_RS_BUILD_TOOLCHAIN:=+$(RS_BUILD_TOOLCHAIN)
CARGO_PROFILE?=release
MIN_RUST_VERSION:=$(shell grep rust-version tfhe/Cargo.toml | cut -d '=' -f 2 | xargs)
MIN_RUST_VERSION:=$(shell grep '^rust-version[[:space:]]*=' tfhe/Cargo.toml | cut -d '=' -f 2 | xargs)
AVX512_SUPPORT?=OFF
WASM_RUSTFLAGS:=
BIG_TESTS_INSTANCE?=FALSE
@@ -16,6 +16,17 @@ PARSE_INTEGER_BENCH_CSV_FILE?=tfhe_rs_integer_benches.csv
FAST_TESTS?=FALSE
FAST_BENCH?=FALSE
BENCH_OP_FLAVOR?=DEFAULT
NODE_VERSION=20
# sed: -n, do not print input stream, -e means a script/expression
# 1,/version/ indicates from the first line, to the line matching version at the start of the line
# p indicates to print, so we keep only the start of the Cargo.toml until we hit the first version
# entry which should be the version of tfhe
TFHE_CURRENT_VERSION:=\
$(shell sed -n -e '1,/^version/p' tfhe/Cargo.toml | \
grep '^version[[:space:]]*=' | cut -d '=' -f 2 | xargs)
# Cargo has a hard time distinguishing between our package from the workspace and a package that
# could be a dependency, so we build an unambiguous spec here
TFHE_SPEC:=tfhe@$(TFHE_CURRENT_VERSION)
# This is done to avoid forgetting it, we still precise the RUSTFLAGS in the commands to be able to
# copy paste the command in the terminal and change them if required without forgetting the flags
export RUSTFLAGS?=-C target-cpu=native
@@ -99,7 +110,7 @@ install_wasm_pack: install_rs_build_toolchain
install_node:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | $(SHELL)
source ~/.bashrc
$(SHELL) -i -c 'nvm install node' || \
$(SHELL) -i -c 'nvm install $(NODE_VERSION)' || \
( echo "Unable to install node, unknown error." && exit 1 )
.PHONY: install_dieharder # Install dieharder for apt distributions or macOS
@@ -120,7 +131,7 @@ install_tarpaulin: install_rs_build_toolchain
.PHONY: check_linelint_installed # Check if linelint newline linter is installed
check_linelint_installed:
@printf "\n" | linelint - > /dev/null 2>&1 || \
( echo "Unable to locate linelint. Try installing it: https://github.com/fernandrone/linelint/releases" && exit 1 )
( echo "Unable to locate linelint. Try installing it: https://github.com/fernandrone/linelint/releases" && exit 1 )
.PHONY: fmt # Format rust code
fmt: install_rs_check_toolchain
@@ -138,50 +149,55 @@ fix_newline: check_linelint_installed
check_newline: check_linelint_installed
linelint .
.PHONY: clippy_float # Run clippy lints on core_crypto with and without experimental features
clippy_float: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
-p concrete-float -- --no-deps -D warnings
.PHONY: clippy_core # Run clippy lints on core_crypto with and without experimental features
clippy_core: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=$(TARGET_ARCH_FEATURE) \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=$(TARGET_ARCH_FEATURE),experimental \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
.PHONY: clippy_boolean # Run clippy lints enabling the boolean features
clippy_boolean: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=$(TARGET_ARCH_FEATURE),boolean \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
.PHONY: clippy_shortint # Run clippy lints enabling the shortint features
clippy_shortint: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=$(TARGET_ARCH_FEATURE),shortint \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
.PHONY: clippy_integer # Run clippy lints enabling the integer features
clippy_integer: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=$(TARGET_ARCH_FEATURE),integer \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
.PHONY: clippy # Run clippy lints enabling the boolean, shortint, integer
clippy: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
.PHONY: clippy_c_api # Run clippy lints enabling the boolean, shortint and the C API
clippy_c_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint, integer and the js wasm API
clippy_js_wasm_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
.PHONY: clippy_tasks # Run clippy lints on helper tasks crate.
clippy_tasks:
@@ -190,15 +206,14 @@ clippy_tasks:
.PHONY: clippy_trivium # Run clippy lints on Trivium app
clippy_trivium: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy -p tfhe-trivium \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
-p tfhe -- --no-deps -D warnings
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
-p tfhe-trivium -- --no-deps -D warnings
.PHONY: clippy_all_targets # Run clippy lints on all targets (benches, examples, etc.)
clippy_all_targets:
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache,safe-deserialization \
-p tfhe -- --no-deps -D warnings
-p $(TFHE_SPEC) -- --no-deps -D warnings
.PHONY: clippy_concrete_csprng # Run clippy lints on concrete-csprng
clippy_concrete_csprng:
@@ -214,62 +229,55 @@ clippy_js_wasm_api clippy_tasks clippy_core clippy_concrete_csprng clippy_triviu
clippy_fast: clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core \
clippy_concrete_csprng
.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 --profile $(CARGO_PROFILE) \
--example generates_test_keys \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache -p tfhe -- \
$(MULTI_BIT_ONLY) $(COVERAGE_ONLY)
.PHONY: build_core # Build core_crypto without experimental features
build_core: install_rs_build_toolchain install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p tfhe
--features=$(TARGET_ARCH_FEATURE) -p $(TFHE_SPEC)
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),$(AVX512_FEATURE) -p tfhe; \
--features=$(TARGET_ARCH_FEATURE),$(AVX512_FEATURE) -p $(TFHE_SPEC); \
fi
.PHONY: build_core_experimental # Build core_crypto with experimental features
build_core_experimental: install_rs_build_toolchain install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),experimental -p tfhe
--features=$(TARGET_ARCH_FEATURE),experimental -p $(TFHE_SPEC)
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe; \
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p $(TFHE_SPEC); \
fi
.PHONY: build_boolean # Build with boolean enabled
build_boolean: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe --all-targets
--features=$(TARGET_ARCH_FEATURE),boolean -p $(TFHE_SPEC) --all-targets
.PHONY: build_shortint # Build with shortint enabled
build_shortint: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),shortint -p tfhe --all-targets
--features=$(TARGET_ARCH_FEATURE),shortint -p $(TFHE_SPEC) --all-targets
.PHONY: build_integer # Build with integer enabled
build_integer: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),integer -p tfhe --all-targets
--features=$(TARGET_ARCH_FEATURE),integer -p $(TFHE_SPEC) --all-targets
.PHONY: build_tfhe_full # Build with boolean, shortint and integer enabled
build_tfhe_full: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer -p tfhe --all-targets
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer -p $(TFHE_SPEC) --all-targets
.PHONY: build_c_api # Build the C API for boolean, shortint and integer
build_c_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,safe-deserialization \
-p tfhe
-p $(TFHE_SPEC)
.PHONY: build_c_api_experimental_deterministic_fft # Build the C API for boolean, shortint and integer with experimental deterministic FFT
build_c_api_experimental_deterministic_fft: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,safe-deserialization,experimental-force_fft_algo_dif4 \
-p tfhe
-p $(TFHE_SPEC)
.PHONY: build_web_js_api # Build the js API targeting the web browser
build_web_js_api: install_rs_build_toolchain install_wasm_pack
@@ -302,16 +310,31 @@ build_concrete_csprng: install_rs_build_toolchain
.PHONY: test_core_crypto # Run the tests of the core_crypto module including experimental ones
test_core_crypto: install_rs_build_toolchain install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),experimental -p tfhe -- core_crypto::
--features=$(TARGET_ARCH_FEATURE),experimental -p $(TFHE_SPEC) -- core_crypto::
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe -- core_crypto::; \
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p $(TFHE_SPEC) -- core_crypto::; \
fi
.PHONY: test_core_crypto_cov # Run the tests of the core_crypto module with code coverage
test_core_crypto_cov: install_rs_build_toolchain install_rs_check_toolchain install_tarpaulin
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) tarpaulin --profile $(CARGO_PROFILE) \
--out xml --output-dir coverage/core_crypto --line --engine llvm --timeout 500 \
--implicit-test-threads $(COVERAGE_EXCLUDED_FILES) \
--features=$(TARGET_ARCH_FEATURE),experimental,internal-keycache,__coverage \
-p $(TFHE_SPEC) -- core_crypto::
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) tarpaulin --profile $(CARGO_PROFILE) \
--out xml --output-dir coverage/core_crypto_avx512 --line --engine llvm --timeout 500 \
--implicit-test-threads $(COVERAGE_EXCLUDED_FILES) \
--features=$(TARGET_ARCH_FEATURE),experimental,internal-keycache,__coverage,$(AVX512_FEATURE) \
-p $(TFHE_SPEC) -- core_crypto::; \
fi
.PHONY: test_boolean # Run the tests of the boolean module
test_boolean: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
--features=$(TARGET_ARCH_FEATURE),boolean -p $(TFHE_SPEC) -- boolean::
.PHONY: test_boolean_cov # Run the tests of the boolean module with code coverage
test_boolean_cov: install_rs_check_toolchain install_tarpaulin
@@ -319,13 +342,13 @@ test_boolean_cov: install_rs_check_toolchain install_tarpaulin
--out xml --output-dir coverage/boolean --line --engine llvm --timeout 500 \
$(COVERAGE_EXCLUDED_FILES) \
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache,__coverage \
-p tfhe -- boolean::
-p $(TFHE_SPEC) -- boolean::
.PHONY: test_c_api_rs # Run the rust tests for the C API
test_c_api_rs: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,safe-deserialization \
-p tfhe \
-p $(TFHE_SPEC) \
c_api
.PHONY: test_c_api_c # Run the C tests for the C API
@@ -352,7 +375,7 @@ test_shortint_multi_bit_ci: install_rs_build_toolchain install_cargo_nextest
.PHONY: test_shortint # Run all the tests for shortint
test_shortint: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- shortint::
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p $(TFHE_SPEC) -- shortint::
.PHONY: test_shortint_cov # Run the tests of the shortint module with code coverage
test_shortint_cov: install_rs_check_toolchain install_tarpaulin
@@ -360,42 +383,74 @@ test_shortint_cov: install_rs_check_toolchain install_tarpaulin
--out xml --output-dir coverage/shortint --line --engine llvm --timeout 500 \
$(COVERAGE_EXCLUDED_FILES) \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,__coverage \
-p tfhe -- shortint::
-p $(TFHE_SPEC) -- shortint::
.PHONY: test_integer_ci # Run the tests for integer ci
test_integer_ci: install_rs_build_toolchain install_cargo_nextest
test_integer_ci: install_rs_check_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)"
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_CHECK_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --avx512-support "$(AVX512_SUPPORT)"
.PHONY: test_unsigned_integer_ci # Run the tests for unsigned integer ci
test_unsigned_integer_ci: install_rs_check_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_CHECK_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --avx512-support "$(AVX512_SUPPORT)" \
--unsigned-only
.PHONY: test_signed_integer_ci # Run the tests for signed integer ci
test_signed_integer_ci: install_rs_check_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_CHECK_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --avx512-support "$(AVX512_SUPPORT)" \
--signed-only
.PHONY: test_integer_multi_bit_ci # Run the tests for integer ci running only multibit tests
test_integer_multi_bit_ci: install_rs_build_toolchain install_cargo_nextest
test_integer_multi_bit_ci: install_rs_check_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --multi-bit
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_CHECK_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --multi-bit --avx512-support "$(AVX512_SUPPORT)"
.PHONY: test_unsigned_integer_multi_bit_ci # Run the tests for nsigned integer ci running only multibit tests
test_unsigned_integer_multi_bit_ci: install_rs_check_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_CHECK_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --multi-bit --avx512-support "$(AVX512_SUPPORT)" \
--unsigned-only
.PHONY: test_signed_integer_multi_bit_ci # Run the tests for nsigned integer ci running only multibit tests
test_signed_integer_multi_bit_ci: install_rs_check_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_CHECK_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --multi-bit --avx512-support "$(AVX512_SUPPORT)" \
--signed-only
.PHONY: test_safe_deserialization # Run the tests for safe deserialization
test_safe_deserialization: install_rs_build_toolchain install_cargo_nextest
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache,safe-deserialization -p tfhe -- safe_deserialization::
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache,safe-deserialization -p $(TFHE_SPEC) -- safe_deserialization::
.PHONY: test_integer # Run all the tests for integer
test_integer: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache -p tfhe -- integer::
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache -p $(TFHE_SPEC) -- integer::
.PHONY: test_high_level_api # Run all the tests for high_level_api
test_high_level_api: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p $(TFHE_SPEC) \
-- high_level_api::
.PHONY: test_user_doc # Run tests from the .md documentation
test_user_doc: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) --doc \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p $(TFHE_SPEC) \
-- test_user_docs::
.PHONY: test_regex_engine # Run tests for regex_engine example
@@ -428,6 +483,59 @@ test_concrete_csprng:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-csprng
.PHONY: test_float # Run minifloat bivariate test
test_float: test_float_add test_float_sub test_float_mul test_float_div test_float_cos test_float_sin test_float_relu test_float_sigmoid test_minifloat
.PHONY: test_minifloat # Run minifloat bivariate test
test_minifloat:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),shortint -p tfhe float_wopbs_bivariate -- --nocapture
.PHONY: test_float_cos # Run floating points cosine test
test_float_cos:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::float_cos" -- --exact --nocapture
.PHONY: test_float_sin # Run floating points sine test
test_float_sin:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::float_sin" -- --exact --nocapture
.PHONY: test_float_mul # Run floating points multiplication test
test_float_mul:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::test_float_mul" -- --exact --nocapture
.PHONY: test_float_add # Run floating points addition test
test_float_add:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::test_float_add" -- --exact --nocapture
.PHONY: test_float_sub # Run floating points subtraction test
test_float_sub:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::test_float_sub" -- --exact --nocapture
.PHONY: test_float_div # Run floating points division test
test_float_div:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::test_float_div" -- --exact --nocapture
.PHONY: test_float_relu # Run floating points relu test
test_float_relu:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::test_float_relu" -- --exact --nocapture
.PHONY: test_float_sigmoid # Run floating points sigmoid test
test_float_sigmoid:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::test_float_sigmoid" -- --exact --nocapture
.PHONY: test_float_depth_test # Run floating points depth test
test_float_depth_test:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE) -p concrete-float "server_key::tests::depth_test_parallelized" -- --exact --nocapture
.PHONY: doc # Build rust doc
doc: install_rs_check_toolchain
RUSTDOCFLAGS="--html-in-header katex-header.html" \
@@ -459,17 +567,17 @@ format_doc_latex:
check_compile_tests:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --no-run \
--features=$(TARGET_ARCH_FEATURE),experimental,boolean,shortint,integer,internal-keycache,safe-deserialization \
-p tfhe
-p $(TFHE_SPEC)
@if [[ "$(OS)" == "Linux" || "$(OS)" == "Darwin" ]]; then \
"$(MAKE)" build_c_api; \
"$(MAKE)" build_c_api && \
./scripts/c_api_tests.sh --build-only; \
fi
.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 .
-f docker/Dockerfile.wasm_tests --build-arg NODE_VERSION=$(NODE_VERSION) -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
@@ -493,7 +601,8 @@ test_web_js_api_parallel: build_web_js_api_parallel
.PHONY: ci_test_web_js_api_parallel # Run tests for the web wasm api
ci_test_web_js_api_parallel: build_web_js_api_parallel
source ~/.nvm/nvm.sh && \
nvm use node && \
nvm install $(NODE_VERSION) && \
nvm use $(NODE_VERSION) && \
$(MAKE) -C tfhe/web_wasm_parallel_tests test-ci
.PHONY: no_tfhe_typo # Check we did not invert the h and f in tfhe
@@ -512,27 +621,42 @@ dieharder_csprng: install_dieharder build_concrete_csprng
# Benchmarks
#
.PHONY: bench_integer # Run benchmarks for integer
.PHONY: bench_integer # Run benchmarks for unsigned integer
bench_integer: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) __TFHE_RS_FAST_BENCH=$(FAST_BENCH) \
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench integer-bench \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p tfhe --
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p $(TFHE_SPEC) --
.PHONY: bench_integer_multi_bit # Run benchmarks for integer using multi-bit parameters
.PHONY: bench_integer_multi_bit # Run benchmarks for unsigned integer using multi-bit parameters
bench_integer_multi_bit: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_TYPE=MULTI_BIT \
__TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) __TFHE_RS_FAST_BENCH=$(FAST_BENCH) \
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench integer-bench \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p tfhe --
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p $(TFHE_SPEC) --
.PHONY: bench_signed_integer # Run benchmarks for signed integer
bench_signed_integer: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) __TFHE_RS_FAST_BENCH=$(FAST_BENCH) \
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench integer-signed-bench \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p $(TFHE_SPEC) --
.PHONY: bench_signed_integer_multi_bit # Run benchmarks for signed integer using multi-bit parameters
bench_signed_integer_multi_bit: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_TYPE=MULTI_BIT \
__TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) __TFHE_RS_FAST_BENCH=$(FAST_BENCH) \
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench integer-signed-bench \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p $(TFHE_SPEC) --
.PHONY: bench_shortint # Run benchmarks for shortint
bench_shortint: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) \
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench shortint-bench \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p $(TFHE_SPEC)
.PHONY: bench_shortint_multi_bit # Run benchmarks for shortint using multi-bit parameters
bench_shortint_multi_bit: install_rs_check_toolchain
@@ -540,20 +664,20 @@ bench_shortint_multi_bit: install_rs_check_toolchain
__TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) \
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench shortint-bench \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe --
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p $(TFHE_SPEC) --
.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
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache,$(AVX512_FEATURE) -p $(TFHE_SPEC)
.PHONY: bench_pbs # Run benchmarks for PBS
bench_pbs: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench pbs-bench \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache,$(AVX512_FEATURE) -p $(TFHE_SPEC)
.PHONY: bench_web_js_api_parallel # Run benchmarks for the web wasm api
bench_web_js_api_parallel: build_web_js_api_parallel
@@ -565,9 +689,53 @@ ci_bench_web_js_api_parallel: build_web_js_api_parallel
nvm use node && \
$(MAKE) -C tfhe/web_wasm_parallel_tests bench-ci
.PHONY: bench_float # Run benchmarks for the floating points
bench_float: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench float-bench
.PHONY: bench_float_8bit # Run benchmarks for the floating points
bench_float_8bit: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench float-bench -- PARAM_8
.PHONY: bench_float_16bit # Run benchmarks for the floating points
bench_float_16bit: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench float-bench -- PARAM_16
.PHONY: bench_float_32bit # Run benchmarks for the floating points
bench_float_32bit: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench float-bench -- PARAM_32
.PHONY: bench_float_64bit # Run benchmarks for the floating points
bench_float_64bit: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench float-bench -- PARAM_64
.PHONY: bench_minifloat # Run benchmarks for Wopbs floating points
bench_minifloat: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench float-wopbs-bench
#
# Utility tools
#
.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 --profile $(CARGO_PROFILE) \
--example generates_test_keys \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache -- \
$(MULTI_BIT_ONLY) $(COVERAGE_ONLY)
.PHONY: gen_key_cache_core_crypto # Run function to generate keys and cache them for core_crypto tests
gen_key_cache_core_crypto: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --tests --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),experimental,internal-keycache -p $(TFHE_SPEC) -- --nocapture \
core_crypto::keycache::generate_keys
.PHONY: measure_hlapi_compact_pk_ct_sizes # Measure sizes of public keys and ciphertext for high-level API
measure_hlapi_compact_pk_ct_sizes: install_rs_check_toolchain

299
README.md
View File

@@ -1,177 +1,160 @@
<p align="center">
<!-- product name logo -->
<img width=600 src="https://user-images.githubusercontent.com/5758427/231206749-8f146b97-3c5a-4201-8388-3ffa88580415.png">
</p>
<hr/>
<p align="center">
<a href="https://docs.zama.ai/tfhe-rs"> 📒 Read documentation</a> | <a href="https://zama.ai/community"> 💛 Community support</a>
</p>
<p align="center">
<!-- Version badge using shields.io -->
<a href="https://github.com/zama-ai/tfhe-rs/releases">
<img src="https://img.shields.io/github/v/release/zama-ai/tfhe-rs?style=flat-square">
</a>
<!-- Zama Bounty Program -->
<a href="https://github.com/zama-ai/bounty-program">
<img src="https://img.shields.io/badge/Contribute-Zama%20Bounty%20Program-yellow?style=flat-square">
</a>
</p>
<hr/>
# Artifact:TFHE Gets Real: an Efficient and Flexible Homomorphic Floating-Point Arithmetic
**TFHE-rs** is a pure Rust implementation of TFHE for boolean and integer
arithmetics over encrypted data. It includes:
- a **Rust** API
- a **C** API
- and a **client-side WASM** API
## Description
**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
production-ready library for all the advanced features of TFHE.
## Getting Started
The steps to run a first example are described below.
In what follows, we provide instructions on how to run the benchmarks from the paper entitled **TFHE Gets Real: An Efficient and Flexible Homomorphic Floating-Point Arithmetic**.
In particular, the benchmarks presented in **Table 5**, **Table 6**, **Table 7**, and the experiments shown in **Table 8** can be easily reproduced using this code. The implementation of the techniques described in the aforementioned paper has been integrated into the **TFHE-rs** library, version 0.5.0. The modified or added source files are organized into two different paths.
### Cargo.toml configuration
To use the latest version of `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
The Minifloats (Section 3.1) are located in *tfhe/src/float-wopbs*
- Test files are located in *tfhe/src/float_wopbs/server_key/tests.rs*
- Benchmarks are located in *tfhe/benches/float_wopbs/bench.rs*
+ For x86_64-based machines running Unix-like OSes:
```toml
tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64-unix"] }
The homomorphic floating points (Section 3.2) are located in *tfhe/concrete-float/*
- Test files are located *tfhe/concrete-float/src/server_key/tests.rs*
- Benchmarks are located in *tfhe/concrete-float/benches/bench.rs*
## Dependencies
Tested on Linux and Mac OS with Rust version >= 1.80 (see [here](https://www.rust-lang.org/tools/install) a guide to install Rust).
Complete list of dependencies and a guide on how to install TFHE-rs can be found in the online documentation [here](https://docs.zama.ai/tfhe-rs/0.5-3/getting-started/installation) or in the local file [here](./README_TFHE-rs.md).
## How to run benchmarks
At the root of the project (i.e., in the TFHE-rs folder), enter the following commands to run the benchmarks:
- ```make bench_minifloat```: returns the timings associated to the Minifloats (**Table 6**).
- ```make bench_float```: returns the timings associated to the HFP (**Table 5**, **Table 7**).
These benchmarks first launch the parallelized and then the sequential experiments.
This outputs the timings depending on the input precision.
**This takes more than 6 hours to run**.
To run benchmarks for a specific precision over homomorphic floating points, here are the dedicated commands:
- ```make bench_float_8bit```: Runs benchmarks for only 8-bit floating point *(around 15 min)*.
- ```make bench_float_16bit```: Runs benchmarks for only 16-bit floating point *(around 30 min)*.
- ```make bench_float_32bit```: Runs benchmarks for only 32-bit floating point *(around 1h40)*.
- ```make bench_float_64bit```: Runs benchmarks for only 64-bit floating point *(around 6h30)*.
We recall that the benchmarks were performed on AWS using an **m6i.metal** instance with an Intel Xeon 8375C (Ice Lake) processor running at 3.5 GHz, 128 vCPUs, and 512 GiB of memory.
### Understanding Benchmark Output (Criterion.rs)
This project uses [Criterion.rs](https://docs.rs/criterion/latest/criterion/) for benchmarking. Criterion is a powerful and statistically robust benchmarking framework for Rust, and it may produce outputs that are unfamiliar at first glance. This section explains how to interpret them.
#### Sample Output Structure
A typical benchmark result looks like this:
```
test_float time: [53.2 µs 54.0 µs 54.8 µs]
change: [+0.2% +1.0% +1.8%] (p = 0.002)
Found 3 outliers among 100 measurements (3.00%)
3 (3.00%) high mild
```
+ For Apple Silicon or aarch64-based machines running Unix-like OSes:
**Here's what this means:**
```toml
tfhe = { version = "*", features = ["boolean", "shortint", "integer", "aarch64-unix"] }
- `time: [low est. median high est.]`: The estimated execution time of the function.
- `change`: The performance change compared to a previous run (if available).
- `outliers`: Some runs deviated from the typical time. Criterion detects and accounts for these using statistical methods.
---
#### Common Warnings and What They Mean
##### `Found X outliers among Y measurements`
Criterion runs each benchmark many times (default: 100) to get statistically significant results.
An *outlier* is a run that was significantly faster or slower than the others.
- **Why does this happen?** Often, it's due to **other processes on the machine** (e.g., background services, OS interrupts, or CPU scheduling) affecting performance temporarily.
- **Why it doesn't invalidate results:** Criterion uses statistical techniques to minimize the impact of these outliers when estimating performance.
- **Best practice to reduce outliers:** Run the benchmarks on a **freshly rebooted machine**, with as few background processes as possible. Ideally, let the system idle for a minute after boot to stabilize before running benchmarks.
##### `Unable to complete 100 samples in 5.0s.`
The benchmark took longer than the expected 5 seconds.
This is merely a warning indicating that the full set of 100 samples could not be collected within the default 5-second measurement window.
- **No action is required**: Criterion will still proceed to run all 100 samples, and the results remain statistically valid.
- **Why the warning appears**: It's there to inform you that benchmarking is taking longer than expected and to help you tune settings if needed.
- **Optional**: If you're constrained by time (e.g., running in CI), you can:
- Reduce the sample size (e.g., to 10 or 20 samples).
- Or increase the measurement time using:
```bash
cargo bench -- --measurement-time 30
```
## How to run the tests
### MiniFloats
To run the tests related to the **minifloats**, run the following command:
- ```make test_minifloat```: Runs a bivariate operation between two minifloats.
The **minifloat** test is available in the file *tfhe/src/float_wopbs/server_key/tests.rs*.
### Homomorphic Floating Points
At the root of the project (i.e., in the TFHE-rs folder), enter the following commands to run the tests per operation on the **homomorphic floating points**:
- ```make test_float_add```: Runs a 32-bit floating-point addition with two random inputs.
- ```make test_float_sub```: Runs a 32-bit floating-point subtraction with two random inputs.
- ```make test_float_mul```: Runs a 32-bit floating-point multiplication with two random inputs.
- ```make test_float_div```: Runs a 32-bit floating-point division with two random inputs.
- ```make test_float_cos```: Runs the experiment from **Table 8** with a random input value.
- ```make test_float_sin```: Runs the experiment from **Table 8** with a random input value.
- ```make test_float_relu```: Runs a 32-bit floating-point relu with a random input.
- ```make test_float_sigmoid```: Runs a 32-bit floating-point sigmoid with a random input.
- ```make test_float```: Runs all previous tests for operations on 32-bit floating-points.
- ```make test_float_depth_test```: This command runs the following experiment:
- **Step 1**: Create 3 blocks, each composed of a clear 32-bit floating point, a clear 64-bit floating point, and a 32-bit homomorphic floating point.
- **Step 2**: Choose two blocks randomly among the 3 blocks and randomly select a parallelized operation (addition, subtraction, or multiplication).
- **Step 3**: Compute the selected operation between the two selected blocks and store the result randomly in one of the two selected blocks.
(The operation is performed respectively between the two 64-bit floating points, the two 32-bit floating points, and homomorphically between the two 32-bit homomorphic floating points.)
- Repeat Steps 2 and 3 for 50 iterations.
- To avoid reaching + or - infinity, or **NaN**, when the clear 64-bit floating point reaches a fixed bound, compute a multiplication to rescale the value close to 1.
This operation is also performed homomorphically for the encrypted data. This test takes several minutes.
The tests are located in the file *tfhe/concrete-float/src/server_key/tests.rs*.
Due to the representation being close to, but not exactly the same as, a given representation, the obtained result is not identical to the one obtained in clear.
To consider a test as "passed", we accept a difference of less than 0.1% compared to the 64-bit floating-point clear results.
Note that using 8 or 16-bit homomorphic floating points might return errors due to a lack of precision and due to the comparisons with clear 64-bit floating points.
In each test, the different results are presented in the following format:
```
--------------------
"Name":
Result :
Clear 32-bits:
Clear 64-bits:
--------------------
```
Note: users with ARM devices must compile `TFHE-rs` using a stable toolchain with version >= 1.72.
where ```name``` stands for the name of the ciphertext or the name of the operation, result always corresponds to the decryption of a homomorphic floating point, and Clear ``` 32-bits``` and Clear ``` 64-bits``` correspond to the clear floating-point witness.
All tests in *tfhe/concrete-float/src/server_key/tests.rs* are conducted for 32-bit floating-point precision, as it provides the best ratio between execution time and precision.
To change the parameter set used, the parameters in the following ``` const ``` must be uncommented (lines 79 to 87 in the file *tfhe/concrete-float/src/server_key/tests.rs*).
+ For x86_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND)
running Windows:
```toml
tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64"] }
```rust
const PARAMS: [(&str, Parameters); 1] =
[
//named_param!(PARAM_FP_64_BITS),
named_param!(PARAM_FP_32_BITS),
//named_param!(PARAM_FP_16_BITS),
//named_param!(PARAM_FP_8_BITS),
];
```
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
Note that the number in ``` [(\&str, Parameters); 1] ``` should correspond to the number of tested parameters, e.g., if another parameter sets is uncommented, this line becomes: ``` [(\&str, Parameters); 2] ```.
The parameter ```PARAM_X``` corresponds to the parameters used in **Table 5**, and ```PARAM_TCHES_X``` corresponds to the parameters used in **Table 7**.
## A simple example
Here is a full example:
``` rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Basic configuration to use homomorphic integers
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
// Key generation
let (client_key, server_keys) = generate_keys(config);
let clear_a = 1344u32;
let clear_b = 5u32;
let clear_c = 7u8;
// Encrypting the input data using the (private) client_key
// FheUint32: Encrypted equivalent to u32
let mut encrypted_a = FheUint32::try_encrypt(clear_a, &client_key)?;
let encrypted_b = FheUint32::try_encrypt(clear_b, &client_key)?;
// FheUint8: Encrypted equivalent to u8
let encrypted_c = FheUint8::try_encrypt(clear_c, &client_key)?;
// On the server side:
set_server_key(server_keys);
// Clear equivalent computations: 1344 * 5 = 6720
let encrypted_res_mul = &encrypted_a * &encrypted_b;
// Clear equivalent computations: 1344 >> 5 = 42
encrypted_a = &encrypted_res_mul >> &encrypted_b;
// Clear equivalent computations: let casted_a = a as u8;
let casted_a: FheUint8 = encrypted_a.cast_into();
// Clear equivalent computations: min(42, 7) = 7
let encrypted_res_min = &casted_a.min(&encrypted_c);
// Operation between clear and encrypted data:
// Clear equivalent computations: 7 & 1 = 1
let encrypted_res = encrypted_res_min & 1_u8;
// Decrypting on the client side:
let clear_res: u8 = encrypted_res.decrypt(&client_key);
assert_eq!(clear_res, 1_u8);
Ok(())
}
```
To run this code, use the following command:
<p align="center"> <code> cargo run --release </code> </p>
Note that when running code that uses `tfhe-rs`, it is highly recommended
to run in release mode with cargo's `--release` flag to have the best performances possible,
## Contributing
There are two ways to contribute to TFHE-rs:
- 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))
Only approved contributors can send pull requests, so please make sure to get in touch before you do!
## Credits
This library uses several dependencies and we would like to thank the contributors of those
libraries.
## Need support?
<a target="_blank" href="https://community.zama.ai">
<img src="https://user-images.githubusercontent.com/5758427/231115030-21195b55-2629-4c01-9809-be5059243999.png">
</a>
## Citing TFHE-rs
To cite TFHE-rs in academic papers, please use the following entry:
```text
@Misc{TFHE-rs,
title={{TFHE-rs: A Pure Rust Implementation of the TFHE Scheme for Boolean and Integer Arithmetics Over Encrypted Data}},
author={Zama},
year={2022},
note={\url{https://github.com/zama-ai/tfhe-rs}},
}
```
## License
This software is distributed under the BSD-3-Clause-Clear license. If you have any questions,
please contact us at `hello@zama.ai`.
## Disclaimers
### Security Estimation
Security estimations are done using the
[Lattice Estimator](https://github.com/malb/lattice-estimator)
with `red_cost_model = reduction.RC.BDGL16`.
When a new update is published in the Lattice Estimator, we update parameters accordingly.
### Side-Channel Attacks
Mitigation for side channel attacks have not yet been implemented in TFHE-rs,
and will be released in upcoming versions.

View File

@@ -6,7 +6,7 @@ use tfhe_trivium::KreyviumStream;
use criterion::Criterion;
pub fn kreyvium_bool_gen(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
@@ -41,7 +41,7 @@ pub fn kreyvium_bool_gen(c: &mut Criterion) {
}
pub fn kreyvium_bool_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();

View File

@@ -6,9 +6,8 @@ use tfhe_trivium::{KreyviumStreamByte, TransCiphering};
use criterion::Criterion;
pub fn kreyvium_byte_gen(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
let config = ConfigBuilder::default()
.enable_function_evaluation()
.build();
let (client_key, server_key) = generate_keys(config);
@@ -36,9 +35,8 @@ pub fn kreyvium_byte_gen(c: &mut Criterion) {
}
pub fn kreyvium_byte_trans(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
let config = ConfigBuilder::default()
.enable_function_evaluation()
.build();
let (client_key, server_key) = generate_keys(config);
@@ -67,9 +65,8 @@ pub fn kreyvium_byte_trans(c: &mut Criterion) {
}
pub fn kreyvium_byte_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
let config = ConfigBuilder::default()
.enable_function_evaluation()
.build();
let (client_key, server_key) = generate_keys(config);

View File

@@ -8,9 +8,7 @@ use tfhe_trivium::{KreyviumStreamShortint, TransCiphering};
use criterion::Criterion;
pub fn kreyvium_shortint_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let underlying_ck: tfhe::shortint::ClientKey = (*hl_client_key.as_ref()).clone().into();
let underlying_sk: tfhe::shortint::ServerKey = (*hl_server_key.as_ref()).clone().into();
@@ -60,9 +58,7 @@ pub fn kreyvium_shortint_warmup(c: &mut Criterion) {
}
pub fn kreyvium_shortint_gen(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let underlying_ck: tfhe::shortint::ClientKey = (*hl_client_key.as_ref()).clone().into();
let underlying_sk: tfhe::shortint::ServerKey = (*hl_server_key.as_ref()).clone().into();
@@ -107,9 +103,7 @@ pub fn kreyvium_shortint_gen(c: &mut Criterion) {
}
pub fn kreyvium_shortint_trans(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let underlying_ck: tfhe::shortint::ClientKey = (*hl_client_key.as_ref()).clone().into();
let underlying_sk: tfhe::shortint::ServerKey = (*hl_server_key.as_ref()).clone().into();

View File

@@ -6,7 +6,7 @@ use tfhe_trivium::TriviumStream;
use criterion::Criterion;
pub fn trivium_bool_gen(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
@@ -41,7 +41,7 @@ pub fn trivium_bool_gen(c: &mut Criterion) {
}
pub fn trivium_bool_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();

View File

@@ -6,9 +6,7 @@ use tfhe_trivium::{TransCiphering, TriviumStreamByte};
use criterion::Criterion;
pub fn trivium_byte_gen(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
@@ -35,9 +33,7 @@ pub fn trivium_byte_gen(c: &mut Criterion) {
}
pub fn trivium_byte_trans(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
@@ -65,9 +61,7 @@ pub fn trivium_byte_trans(c: &mut Criterion) {
}
pub fn trivium_byte_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();

View File

@@ -8,9 +8,7 @@ use tfhe_trivium::{TransCiphering, TriviumStreamShortint};
use criterion::Criterion;
pub fn trivium_shortint_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let underlying_ck: tfhe::shortint::ClientKey = (*hl_client_key.as_ref()).clone().into();
let underlying_sk: tfhe::shortint::ServerKey = (*hl_server_key.as_ref()).clone().into();
@@ -60,9 +58,7 @@ pub fn trivium_shortint_warmup(c: &mut Criterion) {
}
pub fn trivium_shortint_gen(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let underlying_ck: tfhe::shortint::ClientKey = (*hl_client_key.as_ref()).clone().into();
let underlying_sk: tfhe::shortint::ServerKey = (*hl_server_key.as_ref()).clone().into();
@@ -107,9 +103,7 @@ pub fn trivium_shortint_gen(c: &mut Criterion) {
}
pub fn trivium_shortint_trans(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let underlying_ck: tfhe::shortint::ClientKey = (*hl_client_key.as_ref()).clone().into();
let underlying_sk: tfhe::shortint::ServerKey = (*hl_server_key.as_ref()).clone().into();

View File

@@ -170,7 +170,7 @@ fn kreyvium_test_4() {
#[test]
fn kreyvium_test_fhe_long() {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
@@ -217,9 +217,7 @@ use tfhe::shortint::prelude::*;
#[test]
fn kreyvium_test_shortint_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let underlying_ck: tfhe::shortint::ClientKey = (*hl_client_key.as_ref()).clone().into();
let underlying_sk: tfhe::shortint::ServerKey = (*hl_server_key.as_ref()).clone().into();
@@ -302,9 +300,8 @@ fn kreyvium_test_clear_byte() {
#[test]
fn kreyvium_test_byte_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
let config = ConfigBuilder::default()
.enable_function_evaluation()
.build();
let (client_key, server_key) = generate_keys(config);
@@ -342,9 +339,8 @@ fn kreyvium_test_byte_long() {
#[test]
fn kreyvium_test_fhe_byte_transciphering_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
let config = ConfigBuilder::default()
.enable_function_evaluation()
.build();
let (client_key, server_key) = generate_keys(config);

View File

@@ -4,6 +4,7 @@
use crate::{KreyviumStreamByte, KreyviumStreamShortint, TriviumStreamByte, TriviumStreamShortint};
use tfhe::shortint::Ciphertext;
use tfhe::prelude::*;
use tfhe::{set_server_key, unset_server_key, FheUint64, FheUint8, ServerKey};
use rayon::prelude::*;

View File

@@ -232,7 +232,7 @@ fn trivium_test_clear_byte() {
#[test]
fn trivium_test_fhe_long() {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
@@ -277,9 +277,7 @@ fn trivium_test_fhe_long() {
#[test]
fn trivium_test_fhe_byte_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
@@ -316,9 +314,7 @@ fn trivium_test_fhe_byte_long() {
#[test]
fn trivium_test_fhe_byte_transciphering_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
@@ -357,9 +353,7 @@ use tfhe::shortint::prelude::*;
#[test]
fn trivium_test_shortint_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let config = ConfigBuilder::default().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let underlying_ck: tfhe::shortint::ClientKey = (*hl_client_key.as_ref()).clone().into();
let underlying_sk: tfhe::shortint::ServerKey = (*hl_server_key.as_ref()).clone().into();

View File

@@ -1,3 +1,4 @@
{
"m6i.metal": 7.168
"m6i.metal": 7.168,
"hpc7a.96xlarge": 7.7252
}

View File

@@ -20,7 +20,10 @@ def main(args):
bench_function_id = bench_data["function_id"]
split = bench_function_id.split("::")
(_, function_name, parameter_set, bits) = split
if split.len() == 5: # Signed integers
(_, _, function_name, parameter_set, bits) = split
else: # Unsigned integers
(_, function_name, parameter_set, bits) = split
if "_scalar_" in bits:
(bits, scalar) = bits.split("_bits_scalar_")

View File

@@ -3,30 +3,35 @@ region = "eu-west-3"
image_id = "ami-051942e4055555752"
instance_type = "m6i.32xlarge"
[profile.cpu-big_fallback]
region = "us-east-1"
image_id = "ami-04e3bb9aebb6786df"
instance_type = "m6i.32xlarge"
[profile.cpu-small]
region = "eu-west-3"
image_id = "ami-051942e4055555752"
instance_type = "m6i.4xlarge"
[profile.bench]
region = "eu-west-3"
image_id = "ami-051942e4055555752"
instance_type = "m6i.metal"
region = "eu-west-1"
image_id = "ami-0e88d98b86aff13de"
instance_type = "hpc7a.96xlarge"
[command.cpu_test]
workflow = "aws_tfhe_tests.yml"
profile = "cpu-big"
check_run_name = "CPU AWS Tests"
[command.cpu_integer_test]
[command.cpu_unsigned_integer_test]
workflow = "aws_tfhe_integer_tests.yml"
profile = "cpu-big"
check_run_name = "CPU Integer AWS Tests"
check_run_name = "CPU Unsigned Integer AWS Tests"
[command.cpu_multi_bit_test]
workflow = "aws_tfhe_multi_bit_tests.yml"
[command.cpu_signed_integer_test]
workflow = "aws_tfhe_signed_integer_tests.yml"
profile = "cpu-big"
check_run_name = "CPU AWS Multi Bit Tests"
check_run_name = "CPU Signed Integer AWS Tests"
[command.cpu_wasm_test]
workflow = "aws_tfhe_wasm_tests.yml"
@@ -43,6 +48,11 @@ workflow = "integer_full_benchmark.yml"
profile = "bench"
check_run_name = "Integer CPU AWS Benchmarks Full Suite"
[command.signed_integer_full_bench]
workflow = "signed_integer_full_benchmark.yml"
profile = "bench"
check_run_name = "Signed Integer CPU AWS Benchmarks Full Suite"
[command.integer_bench]
workflow = "integer_benchmark.yml"
profile = "bench"
@@ -53,6 +63,16 @@ workflow = "integer_multi_bit_benchmark.yml"
profile = "bench"
check_run_name = "Integer multi bit CPU AWS Benchmarks"
[command.signed_integer_bench]
workflow = "signed_integer_benchmark.yml"
profile = "bench"
check_run_name = "Signed integer CPU AWS Benchmarks"
[command.signed_integer_multi_bit_bench]
workflow = "signed_integer_multi_bit_benchmark.yml"
profile = "bench"
check_run_name = "Signed integer multi bit CPU AWS Benchmarks"
[command.shortint_full_bench]
workflow = "shortint_full_benchmark.yml"
profile = "bench"

View File

@@ -22,6 +22,9 @@ pub trait Seeder {
}
mod implem;
// This import statement can be empty if seeder features are disabled, rustc's behavior changed to
// warn of empty modules, we know this can happen, so allow it.
#[allow(unused_imports)]
pub use implem::*;
#[cfg(test)]

69
concrete-float/Cargo.toml Normal file
View File

@@ -0,0 +1,69 @@
[package]
name = "concrete-float"
version = "0.1.0-beta.0"
edition = "2018"
authors = ["Zama team"]
license = "BSD-3-Clause-Clear"
description = "Homomorphic Integer circuit interface for the concrete FHE library."
homepage = "https://www.zama.ai/concrete-framework"
documentation = "https://docs.zama.ai/home/"
repository = "https://github.com/zama-ai/concrete"
readme = "README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
aligned-vec = { version = "0.5", features = ["serde"] }
dyn-stack = { version = "0.9" }
rayon = "1.5"
lazy_static = { version = "1.4.0", optional = true }
tfhe = { path = "../tfhe", features = ["shortint", "integer"] }
[target.'cfg(target_arch = "x86_64")'.dependencies]
tfhe = { path = "../tfhe", features = ["shortint", "integer", "x86_64-unix"] }
[target.'cfg(target_arch = "aarch64")'.dependencies]
tfhe = { path = "../tfhe", features = ["shortint", "integer", "aarch64-unix"] }
[features]
nightly-avx512 = ["tfhe/nightly-avx512"]
seeder_x86_64_rdseed = []
seeder_unix = []
generator_x86_64_aesni = []
generator_fallback = []
generator_aarch64_aes = []
x86_64 = [
"seeder_x86_64_rdseed",
"generator_x86_64_aesni",
"generator_fallback",
]
x86_64-unix = ["x86_64", "seeder_unix"]
aarch64 = [ "generator_aarch64_aes", "generator_fallback"]
aarch64-unix = ["aarch64", "seeder_unix"]
[dev-dependencies]
criterion = "0.5.1"
lazy_static = "1.4.0"
bincode = "1.3.3"
paste = "1.0.7"
rand = "0.8.4"
doc-comment = "0.3.3"
#concrete-shortint = { path = "../tfhe", features = ["internal-keycache"] }
#[features]
# Keychache used to speed up tests and benches
# by not requiring to regererate keys at each launch
#internal-keycache = ["lazy_static", "shortint/src/internal-keycache"]
[package.metadata.docs.rs]
rustdoc-args = ["--html-in-header", "katex-header.html"]
[[bench]]
name = "float-bench"
path = "benches/bench.rs"
harness = false
required-features = []

32
concrete-float/LICENSE Normal file
View File

@@ -0,0 +1,32 @@
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.

11
concrete-float/README.md Normal file
View File

@@ -0,0 +1,11 @@
# concrete Integer
`concrete-integer` is a Rust library built on top of `concrete-shortint`, it
combines multiple `shortint` to handle encrypted integers of "arbitrary"
size.
## License
This software is distributed under the BSD-3-Clause-Clear license. If you have any questions,
please contact us at `hello@zama.ai`.

View File

@@ -0,0 +1,304 @@
#![allow(dead_code)]
use concrete_float::gen_keys;
use criterion::{criterion_group, criterion_main, Criterion};
use rand::Rng;
// Previous Parameters
#[allow(unused_imports)]
use concrete_float::parameters::{FINAL_PARAM_16,
FINAL_PARAM_2_2_32, FINAL_PARAM_32,
FINAL_PARAM_64, FINAL_PARAM_8,
FINAL_WOP_PARAM_15, FINAL_WOP_PARAM_16,
FINAL_WOP_PARAM_2_2_32, FINAL_WOP_PARAM_32,
FINAL_WOP_PARAM_64, FINAL_WOP_PARAM_8,
FINAL_PARAM_64_TCHESS, FINAL_PARAM_32_TCHESS,
FINAL_WOP_PARAM_64_TCHESS, FINAL_WOP_PARAM_32_TCHESS};
use concrete_float::parameters::{FINAL_PARAM_16_BIS, FINAL_PARAM_32_BIS,
FINAL_PARAM_64_BIS, FINAL_PARAM_8_BIS,
FINAL_WOP_PARAM_16_BIS, FINAL_WOP_PARAM_32_BIS,
FINAL_WOP_PARAM_64_BIS, FINAL_WOP_PARAM_8_BIS};
use tfhe::shortint;
macro_rules! named_param {
($param:ident) => {
(stringify!($param), $param)
};
}
criterion_main!(float_parallelized, float);
struct Parameters {
pbsparameters: shortint::ClassicPBSParameters,
wopbsparameters: shortint::WopbsParameters,
len_man: usize,
len_exp: usize,
}
//Parameter for a Floating point 64-bits equivalent
const PARAM_64: Parameters = Parameters {
pbsparameters: FINAL_PARAM_64_BIS,
wopbsparameters: FINAL_WOP_PARAM_64_BIS,
len_man: 27,
len_exp: 5,
};
//Parameter for a Floating point 32-bits equivalent
const PARAM_32: Parameters = Parameters {
pbsparameters: FINAL_PARAM_32_BIS,
wopbsparameters: FINAL_WOP_PARAM_32_BIS,
len_man: 13,
len_exp: 4,
};
//Parameter for a Floating point 16-bits equivalent
const PARAM_16: Parameters = Parameters {
pbsparameters: FINAL_PARAM_16_BIS,
wopbsparameters: FINAL_WOP_PARAM_16_BIS,
len_man: 6,
len_exp: 3,
};
//Parameter for a Floating point 8-bits equivalent
const PARAM_8: Parameters = Parameters {
pbsparameters: FINAL_PARAM_8_BIS,
wopbsparameters: FINAL_WOP_PARAM_8_BIS,
len_man: 3,
len_exp: 2,
};
//Parameter for a Floating point 64-bits equivalent
//With failure probability smaller than PARAM_64
const PARAM_TCHESS_64: Parameters = Parameters {
pbsparameters: FINAL_PARAM_64_TCHESS,
wopbsparameters: FINAL_WOP_PARAM_64_TCHESS,
len_man: 27,
len_exp: 5,
};
//Parameter for a Floating point 32-bits equivalent
//With failure probability smaller than PARAM_32
const PARAM_TCHESS_32: Parameters = Parameters {
pbsparameters: FINAL_PARAM_32_TCHESS,
wopbsparameters: FINAL_WOP_PARAM_32_TCHESS,
len_man: 13,
len_exp: 4,
};
const SERVER_KEY_BENCH_PARAMS: [(&str, Parameters);6] =
[
named_param!(PARAM_8),
named_param!(PARAM_16),
named_param!(PARAM_32),
named_param!(PARAM_64),
named_param!(PARAM_TCHESS_32),
named_param!(PARAM_TCHESS_64),
];
criterion_group!(
float,
add,
mul,
relu,
sigmoid,
);
criterion_group!(
float_parallelized,
add_parallelized,
mul_parallelized,
div_parallelized,,
);
fn relu(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("operation");
let mut rng = rand::thread_rng();
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg = rng.gen::<f32>() as f64;
let ct = cks.encrypt(msg);
let bench_id = format!("{}::{}", "Relu", param_name);
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
sks.relu(&ct);
})
});
}
bench_group.finish()
}
fn sigmoid(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("operation");
let mut rng = rand::thread_rng();
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg = rng.gen::<f32>() as f64;
let ct = cks.encrypt(msg);
let bench_id = format!("{}::{}", "sigmoid", param_name);
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
sks.sigmoid(&ct);
})
});
}
bench_group.finish()
}
fn mul(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("operation");
let mut rng = rand::thread_rng();
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg);
let msg = rng.gen::<f32>() as f64;
let ct2 = cks.encrypt(msg);
let bench_id = format!("{}::{}", "mul", param_name);
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
sks.mul_total(&ct1, &ct2);
})
});
}
bench_group.finish()
}
fn mul_parallelized(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("operation");
let mut rng = rand::thread_rng();
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg);
let msg = rng.gen::<f32>() as f64;
let ct2 = cks.encrypt(msg);
let bench_id = format!("{}::{}", "mul parallelized", param_name);
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
sks.mul_total_parallelized(&ct1, &ct2);
})
});
}
bench_group.finish()
}
fn div_parallelized(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("operation");
let mut rng = rand::thread_rng();
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg);
let msg = rng.gen::<f32>() as f64;
let ct2 = cks.encrypt(msg);
let bench_id = format!("{}::{}", "div parallelized", param_name);
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
sks.division(&ct1, &ct2);
})
});
}
bench_group.finish()
}
fn add(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("operation");
let mut rng = rand::thread_rng();
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg);
let msg = rng.gen::<f32>() as f64;
let ct2 = cks.encrypt(msg);
let bench_id = format!("{}::{}", "add", param_name);
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
sks.add_total(&ct1, &ct2);
})
});
}
bench_group.finish()
}
fn add_parallelized(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("operation");
let mut rng = rand::thread_rng();
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg);
let msg = rng.gen::<f32>() as f64;
let ct2 = cks.encrypt(msg);
let bench_id = format!("{}::{}", "add parallelized", param_name);
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
sks.add_total_parallelized(&ct1, &ct2);
})
});
}
bench_group.finish()
}

View File

@@ -0,0 +1,20 @@
# Concrete-Integer User Guide
[Introduction](introduction.md)
# Getting Started
[Installation](getting_started/installation.md)
[Writing Your First Circuit](getting_started/first_circuit.md)
[Types Of Operations](getting_started/operation_types.md)
[List of Operations](getting_started/operation_list.md)
[Cryptographic Parameters](getting_started/parameters.md)
# How to
[Serialization / Deserialization](tutorials/serialization.md)

View File

@@ -0,0 +1,105 @@
# Writing Your First Circuit
## Key Types
`concrete-integer` provides 2 basic key types:
- `ClientKey`
- `ServerKey`
The `ClientKey` is the key that encrypts and decrypts messages,
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.
The `ServerKey` is the key that is used to actually do the FHE computations. It contains (among other things)
a bootstrapping key and a keyswitching key.
This key is created from a `ClientKey` that needs to be shared to the server, therefore it is not
meant to be kept private.
A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated
`ClientKey`.
To reflect that, computation/operation methods are tied to the `ServerKey` type.
## 1. Key Generation
To generate the keys, a user needs two parameters:
- A set of `shortint` cryptographic parameters.
- The number of ciphertexts used to encrypt an integer (we call them "shortint blocks").
For this example we are going to build a pair of keys that can encrypt an **8-bit** integer
by using **4** shortint blocks that store **2** bits of message each.
```rust
use concrete_integer::gen_keys;
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
}
```
## 2. Encrypting values
Once we have our keys we can encrypt values:
```rust
use concrete_integer::gen_keys;
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 128;
let msg2 = 13;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
}
```
## 3. Computing and decrypting
With our `server_key`, and encrypted values, we can now do an addition
and then decrypt the result.
```rust
use concrete_integer::gen_keys;
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 128;
let msg2 = 13;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
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);
// 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);
}
```

View File

@@ -0,0 +1,49 @@
# Installation
## Cargo.toml
To use `concrete-integer`, you will need to add it to the list of dependencies
of your project, by updating your `Cargo.toml` file.
```toml
concrete-integer = "0.1.0"
```
### Supported platforms
As `concrete-integer` relies on `concrete-shortint`, which in turn relies on `concrete-core`,
the support ted platforms supported are:
- `x86_64 Linux`
- `x86_64 macOS`.
Windows users can use `concrete-integer` through the `WSL`.
macOS users which have the newer M1 (`arm64`) devices can use `concrete-integer` by cross-compiling to
`x86_64` and run their program with Rosetta.
First install the needed Rust toolchain:
```console
# Install the macOS x86_64 toolchain (you only need to do this once)
rustup toolchain install --force-non-host stable-x86_64-apple-darwin
```
Then you can either:
- Manually specify the toolchain to use in each of the cargo commands:
For example:
```console
cargo +stable-x86_64-apple-darwin build
cargo +stable-x86_64-apple-darwin test
```
- Or override the toolchain to use for the current project:
```console
rustup override set stable-x86_64-apple-darwin
# cargo will use the `stable-x86_64-apple-darwin` toolchain.
cargo build
```

View File

@@ -0,0 +1,15 @@
# List of available operations
`concrete-integer` comes with a set of already implemented functions:
- addition between two ciphertexts
- addition between a ciphertext and an unencrypted scalar
- multiplication of a ciphertext by an unencrypted scalar
- bitwise shift `<<`, `>>`
- bitwise and, or and xor
- multiplication between two ciphertexts
- subtraction of a ciphertext by another ciphertext
- subtraction of a ciphertext by an unencrypted scalar
- negation of a ciphertext

View File

@@ -0,0 +1,86 @@
# How Integers are represented
In `concrete-integer`, the encrypted data is split amongst many ciphertexts
encrypted using the `concrete-shortint` library.
This crate implements two ways to represent an integer:
- the Radix representation
- the CRT (Chinese Reminder Theorem) representation
## Radix based Integers
The first possibility to represent a large integer is to use a radix-based decomposition on the
plaintexts. Let $$B \in \mathbb{N}$$ be a basis such that the size of $$B$$ is smaller (or equal)
to four bits.
Then, an integer $$m \in \mathbb{N}$$ can be written as $$m = m_0 + m_1*B + m_2*B^2 + ... $$, where
each $$m_i$$ is strictly smaller than $$B$$. Each $$m_i$$ is then independently encrypted. In
the end, an Integer ciphertext is defined as a set of Shortint ciphertexts.
In practice, the definition of an Integer requires the basis and the number of blocks. This is
done at the key creation step.
```rust
use concrete_integer::gen_keys;
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
}
```
In this example, the keys are dedicated to Integers decomposed as four blocks using the basis
$$B=2^2$$. Otherwise said, they allow to work on Integers modulus $$(2^2)^4 = 2^8$$.
In this representation, the correctness of operations requires to propagate the carries
between the ciphertext. This operation is costly since it relies on the computation of many
programmable bootstrapping over Shortints.
## CRT based Integers
The second approach to represent large integers is based on the Chinese Remainder Theorem.
In this cases, the basis $$B$$ is composed of several integers $$b_i$$, such that there are
pairwise coprime, and each b_i has a size smaller than four bits. Then, the Integer will be
defined modulus $$\prod b_i$$. For an integer $$m$$, its CRT decomposition is simply defined as
$$m % b_0, m % b_1, ...$$. Each part is then encrypted as a Shortint ciphertext. In
the end, an Integer ciphertext is defined as a set of Shortint ciphertexts.
An example of such a basis
could be $$B = [2, 3, 5]$$. This means that the Integer is defined modulus $$2*3*5 = 30$$.
This representation has many advantages: no carry propagation is required, so that only cleaning
the carry buffer of each ciphertexts is enough. This implies that operations can easily be
parallelized. Moreover, it allows to efficiently compute PBS in the case where the function is
CRT compliant.
A variant of the CRT is proposed, where each block might be associated to a different key couple.
In the end, a keychain is required to the computations, but performance might be improved.
# Types of operations
Much like `concrete-shortint`, 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.
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.
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.
- `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 propagate the carry buffer 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.

View File

@@ -0,0 +1,6 @@
# Use of parameters
`concrete-integer` does not come with its own set of parameters, instead it uses
parameters from the `concrete-shortint` crate. Currently, only the parameters
`PARAM_MESSAGE_{X}_CARRY_{X}` with `X` in [1,4] can be used in `concrete-integer`.

View File

@@ -0,0 +1,47 @@
# The tree programmable bootstrapping
In `concrete-integer`, the user can evaluate any function on an encrypted ciphertext. To do so the user must first
create a `treepbs key`, choose a function to evaluate and give them as parameters to the `tree programmable bootstrapping`.
Two versions of the tree pbs are implemented: the `standard` version that computes a result according to every encrypted
bit (message and carry), and the `base` version that only takes into account the message bits of each block.
{% hint style="warning" %}
The `tree pbs` is quite slow, therefore its use is currently restricted to two and three blocks integer ciphertexts.
{% endhint %}
```rust
use concrete_integer::gen_keys;
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use concrete_integer::treepbs::TreepbsKey;
fn main() {
let num_block = 2;
// Generate the client key and the server key:
let (cks, sks) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg: u64 = 27;
let ct = cks.encrypt(msg);
// message_modulus^vec_length
let modulus = cks.parameters().message_modulus.0.pow(2 as u32) as u64;
let treepbs_key = TreepbsKey::new(&cks);
let f = |x: u64| x * x;
// evaluate f
let vec_res = treepbs_key.two_block_pbs(&sks, &ct, f);
// decryption
let res = cks.decrypt(&vec_res);
let clear = f(msg) % modulus;
assert_eq!(res, clear);
}
```
# The WOP programmable bootstrapping

View File

@@ -0,0 +1,8 @@
# Concrete-integer
## Introduction
`concrete-integer` is a Rust library (crate) based on `concrete-shortint`, this crate provides
large precision integers by using multiple `shortint` ciphertexts.
The intended target audience for this library is people who are somewhat familiar with cryptography.

View File

@@ -0,0 +1,120 @@
# Circuit evaluation
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 an addition.
```rust
use concrete_integer::gen_keys;
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12;
let msg2 = 11;
let msg3 = 9;
let scalar = 3;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
let ct_3 = client_key.encrypt(msg2);
server_key.unchecked_small_scalar_mul_assign(&mut ct_1, scalar);
server_key.unchecked_sub_assign(&mut ct_1, &ct_2);
server_key.unchecked_add_assign(&mut ct_1, &ct_3);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_1);
// The carry buffer has been overflowed, the result is not correct
assert_ne!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
}
```
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.
```rust
use concrete_integer::gen_keys;
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 2;
let (client_key, server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12;
let msg2 = 11;
let msg3 = 9;
let scalar = 3;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
let ct_3 = client_key.encrypt(msg3);
let result = server_key.checked_small_scalar_mul_assign(&mut ct_1, scalar);
assert!(result.is_ok());
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
assert!(result.is_err());
// We use the client key to decrypt the output of the circuit:
// Only the scalar multiplication could be done
let output = client_key.decrypt(&ct_1);
assert_eq!(output, (msg1 * scalar) % modulus as u64);
}
```
Therefore the `checked` flavour permits to manually manage 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 propagated during the computations.
```rust
use concrete_integer::gen_keys;
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12;
let msg2 = 11;
let msg3 = 9;
let scalar = 3;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let mut ct_2 = client_key.encrypt(msg2);
let mut ct_3 = client_key.encrypt(msg3);
server_key.smart_scalar_mul_assign(&mut ct_1, scalar);
server_key.smart_sub_assign(&mut ct_1, &mut ct_2);
server_key.smart_add_assign(&mut ct_1, &mut ct_3);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_1);
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
}
```

View File

@@ -0,0 +1,78 @@
# Serialization / Deserialization
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared
with the server that does the computations.
The easiest way to send these data to a server is to use the serialization and deserialization features.
concrete-integer uses the serde framework, serde's Serialize and Deserialize are implemented.
To be able to serialize our data, we need to pick a [data format], for our use case,
[bincode] is a good choice, mainly because it is binary format.
```toml
# Cargo.toml
[dependencies]
# ...
bincode = "1.3.3"
```
```rust
// main.rs
use bincode;
use std::io::Cursor;
use concrete_integer::{gen_keys, ServerKey, Ciphertext};
use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 201;
let msg2 = 12;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
let mut serialized_data = Vec::new();
bincode::serialize_into(&mut serialized_data, &server_key)?;
bincode::serialize_into(&mut serialized_data, &ct_1)?;
bincode::serialize_into(&mut serialized_data, &ct_2)?;
// Simulate sending serialized data to a server and getting
// back the serialized result
let serialized_result = server_function(&serialized_data)?;
let result: Ciphertext = bincode::deserialize(&serialized_result)?;
let output = client_key.decrypt(&result);
assert_eq!(output, (msg1 + msg2) % modulus);
Ok(())
}
fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut serialized_data = Cursor::new(serialized_data);
let server_key: ServerKey = bincode::deserialize_from(&mut serialized_data)?;
let ct_1: Ciphertext = bincode::deserialize_from(&mut serialized_data)?;
let ct_2: Ciphertext = bincode::deserialize_from(&mut serialized_data)?;
let result = server_key.unchecked_add(&ct_1, &ct_2);
let serialized_result = bincode::serialize(&result)?;
Ok(serialized_result)
}
```
[serde]: https://crates.io/crates/serde
[data format]: https://serde.rs/#data-formats
[bincode]: https://crates.io/crates/bincode

View File

@@ -0,0 +1,15 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.css" integrity="sha384-9eLZqc9ds8eNjO3TmqPeYcDj8n+Qfa4nuSiGYa6DjLNcv9BtN69ZIulL9+8CqC9Y" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.js" integrity="sha384-K3vbOmF2BtaVai+Qk37uypf7VrgBubhQreNQe9aGsz9lB63dIFiQVlJbr92dw2Lx" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/contrib/auto-render.min.js" integrity="sha384-kmZOZB5ObwgQnS/DuDg6TScgOiWWBiVt0plIRkZCmE6rDZGrEOQeHM5PcHi+nyqe" 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>

BIN
concrete-float/long_run Normal file

Binary file not shown.

View File

@@ -0,0 +1,30 @@
//! This module implements the ciphertext structure containing an encryption of an integer message.
use serde::{Deserialize, Serialize};
use tfhe::shortint;
/// Id to recognize the key used to encrypt a block.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub struct KeyId(pub usize);
#[derive(Serialize, Clone, Deserialize, PartialEq, Eq, Debug)]
pub struct Ciphertext {
pub ct_vec_mantissa: Vec<shortint::ciphertext::Ciphertext>,
pub ct_vec_exponent: Vec<shortint::ciphertext::Ciphertext>,
pub ct_sign: shortint::ciphertext::Ciphertext,
pub(crate) e_min: i64,
}
impl Ciphertext {
/// Returns the slice of blocks that the ciphertext is composed of.
pub fn mantissa_blocks(&self) -> &[shortint::Ciphertext] {
&self.ct_vec_mantissa
}
pub fn exponent_blocks(&self) -> &[shortint::Ciphertext] {
&self.ct_vec_exponent
}
pub fn sign(&self) -> &shortint::Ciphertext {
&self.ct_sign
}
pub fn e_min(&self) -> &i64 {
&self.e_min
}
}

View File

@@ -0,0 +1,265 @@
//! This module implements the generation of the client secret keys, together with the
//! encryption and decryption methods.
pub(crate) mod utils;
use crate::ciphertext::Ciphertext;
use serde::{Deserialize, Serialize};
use tfhe::shortint;
use tfhe::shortint::{ClassicPBSParameters, WopbsParameters};
pub use utils::radix_decomposition;
/// The number of ciphertexts in the vector.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub struct VecLength(pub usize);
/// A structure containing the client key, which must be kept secret.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct ClientKey {
pub(crate) key: shortint::client_key::ClientKey,
pub(crate) vector_length_mantissa: VecLength,
pub(crate) vector_length_exponent: VecLength,
}
impl ClientKey {
/// Allocates and generates a client key.
///
/// # Example
///
/// ```rust
/// use concrete_float::client_key::ClientKey;
/// use concrete_float::parameters::{PARAM_MESSAGE_2_CARRY_2_32, WOP_PARAM_MESSAGE_2_CARRY_2_32};
/// use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
///
/// // Generate the client key associated to integers over 4 blocks
/// // of messages with modulus over 2 bits
/// let param = (PARAM_MESSAGE_2_CARRY_2_32, WOP_PARAM_MESSAGE_2_CARRY_2_32);
/// let cks = ClientKey::new(param, 4, 1);
/// ```
pub fn new(
parameter_set: (ClassicPBSParameters, WopbsParameters),
size_mantissa: usize,
size_exponent: usize,
) -> Self {
let key = shortint::ClientKey::new(parameter_set);
Self {
key,
vector_length_mantissa: VecLength(size_mantissa),
vector_length_exponent: VecLength(size_exponent),
}
}
/// Returns the parameters used by the client key.
pub fn parameters(&self) -> shortint::parameters::ShortintParameterSet {
self.key.parameters
}
/// Encrypts a float message using the client key.
///
/// # Example
///
/// ```rust
/// use concrete_float::client_key::ClientKey;
/// use concrete_float::parameters::{PARAM_MESSAGE_2_CARRY_2_32, WOP_PARAM_MESSAGE_2_CARRY_2_32};
///
/// let param = (PARAM_MESSAGE_2_CARRY_2_32, WOP_PARAM_MESSAGE_2_CARRY_2_32);
/// let mut cks = ClientKey::new(param, 3, 1);
///
/// let msg = 1844640.;
/// // Encryption of one message:
/// let ct = cks.encrypt(msg);
/// let res = cks.decrypt(&ct);
///
/// //approximation less than 0.1%
/// assert_eq!(res, msg)
/// ```
pub fn encrypt(&self, message: f64) -> Ciphertext {
let ct_sign = self.encrypt_sign(message);
let log_msg_modulus = f64::log2(self.parameters().message_modulus().0 as f64) as usize;
let e_min = -((1 << (self.vector_length_exponent.0 * log_msg_modulus - 1)) as i64)
- (self.vector_length_mantissa.0 as i64 - 1);
if message == 0. {
let exponent = 0;
let mantissa = 0.0;
let ct_vec_mantissa = self.encrypt_mantissa(mantissa as u64);
let ct_vec_exponent = self.encrypt_exponent(exponent as u64);
Ciphertext {
ct_vec_mantissa,
ct_vec_exponent,
ct_sign,
e_min,
}
} else {
let length_mantissa = self.vector_length_mantissa.0;
let log_message_modulus =
f64::log2(self.parameters().message_modulus().0 as f64) as usize;
let value_exponent = log_message_modulus as u64;
let mut exponent = e_min.abs();
let mut cpy_message = message.abs();
while cpy_message < (1_u128 << (length_mantissa * log_message_modulus)) as f64 {
cpy_message *= (1 << value_exponent) as f64;
exponent -= 1;
}
while cpy_message >= (1_u128 << (length_mantissa * log_message_modulus)) as f64 {
cpy_message /= (1 << value_exponent) as f64;
exponent += 1;
}
//TODO
if exponent >= (1 << (log_message_modulus * self.vector_length_exponent.0) as i64) {
println!("encrypt overflow");
}
if exponent < 0 {
for _ in 0..exponent.abs() {
cpy_message /= (1 << value_exponent) as f64;
}
exponent = 0;
//panic!()
}
let mantissa = cpy_message.round() as u64;
let ct_vec_mantissa = self.encrypt_mantissa(mantissa);
let ct_vec_exponent = self.encrypt_exponent(exponent as u64);
Ciphertext {
ct_vec_mantissa,
ct_vec_exponent,
ct_sign,
e_min,
}
}
}
fn encrypt_sign(&self, message: f64) -> shortint::ciphertext::Ciphertext {
let sign: u64;
if message >= 0. {
sign = 0;
} else {
sign = 1
}
self.key.encrypt_without_padding(
sign * (self.key.parameters.message_modulus().0 * self.key.parameters.carry_modulus().0
/ 2) as u64,
)
}
fn encrypt_mantissa(&self, mantissa: u64) -> Vec<shortint::Ciphertext> {
let mut ct_vec_mantissa: Vec<shortint::ciphertext::Ciphertext> = Vec::new();
let mut power = 1_u128;
let message_modulus = self.parameters().message_modulus().0 as u128;
for _ in 0..self.vector_length_mantissa.0 {
let mut decomp = mantissa as u128 & ((message_modulus - 1) * power);
decomp /= power;
// encryption
let ct = self.key.encrypt(decomp as u64);
ct_vec_mantissa.push(ct);
//modulus to the power i
power *= message_modulus;
}
ct_vec_mantissa
}
fn encrypt_exponent(&self, exponent: u64) -> Vec<shortint::Ciphertext> {
let mut ct_vec_exponent: Vec<shortint::ciphertext::Ciphertext> = Vec::new();
let mut power = 1_u64;
let message_modulus = self.parameters().message_modulus().0 as u64;
for _ in 0..self.vector_length_exponent.0 {
let mut decomp = exponent as u64 & ((message_modulus - 1) * power);
decomp /= power;
// encryption
let ct = self.key.encrypt(decomp);
ct_vec_exponent.push(ct);
//modulus to the power i
power *= message_modulus;
}
ct_vec_exponent
}
/// Decrypts a ciphertext encrypting an float message
///
/// # Example
///
/// ```rust
/// use concrete_float::client_key::ClientKey;
/// use concrete_float::parameters::{PARAM_MESSAGE_2_CARRY_2_32, WOP_PARAM_MESSAGE_2_CARRY_2_32};
///
/// let param = (PARAM_MESSAGE_2_CARRY_2_32, WOP_PARAM_MESSAGE_2_CARRY_2_32);
/// let mut cks = ClientKey::new(param, 3, 1);
///
/// let msg = 1844640.;
/// // Encryption of one message:
/// let ct = cks.encrypt(msg);
/// let res = cks.decrypt(&ct);
///
/// //approximation less than 0.1%
/// assert_eq!(res, msg)
/// ```
pub fn decrypt(&self, ctxt: &Ciphertext) -> f64 {
let log_message_modulus = f64::log2(self.parameters().message_modulus().0 as f64) as usize;
let value_exponent = log_message_modulus as i64;
let mut mantissa = self.decrypt_mantissa(&ctxt.ct_vec_mantissa) as f64;
let mut exponent = self.decrypt_exponent(&ctxt.ct_vec_exponent) as i64;
let sign = self.decrypt_sign(&ctxt.ct_sign);
exponent += ctxt.e_min;
if exponent > 0 {
for _ in 0..exponent.abs() {
mantissa *= (1_u128 << value_exponent) as f64
}
} else {
for _ in 0..exponent.abs() {
mantissa /= (1_u128 << value_exponent) as f64
}
}
let res;
if sign == 1 {
res = -mantissa
} else {
res = mantissa
}
res
}
pub fn decrypt_mantissa(&self, ctxt: &Vec<shortint::Ciphertext>) -> u128 {
let mut result = 0_u128;
let mut shift = 1_u128;
for c_i in ctxt.iter() {
//decrypt the component i of the integer and multiply it by the radix product
let tmp = (self.key.decrypt_message_and_carry(c_i) as u128).wrapping_mul(shift);
// update the result
result = result.wrapping_add(tmp as u128);
// update the shift for the next iteration
shift = shift.wrapping_mul(self.parameters().message_modulus().0 as u128);
}
result
}
pub fn decrypt_exponent(&self, ctxt: &Vec<shortint::Ciphertext>) -> u64 {
let mut result = 0_u64;
let mut shift = 1_u64;
for c_i in ctxt.iter() {
//decrypt the component i of the integer and multiply it by the radix product
let tmp = self.key.decrypt_message_and_carry(c_i).wrapping_mul(shift);
// update the result
result = result.wrapping_add(tmp);
// update the shift for the next iteration
shift = shift.wrapping_mul(self.parameters().message_modulus().0 as u64);
}
result
}
pub fn decrypt_sign(&self, ctxt: &shortint::Ciphertext) -> u64 {
let result = self.key.decrypt_message_and_carry_without_padding(ctxt);
result
/ (self.key.parameters.message_modulus().0 * self.key.parameters.carry_modulus().0 / 2)
as u64
}
}

View File

@@ -0,0 +1,51 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub struct RadixDecomposition {
pub msg_space: usize,
pub block_number: usize,
}
/// Computes possible radix decompositions
///
/// Takes the number of bit of the message space as input and output a vector containing all the
/// correct
/// possible block decomposition assuming the same message space for all blocks.
/// Lower and upper bounds define the minimal and maximal space to be considered
/// Example: 6,2,4 -> [ [2,3], [3,2]] : [msg_space = 2 bits, block_number = 3]
///
/// # Example
///
/// ```rust
/// use concrete_float::client_key::radix_decomposition;
/// let input_space = 16; //
/// let min = 2;
/// let max = 4;
/// let decomp = radix_decomposition(input_space, min, max);
///
/// // Check that 3 possible radix decompositions are provided
/// assert_eq!(decomp.len(), 3);
/// ```
pub fn radix_decomposition(
input_space: usize,
min_space: usize,
max_space: usize,
) -> Vec<RadixDecomposition> {
let mut out: Vec<RadixDecomposition> = vec![];
let mut max = max_space;
if max_space > input_space {
max = input_space;
}
for msg_space in min_space..max + 1 {
let mut block_number = input_space / msg_space;
//Manual ceil of the division
if input_space % msg_space != 0 {
block_number += 1;
}
out.push(RadixDecomposition {
msg_space,
block_number,
})
}
out
}

View File

@@ -0,0 +1,41 @@
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use lazy_static::lazy_static;
use crate::{ClientKey, ServerKey};
#[derive(Default)]
pub struct FloatKeyCache;
lazy_static! {
pub static ref KEY_CACHE: FloatKeyCache = FloatKeyCache::default();
}
pub fn get_sks(str: &str) -> ServerKey {
let fiptr = format!("key/sks_key/{}", str);
let filepath = Path::new(&fiptr);
let file = BufReader::new(File::open(filepath).unwrap());
let saved_key: ServerKey = bincode::deserialize_from(file).unwrap();
saved_key
}
pub fn get_cks(str: &str) -> ClientKey {
let fiptr = format!("key/cks_key/{}", str);
let filepath = Path::new(&fiptr);
let file = BufReader::new(File::open(filepath).unwrap());
let saved_key: ClientKey = bincode::deserialize_from(file).unwrap();
saved_key
}
pub fn save_sks(key: ServerKey, str: &str) {
let filepath = format!("key/sks_key/{}", str);
let file = BufWriter::new(File::create(filepath).unwrap());
bincode::serialize_into(file, &key).unwrap();
}
pub fn save_cks(key: ClientKey ,str: &str) {
let filepath = format!("key/cks_key/{}", str);
let file = BufWriter::new(File::create(filepath).unwrap());
bincode::serialize_into(file, &key).unwrap();
}

92
concrete-float/src/lib.rs Executable file
View File

@@ -0,0 +1,92 @@
/*
#![allow(clippy::excessive_precision)]
//! Welcome the the `concrete-integer` documentation!
//!
//! # Description
//!
//! This library makes it possible to execute modular operations over encrypted integer.
//!
//! It allows to execute an integer circuit on an untrusted server because both circuit inputs
//! outputs are kept private.
//!
//! Data are encrypted on the client side, before being sent to the server.
//! On the server side every computation is performed on ciphertexts
//!
//! # Quick Example
//!
//! The following piece of code shows how to generate keys and run a integer circuit
//! homomorphically.
//!
//! ```rust
//! use concrete_float::gen_keys;
//! use concrete_shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
//!
//! //4 blocks for the radix decomposition
//! let number_of_blocks = 4;
//! // Modulus = (2^2)*4 = 2^8 (from the parameters chosen and the number of blocks
//! let modulus = 1 << 8;
//!
//! // Generation of the client/server keys, using the default parameters:
//! let (mut client_key, mut server_key) = gen_keys(&PARAM_MESSAGE_2_CARRY_2, number_of_blocks);
//!
//! let msg1 = 153;
//! let msg2 = 125;
//!
//! // Encryption of two messages using the client key:
//! let ct_1 = client_key.encrypt(msg1);
//! let ct_2 = client_key.encrypt(msg2);
//!
//! // Homomorphic evaluation of an integer circuit (here, an addition) using the server key:
//! let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);
//!
//! // Decryption of the ciphertext using the client key:
//! let output = client_key.decrypt(&ct_3);
//! assert_eq!(output, (msg1 + msg2) % modulus);
//! ```
//!
//! # Warning
//! This uses cryptographic parameters from the `concrete-shortint` crates.
//! Currently, the radix approach is only compatible with parameter sets such
//! that the message and carry buffers have the same size.
extern crate core;
*/
extern crate core;
pub mod ciphertext;
pub mod client_key;
pub mod parameters;
pub mod server_key;
use crate::client_key::ClientKey;
use crate::server_key::ServerKey;
//pub mod keycache;
//pub mod wopbs;
#[cfg(doctest)]
//mod test_user_docs;
use tfhe::shortint;
use tfhe::shortint;
/// Generate a couple of client and server keys with given parameters
///
/// * the client key is used to encrypt and decrypt and has to be kept secret;
/// * the server key is used to perform homomorphic operations on the server side and it is meant to
/// be published (the client sends it to the server).
///
/// ```rust
/// use concrete_float::gen_keys;
/// use concrete_shortint::parameters::DEFAULT_PARAMETERS;
///
/// let size_mantissa = 4;
/// let size_exponent = 1;
/// ```
pub fn gen_keys(
parameters_set: shortint::ClassicPBSParameters,
parameters_set_wopbs: shortint::WopbsParameters,
size_mantissa: usize,
size_exponent: usize,
) -> (ClientKey, ServerKey) {
let params = (parameters_set, parameters_set_wopbs);
let cks = ClientKey::new(params, size_mantissa, size_exponent);
let sks = ServerKey::new(&cks);
(cks, sks)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,158 @@
use crate::server_key::Ciphertext;
use crate::ServerKey;
use rayon::prelude::*;
use tfhe::shortint;
//use crate::keycache::{get_sks, get_cks};
impl ServerKey {
/// Computes homomorphically an addition between two ciphertexts encrypting integer values.
///
/// This function computes the operation without checking if it exceeds the capacity of the
/// ciphertext.
///
/// The result is returned as a new ciphertext.
///
/// # Example
///
/// ```rust
/// ```
pub fn unchecked_add_mantissa(
&self,
ct_left: &Ciphertext,
ct_right: &Ciphertext,
) -> Ciphertext {
let mut result = ct_left.clone();
self.unchecked_add_mantissa_assign(&mut result, ct_right);
result
}
/// Computes homomorphically an addition between two ciphertexts encrypting integer values.
///
/// This function computes the operation without checking if it exceeds the capacity of the
/// ciphertext.
///
/// The result is assigned to the `ct_left` ciphertext.
/// ```rust
/// ```
pub fn unchecked_add_mantissa_assign(&self, ct_left: &mut Ciphertext, ct_right: &Ciphertext) {
for (ct_left_i, ct_right_i) in ct_left
.ct_vec_mantissa
.iter_mut()
.zip(ct_right.ct_vec_mantissa.iter())
{
self.key.unchecked_add_assign(ct_left_i, ct_right_i);
}
}
/// we suppose that the mantissa are align
pub fn add_mantissa(&self, ct_left: &mut Ciphertext, ct_right: &mut Ciphertext) {
for (ct_left_i, ct_right_i) in ct_left
.ct_vec_mantissa
.iter_mut()
.zip(ct_right.ct_vec_mantissa.iter())
{
self.key.unchecked_add_assign(ct_left_i, ct_right_i);
}
}
/// Verifies if ct1 and ct2 can be added together.
///
/// # Example
///
///```rust
/// ```
pub fn is_add_possible(
&self,
ct_left: &[shortint::ciphertext::Ciphertext],
ct_right: &[shortint::ciphertext::Ciphertext],
) -> bool {
for (ct_left_i, ct_right_i) in ct_left.iter().zip(ct_right.iter()) {
if self.key.is_add_possible(ct_left_i, ct_right_i).is_err() {
return false;
}
}
true
}
pub fn add_total(&self, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
let res_sign = self.key.unchecked_add(&ct1.ct_sign, &ct2.ct_sign);
let (mut ct1_aligned, mut ct2_aligned) = self.align_mantissa(&ct1, &ct2);
let ct_sub = self.sub_mantissa(&ct1_aligned, &ct2_aligned);
self.add_mantissa(&mut ct1_aligned, &mut ct2_aligned);
// message space == 0 because the sign is on the padding bit
let ggsw = self.ggsw_ks_cbs(&res_sign, 0); // let ggsw = self.wopbs_key.extract_one_bit_cbs(&self.key, &res_sign, 63);
let mut res = self.cmuxes_full(&ct1_aligned, &ct_sub, &ggsw);
self.clean_degree(&mut res);
res
}
/// Computes homomorphically an addition between two ciphertexts encrypting integer values.
///
/// This function computes the operation without checking if it exceeds the capacity of the
/// ciphertext.
///
/// The result is returned as a new ciphertext.
///
/// # Example
///
/// ```rust
/// ```
pub fn unchecked_add_mantissa_parallelized(
&self,
ct_left: &Ciphertext,
ct_right: &Ciphertext,
) -> Ciphertext {
let mut result = ct_left.clone();
self.unchecked_add_mantissa_assign_parallelized(&mut result, ct_right);
result
}
/// Computes homomorphically an addition between two ciphertexts encrypting integer values.
///
/// This function computes the operation without checking if it exceeds the capacity of the
/// ciphertext.
///
/// The result is assigned to the `ct_left` ciphertext.
/// ```rust
/// ```
pub fn unchecked_add_mantissa_assign_parallelized(
&self,
ct_left: &mut Ciphertext,
ct_right: &Ciphertext,
) {
ct_left
.ct_vec_mantissa
.par_iter_mut()
.zip(ct_right.ct_vec_mantissa.par_iter())
.for_each(|(ct_left_i, ct_right_i)| {
self.key.unchecked_add_assign(ct_left_i, ct_right_i);
});
}
/// we suppose that the mantissa are align
pub fn add_mantissa_parallelized(&self, ct_left: &mut Ciphertext, ct_right: &mut Ciphertext) {
// The operation is too small to be worth parallelizing
ct_left
.ct_vec_mantissa
.iter_mut()
.zip(ct_right.ct_vec_mantissa.iter())
.for_each(|(ct_left_i, ct_right_i)| {
self.key.unchecked_add_assign(ct_left_i, ct_right_i);
});
}
pub fn add_total_parallelized(&self, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
let res_sign = self.key.unchecked_add(&ct1.ct_sign, &ct2.ct_sign);
let (mut ct1_aligned, mut ct2_aligned) = self.align_mantissa_parallelized(&ct1, &ct2);
let ct_sub = self.sub_mantissa_parallelized(&ct1_aligned, &ct2_aligned);
self.add_mantissa_parallelized(&mut ct1_aligned, &mut ct2_aligned);
// message space == 0 because the sign is on the padding bit
let ggsw = self.ggsw_ks_cbs_parallelized(&res_sign, 0); // let ggsw = self.wopbs_key.extract_one_bit_cbs(&self.key, &res_sign, 63);
let mut res = self.cmuxes_full_parallelized(&ct1_aligned, &ct_sub, &ggsw);
self.clean_degree_parallelized(&mut res);
res
}
}

View File

@@ -0,0 +1,162 @@
use crate::server_key::Ciphertext;
use crate::ServerKey;
use aligned_vec::ABox;
use rayon::prelude::*;
use tfhe::core_crypto::fft_impl::fft64::c64;
use tfhe::core_crypto::fft_impl::fft64::crypto::ggsw::FourierGgswCiphertext;
use tfhe::shortint;
impl ServerKey {
// align the two mantissas of to floating points
pub fn align_mantissa(
&self,
ct_left: &Ciphertext,
ct_right: &Ciphertext,
) -> (Ciphertext, Ciphertext) {
let (ct_res, sign) = self.sub(&ct_left.ct_vec_exponent, &ct_right.ct_vec_exponent);
let (vec_ggsw, sign_ggsw) =
self.create_vec_ggsw_after_sub(&ct_res, &sign, ct_left.ct_vec_mantissa.len());
let mut need_to_be_aligned = self.cmuxes(
&ct_left.ct_vec_mantissa,
&ct_right.ct_vec_mantissa,
&sign_ggsw,
);
let aligned_exp = self.cmuxes(
&ct_right.ct_vec_exponent,
&ct_left.ct_vec_exponent,
&sign_ggsw,
);
let aligned = self.cmux_tree_mantissa(&mut need_to_be_aligned, &vec_ggsw);
let ct_left_aligned = self.cmuxes(&aligned, &ct_left.ct_vec_mantissa, &sign_ggsw);
let ct_right_aligned = self.cmuxes(&ct_right.ct_vec_mantissa, &aligned, &sign_ggsw);
let new_left = Ciphertext {
ct_vec_mantissa: ct_left_aligned,
ct_vec_exponent: aligned_exp.clone(),
ct_sign: ct_left.ct_sign.clone(),
e_min: ct_left.e_min,
};
let new_right = Ciphertext {
ct_vec_mantissa: ct_right_aligned,
ct_vec_exponent: aligned_exp,
ct_sign: ct_right.ct_sign.clone(),
e_min: ct_right.e_min,
};
(new_left, new_right)
}
pub fn align_mantissa_parallelized(
&self,
ct_left: &Ciphertext,
ct_right: &Ciphertext,
) -> (Ciphertext, Ciphertext) {
let (mut ct_res, sign) =
self.abs_diff_parallelized(&ct_left.ct_vec_exponent, &ct_right.ct_vec_exponent);
let (vec_ggsw, sign_ggsw) = self.create_vec_ggsw_after_sub_parallelized(
&mut ct_res,
&sign,
ct_left.ct_vec_mantissa.len(),
);
let (mut need_to_be_aligned, aligned_exp) = rayon::join(
|| {
self.cmuxes_parallelized(
&ct_left.ct_vec_mantissa,
&ct_right.ct_vec_mantissa,
&sign_ggsw,
)
},
|| {
self.cmuxes_parallelized(
&ct_right.ct_vec_exponent,
&ct_left.ct_vec_exponent,
&sign_ggsw,
)
},
);
let aligned = self.cmux_tree_mantissa_parallelized(&mut need_to_be_aligned, &vec_ggsw);
let (ct_left_aligned, ct_right_aligned) = rayon::join(
|| self.cmuxes_parallelized(&aligned, &ct_left.ct_vec_mantissa, &sign_ggsw),
|| self.cmuxes_parallelized(&ct_right.ct_vec_mantissa, &aligned, &sign_ggsw),
);
let new_left = Ciphertext {
ct_vec_mantissa: ct_left_aligned,
ct_vec_exponent: aligned_exp.clone(),
ct_sign: ct_left.ct_sign.clone(),
e_min: ct_left.e_min,
};
let new_right = Ciphertext {
ct_vec_mantissa: ct_right_aligned,
ct_vec_exponent: aligned_exp,
ct_sign: ct_right.ct_sign.clone(),
e_min: ct_right.e_min,
};
(new_left, new_right)
}
pub fn create_vec_ggsw_after_sub(
&self,
ct_res: &Vec<shortint::ciphertext::Ciphertext>,
sign: &shortint::ciphertext::Ciphertext,
len_mantissa: usize,
) -> (
Vec<FourierGgswCiphertext<ABox<[c64]>>>,
FourierGgswCiphertext<ABox<[c64]>>,
) {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let msg_space = (msg_modulus * car_modulus) as usize;
let mut ct_res = ct_res.clone();
self.full_propagate_exponent(&mut ct_res);
let mut vec_ggsw = Vec::new();
for i in 0..ct_res.len() {
if len_mantissa < ((f64::log2(msg_modulus as f64) as usize) * i) {
let mut ggsw = vec![self.ggsw_pbs_ks_cbs(&ct_res[i], msg_space)];
ggsw.append(&mut vec_ggsw);
vec_ggsw = ggsw
} else {
let mut ggsw = self.extract_bit_cbs(&ct_res[i]);
ggsw.append(&mut vec_ggsw);
vec_ggsw = ggsw;
}
}
// message space == 0 because the sign is on the padding bit
let sign_ggsw = self.ggsw_ks_cbs(&sign, 0);
(vec_ggsw, sign_ggsw)
}
pub fn create_vec_ggsw_after_sub_parallelized(
&self,
ct_res: &mut [shortint::ciphertext::Ciphertext],
sign: &shortint::ciphertext::Ciphertext,
len_mantissa: usize,
) -> (
Vec<FourierGgswCiphertext<ABox<[c64]>>>,
FourierGgswCiphertext<ABox<[c64]>>,
) {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let msg_space = (msg_modulus * car_modulus) as usize;
self.full_propagate_exponent_parallelized(ct_res);
let vec_ggsw: Vec<_> = ct_res
.par_iter()
.enumerate()
.rev()
.map(|(i, block)| {
if (msg_modulus.ilog2() as usize * i) > len_mantissa {
vec![self.is_block_non_zero_ggsw_pbs_ks_cbs_parallelized(&block, msg_space)]
} else {
self.extract_bit_cbs_parallelized(&block)
}
})
.flatten()
.collect();
// message space == 0 because the sign is on the padding bit
let sign_ggsw = self.ggsw_ks_cbs_parallelized(&sign, 0);
(vec_ggsw, sign_ggsw)
}
}

View File

@@ -0,0 +1,53 @@
use crate::ciphertext::Ciphertext;
use crate::server_key::ServerKey;
use tfhe::integer::ciphertext::RadixCiphertext;
use tfhe::integer::IntegerCiphertext;
impl ServerKey {
pub fn division(&self, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let log_msg_modulus = f64::log2(msg_modulus as f64) as u64;
let len_vec_exp = ct1.ct_vec_exponent.len();
let len_vec_man = ct1.ct_vec_mantissa.len();
let mut res = self.create_trivial_zero(
ct1.ct_vec_mantissa.len(),
ct1.ct_vec_exponent.len(),
ct1.e_min,
);
let zero = self.create_trivial_zero(
ct1.ct_vec_mantissa.len(),
ct1.ct_vec_exponent.len(),
ct1.e_min,
);
res.ct_sign = self.key.unchecked_add(&ct1.ct_sign, &ct2.ct_sign);
res.ct_vec_exponent = ct1.ct_vec_exponent.clone();
let cst = ct1.e_min + len_vec_man as i64 - 1;
for i in 0..len_vec_exp {
let cst = (cst.abs() as u64) >> (log_msg_modulus * i as u64);
self.key.unchecked_scalar_add_assign(
&mut res.ct_vec_exponent[i],
(cst % msg_modulus) as u8,
);
}
let (res_exp, sign) = self.sub(&res.ct_vec_exponent, &ct2.ct_vec_exponent);
res.ct_vec_exponent = res_exp;
let mut cct1 = RadixCiphertext::from(ct1.ct_vec_mantissa.clone());
let mut cct2 = RadixCiphertext::from(ct2.ct_vec_mantissa.clone());
let int_key = tfhe::integer::ServerKey::from_shortint_ex(self.key.clone());
int_key.extend_radix_with_trivial_zero_blocks_lsb_assign(&mut cct1, len_vec_man - 1);
int_key.extend_radix_with_trivial_zero_blocks_msb_assign(&mut cct2, len_vec_man - 1);
let res_mantissa = int_key.unchecked_div_parallelized(&cct1, &cct2);
// message space == 0 because the sign is on the padding bit
let sign_ggsw = self.ggsw_ks_cbs(&sign, 0);
res.ct_vec_mantissa = res_mantissa.blocks()[..len_vec_man].to_vec();
res = self.cmuxes_full(&zero, &res, &sign_ggsw);
res
}
}

View File

@@ -0,0 +1,390 @@
//! Module with the definition of the ServerKey.
//!
//! This module implements the generation of the server public key, together with all the
//! available homomorphic integer operations.
mod add;
mod align_mantissa;
mod division;
mod mul;
mod relu;
mod sigmoid;
mod sub;
mod tools;
#[cfg(test)]
mod tests;
use tfhe::shortint;
use crate::ciphertext::Ciphertext;
use crate::client_key::ClientKey;
use serde::{Deserialize, Serialize};
use shortint::ciphertext::{Degree, MaxDegree};
/// Error returned when the carry buffer is full.
pub use shortint::CheckError;
/// A structure containing the server public key.
///
/// 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 integer circuits.
#[derive(Serialize, Deserialize, Clone)]
pub struct ServerKey {
pub key: shortint::server_key::ServerKey,
pub integer_key: tfhe::integer::server_key::ServerKey,
pub wopbs_key: shortint::wopbs::WopbsKey,
}
impl ServerKey {
/// Generates a server key.
///
/// # Example
///
/// ```rust
/// use concrete_float::parameters::{PARAM_MESSAGE_2_CARRY_2_32, WOP_PARAM_MESSAGE_2_CARRY_2_32};
/// use concrete_float::{ClientKey, ServerKey};
/// //mantissa and exponent defined over 4 blocks ///
/// let size_mantissa = 4;
/// let size_exponent = 2;
///
/// // Generate the client key:
/// let param = (PARAM_MESSAGE_2_CARRY_2_32, WOP_PARAM_MESSAGE_2_CARRY_2_32);
/// let cks = ClientKey::new(param, size_mantissa, size_exponent);
///
/// // Generate the server key:
/// let sks = ServerKey::new(&cks);
/// ```
pub fn new(cks: &ClientKey) -> ServerKey {
// It should remain just enough space to add a carry
let max =
(cks.key.parameters.message_modulus().0 - 1) * cks.key.parameters.carry_modulus().0 - 1;
let key =
shortint::server_key::ServerKey::new_with_max_degree(&cks.key, MaxDegree::new(max));
let integer_key = tfhe::integer::server_key::ServerKey::from_shortint_ex(key.clone());
let wopbs_key =
shortint::wopbs::WopbsKey::new_wopbs_key_only_for_wopbs(&cks.key, &key.clone());
ServerKey {
key,
integer_key,
wopbs_key,
}
}
/// Create a ciphertext filled with zeros
///
/// # Example
///
/// ```rust
/// use concrete_float::gen_keys;
/// use concrete_shortint::parameters::DEFAULT_PARAMETERS;
///
/// let size_mantissa = 4;
/// let size_exponent = 4;
/// let e_min = -2;
/// // Generate the client key and the server key:
/// let (cks, sks) = gen_keys(&DEFAULT_PARAMETERS, size, size);
///
/// let ctxt = sks.create_trivial_zero(size_mantissa, size_exponent, e_min, vec![]);
///
/// // Decrypt:
/// let dec = cks.decrypt(&ctxt);
/// assert_eq!(0, dec);
/// ```
pub fn create_trivial_zero(
&self,
size_mantissa: usize,
size_exponent: usize,
e_min: i64,
) -> Ciphertext {
let mut vec_res_mantissa = Vec::<shortint::Ciphertext>::with_capacity(size_mantissa);
let mut zero = self.key.create_trivial(0_u64);
zero.degree = Degree::new(0);
for _ in 0..size_mantissa {
vec_res_mantissa.push(zero.clone());
}
let mut vec_res_exponent = Vec::<shortint::Ciphertext>::with_capacity(size_exponent);
for _ in 0..size_exponent {
vec_res_exponent.push(zero.clone());
}
let sign = zero;
Ciphertext {
ct_vec_mantissa: vec_res_mantissa,
ct_vec_exponent: vec_res_exponent,
ct_sign: sign,
e_min,
}
}
pub fn create_trivial_zero_from_ct(&self, ctxt: &Ciphertext) -> Ciphertext {
self.create_trivial_zero(
ctxt.ct_vec_mantissa.len(),
ctxt.ct_vec_exponent.len(),
ctxt.e_min,
)
}
/// Propagate the carry of the 'index' block to the next one.
/// if index is equals to the MS LWE, this operation do nothing.
/// We want to keep all the information on this LWE ( with this operation we can't create a
/// new LWE
pub fn propagate_mantissa(&self, ctxt: &mut [shortint::Ciphertext], index: usize) {
if index < ctxt.len() - 1 {
let carry = self.key.carry_extract(&ctxt[index]);
ctxt[index] = self.key.message_extract(&ctxt[index]);
self.key.unchecked_add_assign(&mut ctxt[index + 1], &carry);
}
//TODO maybe just BS to decrease the noise ?
}
/// Propagate all the carries.
pub fn full_propagate_mantissa(&self, ctxt: &mut [shortint::Ciphertext]) {
let len = ctxt.len();
for i in 0..len {
self.propagate_mantissa(ctxt, i);
}
}
pub fn propagate_exponent(&self, ctxt: &mut Vec<shortint::Ciphertext>, index: usize) {
if index < ctxt.len() - 1 {
let carry = self.key.carry_extract(&ctxt[index]);
ctxt[index] = self.key.message_extract(&ctxt[index]);
self.key.unchecked_add_assign(&mut ctxt[index + 1], &carry);
} else {
ctxt[index] = self.key.message_extract(&ctxt[index]);
}
}
/// Propagate all the carries.
/// except the msb lwe
pub fn partial_propagate(&self, ctxt: &mut Vec<shortint::Ciphertext>) {
for i in 0..(ctxt.len() - 1) {
self.propagate_exponent(ctxt, i);
}
}
/// Propagate all the carries.
pub fn full_propagate_exponent(&self, ctxt: &mut Vec<shortint::Ciphertext>) {
for i in 0..(ctxt.len()) {
self.propagate_exponent(ctxt, i);
}
}
/// boolean bootstrapping
pub fn reduce_noise_sign(&self, ctxt: &mut Ciphertext) {
let msg_modulus = ctxt.ct_sign.message_modulus.0 as u64;
let car_modulus = ctxt.ct_sign.carry_modulus.0 as u64;
let msg_space = msg_modulus * car_modulus;
self.key
.unchecked_scalar_add_assign(&mut ctxt.ct_sign, (msg_space / 2) as u8);
let accumulator = self
.key
.generate_lookup_table(|x| (x & (msg_space / 2)).wrapping_neg());
//self.key.keyswitch_programmable_bootstrap_assign(&mut ctxt.ct_sign, &accumulator);
self.key
.apply_lookup_table_assign(&mut ctxt.ct_sign, &accumulator);
self.key
.unchecked_scalar_add_assign(&mut ctxt.ct_sign, (msg_space / 2) as u8);
// We can always add as the sign is managed on the padding bit, the only important thing is
// the noise
ctxt.ct_sign.degree = Degree::new(0);
}
fn propagate_mantissa_increase_exponent_if_necessary(
&self,
ctxt: &mut Ciphertext,
index: usize,
) {
if index < ctxt.ct_vec_mantissa.len() - 1 {
let carry = self.key.carry_extract(&ctxt.ct_vec_mantissa[index]);
ctxt.ct_vec_mantissa[index] = self.key.message_extract(&ctxt.ct_vec_mantissa[index]);
self.key
.unchecked_add_assign(&mut ctxt.ct_vec_mantissa[index + 1], &carry);
} else {
self.increase_exponent_if_necessary(ctxt);
}
}
fn increase_exponent_if_necessary(&self, ctxt: &mut Ciphertext) {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as usize;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as usize;
let msg_space = f64::log2((msg_modulus * car_modulus) as f64) as usize;
let len = ctxt.ct_vec_mantissa.len();
let carry = self
.key
.carry_extract(&ctxt.ct_vec_mantissa.last().unwrap());
ctxt.ct_vec_mantissa[len - 1] = self
.key
.message_extract(&ctxt.ct_vec_mantissa.last().clone().unwrap());
let mut tmp = ctxt.clone();
tmp.ct_vec_mantissa.push(carry.clone());
let _ = tmp.ct_vec_mantissa.remove(0);
self.key
.unchecked_scalar_add_assign(&mut tmp.ct_vec_exponent[0], 1);
let ggsw_carry = self.ggsw_pbs_ks_cbs(&carry, msg_space);
let res = self.cmuxes_full(ctxt, &tmp, &ggsw_carry);
ctxt.ct_vec_mantissa = res.ct_vec_mantissa;
ctxt.ct_vec_exponent = res.ct_vec_exponent;
}
pub fn full_propagate_mantissa_increase_exponent_if_necessary(&self, ctxt: &mut Ciphertext) {
let len = ctxt.ct_vec_mantissa.len();
for i in 0..len {
self.propagate_mantissa_increase_exponent_if_necessary(ctxt, i);
}
}
pub fn clean_degree(&self, ctxt: &mut Ciphertext) {
self.reduce_noise_sign(ctxt);
self.full_propagate_exponent(&mut ctxt.ct_vec_exponent);
self.full_propagate_mantissa_increase_exponent_if_necessary(ctxt)
}
/// Propagate the carry of the 'index' block to the next one.
/// if index is equals to the MS LWE, this operation do nothing.
/// We want to keep all the information on this LWE ( with this operation we can't create a
/// new LWE
pub fn propagate_mantissa_parallelized(&self, ctxt: &mut [shortint::Ciphertext], index: usize) {
// todo!("propagate_mantissa_parallelized");
if index < ctxt.len() - 1 {
let (carry, msg) = rayon::join(
|| self.key.carry_extract(&ctxt[index]),
|| self.key.message_extract(&ctxt[index]),
);
ctxt[index] = msg;
self.key.unchecked_add_assign(&mut ctxt[index + 1], &carry);
}
//TODO maybe just BS to decrease the noise ?
}
/// Propagate all the carries.
pub fn full_propagate_mantissa_parallelized(&self, ctxt: &mut [shortint::Ciphertext]) {
// todo!("full_propagate_mantissa_parallelized");
let len = ctxt.len();
for i in 0..len {
self.propagate_mantissa_parallelized(ctxt, i);
}
}
// TODO use the low latency propagation
pub fn propagate_exponent_parallelized(&self, ctxt: &mut [shortint::Ciphertext], index: usize) {
if index < ctxt.len() - 1 {
let (carry, msg) = rayon::join(
|| self.key.carry_extract(&ctxt[index]),
|| self.key.message_extract(&ctxt[index]),
);
ctxt[index] = msg;
self.key.unchecked_add_assign(&mut ctxt[index + 1], &carry);
} else {
self.key.message_extract_assign(&mut ctxt[index]);
}
}
/// Propagate all the carries.
/// except the msb lwe
pub fn partial_propagate_parallelized(&self, ctxt: &mut Vec<shortint::Ciphertext>) {
for i in 0..(ctxt.len() - 1) {
self.propagate_exponent_parallelized(ctxt, i);
}
}
/// Propagate all the carries.
pub fn full_propagate_exponent_parallelized(&self, ctxt: &mut [shortint::Ciphertext]) {
for i in 0..(ctxt.len()) {
self.propagate_exponent_parallelized(ctxt, i);
}
}
fn propagate_mantissa_increase_exponent_if_necessary_parallelized(
&self,
ctxt: &mut Ciphertext,
index: usize,
) {
if index < ctxt.ct_vec_mantissa.len() - 1 {
let (carry, msg) = rayon::join(
|| self.key.carry_extract(&ctxt.ct_vec_mantissa[index]),
|| self.key.message_extract(&ctxt.ct_vec_mantissa[index]),
);
ctxt.ct_vec_mantissa[index] = msg;
self.key
.unchecked_add_assign(&mut ctxt.ct_vec_mantissa[index + 1], &carry);
} else {
self.increase_exponent_if_necessary_parallelized(ctxt);
}
}
fn increase_exponent_if_necessary_parallelized(&self, ctxt: &mut Ciphertext) {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as usize;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as usize;
let msg_space = f64::log2((msg_modulus * car_modulus) as f64) as usize;
let len = ctxt.ct_vec_mantissa.len();
let (carry, msg) = rayon::join(
|| {
self.key
.carry_extract(&ctxt.ct_vec_mantissa.last().unwrap())
},
|| {
self.key
.message_extract(&ctxt.ct_vec_mantissa.last().clone().unwrap())
},
);
ctxt.ct_vec_mantissa[len - 1] = msg;
let mut tmp = ctxt.clone();
tmp.ct_vec_mantissa.push(carry.clone());
let _ = tmp.ct_vec_mantissa.remove(0);
self.key
.unchecked_scalar_add_assign(&mut tmp.ct_vec_exponent[0], 1);
let ggsw_carry = self.is_block_non_zero_ggsw_pbs_ks_cbs_parallelized(&carry, msg_space);
let res = self.cmuxes_full_parallelized(ctxt, &tmp, &ggsw_carry);
ctxt.ct_vec_mantissa = res.ct_vec_mantissa;
ctxt.ct_vec_exponent = res.ct_vec_exponent;
}
fn increase_exponent_if_necessary_parallelized_carry(
&self,
ctxt: &mut Ciphertext,
mantissa_carry: &shortint::Ciphertext,
) {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as usize;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as usize;
let msg_space = (msg_modulus * car_modulus).ilog2() as usize;
let mut tmp = ctxt.clone();
tmp.ct_vec_mantissa.push(mantissa_carry.clone());
let _ = tmp.ct_vec_mantissa.remove(0);
self.key
.unchecked_scalar_add_assign(&mut tmp.ct_vec_exponent[0], 1);
let ggsw_carry =
self.is_block_non_zero_ggsw_pbs_ks_cbs_parallelized(&mantissa_carry, msg_space);
let res = self.cmuxes_full_parallelized(ctxt, &tmp, &ggsw_carry);
ctxt.ct_vec_mantissa = res.ct_vec_mantissa;
ctxt.ct_vec_exponent = res.ct_vec_exponent;
}
pub fn full_propagate_mantissa_increase_exponent_if_necessary_parallelized(
&self,
ctxt: &mut Ciphertext,
) {
let len = ctxt.ct_vec_mantissa.len();
for i in 0..len {
self.propagate_mantissa_increase_exponent_if_necessary_parallelized(ctxt, i);
}
}
pub fn clean_degree_parallelized(&self, ctxt: &mut Ciphertext) {
// todo!("clean_degree_parallelized");
self.reduce_noise_sign(ctxt);
// let now = std::time::Instant::now();
self.full_propagate_exponent_parallelized(&mut ctxt.ct_vec_exponent);
// let elapsed = now.elapsed();
// println!("elapsed exponent propagate: {elapsed:?}");
// let now = std::time::Instant::now();
self.full_propagate_mantissa_increase_exponent_if_necessary_parallelized(ctxt);
// let elapsed = now.elapsed();
// println!("elapsed mantissa propagate: {elapsed:?}");
}
}

View File

@@ -0,0 +1,373 @@
use crate::server_key::Ciphertext;
use crate::ServerKey;
use std::cmp::{max, min};
use tfhe::shortint;
impl ServerKey {
pub fn mul(&self, ct1: &mut Ciphertext, ct2: &mut Ciphertext) -> Ciphertext {
// carry need to be empty
for ct in ct1.ct_vec_mantissa.iter_mut() {
if ct.degree.get() > self.wopbs_key.param.message_modulus.0 {
self.full_propagate_mantissa(&mut ct1.ct_vec_mantissa);
break;
}
}
for ct in ct2.ct_vec_mantissa.iter() {
if ct.degree.get() > self.wopbs_key.param.message_modulus.0 {
self.full_propagate_mantissa(&mut ct2.ct_vec_mantissa);
break;
}
}
let mut res = self.mul_mantissa(ct1, ct2);
res = self.add_exponent_for_mul(&mut res.clone(), ct2);
res.ct_sign = self.add_sign_for_mul(ct1, ct2);
res
}
pub fn mul_parallelized(
&self,
ct1: &mut Ciphertext,
ct2: &mut Ciphertext,
) -> (Ciphertext, shortint::Ciphertext) {
// let now = std::time::Instant::now();
let (mut res, mantissa_carry) = self.mul_mantissa_parallelized(ct1, ct2);
// let elapsed = now.elapsed();
// println!("mul_mantissa: {elapsed:?}");
res = self.add_exponent_for_mul_parallelized(&mut res.clone(), ct2, &mantissa_carry);
res.ct_sign = self.add_sign_for_mul_parallelized(ct1, ct2);
(res, mantissa_carry)
}
fn mul_mantissa(&self, ct1: &mut Ciphertext, ct2: &mut Ciphertext) -> Ciphertext {
let mantissa_len = ct1.ct_vec_mantissa.len();
let value = (mantissa_len - 1) / 2;
let mut result = self.create_trivial_zero(
2 * mantissa_len - value - 1,
ct1.ct_vec_exponent.len(),
ct1.e_min,
);
for (i, ct2_i) in ct2.ct_vec_mantissa.iter().enumerate() {
let bound = max((value - i) as i64, 0) as usize;
let tmp = self.block_mul(
&ct1.ct_vec_mantissa[bound..].to_vec(),
ct2_i,
i,
ct1.ct_vec_mantissa.len(),
);
if !self.is_add_possible(
&tmp,
&result.ct_vec_mantissa
[min(0, (value - i) as i64).abs() as usize..(i + mantissa_len - value)],
) {
// we propagate only the necessary blocks,
// to not loose any information, we propagate one blocks before and one blocks after
self.full_propagate_mantissa(
&mut result.ct_vec_mantissa[min(0, (value + 1 - i) as i64).abs() as usize
..min(i + mantissa_len + 2 - value, 2 * mantissa_len - 1 - value)],
);
//self.full_propagate_mantissa(&mut result.ct_vec_mantissa);
}
for (ct_left_j, ct_right_j) in result.ct_vec_mantissa[min(0, (value - i) as i64).abs()
as usize
..min(i + mantissa_len + 1 - value, 2 * mantissa_len - 1 - value)]
.iter_mut()
.zip(tmp.iter())
{
self.key.unchecked_add_assign(ct_left_j, ct_right_j);
}
}
// the (log_msg_modulus * mantissa.len()) most significant bit of a multiplication are
// include either in the [mantissa_len, 2*mantissa_len] or in [mantissa_len - 1,
// 2*mantissa_len - 1] we choose the first one if the block 2*mantissa_len is not
// empty otherwise we choose the first one
let mut result_trunc = self.create_trivial_zero_from_ct(ct1);
result_trunc.ct_vec_mantissa =
result.ct_vec_mantissa[(mantissa_len - 1 - value)..].to_vec();
result_trunc.ct_vec_exponent = ct1.ct_vec_exponent.clone();
result_trunc
}
// Return the float ciphertext and the mantissa carry
fn mul_mantissa_parallelized(
&self,
ct1: &Ciphertext,
ct2: &Ciphertext,
) -> (Ciphertext, shortint::Ciphertext) {
use tfhe::integer::{IntegerCiphertext, IntegerRadixCiphertext, RadixCiphertext};
let mantissa_len = ct1.ct_vec_mantissa.len();
let mantissa_len_for_mul_with_carry = mantissa_len * 2;
let mut ct1_mantissa = ct1.ct_vec_mantissa.to_vec();
ct1_mantissa.resize(mantissa_len_for_mul_with_carry, self.key.create_trivial(0));
let mut ct2_mantissa = ct2.ct_vec_mantissa.to_vec();
ct2_mantissa.resize(mantissa_len_for_mul_with_carry, self.key.create_trivial(0));
let ct1_mantissa_as_integer = RadixCiphertext::from_blocks(ct1_mantissa);
let ct2_mantissa_as_integer = RadixCiphertext::from_blocks(ct2_mantissa);
// println!("ct1_len = {}", ct1_mantissa_as_integer.blocks().len());
// println!("ct2_len = {}", ct2_mantissa_as_integer.blocks().len());
// let now = std::time::Instant::now();
let mul_result = self
.integer_key
.mul_parallelized(&ct1_mantissa_as_integer, &ct2_mantissa_as_integer);
// let elapsed = now.elapsed();
// println!("integer mul: {elapsed:?}");
let mut mul_result_blocks = mul_result.into_blocks();
let carry_block = mul_result_blocks.pop().unwrap();
let mantissa = mul_result_blocks[mantissa_len - 1..].to_vec();
assert_eq!(mantissa.len(), ct1.ct_vec_mantissa.len());
let mut result_trunc = self.create_trivial_zero_from_ct(ct1);
result_trunc.ct_vec_mantissa = mantissa;
result_trunc.ct_vec_exponent = ct1.ct_vec_exponent.clone();
(result_trunc, carry_block)
}
// multiply one block of a mantissa by each block of another mantissa and create a mantissa of
// this mul
fn block_mul(
&self,
ct1: &Vec<shortint::ciphertext::Ciphertext>,
ct2: &shortint::ciphertext::Ciphertext,
index: usize,
len_man: usize,
) -> Vec<shortint::ciphertext::Ciphertext> {
let zero = self.key.create_trivial(0);
let mut result = vec![zero.clone()];
let mut result_lsb = ct1.clone();
let mut result_msb = ct1.clone();
if index != len_man - 1 {
for (ct_lsb_i, ct_msb_i) in result_lsb.iter_mut().zip(result_msb.iter_mut()) {
self.key.unchecked_mul_msb_assign(ct_msb_i, ct2);
self.key.unchecked_mul_lsb_assign(ct_lsb_i, ct2);
}
result_lsb.push(zero.clone());
result.append(&mut result_msb.clone());
} else {
for (ct_lsb_i, ct_msb_i) in result_lsb[..len_man - 1]
.iter_mut()
.zip(result_msb[..len_man - 1].iter_mut())
{
self.key.unchecked_mul_msb_assign(ct_msb_i, ct2);
self.key.unchecked_mul_lsb_assign(ct_lsb_i, ct2);
}
let msg_mod = self.key.message_modulus.0 as u64;
let tmp = self.key.unchecked_scalar_mul(ct2, msg_mod as u8);
self.key
.unchecked_add_assign(result_lsb.last_mut().unwrap(), &tmp);
// Generate the accumulator for the multiplication
let acc = self
.key
.generate_lookup_table(|x| (x / msg_mod) * (x % msg_mod));
self.key
.apply_lookup_table_assign(result_lsb.last_mut().unwrap(), &acc);
result.append(&mut result_msb.clone());
result.pop();
}
for (ct1_i, ct2_i) in result.iter_mut().zip(result_lsb.iter()) {
self.key.unchecked_add_assign(ct1_i, ct2_i)
}
result
}
//sum the two sign for the mul
fn add_sign_for_mul(
&self,
ct1: &mut Ciphertext,
ct2: &mut Ciphertext,
) -> shortint::ciphertext::Ciphertext {
if self
.key
.is_add_possible(&ct1.ct_sign, &ct2.ct_sign)
.is_err()
{
self.reduce_noise_sign(ct1);
self.reduce_noise_sign(ct2);
}
self.key.unchecked_add(&ct1.ct_sign, &ct2.ct_sign)
}
fn add_sign_for_mul_parallelized(
&self,
ct1: &mut Ciphertext,
ct2: &mut Ciphertext,
) -> shortint::ciphertext::Ciphertext {
if self
.key
.is_add_possible(&ct1.ct_sign, &ct2.ct_sign)
.is_err()
{
rayon::join(
|| self.reduce_noise_sign(ct1),
|| self.reduce_noise_sign(ct2),
);
}
self.key.unchecked_add(&ct1.ct_sign, &ct2.ct_sign)
}
// add the two exponent and subtract the value e_min and the shift on the MSB blocks
fn add_exponent_for_mul(&self, ct1: &mut Ciphertext, ct2: &mut Ciphertext) -> Ciphertext {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let carry_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let log_msg_modulus = f64::log2(msg_modulus as f64) as u64;
let log_msg_space = f64::log2((carry_modulus * msg_modulus) as f64) as usize;
let len_vec_exp = ct1.ct_vec_exponent.len();
if !self.is_add_possible(&ct1.ct_vec_exponent, &ct2.ct_vec_exponent) {
self.partial_propagate(&mut ct1.ct_vec_exponent);
self.partial_propagate(&mut ct2.ct_vec_exponent);
}
let mut res = ct1.clone();
for (ct_left_j, ct_right_j) in res
.ct_vec_exponent
.iter_mut()
.zip(ct2.ct_vec_exponent.iter())
{
self.key.unchecked_add_assign(ct_left_j, ct_right_j);
}
let cst = ct1.e_min + ct1.ct_vec_mantissa.len() as i64 - 1;
let cst = (cst.abs() as u64) >> (log_msg_modulus * (len_vec_exp - 1) as u64);
//check if the exponent is big enough (return 1 if e is to small, 0 otherwise)
let accumulator = self.key.generate_lookup_table(|x| ((x < cst) as u64));
let mut ct_sign = self
.key
.apply_lookup_table(&mut res.ct_vec_exponent.last().unwrap(), &accumulator);
//check if the mantissa is not equals to zero (return 1 if ms_lwe== 0, 0 otherwise)
let accumulator = self.key.generate_lookup_table(|x| ((x == 0) as u64));
let ms_lwe = self
.key
.apply_lookup_table(&mut ct1.ct_vec_mantissa.last().unwrap(), &accumulator);
self.key.unchecked_add_assign(&mut ct_sign, &ms_lwe);
let accumulator = self.key.generate_lookup_table(|x| ((x > 0) as u64));
let ct_sign = self.key.apply_lookup_table(&mut ct_sign, &accumulator);
let sign_ggsw = self.ggsw_ks_cbs(&ct_sign, log_msg_space);
let zero = self.create_trivial_zero_from_ct(ct1);
let accumulator = self.key.generate_lookup_table(|x| (x - cst) % msg_modulus);
self.key
.apply_lookup_table_assign(&mut res.ct_vec_exponent[len_vec_exp - 1], &accumulator);
res = self.cmuxes_full(&res, &zero, &sign_ggsw);
res
}
// add the two exponent and subtract the value e_min and the shift on the MSB blocks
fn add_exponent_for_mul_parallelized(
&self,
ct1: &mut Ciphertext,
ct2: &mut Ciphertext,
mantissa_carry: &shortint::Ciphertext,
) -> Ciphertext {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let carry_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let log_msg_modulus = msg_modulus.ilog2() as u64;
let log_msg_space = (carry_modulus * msg_modulus).ilog2() as usize;
let len_vec_exp = ct1.ct_vec_exponent.len();
if !self.is_add_possible(&ct1.ct_vec_exponent, &ct2.ct_vec_exponent) {
rayon::join(
|| self.partial_propagate(&mut ct1.ct_vec_exponent),
|| self.partial_propagate(&mut ct2.ct_vec_exponent),
);
}
let mut res = ct1.clone();
for (ct_left_j, ct_right_j) in res
.ct_vec_exponent
.iter_mut()
.zip(ct2.ct_vec_exponent.iter())
{
self.key.unchecked_add_assign(ct_left_j, ct_right_j);
}
let cst = ct1.e_min + ct1.ct_vec_mantissa.len() as i64 - 1;
let cst = (cst.abs() as u64) >> (log_msg_modulus * (len_vec_exp - 1) as u64);
let (mut ct_sign, ms_lwe) = rayon::join(
|| {
//check if the exponent is big enough (return 1 if e is to small, 0 otherwise)
let accumulator = self.key.generate_lookup_table(|x| ((x < cst) as u64));
self.key
.apply_lookup_table(&mut res.ct_vec_exponent.last().unwrap(), &accumulator)
},
|| {
//check if the mantissa is not equals to zero (return 1 if ms_lwe== 0, 0 otherwise)
let accumulator = self.key.generate_lookup_table(|x| ((x == 0) as u64));
let mut last_mantissa_block = ct1.ct_vec_mantissa.last().unwrap().clone();
// We recreate a mantissa block containing the msg + carry as we only want to know
// if it was 0
self.key
.unchecked_add_assign(&mut last_mantissa_block, &mantissa_carry);
self.key
.apply_lookup_table(&last_mantissa_block, &accumulator)
},
);
self.key.unchecked_add_assign(&mut ct_sign, &ms_lwe);
rayon::join(
|| {
let accumulator = self.key.generate_lookup_table(|x| ((x > 0) as u64));
self.key
.apply_lookup_table_assign(&mut ct_sign, &accumulator);
},
|| {
let accumulator = self.key.generate_lookup_table(|x| (x - cst) % msg_modulus);
self.key.apply_lookup_table_assign(
&mut res.ct_vec_exponent[len_vec_exp - 1],
&accumulator,
);
},
);
let sign_ggsw = self.ggsw_ks_cbs_parallelized(&ct_sign, log_msg_space);
let zero = self.create_trivial_zero_from_ct(ct1);
res = self.cmuxes_full_parallelized(&res, &zero, &sign_ggsw);
res
}
pub fn mul_total(&self, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
let mut res = self.mul(&mut ct1.clone(), &mut ct2.clone());
self.clean_degree(&mut res);
res
}
pub fn mul_total_parallelized(&self, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
// let now = std::time::Instant::now();
let (mut res, mantissa_carry) = self.mul_parallelized(&mut ct1.clone(), &mut ct2.clone());
// let elapsed = now.elapsed();
// println!("mul_parallelized: {elapsed:?}");
// self.clean_degree_parallelized(&mut res);
self.reduce_noise_sign(&mut res);
// let now = std::time::Instant::now();
self.full_propagate_exponent_parallelized(&mut res.ct_vec_exponent);
// let elapsed = now.elapsed();
// println!("elapsed exponent propagate: {elapsed:?}");
// let now = std::time::Instant::now();
// No need to propagate the mantissa it is clean after the integer mul parallelized
// self.full_propagate_mantissa_increase_exponent_if_necessary_parallelized(&mut res);
// TODO change the management of the carry
self.increase_exponent_if_necessary_parallelized_carry(&mut res, &mantissa_carry);
// let elapsed = now.elapsed();
// println!("elapsed mantissa propagate: {elapsed:?}");
res
}
}

View File

@@ -0,0 +1,10 @@
use crate::server_key::Ciphertext;
use crate::ServerKey;
impl ServerKey {
pub fn relu(&self, ct: &Ciphertext) -> Ciphertext {
let zero = self.create_trivial_zero_from_ct(ct);
let ggsw = self.ggsw_ks_cbs(&ct.ct_sign, 0);
self.cmuxes_full(&ct, &zero, &ggsw)
}
}

View File

@@ -0,0 +1,43 @@
use crate::ciphertext::Ciphertext;
use crate::server_key::ServerKey;
impl ServerKey {
pub fn sigmoid(&self, ct: &Ciphertext) -> Ciphertext {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let carry_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let log_msg_modulus = f64::log2(msg_modulus as f64) as u64;
let log_carry_modulus = f64::log2(carry_modulus as f64) as u64;
let cst = ct.e_min + ct.ct_vec_mantissa.len() as i64 - 1;
let cst = (cst.abs() as u64) >> (log_msg_modulus * (ct.ct_vec_exponent.len() - 1) as u64);
let mut one = self.create_trivial_zero_from_ct(ct);
self.key
.unchecked_scalar_add_assign(&mut one.ct_vec_mantissa.last_mut().unwrap(), 1 as u8);
self.key
.unchecked_scalar_add_assign(&mut one.ct_vec_exponent.last_mut().unwrap(), cst as u8);
let mut minus_one = one.clone();
self.change_sign_assign(&mut minus_one);
let ggsw = self.ggsw_ks_cbs(&ct.ct_sign, 0);
let tmp = self.cmuxes_full(&one, &minus_one, &ggsw);
let value = msg_modulus / 2;
let accumulator = self.key.generate_lookup_table(|x| (x > value) as u64);
let ct_last = self
.key
.apply_lookup_table(&mut ct.ct_vec_mantissa.last().unwrap(), &accumulator);
//check if the exponent is big enough (return 1 if e is to small, 0 otherwise)
let accumulator = self.key.generate_lookup_table(|x| ((x < cst) as u64));
let mut ct_sign = self
.key
.apply_lookup_table(&mut ct.ct_vec_exponent.last().unwrap(), &accumulator);
self.key.unchecked_add_assign(&mut ct_sign, &ct_last);
let accumulator = self.key.generate_lookup_table(|x| ((x > 0) as u64));
let ct_sign = self.key.apply_lookup_table(&mut ct_sign, &accumulator);
let ggsw = self.ggsw_ks_cbs(&ct_sign, (log_carry_modulus + log_msg_modulus) as usize);
self.cmuxes_full(&tmp, &ct, &ggsw)
}
}

View File

@@ -0,0 +1,448 @@
use crate::ciphertext::Ciphertext;
use crate::ServerKey;
use rayon::prelude::*;
use shortint::ciphertext::Degree;
use std::cmp::max;
use tfhe::core_crypto::prelude::{Cleartext, Plaintext};
use tfhe::shortint;
impl ServerKey {
// This operation return |a - b| and sing(a-b)
// after sub all the blocks have the smallest degree except the most significant block
pub fn sub(
&self,
ctxt_left: &Vec<shortint::Ciphertext>,
ctxt_right: &Vec<shortint::Ciphertext>,
) -> (Vec<shortint::Ciphertext>, shortint::Ciphertext) {
let mut ct_tmp: Vec<shortint::Ciphertext> = Vec::new();
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let msg_space = (msg_modulus * car_modulus) as u64;
let size_ct = ctxt_left.len();
for ct in ctxt_left.iter() {
ct_tmp.push(
self.key
.unchecked_scalar_add(ct, ((msg_space / 2) - car_modulus / 2) as u8),
);
}
self.key
.unchecked_scalar_add_assign(&mut ct_tmp[0], (car_modulus / 2) as u8);
let cpy_right = ctxt_right.clone();
for (c_left, c_right) in ct_tmp.iter_mut().zip(cpy_right.iter()) {
tfhe::core_crypto::algorithms::lwe_ciphertext_sub_assign(&mut c_left.ct, &c_right.ct);
let noise_level = c_left.noise_level() + c_right.noise_level();
c_left.set_noise_level(noise_level);
}
self.partial_propagate(&mut ct_tmp);
//extract the sign (the first value add on the most significant block)
let accumulator = self.key.generate_lookup_table(|x| (x & (msg_space / 2)));
let mut sign = self
.key
.apply_lookup_table(ct_tmp.last_mut().unwrap(), &accumulator);
// the value sign encrypt only 1 or 0 so the degree is 1
// We can always add as the sign is managed on the padding bit, the only important thing is
// the noise
sign.degree = Degree::new(0);
// add the sign on each block
for i in 0..(size_ct - 1) {
self.key.unchecked_add_assign(&mut ct_tmp[i], &sign);
}
// if the sign on each block ==0, we take the opposite, otherwise we return the value.
// to find the opposite we perform the same idea than the subtraction (but only with pbs as
// we know one value ) opposite = (1 << (len * precision)) - x
for (i, ct) in ct_tmp.iter_mut().enumerate() {
if i == 0 {
let accumulator = self.key.generate_lookup_table(|x| {
(((x - (msg_space / 2)) - (msg_modulus - x))
* ((x & (msg_space / 2)) / (msg_space / 2)))
+ (msg_modulus - x)
});
self.key.apply_lookup_table_assign(ct, &accumulator);
ct.degree = Degree::new(msg_modulus as usize)
} else if i == size_ct - 1 {
let accumulator = self.key.generate_lookup_table(|x| {
(((x - (msg_space / 2)) - (msg_space / 2 - x - 1))
* ((x & (msg_space / 2)) / (msg_space / 2)))
+ (msg_space / 2 - x - 1)
});
self.key.apply_lookup_table_assign(ct, &accumulator);
ct.degree = Degree::new(max(
(msg_space as usize / 2) - ct.degree.get(),
ct.degree.get(),
));
} else {
let accumulator = self.key.generate_lookup_table(|x| {
(((x - (msg_space / 2)) - (msg_modulus - x - 1))
* ((x & (msg_space / 2)) / (msg_space / 2)))
+ (msg_modulus - x - 1)
});
self.key.apply_lookup_table_assign(ct, &accumulator);
ct.degree = Degree::new(msg_modulus as usize)
}
}
// move the sign bit on the msb
// uncheck add, we juste create the sign
tfhe::core_crypto::algorithms::lwe_ciphertext_cleartext_mul_assign(
&mut sign.ct,
Cleartext(2),
);
//self.key.unchecked_scalar_mul_assign(&mut sign, 2);
(ct_tmp, sign)
}
// subtract the two mantissas
// after the subtraction put the msb of the result on the mst significant block
// if exponent == 0 and the first block == 0, the result is 0
pub fn sub_mantissa(&self, ctxt_left: &Ciphertext, ctxt_right: &Ciphertext) -> Ciphertext {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let msg_space = (msg_modulus * car_modulus) as usize;
let (res, sign) = self.sub(&ctxt_left.ct_vec_mantissa, &ctxt_right.ct_vec_mantissa);
let mut new = self.create_trivial_zero_from_ct(ctxt_left);
new.ct_vec_mantissa = res;
new.ct_vec_exponent = ctxt_left.ct_vec_exponent.clone();
// if sign == 0 => need to change the sign of the operation
// if sign == 1 we want to keep the same sign
// new_s = old_s + sign + 1
new.ct_sign = self.key.unchecked_add(&sign, ctxt_left.sign());
self.key
.unchecked_scalar_add_assign(&mut new.ct_sign, msg_space as u8);
new = self.realign_sub(&new);
new
}
// move the msb on the most significant block.
// if e = 0 and the first block is empty, return zero
// (no subnormal value)
pub fn realign_sub(&self, ct0: &Ciphertext) -> Ciphertext {
let msg_modulus = self.wopbs_key.param.message_modulus.0 as usize;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as usize;
let msg_space = f64::log2((msg_modulus * car_modulus) as f64) as usize;
let size_mantissa = ct0.ct_vec_mantissa.len();
let zero = self.create_trivial_zero_from_ct(ct0);
let mut res = zero.clone();
res.ct_vec_mantissa = ct0.ct_vec_mantissa.clone();
res.ct_sign = ct0.ct_sign.clone();
let mut msb_mantissa_ggsw =
self.ggsw_pbs_ks_cbs(&res.ct_vec_mantissa[size_mantissa - 1], msg_space);
for i in 0..size_mantissa {
let mut tmp = zero.clone();
tmp.ct_vec_mantissa = zero.ct_vec_mantissa.clone();
for j in 0..(size_mantissa - 1) {
tmp.ct_vec_mantissa[j + 1] = res.ct_vec_mantissa[j].clone();
}
for (k, ct_exp_i) in tmp.ct_vec_exponent.iter_mut().enumerate() {
self.key.unchecked_scalar_add_assign(
ct_exp_i,
(((i + 1) >> (f64::log2(msg_modulus as f64) as usize * (k))) % msg_modulus)
as u8,
);
}
// return tmp if ggsw == 0; res otherwise
res.ct_vec_mantissa = self.cmuxes(
&tmp.ct_vec_mantissa,
&res.ct_vec_mantissa,
&msb_mantissa_ggsw,
);
res.ct_vec_exponent = self.cmuxes(
&tmp.ct_vec_exponent,
&res.ct_vec_exponent,
&msb_mantissa_ggsw,
);
if i < size_mantissa - 1 {
msb_mantissa_ggsw =
self.ggsw_pbs_ks_cbs(&res.ct_vec_mantissa[size_mantissa - 1], msg_space);
}
}
let (mut diff_exp, sub_exp_sign) = self.sub(&ct0.ct_vec_exponent, &res.ct_vec_exponent);
// message space == 0 because the sign is on the padding bit
let sign_ggsw = self.ggsw_ks_cbs(&sub_exp_sign, 0); //let sign_ggsw = self.wopbs_key.extract_one_bit_cbs(&self.key, &sub_exp_sign, 63);
diff_exp = self.cmuxes(&zero.ct_vec_exponent, &diff_exp, &msb_mantissa_ggsw);
res.ct_vec_exponent = self.cmuxes(&zero.ct_vec_exponent, &diff_exp, &sign_ggsw);
res.ct_vec_mantissa = self.cmuxes(&zero.ct_vec_mantissa, &res.ct_vec_mantissa, &sign_ggsw);
res.ct_sign = res.ct_sign;
res
}
// change the sign
pub fn change_sign_assign(&self, ct0: &mut Ciphertext) {
tfhe::core_crypto::algorithms::lwe_ciphertext_plaintext_add_assign(
&mut ct0.ct_sign.ct,
Plaintext(1 << 63),
);
}
pub fn change_sign(&self, ct0: &Ciphertext) -> Ciphertext {
let mut ct = ct0.clone();
self.change_sign_assign(&mut ct);
ct
}
pub fn sub_total(&self, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
let ct2 = self.change_sign(ct2);
self.add_total(&ct1, &ct2)
}
// This operation return |a - b| and sing(a-b)
// after sub all the blocks have the smallest degree except the most significant block
// TODO: would the overflowing_sub from integer (with some slight adaptations perhaps) do the
// trick ?
pub fn abs_diff_parallelized(
&self,
ctxt_left: &Vec<shortint::Ciphertext>,
ctxt_right: &Vec<shortint::Ciphertext>,
) -> (Vec<shortint::Ciphertext>, shortint::Ciphertext) {
let mut ct_tmp: Vec<shortint::Ciphertext> = Vec::with_capacity(ctxt_left.len());
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let msg_space = (msg_modulus * car_modulus) as u64;
let size_ct = ctxt_left.len();
for ct in ctxt_left.iter() {
ct_tmp.push(
self.key
.unchecked_scalar_add(ct, ((msg_space / 2) - car_modulus / 2) as u8),
);
}
self.key
.unchecked_scalar_add_assign(&mut ct_tmp[0], (car_modulus / 2) as u8);
let cpy_right = ctxt_right.clone();
// The operation is too small to be worth parallelizing
ct_tmp
.iter_mut()
.zip(cpy_right.iter())
.for_each(|(c_left, c_right)| {
tfhe::core_crypto::algorithms::lwe_ciphertext_sub_assign(
&mut c_left.ct,
&c_right.ct,
);
let noise_level = c_left.noise_level() + c_right.noise_level();
c_left.set_noise_level(noise_level);
});
self.partial_propagate_parallelized(&mut ct_tmp);
//extract the sign (the first value add on the most significant block)
let accumulator = self.key.generate_lookup_table(|x| (x & (msg_space / 2)));
let mut sign = self
.key
.apply_lookup_table(ct_tmp.last_mut().unwrap(), &accumulator);
// the value sign encrypt only 1 or 0 so the degree is 1
// We can always add as the sign is managed on the padding bit, the only important thing is
// the noise
sign.degree = Degree::new(0);
// add the sign on each block, except the last one
// Operation is too small to be worth parallelizing
ct_tmp[0..(size_ct - 1)]
.iter_mut()
.for_each(|tmp_block| self.key.unchecked_add_assign(tmp_block, &sign));
// if the sign on each block ==0, we take the opposite, otherwise we return the value.
// to find the opposite we perform the same idea than the subtraction (but only with pbs as
// we know one value ) opposite = (1 << (len * precision)) - x
ct_tmp.par_iter_mut().enumerate().for_each(|(i, ct)| {
if i == 0 {
let accumulator = self.key.generate_lookup_table(|x| {
(((x - (msg_space / 2)) - (msg_modulus - x))
* ((x & (msg_space / 2)) / (msg_space / 2)))
+ (msg_modulus - x)
});
self.key.apply_lookup_table_assign(ct, &accumulator);
} else if i == size_ct - 1 {
let accumulator = self.key.generate_lookup_table(|x| {
(((x - (msg_space / 2)) - (msg_space / 2 - x - 1))
* ((x & (msg_space / 2)) / (msg_space / 2)))
+ (msg_space / 2 - x - 1)
});
self.key.apply_lookup_table_assign(ct, &accumulator);
} else {
let accumulator = self.key.generate_lookup_table(|x| {
(((x - (msg_space / 2)) - (msg_modulus - x - 1))
* ((x & (msg_space / 2)) / (msg_space / 2)))
+ (msg_modulus - x - 1)
});
self.key.apply_lookup_table_assign(ct, &accumulator);
}
});
// move the sign bit on the msb
// uncheck add, we juste create the sign
tfhe::core_crypto::algorithms::lwe_ciphertext_cleartext_mul_assign(
&mut sign.ct,
Cleartext(2),
);
//self.key.unchecked_scalar_mul_assign(&mut sign, 2);
(ct_tmp, sign)
}
// subtract the two mantissas
// after the subtraction put the msb of the result on the mst significant block
// if exponent == 0 and the first block == 0, the result is 0
pub fn sub_mantissa_parallelized(
&self,
ctxt_left: &Ciphertext,
ctxt_right: &Ciphertext,
) -> Ciphertext {
// todo!("sub_mantissa_parallelized");
let msg_modulus = self.wopbs_key.param.message_modulus.0 as u64;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as u64;
let msg_space = (msg_modulus * car_modulus) as usize;
// let now = std::time::Instant::now();
let (res, sign) =
self.abs_diff_parallelized(&ctxt_left.ct_vec_mantissa, &ctxt_right.ct_vec_mantissa);
// let elapsed = now.elapsed();
// println!("sub_mantissa_parallelized::sub_parallelized: {elapsed:?}");
let mut new = self.create_trivial_zero_from_ct(ctxt_left);
new.ct_vec_mantissa = res;
new.ct_vec_exponent = ctxt_left.ct_vec_exponent.clone();
// if sign == 0 => need to change the sign of the operation
// if sign == 1 we want to keep the same sign
// new_s = old_s + sign + 1
new.ct_sign = self.key.unchecked_add(&sign, ctxt_left.sign());
self.key
.unchecked_scalar_add_assign(&mut new.ct_sign, msg_space as u8);
// let now = std::time::Instant::now();
new = self.realign_sub_parallelized(&new);
// let elapsed = now.elapsed();
// println!("sub_mantissa_parallelized::realign_sub_parallelized: {elapsed:?}");
new
}
// move the msb on the most significant block.
// if e = 0 and the first block is empty, return zero
// (no subnormal value)
pub fn realign_sub_parallelized(&self, ct0: &Ciphertext) -> Ciphertext {
// todo!("realign_sub_parallelized");
let msg_modulus = self.wopbs_key.param.message_modulus.0 as usize;
let car_modulus = self.wopbs_key.param.carry_modulus.0 as usize;
let msg_space = (msg_modulus * car_modulus).ilog2() as usize;
let size_mantissa = ct0.ct_vec_mantissa.len();
let zero = self.create_trivial_zero_from_ct(ct0);
let cmux_tree_size = if size_mantissa.is_power_of_two() {
size_mantissa
} else {
size_mantissa.next_power_of_two()
};
let mut ciphertexts_to_cmux: Vec<Ciphertext> = Vec::with_capacity(cmux_tree_size);
let mut cmux_outputs: Vec<Ciphertext> = Vec::with_capacity(cmux_tree_size / 2);
(0..cmux_tree_size)
.into_par_iter()
.map(|ciphertext_idx| {
if ciphertext_idx < size_mantissa {
let mut ciphertext = zero.clone();
for (k, ct_exp_i) in ciphertext.ct_vec_exponent.iter_mut().enumerate() {
self.key.unchecked_scalar_add_assign(
ct_exp_i,
((ciphertext_idx >> (msg_modulus.ilog2() as usize * (k))) % msg_modulus)
as u8,
);
}
let exponent_block_count = size_mantissa - ciphertext_idx;
ciphertext.ct_vec_mantissa[ciphertext_idx..]
.clone_from_slice(&ct0.ct_vec_mantissa[..exponent_block_count]);
ciphertext
} else {
zero.clone()
}
})
.collect_into_vec(&mut ciphertexts_to_cmux);
while ciphertexts_to_cmux.len() > 1 {
ciphertexts_to_cmux
.par_chunks_exact(2)
.map(|chunk| {
let less_modified_exponent = &chunk[0];
let more_modified_exponent = &chunk[1];
let msb_mantissa_ggsw = self.is_block_non_zero_ggsw_pbs_ks_cbs_parallelized(
&less_modified_exponent.ct_vec_mantissa[size_mantissa - 1],
msg_space,
);
// return tmp if ggsw == 0; res otherwise
let (mantissa, exponent) = rayon::join(
|| {
self.cmuxes_parallelized(
&more_modified_exponent.ct_vec_mantissa,
&less_modified_exponent.ct_vec_mantissa,
&msb_mantissa_ggsw,
)
},
|| {
self.cmuxes_parallelized(
&more_modified_exponent.ct_vec_exponent,
&less_modified_exponent.ct_vec_exponent,
&msb_mantissa_ggsw,
)
},
);
let mut res = zero.clone();
res.ct_vec_exponent = exponent;
res.ct_vec_mantissa = mantissa;
res
})
.collect_into_vec(&mut cmux_outputs);
std::mem::swap(&mut ciphertexts_to_cmux, &mut cmux_outputs);
}
let mut res = ciphertexts_to_cmux.into_iter().next().unwrap();
let (mut diff_exp, sub_exp_sign) =
self.abs_diff_parallelized(&ct0.ct_vec_exponent, &res.ct_vec_exponent);
// message space == 0 because the sign is on the padding bit
let (sign_ggsw, msb_mantissa_ggsw) = rayon::join(
|| self.ggsw_ks_cbs_parallelized(&sub_exp_sign, 0),
|| {
self.is_block_non_zero_ggsw_pbs_ks_cbs_parallelized(
&res.ct_vec_mantissa[size_mantissa - 1],
msg_space,
)
},
);
let (exponent, mantissa) = rayon::join(
|| {
diff_exp =
self.cmuxes_parallelized(&zero.ct_vec_exponent, &diff_exp, &msb_mantissa_ggsw);
self.cmuxes_parallelized(&zero.ct_vec_exponent, &diff_exp, &sign_ggsw)
},
|| self.cmuxes_parallelized(&zero.ct_vec_mantissa, &res.ct_vec_mantissa, &sign_ggsw),
);
res.ct_vec_exponent = exponent;
res.ct_vec_mantissa = mantissa;
res.ct_sign = ct0.ct_sign.clone();
res
}
pub fn sub_total_parallelized(&self, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
let ct2 = self.change_sign(ct2);
self.add_total_parallelized(&ct1, &ct2)
}
}

View File

@@ -0,0 +1,835 @@
#![allow(dead_code)]
use std::cmp::{max, min};
use rand::Rng;
use tfhe::shortint;
#[allow(unused_imports)]
use crate::parameters::{PARAM_SAM_32, WOP_PARAM_SAM_32, PARAM_MESSAGE_2_CARRY_2_32,
PARAM_MESSAGE_2_CARRY_2_64, WOP_PARAM_MESSAGE_2_CARRY_2_32,
WOP_PARAM_MESSAGE_2_CARRY_2_64, FINAL_WOP_PARAM_2_2_32, FINAL_PARAM_2_2_32,
FINAL_WOP_PARAM_8, FINAL_PARAM_8, FINAL_PARAM_15,
FINAL_WOP_PARAM_15, FINAL_PARAM_16, FINAL_WOP_PARAM_16, FINAL_PARAM_32,
FINAL_WOP_PARAM_32, FINAL_PARAM_64, FINAL_WOP_PARAM_64,
FINAL_PARAM_64_BIS, FINAL_WOP_PARAM_64_BIS,
FINAL_PARAM_32_BIS, FINAL_WOP_PARAM_32_BIS, FINAL_PARAM_16_BIS,
FINAL_WOP_PARAM_16_BIS, FINAL_PARAM_15_BIS, FINAL_WOP_PARAM_15_BIS,
FINAL_PARAM_8_BIS, FINAL_WOP_PARAM_8_BIS, FINAL_PARAM_32_TCHESS, FINAL_WOP_PARAM_32_TCHESS
};
use crate::server_key::*;
use crate::{gen_keys, ClientKey};
const NB_OPE: i32 = 50;
const LEN_MAN: usize = 13; //13;
const LEN_EXP: usize = 4; //4;
const LEN_MAN8: usize = 2;
const LEN_EXP8: usize = 2;
const LEN_MAN16: usize = 6;
const LEN_EXP16: usize = 3;
const LEN_MAN32: usize = 13;
const LEN_EXP32: usize = 4;
const LEN_MAN64: usize = 27;
const LEN_EXP64: usize = 5;
macro_rules! named_param {
($param:ident) => {
(stringify!($param), $param)
};
}
struct Parameters {
pbsparameters: shortint::ClassicPBSParameters,
wopbsparameters: shortint::WopbsParameters,
len_man: usize,
len_exp: usize,
}
const PARAM_FP_64_BITS: Parameters = Parameters {
pbsparameters: FINAL_PARAM_64_BIS,
wopbsparameters: FINAL_WOP_PARAM_64_BIS,
len_man: LEN_MAN64,
len_exp: LEN_EXP64,
};
const PARAM_FP_32_BITS: Parameters = Parameters {
pbsparameters: FINAL_PARAM_32_BIS,
wopbsparameters: FINAL_WOP_PARAM_32_BIS,
len_man: LEN_MAN32,
len_exp: LEN_EXP32,
};
const PARAM_FP_16_BITS: Parameters = Parameters {
pbsparameters: FINAL_PARAM_16_BIS,
wopbsparameters: FINAL_WOP_PARAM_16_BIS,
len_man: LEN_MAN16,
len_exp: LEN_EXP16,
};
const PARAM_FP_8_BITS: Parameters = Parameters {
pbsparameters: FINAL_PARAM_8_BIS,
wopbsparameters: FINAL_WOP_PARAM_8_BIS,
len_man: LEN_MAN8,
len_exp: LEN_EXP8,
};
const PARAMS: [(&str, Parameters); 1] =
[
//named_param!(PARAM_FP_64_BITS),
named_param!(PARAM_FP_32_BITS),
//named_param!(PARAM_FP_16_BITS),
//named_param!(PARAM_FP_8_BITS),
];
#[test]
fn test_float_encrypt() {
for (_, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
print_info(&cks);
println!("parameters :: {:?}", cks.key.parameters);
let msg = 1.;
// Encryption of one message:
let mut ct = cks.encrypt(msg);
print_res(&cks, &ct, "decrypt", msg as f32, msg);
sks.clean_degree(&mut ct);
print_res(&cks, &ct, "decrypt", msg as f32, msg);
let res = cks.decrypt(&ct);
assert_eq!(res, msg);
}
}
#[test]
pub fn test_float_mul() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg1 = rng.gen::<f32>() as f64;
let msg2 = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg1);
let ct2 = cks.encrypt(msg2);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
print_res(&cks, &ct1, "ct 1", msg1 as f32, msg1);
print_res(&cks, &ct2, "ct 2", msg2 as f32, msg2);
let res = sks.mul_total_parallelized(&mut ct1.clone(), &mut ct2.clone());
print_res(&cks, &res, "Multiplication", (msg2 * msg1) as f32, msg2 * msg1);
let res = cks.decrypt(&res);
assert!(res.abs() < ((msg1 * msg2) * 1.001).abs());
assert!(res.abs() > ((msg1 * msg2) * 0.999).abs());
}
}
#[test]
pub fn test_float_div() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
let msg2 = rng.gen::<f32>() as f64;
let msg1 = -rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg1);
let ct2 = cks.encrypt(msg2);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
print_res(&cks, &ct1, "ct1", (msg1) as f32, msg1);
print_res(&cks, &ct2, "ct2", (msg2) as f32, msg2);
let mut res = sks.division(&ct1, &ct2);
print_res(&cks, &res, "Division", (msg1 / msg2) as f32, msg1 / msg2);
sks.clean_degree(&mut res);
let res = cks.decrypt(&res);
assert!(res.abs() < ((msg1 / msg2) * 1.001).abs());
assert!(res.abs() > ((msg1 / msg2) * 0.999).abs());
}
}
#[test]
pub fn float_cos() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
let msg1 = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg1);
let one = cks.encrypt(1.); //should be in trivial encrypt
let one_div_by_2 = cks.encrypt(1. / 2.); //should be in trivial encrypt
let one_div_by_24 = cks.encrypt(1. / 24.); //should be in trivial encrypt
print_res(&cks, &one, "one", 1 as f32, 1.);
print_res(&cks, &one_div_by_2, "oneDivBy2", (1. / 2.) as f32, 1. / 2.);
print_res(&cks, &one_div_by_24, "oneDivBy24", (1. / 24.) as f32, 1. / 24.);
print_res(&cks, &ct1, "ct1", msg1 as f32, msg1);
let ct1_square = sks.mul_total_parallelized(&ct1, &ct1);
print_res(&cks, &ct1_square, "ct1_square", (msg1 * msg1) as f32, msg1 * msg1);
let ct1_square_square = sks.mul_total_parallelized(&ct1_square, &ct1_square);
print_res(&cks, &ct1_square_square, "ct1_square_square", (msg1 * msg1 * msg1 * msg1) as f32, msg1 * msg1 * msg1 * msg1);
let ct1_square_time_one_div_by_2 = sks.mul_total_parallelized(&ct1_square, &one_div_by_2);
print_res(&cks, &ct1_square_time_one_div_by_2, "ct1_square_time_1DivBy2", (msg1 * msg1 / 2.) as f32, msg1 * msg1 / 2.);
let ct1_square_square_time_one_div_by_24 = sks.mul_total_parallelized(&ct1_square_square, &one_div_by_24);
print_res(&cks, &ct1_square_square_time_one_div_by_24, "ct1_square_square_time_1DivBy24", (msg1 * msg1 * msg1 * msg1 / 24.) as f32, msg1 * msg1 * msg1 * msg1 / 24.);
let res = sks.add_total_parallelized(&one, &ct1_square_square_time_one_div_by_24);
print_res(&cks, &res, "first res", (1. + msg1 * msg1 * msg1 * msg1 / 24.) as f32, 1. + msg1 * msg1 * msg1 * msg1 / 24.);
let res = sks.sub_total_parallelized(&res, &ct1_square_time_one_div_by_2);
println!("Cosine, exact result : {:?}", msg1.cos());
let approximation = 1. + msg1 * msg1 * msg1 * msg1 / 24. - msg1 * msg1 / 2.;
print_res(&cks, &res, "Cosine approximation", approximation as f32, approximation);
let res = cks.decrypt(&res);
assert!(res < (approximation * 1.001).abs());
assert!(res > (approximation * 0.999).abs());
}
}
#[test]
pub fn float_sin() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
let msg1 = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg1);
print_res(&cks, &ct1, "ct1", msg1 as f32, msg1);
let one_div_by_6 = cks.encrypt(1. / 6.); //should be in trivial encrypt
let one_div_by_120 = cks.encrypt(1. / 120.); //should be in trivial encrypt
let ct1_square = sks.mul_total_parallelized(&ct1, &ct1);
print_res(&cks, &ct1_square, "ct1_square", (msg1 * msg1) as f32, msg1 * msg1);
let ct1_cube = sks.mul_total_parallelized(&ct1_square, &ct1);
print_res(&cks, &ct1_cube, "ct1_cube", (msg1 * msg1 * msg1) as f32, msg1 * msg1 * msg1);
let ct1_power_five = sks.mul_total_parallelized(&ct1_square, &ct1_cube);
print_res(&cks, &ct1_power_five, "ct1_power_five", (msg1 * msg1 * msg1 * msg1 * msg1) as f32, msg1 * msg1 * msg1 * msg1 * msg1);
let ct1_cube_time_one_div_by_6 = sks.mul_total_parallelized(&ct1_cube, &one_div_by_6);
print_res(&cks, &ct1_cube_time_one_div_by_6, "ct1_cube_time_one_div_by_6", (msg1 * msg1 * msg1 / 6.) as f32, msg1 * msg1 * msg1 / 6.);
let ct1_power_five_time_one_div_by_120 = sks.mul_total_parallelized(&ct1_power_five, &one_div_by_120);
print_res(&cks, &ct1_power_five_time_one_div_by_120, "ct1_power_five_time_one_div_by_120", (msg1 * msg1 * msg1 * msg1 * msg1 / 120.) as f32, msg1 * msg1 * msg1 * msg1 * msg1 / 120.);
let res = sks.add_total_parallelized(&ct1, &ct1_power_five_time_one_div_by_120);
print_res(&cks, &ct1_power_five_time_one_div_by_120, "res_1", (msg1 * msg1 * msg1 * msg1 * msg1 / 120.) as f32, msg1 * msg1 * msg1 * msg1 * msg1 / 120.);
let res = sks.sub_total_parallelized(&res, &ct1_cube_time_one_div_by_6);
println!("Sine, exact result : {:?}", msg1.sin());
let approximation = msg1 + msg1 * msg1 * msg1 * msg1 * msg1 / 120. - msg1 * msg1 * msg1 / 6.;
print_res(&cks, &res, "Sine approximation", approximation as f32, approximation);
let res = cks.decrypt(&res);
assert!(res < (approximation * 1.001).abs());
assert!(res > (approximation * 0.999).abs());
}
}
#[test]
pub fn test_float_add() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
let msg2 = rng.gen::<f32>() as f64;
let msg1 = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg1);
let ct2 = cks.encrypt(msg2);
print_res(&cks, &ct1, "ct 1", msg1 as f32, msg1);
print_res(&cks, &ct2, "ct 2", msg2 as f32, msg2);
let res = sks.add_total_parallelized(&ct1, &ct2);
print_res(&cks, &res, "Addition", (msg1 + msg2) as f32, msg1 + msg2);
let res = cks.decrypt(&res);
assert!(res.abs() < ((msg1 + msg2) * 1.001).abs());
assert!(res.abs() > ((msg1 + msg2) * 0.999).abs());
}
}
#[test]
pub fn test_float_sub() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
let msg1 = rng.gen::<f32>() as f64;
let msg2 = rng.gen::<f32>() as f64;
let ct1 = cks.encrypt(msg1);
let ct2 = cks.encrypt(msg2);
print_res(&cks, &ct1, "ct 1", msg1 as f32, msg1);
print_res(&cks, &ct2, "ct 2", msg2 as f32, msg2);
let res = sks.sub_total_parallelized(&ct1, &ct2);
print_res(&cks, &res, "Subtraction", (msg1 - msg2) as f32, msg1 - msg2);
let res = cks.decrypt(&res);
assert!(res.abs() < ((msg1 - msg2) * 1.001).abs());
assert!(res.abs() > ((msg1 - msg2) * 0.999).abs());
}
}
#[test]
pub fn depth_test_parallelized() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
let max_i = 1_000.;
let mut vec_float_32 = vec![];
let mut vec_float_64 = vec![];
let mut vec_hom_float = vec![];
let mut vec_deep = vec![];
let mut vec_nb_operation = vec![];
let len_vec = 3 as u16;
for i in 0..len_vec {
let msg = rng.gen::<f32>() as f64;
println!("msg_{:?}: {:?}", i, msg);
let ct = cks.encrypt(msg);
print_res(&cks, &ct, "encrypt/decrypt", msg as f32, msg);
vec_float_64.push(msg);
vec_float_32.push(msg as f32);
vec_hom_float.push(ct);
vec_deep.push(0);
vec_nb_operation.push(0);
}
for i in 0..NB_OPE {
println!("\n----Round {:?}----", i);
let r_ope = rng.gen::<u16>() % 3;
let r_value_1 = (rng.gen::<u16>() % len_vec) as usize;
let mut r_value_2 = (rng.gen::<u16>() % len_vec) as usize;
let mut r_place = (rng.gen::<u16>() % 2) as usize;
while r_value_1 == r_value_2 {
r_value_2 = (rng.gen::<u16>() % len_vec) as usize;
}
if r_place == 0 {
r_place = r_value_1
} else {
r_place = r_value_2
}
vec_deep[r_place] = min(vec_deep[r_value_1], vec_deep[r_value_2]) + 1;
vec_nb_operation[r_place] =
max(vec_nb_operation[r_value_1], vec_nb_operation[r_value_2]) + 1;
if r_ope == 0 {
println!(
"block {:?} * block {:?} -> block{:?}\n",
r_value_1, r_value_2, r_place
);
println!(
"expected: {:?} * {:?} = {:?}",
vec_float_64[r_value_1],
vec_float_64[r_value_2],
vec_float_64[r_value_1] * vec_float_64[r_value_2]
);
vec_hom_float[r_place] = sks.mul_total_parallelized(
&mut vec_hom_float[r_value_1].clone(),
&mut vec_hom_float[r_value_2].clone(),
);
vec_float_32[r_place] = vec_float_32[r_value_1] * vec_float_32[r_value_2];
vec_float_64[r_place] = vec_float_64[r_value_1] * vec_float_64[r_value_2];
print_res(
&cks,
&vec_hom_float[r_place],
"res mul",
vec_float_32[r_place],
vec_float_64[r_place],
);
} else if r_ope == 1 {
println!(
"block {:?} + block {:?} -> block{:?}\n",
r_value_1, r_value_2, r_place
);
println!(
"expected: {:?} + {:?} = {:?}",
vec_float_64[r_value_1],
vec_float_64[r_value_2],
vec_float_64[r_value_1] + vec_float_64[r_value_2]
);
vec_hom_float[r_place] =
sks.add_total_parallelized(&vec_hom_float[r_value_1], &vec_hom_float[r_value_2]);
vec_float_32[r_place] = vec_float_32[r_value_1] + vec_float_32[r_value_2];
vec_float_64[r_place] = vec_float_64[r_value_1] + vec_float_64[r_value_2];
print_res(
&cks,
&vec_hom_float[r_place],
"res add",
vec_float_32[r_place],
vec_float_64[r_place],
);
} else {
println!(
"block {:?} - block {:?} -> block{:?}\n",
r_value_1, r_value_2, r_place
);
println!(
"expected: {:?} - {:?} = {:?}",
vec_float_64[r_value_1],
vec_float_64[r_value_2],
vec_float_64[r_value_1] - vec_float_64[r_value_2]
);
vec_hom_float[r_place] =
sks.sub_total_parallelized(&vec_hom_float[r_value_1], &vec_hom_float[r_value_2]);
vec_float_32[r_place] = vec_float_32[r_value_1] - vec_float_32[r_value_2];
vec_float_64[r_place] = vec_float_64[r_value_1] - vec_float_64[r_value_2];
print_res(
&cks,
&vec_hom_float[r_place],
"res sub",
vec_float_32[r_place],
vec_float_64[r_place],
);
}
if vec_float_64[r_value_1].abs() > max_i {
let msg_tmp = (1. / max_i) * rng.gen::<f32>() as f64; // 1. / (vec_float_64[r_value_1].abs() + vec_float_64[r_value_2].clone().abs() );
let mut ct_tmp = cks.encrypt(msg_tmp);
println!(
"block {:?} * {:?} -> block{:?}\n",
r_value_1, msg_tmp, r_value_1
);
println!(
"expected: {:?} * {:?} = {:?}",
vec_float_64[r_value_1],
msg_tmp,
vec_float_64[r_value_1] * msg_tmp
);
vec_hom_float[r_place] =
sks.mul_total_parallelized(&mut vec_hom_float[r_value_1].clone(), &mut ct_tmp);
vec_float_32[r_value_1] = vec_float_32[r_value_1] * msg_tmp as f32;
vec_float_64[r_value_1] = vec_float_64[r_value_1] * msg_tmp;
vec_nb_operation[r_value_1] += 1;
print_res(
&cks,
&vec_hom_float[r_place],
"res mul",
vec_float_32[r_place],
vec_float_64[r_place],
);
}
if vec_float_64[r_value_2].abs() > max_i {
let msg_tmp = (1. / max_i) * rng.gen::<f32>() as f64; // 1. / (vec_float_64[r_value_1].abs() + vec_float_64[r_value_2].clone().abs() );
let mut ct_tmp = cks.encrypt(msg_tmp);
println!(
"block {:?} * {:?} -> block{:?}\n",
r_value_2, msg_tmp, r_value_2
);
println!(
"expected: {:?} * {:?} = {:?}",
vec_float_64[r_value_1],
msg_tmp,
vec_float_64[r_value_1] * msg_tmp
);
vec_hom_float[r_value_2] =
sks.mul_total_parallelized(&mut vec_hom_float[r_value_2].clone(), &mut ct_tmp);
vec_float_32[r_value_2] = vec_float_32[r_value_2] * msg_tmp as f32;
vec_float_64[r_value_2] = vec_float_64[r_value_2] * msg_tmp;
vec_nb_operation[r_value_2] += 1;
print_res(
&cks,
&vec_hom_float[r_place],
"res mul",
vec_float_32[r_place],
vec_float_64[r_place],
);
}
if vec_float_64[r_value_1].abs() < 1. / max_i {
let msg_tmp = max_i * rng.gen::<f32>() as f64; // 1. / (vec_float_64[r_value_1].abs() + vec_float_64[r_value_2].clone().abs() );
let mut ct_tmp = cks.encrypt(msg_tmp);
println!(
"block {:?} * {:?} -> block{:?}\n",
r_value_1, msg_tmp, r_value_1
);
println!(
"expected: {:?} * {:?} = {:?}",
vec_float_64[r_value_1],
msg_tmp,
vec_float_64[r_value_1] * msg_tmp
);
vec_hom_float[r_value_1] =
sks.mul_total_parallelized(&mut vec_hom_float[r_value_1].clone(), &mut ct_tmp);
vec_float_32[r_value_1] = vec_float_32[r_value_1] * msg_tmp as f32;
vec_float_64[r_value_1] = vec_float_64[r_value_1] * msg_tmp;
vec_nb_operation[r_value_1] += 1;
print_res(
&cks,
&vec_hom_float[r_place],
"res mul",
vec_float_32[r_place],
vec_float_64[r_place],
);
}
if vec_float_64[r_value_2].abs() < 1. / max_i {
let msg_tmp = max_i * rng.gen::<f32>() as f64; // 1. / (vec_float_64[r_value_1].abs() + vec_float_64[r_value_2].clone().abs() );
let mut ct_tmp = cks.encrypt(msg_tmp);
println!(
"block {:?} * {:?} -> block{:?}\n",
r_value_2, msg_tmp, r_value_2
);
println!(
"expected: {:?} * {:?} = {:?}",
vec_float_64[r_value_1],
msg_tmp,
vec_float_64[r_value_1] * msg_tmp
);
vec_hom_float[r_value_2] =
sks.mul_total_parallelized(&mut vec_hom_float[r_value_2].clone(), &mut ct_tmp);
vec_float_32[r_value_2] = vec_float_32[r_value_2] * msg_tmp as f32;
vec_float_64[r_value_2] = vec_float_64[r_value_2] * msg_tmp;
vec_nb_operation[r_value_2] += 1;
print_res(
&cks,
&vec_hom_float[r_place],
"res mul",
vec_float_32[r_place],
vec_float_64[r_place],
);
}
println!("----End Round {:?}----", i);
println!("--------------------");
println!("--------------------");
println!("--------------------");
}
for i in 0..len_vec as usize {
println!("------");
print_res(
&cks,
&vec_hom_float[i],
"Final result",
vec_float_32[i],
vec_float_64[i],
);
//println!("Deep : {:?}", vec_deep[i]);
//println!("Ope : {:?}", vec_nb_operation[i]);
let res = cks.decrypt(&vec_hom_float[i]);
assert!(res.abs() < (vec_float_64[i] * 1.001).abs());
assert!(res.abs() > (vec_float_64[i] * 0.999).abs());
//println!("------");
}
//println!("Info :");
//println!("len mantissa : {:?}", LEN_MAN);
//println!("len exponent : {:?}", LEN_EXP);
//println!("number operations : {:?}", NB_OPE);
}
}
#[test]
pub fn float_same_as_ls_22_32() {
let (cks, sks) = gen_keys(
PARAM_MESSAGE_2_CARRY_2_32,
WOP_PARAM_MESSAGE_2_CARRY_2_32,
LEN_MAN32,
LEN_EXP32,
);
print_info(&cks);
let msg1 = -2.7914999921796382_e-15;
let ct1 = cks.encrypt(msg1);
print_res(&cks, &ct1, "Encrypt/Decrypt", msg1 as f32, msg1);
let msg2 = 8.3867001884896375_e-12;
let ct2 = cks.encrypt(msg2);
print_res(&cks, &ct2, "Encrypt/Decrypt", msg2 as f32, msg2);
let msg3 = 1.82634005135360_e14;
let ct3 = cks.encrypt(msg3);
print_res(&cks, &ct3, "Encrypt/Decrypt", msg3 as f32, msg3);
let msg4 = -6.278269952_e9;
let ct4 = cks.encrypt(msg4);
print_res(&cks, &ct4, "Encrypt/Decrypt", msg4 as f32, msg4);
let res_1 = sks.add_total_parallelized(&ct1, &ct2);
print_res(
&cks,
&res_1,
"res add",
msg1 as f32 + msg2 as f32,
msg1 + msg2,
);
let res_2 = sks.sub_total_parallelized(&ct3, &ct4);
print_res(
&cks,
&res_2,
"res add",
msg3 as f32 - msg4 as f32,
msg3 - msg4,
);
let mut witness32 = (msg3 as f32 - msg4 as f32) * (msg1 as f32 + msg2 as f32);
let mut witness64 = (msg3 - msg4) * (msg1 + msg2);
let res = sks.mul_total_parallelized(&res_1, &res_2);
print_res(&cks, &res, "res mul", witness32, witness64);
let res = sks.mul_total_parallelized(&res, &res);
witness32 *= witness32;
witness64 *= witness64;
print_res(&cks, &res, "res mul", witness32, witness64);
let res = cks.decrypt(&res);
assert!(res.abs() < ((witness32 * 1.001 as f32) as f64).abs());
assert!(res.abs() > ((witness32 * 0.999 as f32) as f64).abs());
}
#[test]
pub fn float_same_as_ls_22_64() {
let (cks, sks) = gen_keys(
PARAM_MESSAGE_2_CARRY_2_64,
WOP_PARAM_MESSAGE_2_CARRY_2_64,
LEN_MAN64,
LEN_EXP64,
);
print_info(&cks);
let msg1 = -9.1763514236254290_e-32;
let ct1 = cks.encrypt(msg1);
print_res(&cks, &ct1, "Encrypt/Decrypt", msg1 as f32, msg1);
let msg2 = 6.2467247246375865_e-24;
let ct2 = cks.encrypt(msg2);
print_res(&cks, &ct2, "Encrypt/Decrypt", msg2 as f32, msg2);
let msg3 = 2.4523526872362373_e22;
let ct3 = cks.encrypt(msg3);
print_res(&cks, &ct3, "Encrypt/Decrypt", msg3 as f32, msg3);
let msg4 = -5.4324663335297274_e17;
let ct4 = cks.encrypt(msg4);
print_res(&cks, &ct4, "Encrypt/Decrypt", msg4 as f32, msg4);
let res_1 = sks.add_total_parallelized(&ct1, &ct2);
print_res(
&cks,
&res_1,
"res add",
msg1 as f32 + msg2 as f32,
msg1 + msg2,
);
let res_2 = sks.sub_total_parallelized(&ct3, &ct4);
print_res(
&cks,
&res_2,
"res add",
msg3 as f32 - msg4 as f32,
msg3 - msg4,
);
let mut witness32 = (msg3 as f32 - msg4 as f32) * (msg1 as f32 + msg2 as f32);
let mut witness64 = (msg3 - msg4) * (msg1 + msg2);
let res = sks.mul_total_parallelized(&res_1, &res_2);
print_res(&cks, &res, "res mul", witness32, witness64);
let res = sks.mul_total_parallelized(&res, &res);
witness32 *= witness32;
witness64 *= witness64;
print_res(&cks, &res, "res mul", witness32, witness64);
let res = cks.decrypt(&res);
assert!(res.abs() < (witness64 * 1.001).abs());
assert!(res.abs() > (witness64 * 0.999).abs());
}
#[test]
pub fn test_float_relu() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
let msg = rng.gen::<f32>() as f64 - rng.gen::<f32>() as f64;
let ct = cks.encrypt(msg);
print_res(&cks, &ct, "decrypt", msg as f32, msg);
let res = sks.relu(&ct);
print_res(&cks, &res, "relu", 0.0_f32.max(msg as f32), msg.max(0.));
let res = cks.decrypt(&res);
assert_eq!(res, msg.max(0.));
}
}
#[test]
pub fn test_float_sigmoid() {
let mut rng = rand::thread_rng();
for (name_parameters, param) in PARAMS {
let (cks, sks) = gen_keys(
param.pbsparameters,
param.wopbsparameters,
param.len_man,
param.len_exp,
);
println!("--------------------------");
println!("---- {name_parameters} ----");
println!("--------------------------");
let msg = (rng.gen::<f32>() as f64 + 0.4).abs();
let ct = cks.encrypt(msg);
print_res(&cks, &ct, "ct", msg as f32, msg);
let res = sks.sigmoid(&ct);
print_res(&cks, &res, "approx sigmoid", 1.0_f32.min(msg as f32), msg.min(1.));
let res = cks.decrypt(&res);
assert!(res > msg.min(1.) * 0.999);
assert!(res < msg.min(1.) * 1.001);
}
}
pub fn print_res(
cks: &ClientKey,
ct: &Ciphertext,
operation: &str,
witness32: f32,
witness64: f64,
) {
println!("\n--------------------",);
println!("{:?}:\n", operation);
println!("Result : {:?}", cks.decrypt(&ct));
println!("Clear 32-bits: {:?}", witness32);
println!("Clear 64-bits: {:?}\n", witness64);
println!("--------------------");
}
pub fn print_info(cks: &ClientKey) {
println!("\n-----Info-----");
println!("length exp {:?}", cks.vector_length_exponent);
println!("length man {:?}", cks.vector_length_mantissa);
let msg_modulus = cks.parameters().message_modulus().0;
let car_modulus = cks.parameters().carry_modulus().0;
println!("msg modulus {:?}, 0b{:b}", msg_modulus, msg_modulus);
println!("car modulus {:?}, 0b{:b}", car_modulus, car_modulus);
println!(
"total space {:?}, 0b{:b}",
car_modulus * msg_modulus,
car_modulus * msg_modulus
);
let log_msg_modulus = f64::log2(msg_modulus as f64) as usize;
let bias = -((1 << (cks.vector_length_exponent.0 * log_msg_modulus - 1)) as i64)
- (cks.vector_length_mantissa.0 as i64 - 1);
println!("Bias {:?}", bias);
println!("--------------\n");
}

View File

@@ -0,0 +1,551 @@
use crate::server_key::Ciphertext;
use crate::ServerKey;
use shortint::ciphertext::{Ciphertext as ShortintCiphertext, Degree};
use std::cmp::{max, min};
use tfhe::core_crypto::algorithms::{
cmux_assign, extract_lwe_sample_from_glwe_ciphertext, keyswitch_lwe_ciphertext,
par_keyswitch_lwe_ciphertext,
};
use aligned_vec::ABox;
use dyn_stack::{GlobalPodBuffer, PodStack, ReborrowMut, StackReq};
use tfhe::core_crypto::commons::parameters::*;
use tfhe::core_crypto::entities::*;
use tfhe::core_crypto::fft_impl::fft64::c64;
use tfhe::core_crypto::fft_impl::fft64::crypto::ggsw::fill_with_forward_fourier_scratch;
use tfhe::core_crypto::fft_impl::fft64::crypto::wop_pbs::{
circuit_bootstrap_boolean, circuit_bootstrap_boolean_parallelized,
circuit_bootstrap_boolean_scratch, extract_bits, extract_bits_parallelized,
extract_bits_scratch,
};
use tfhe::core_crypto::fft_impl::fft64::math::fft::par_convert_polynomials_list_to_fourier;
use tfhe::core_crypto::prelude::{ContiguousEntityContainer, Fft};
use tfhe::shortint;
use tfhe::shortint::ciphertext::NoiseLevel;
use rayon::prelude::*;
impl ServerKey {
pub fn ggsw_pbs_ks_cbs(
&self,
ct1: &ShortintCiphertext,
message_space: usize,
) -> FourierGgswCiphertext<ABox<[c64]>> {
let accumulator = self.key.generate_lookup_table(|x| min(1, x) as u64);
let res = self.key.apply_lookup_table(&ct1, &accumulator);
self.ggsw_ks_cbs(&res, message_space)
}
/// return ggsw(0) if ct1 = 0, return ggsw(1) otherwise
pub fn ggsw_ks_cbs(
&self,
ct1: &ShortintCiphertext,
message_space: usize,
) -> FourierGgswCiphertext<ABox<[c64]>> {
let ciphertext_modulus = ct1.ct.ciphertext_modulus();
let mut res_ks = LweCiphertext::new(
0u64,
LweSize(self.wopbs_key.param.lwe_dimension.to_lwe_size().0),
ciphertext_modulus,
);
keyswitch_lwe_ciphertext(&self.key.key_switching_key, &ct1.ct, &mut res_ks);
self.ggsw_cbs(&res_ks.as_view(), message_space)
}
/// return ggsw(0) if ct1 = 0, return ggsw(1) otherwise
pub fn ggsw_cbs(
&self,
ct: &LweCiphertext<&[u64]>,
message_space: usize,
) -> FourierGgswCiphertext<ABox<[c64]>> {
let glwe_dimension = self.wopbs_key.param.glwe_dimension;
let polynomial_size = self.wopbs_key.param.polynomial_size;
let base_log_cbs = self.wopbs_key.param.cbs_base_log;
let level_count_cbs = self.wopbs_key.param.cbs_level;
let ciphertext_modulus = ct.ciphertext_modulus();
let fourier_bsk = match &self.wopbs_key.wopbs_server_key.bootstrapping_key {
shortint::server_key::ShortintBootstrappingKey::Classic(fbsk) => fbsk.as_view(),
_ => unreachable!(),
};
let fft = Fft::new(polynomial_size);
let fft = fft.as_view();
let mut cbs_res = GgswCiphertext::new(
0u64,
glwe_dimension.to_glwe_size(),
polynomial_size,
base_log_cbs,
level_count_cbs,
ciphertext_modulus,
);
let mut ggsw = FourierGgswCiphertext::new(
glwe_dimension.to_glwe_size(),
polynomial_size,
base_log_cbs,
level_count_cbs,
);
let mut mem = GlobalPodBuffer::new(
circuit_bootstrap_boolean_scratch::<u64>(
ct.lwe_size(),
fourier_bsk.output_lwe_dimension().to_lwe_size(),
glwe_dimension.to_glwe_size(),
polynomial_size,
fft,
)
.unwrap(),
);
let mut stack = PodStack::new(&mut mem);
circuit_bootstrap_boolean(
fourier_bsk,
ct.as_view(),
cbs_res.as_mut_view(),
DeltaLog(63 - message_space),
self.wopbs_key.cbs_pfpksk.as_view(),
fft,
stack.rb_mut(),
);
let mut mem = GlobalPodBuffer::new(fill_with_forward_fourier_scratch(fft).unwrap());
let mut stack = PodStack::new(&mut mem);
ggsw.as_mut_view()
.fill_with_forward_fourier(cbs_res.as_view(), fft, stack.rb_mut());
ggsw
}
pub fn extract_bit_cbs(
&self,
ct1: &ShortintCiphertext,
) -> Vec<FourierGgswCiphertext<ABox<[c64]>>> {
let glwe_dimension = self.wopbs_key.param.glwe_dimension;
let polynomial_size = self.wopbs_key.param.polynomial_size;
let lwe_dimension = self.wopbs_key.param.lwe_dimension;
let message_modulus = self.wopbs_key.param.message_modulus;
let log_message_modulus = f64::log2(message_modulus.0 as f64) as usize;
let log_carry_modulus = f64::log2(self.wopbs_key.param.carry_modulus.0 as f64) as usize;
let ciphertext_modulus = ct1.ct.ciphertext_modulus();
let ksk = &self.key.key_switching_key;
let delta_log = 63 - log_message_modulus * log_carry_modulus;
let fft = Fft::new(polynomial_size);
let fft = fft.as_view();
let req = || {
StackReq::try_any_of([
fill_with_forward_fourier_scratch(fft)?,
extract_bits_scratch::<u64>(
lwe_dimension,
LweDimension(polynomial_size.0 * glwe_dimension.0 + 1),
glwe_dimension.to_glwe_size(),
polynomial_size,
fft,
)?,
])
};
let req = req().unwrap();
let mut mem = GlobalPodBuffer::new(req);
let stack = PodStack::new(&mut mem);
let fourier_bsk = match &self.wopbs_key.wopbs_server_key.bootstrapping_key {
shortint::server_key::ShortintBootstrappingKey::Classic(fbsk) => fbsk.as_view(),
_ => unreachable!(),
};
let mut lwe_out_list = LweCiphertextList::new(
0u64,
ksk.output_lwe_size(),
LweCiphertextCount(log_message_modulus),
ciphertext_modulus,
);
extract_bits(
lwe_out_list.as_mut_view(),
ct1.ct.as_view(),
ksk.as_view(),
fourier_bsk,
DeltaLog(delta_log),
ExtractedBitsCount(log_message_modulus),
fft,
stack,
);
let mut out_vec_ggsw: Vec<FourierGgswCiphertext<ABox<[c64]>>> = Vec::new();
for lwe in lwe_out_list.iter() {
let ggsw = self.ggsw_cbs_parallelized(&lwe, 0);
out_vec_ggsw.append(&mut vec![ggsw]);
}
out_vec_ggsw
}
//return ct0 if we have ggsw(0)
//return ct1 if we have ggsw(1)
//with cti a ShortintCiphertext
pub fn cmux(
&self,
ct0: &ShortintCiphertext,
ct1: &ShortintCiphertext,
ggsw: &FourierGgswCiphertext<ABox<[c64]>>,
) -> ShortintCiphertext {
let polynomial_size = self.wopbs_key.param.polynomial_size;
let glwe_dim = self.wopbs_key.param.glwe_dimension;
let mut vec_0 = vec![0u64; polynomial_size.0 * (glwe_dim.0 + 1)];
let mut vec_1 = vec![0u64; polynomial_size.0 * (glwe_dim.0 + 1)];
for (i, (ct_i_0, ct_i_1)) in ct0
.ct
.as_ref()
.iter()
.zip(ct1.ct.as_ref().iter())
.enumerate()
{
if i % polynomial_size.0 == 0 {
vec_0[i] = *ct_i_0;
vec_1[i] = *ct_i_1;
} else {
let index =
(i / polynomial_size.0 + 1) * polynomial_size.0 - (i % polynomial_size.0);
vec_0[index] = 0 - (*ct_i_0);
vec_1[index] = 0 - (*ct_i_1);
}
}
let mut rlwe_0 =
GlweCiphertext::from_container(vec_0, polynomial_size, self.key.ciphertext_modulus);
let mut rlwe_1 =
GlweCiphertext::from_container(vec_1, polynomial_size, self.key.ciphertext_modulus);
cmux_assign(&mut rlwe_0, &mut rlwe_1, ggsw);
let mut output = LweCiphertext::new(
0_u64,
LweSize(polynomial_size.0 * glwe_dim.0 + 1),
self.key.ciphertext_modulus,
);
extract_lwe_sample_from_glwe_ciphertext(&rlwe_0, &mut output, MonomialDegree(0));
let ct_out = shortint::Ciphertext::new(
output,
Degree::new(max(ct0.degree.get(), ct1.degree.get())),
NoiseLevel::NOMINAL, // TODO: check this is valid in the context of floats
ct0.message_modulus,
ct0.carry_modulus,
PBSOrder::KeyswitchBootstrap,
);
ct_out
}
//return ct0 in ct0 if we have ggsw(0)
//return ct1 in ct0 if we have ggsw(1)
//with cti = [Ciphertext]
pub fn cmuxes(
&self,
ct0: &[ShortintCiphertext],
ct1: &[ShortintCiphertext],
ggsw: &FourierGgswCiphertext<ABox<[c64]>>,
) -> Vec<shortint::Ciphertext> {
let mut vec_output: Vec<ShortintCiphertext> = Vec::new();
for (ct_0, ct_1) in ct0.iter().zip(ct1.iter()) {
let output = self.cmux(ct_0, ct_1, ggsw);
vec_output.push(output);
}
vec_output
}
//return ct0 in a nwe ct if we have ggsw(0)
//return ct1 in a new ct if we have ggsw(1)
//with cti a fp
pub fn cmuxes_full(
&self,
ct0: &Ciphertext,
ct1: &Ciphertext,
ggsw: &FourierGgswCiphertext<ABox<[c64]>>,
) -> Ciphertext {
let res_man = self.cmuxes(&ct0.ct_vec_mantissa, &ct1.ct_vec_mantissa, &ggsw);
let res_exp = self.cmuxes(&ct0.ct_vec_exponent, &ct1.ct_vec_exponent, &ggsw);
let res_sig = self.cmux(&ct0.ct_sign, &ct1.ct_sign, &ggsw);
let mut new = self.create_trivial_zero_from_ct(ct0);
new.ct_vec_mantissa = res_man;
new.ct_vec_exponent = res_exp;
new.ct_sign = res_sig;
new
}
pub fn cmux_tree_mantissa(
&self,
vec_mantissa: &Vec<shortint::Ciphertext>,
vec_ggsw: &[FourierGgswCiphertext<ABox<[c64]>>],
) -> Vec<shortint::Ciphertext> {
let zero = self.key.create_trivial(0_u64);
let mut cpy = vec_mantissa.clone();
let mut vec_fp = Vec::new();
for _ in 0..(vec_mantissa.len() + 1) {
vec_fp.push(cpy.clone());
cpy.push(zero.clone());
let _ = cpy.remove(0);
}
let vec_zero = cpy;
for ggsw in vec_ggsw.iter().rev() {
if vec_fp.len() == 1 {
vec_fp[0] = self.cmuxes(&mut vec_fp[0], &vec_zero, ggsw);
} else {
if vec_fp.len() % 2 == 0 {
for i in 0..vec_fp.len() / 2 {
let ct_0 = vec_fp.get_mut(2 * i).unwrap().clone();
let ct_1 = vec_fp.get_mut(2 * i + 1).unwrap().clone();
vec_fp[i] = self.cmuxes(&ct_0, &ct_1, ggsw);
}
vec_fp.truncate(vec_fp.len() / 2);
} else {
for i in 0..vec_fp.len() / 2 {
let ct_0 = vec_fp.get_mut(2 * i).unwrap().clone();
let ct_1 = vec_fp.get_mut(2 * i + 1).unwrap().clone();
vec_fp[i] = self.cmuxes(&ct_0, &ct_1, ggsw);
}
let last = vec_fp.len();
let ct_0 = vec_fp.last().unwrap().clone();
let ct_1 = &vec_zero;
vec_fp[last / 2] = self.cmuxes(&ct_0, &ct_1, ggsw);
vec_fp.truncate((vec_fp.len() + 1) / 2);
}
}
}
vec_fp[0].clone()
}
pub fn is_block_non_zero_ggsw_pbs_ks_cbs_parallelized(
&self,
ct1: &ShortintCiphertext,
message_space: usize,
) -> FourierGgswCiphertext<ABox<[c64]>> {
let accumulator = self.key.generate_lookup_table(|x| u64::from(x != 0));
let res = self.key.apply_lookup_table(&ct1, &accumulator);
self.ggsw_ks_cbs_parallelized(&res, message_space)
}
/// return ggsw(0) if ct1 = 0, return ggsw(1) otherwise
pub fn ggsw_ks_cbs_parallelized(
&self,
ct1: &ShortintCiphertext,
message_space: usize,
) -> FourierGgswCiphertext<ABox<[c64]>> {
let ciphertext_modulus = ct1.ct.ciphertext_modulus();
let mut res_ks = LweCiphertext::new(
0u64,
LweSize(self.wopbs_key.param.lwe_dimension.to_lwe_size().0),
ciphertext_modulus,
);
par_keyswitch_lwe_ciphertext(&self.key.key_switching_key, &ct1.ct, &mut res_ks);
self.ggsw_cbs_parallelized(&res_ks.as_view(), message_space)
}
/// return ggsw(0) if ct1 = 0, return ggsw(1) otherwise
pub fn ggsw_cbs_parallelized(
&self,
ct: &LweCiphertext<&[u64]>,
message_space: usize,
) -> FourierGgswCiphertext<ABox<[c64]>> {
// todo!("ggsw_cbs_parallelized");
let glwe_dimension = self.wopbs_key.param.glwe_dimension;
let polynomial_size = self.wopbs_key.param.polynomial_size;
let base_log_cbs = self.wopbs_key.param.cbs_base_log;
let level_count_cbs = self.wopbs_key.param.cbs_level;
let ciphertext_modulus = ct.ciphertext_modulus();
let fourier_bsk = match &self.wopbs_key.wopbs_server_key.bootstrapping_key {
shortint::server_key::ShortintBootstrappingKey::Classic(fbsk) => fbsk.as_view(),
_ => unreachable!(),
};
let fft = Fft::new(polynomial_size);
let fft = fft.as_view();
let mut cbs_res = GgswCiphertext::new(
0u64,
glwe_dimension.to_glwe_size(),
polynomial_size,
base_log_cbs,
level_count_cbs,
ciphertext_modulus,
);
let mut ggsw = FourierGgswCiphertext::new(
glwe_dimension.to_glwe_size(),
polynomial_size,
base_log_cbs,
level_count_cbs,
);
let mut mem = GlobalPodBuffer::new(
circuit_bootstrap_boolean_scratch::<u64>(
ct.lwe_size(),
fourier_bsk.output_lwe_dimension().to_lwe_size(),
glwe_dimension.to_glwe_size(),
polynomial_size,
fft,
)
.unwrap(),
);
let mut stack = PodStack::new(&mut mem);
circuit_bootstrap_boolean_parallelized(
fourier_bsk,
ct.as_view(),
cbs_res.as_mut_view(),
DeltaLog(63 - message_space),
self.wopbs_key.cbs_pfpksk.as_view(),
fft,
stack.rb_mut(),
);
let mut mem = GlobalPodBuffer::new(fill_with_forward_fourier_scratch(fft).unwrap());
let mut _stack = PodStack::new(&mut mem);
par_convert_polynomials_list_to_fourier(
ggsw.as_mut_view().data(),
cbs_res.as_ref(),
polynomial_size,
fft,
);
// ggsw.as_mut_view()
// .fill_with_forward_fourier(cbs_res.as_view(), fft, stack.rb_mut());
ggsw
}
pub fn extract_bit_cbs_parallelized(
&self,
ct1: &ShortintCiphertext,
) -> Vec<FourierGgswCiphertext<ABox<[c64]>>> {
// todo!("extract_bit_cbs_parallelized");
let glwe_dimension = self.wopbs_key.param.glwe_dimension;
let polynomial_size = self.wopbs_key.param.polynomial_size;
let lwe_dimension = self.wopbs_key.param.lwe_dimension;
let message_modulus = self.wopbs_key.param.message_modulus;
let log_message_modulus = f64::log2(message_modulus.0 as f64) as usize;
let log_carry_modulus = f64::log2(self.wopbs_key.param.carry_modulus.0 as f64) as usize;
let ciphertext_modulus = ct1.ct.ciphertext_modulus();
let ksk = &self.key.key_switching_key;
let delta_log = 63 - log_message_modulus * log_carry_modulus;
let fft = Fft::new(polynomial_size);
let fft = fft.as_view();
let req = || {
StackReq::try_any_of([
fill_with_forward_fourier_scratch(fft)?,
extract_bits_scratch::<u64>(
lwe_dimension,
LweDimension(polynomial_size.0 * glwe_dimension.0 + 1),
glwe_dimension.to_glwe_size(),
polynomial_size,
fft,
)?,
])
};
let req = req().unwrap();
let mut mem = GlobalPodBuffer::new(req);
let stack = PodStack::new(&mut mem);
let fourier_bsk = match &self.wopbs_key.wopbs_server_key.bootstrapping_key {
shortint::server_key::ShortintBootstrappingKey::Classic(fbsk) => fbsk.as_view(),
_ => unreachable!(),
};
let mut lwe_out_list = LweCiphertextList::new(
0u64,
ksk.output_lwe_size(),
LweCiphertextCount(log_message_modulus),
ciphertext_modulus,
);
extract_bits_parallelized(
lwe_out_list.as_mut_view(),
ct1.ct.as_view(),
ksk.as_view(),
fourier_bsk,
DeltaLog(delta_log),
ExtractedBitsCount(log_message_modulus),
fft,
stack,
);
let mut out_vec_ggsw: Vec<FourierGgswCiphertext<ABox<[c64]>>> = Vec::new();
for lwe in lwe_out_list.iter() {
let ggsw = self.ggsw_cbs(&lwe, 0);
out_vec_ggsw.append(&mut vec![ggsw]);
}
out_vec_ggsw
}
//return ct0 in ct0 if we have ggsw(0)
//return ct1 in ct0 if we have ggsw(1)
//with cti = [Ciphertext]
pub fn cmuxes_parallelized(
&self,
ct0: &[ShortintCiphertext],
ct1: &[ShortintCiphertext],
ggsw: &FourierGgswCiphertext<ABox<[c64]>>,
) -> Vec<shortint::Ciphertext> {
assert_eq!(ct0.len(), ct1.len());
let len = ct0.len();
let mut vec_output: Vec<ShortintCiphertext> = Vec::with_capacity(len);
ct0.par_iter()
.zip(ct1.par_iter())
.map(|(ct_0_i, ct_1_i)| self.cmux(ct_0_i, ct_1_i, ggsw))
.collect_into_vec(&mut vec_output);
vec_output
}
//return ct0 in a nwe ct if we have ggsw(0)
//return ct1 in a new ct if we have ggsw(1)
//with cti a fp
pub fn cmuxes_full_parallelized(
&self,
ct0: &Ciphertext,
ct1: &Ciphertext,
ggsw: &FourierGgswCiphertext<ABox<[c64]>>,
) -> Ciphertext {
// todo!("cmuxes_full_parallelized");
let (res_man, res_exp) = rayon::join(
|| self.cmuxes_parallelized(&ct0.ct_vec_mantissa, &ct1.ct_vec_mantissa, &ggsw),
|| self.cmuxes_parallelized(&ct0.ct_vec_exponent, &ct1.ct_vec_exponent, &ggsw),
);
let res_sig = self.cmux(&ct0.ct_sign, &ct1.ct_sign, &ggsw);
let mut new = self.create_trivial_zero_from_ct(ct0);
new.ct_vec_mantissa = res_man;
new.ct_vec_exponent = res_exp;
new.ct_sign = res_sig;
new
}
pub fn cmux_tree_mantissa_parallelized(
&self,
vec_mantissa: &Vec<shortint::Ciphertext>,
vec_ggsw: &[FourierGgswCiphertext<ABox<[c64]>>],
) -> Vec<shortint::Ciphertext> {
// todo!("cmux_tree_mantissa_parallelized");
let zero = self.key.create_trivial(0_u64);
let mut cpy = vec_mantissa.clone();
let mut vec_fp = Vec::new();
for _ in 0..(vec_mantissa.len() + 1) {
vec_fp.push(cpy.clone());
cpy.push(zero.clone());
let _ = cpy.remove(0);
}
let vec_zero = cpy;
// TODO cmux tree in parallel
for ggsw in vec_ggsw.iter().rev() {
if vec_fp.len() == 1 {
vec_fp[0] = self.cmuxes_parallelized(&mut vec_fp[0], &vec_zero, ggsw);
} else {
if vec_fp.len() % 2 == 0 {
for i in 0..vec_fp.len() / 2 {
let ct_0 = vec_fp.get_mut(2 * i).unwrap().clone();
let ct_1 = vec_fp.get_mut(2 * i + 1).unwrap().clone();
vec_fp[i] = self.cmuxes_parallelized(&ct_0, &ct_1, ggsw);
}
vec_fp.truncate(vec_fp.len() / 2);
} else {
for i in 0..vec_fp.len() / 2 {
let ct_0 = vec_fp.get_mut(2 * i).unwrap().clone();
let ct_1 = vec_fp.get_mut(2 * i + 1).unwrap().clone();
vec_fp[i] = self.cmuxes_parallelized(&ct_0, &ct_1, ggsw);
}
let last = vec_fp.len();
let ct_0 = vec_fp.last().unwrap().clone();
let ct_1 = &vec_zero;
vec_fp[last / 2] = self.cmuxes_parallelized(&ct_0, &ct_1, ggsw);
vec_fp.truncate((vec_fp.len() + 1) / 2);
}
}
}
vec_fp[0].clone()
}
}

View File

@@ -0,0 +1,9 @@
use doc_comment::doctest;
doctest!("../docs/getting_started/first_circuit.md", first_circuit);
doctest!("../docs/tutorials/serialization.md", serialization_tuto);
doctest!(
"../docs/tutorials/circuit_evaluation.md",
circuit_evaluation
);
doctest!("../docs/how_to/pbs.md", pbs);

View File

@@ -11,6 +11,7 @@ RUN sed -i 's|^deb http://archive.ubuntu.com/ubuntu/|deb http://mirror.ubuntu.ik
ENV CARGO_TARGET_DIR=/root/tfhe-rs-target
ARG RUST_TOOLCHAIN="stable"
ARG NODE_VERSION
WORKDIR /tfhe-wasm-tests
@@ -34,6 +35,6 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > install-rustup.s
chmod +x install-node.sh && \
./install-node.sh && \
. "$HOME/.nvm/nvm.sh" && \
bash -i -c 'nvm install node && nvm use node'
bash -i -c 'nvm install ${NODE_VERSION} && nvm use ${NODE_VERSION}'
WORKDIR /tfhe-wasm-tests/tfhe-rs/

View File

@@ -8,14 +8,21 @@ function usage() {
echo "--help Print this message"
echo "--rust-toolchain The toolchain to run the tests with default: stable"
echo "--multi-bit Run multi-bit tests only: default off"
echo "--unsigned-only Run only unsigned integer tests, by default both signed and unsigned tests are run"
echo "--signed-only Run only signed integer tests, by default both signed and unsigned tests are run"
echo "--cargo-profile The cargo profile used to build tests"
echo "--avx512-support Set to ON to enable avx512"
echo
}
RUST_TOOLCHAIN="+stable"
multi_bit=""
not_multi_bit="_multi_bit"
# Run signed test by default
signed=""
not_signed=""
cargo_profile="release"
avx512_feature=""
while [ -n "$1" ]
do
@@ -35,11 +42,28 @@ do
not_multi_bit=""
;;
"--unsigned-only" )
signed=""
not_signed="_signed"
;;
"--signed-only" )
signed="_signed"
not_signed=""
;;
"--cargo-profile" )
shift
cargo_profile="$1"
;;
"--avx512-support" )
shift
if [[ "$1" == "ON" ]]; then
avx512_feature=nightly-avx512
fi
;;
*)
echo "Unknown param : $1"
exit 1
@@ -62,104 +86,86 @@ if [[ $(uname) == "Darwin" ]]; then
nproc_bin="sysctl -n hw.logicalcpu"
fi
n_threads="$(${nproc_bin})"
# TODO autodetect/have a finer CPU count depending on memory
num_cpu_threads="$(${nproc_bin})"
if uname -a | grep "arm64"; then
if [[ $(uname) == "Darwin" ]]; then
# Keys are 4.7 gigs at max, CI M1 macs only has 8 gigs of RAM
n_threads=1
small_instance_n_threads=1
fi
else
# Keys are 4.7 gigs at max, test machine has 32 gigs of RAM
n_threads=6
small_instance_n_threads=6
fi
if [[ "${BIG_TESTS_INSTANCE}" != TRUE ]]; then
if [[ "${FAST_TESTS}" != TRUE ]]; then
# block pbs are too slow for high params
# mul_crt_4_4 is extremely flaky (~80% failure)
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
# test_integer_smart_mul_param_message_4_carry_4_ks_pbs is too slow
# so is test_integer_default_add_sequence_multi_thread_param_message_4_carry_4_ks_pbs
filter_expression="""\
test(/^integer::.*${multi_bit}/) \
${not_multi_bit:+"and not test(~${not_multi_bit})"} \
and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]_ks_pbs$/) \
and not test(~mul_crt_param_message_4_carry_4_ks_pbs) \
and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]_ks_pbs$/) \
and not test(/.*test_integer_smart_mul_param_message_4_carry_4_ks_pbs$/) \
and not test(/.*test_integer_default_add_sequence_multi_thread_param_message_4_carry_4_ks_pbs$/)"""
else
# test only fast default operations with only two set of parameters
filter_expression="""\
test(/^integer::.*${multi_bit}/) \
${not_multi_bit:+"and not test(~${not_multi_bit})"} \
and test(/.*_default_.*?_param${multi_bit}_message_[2-3]_carry_[2-3]${multi_bit:+"_group_2"}_ks_pbs/) \
and not test(/.*_param_message_[14]_carry_[14]_ks_pbs$/) \
and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3_ks_pbs$/)"""
fi
cargo "${RUST_TOOLCHAIN}" nextest run \
--tests \
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--test-threads "${n_threads}" \
-E "$filter_expression"
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--profile "${cargo_profile}" \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
-- integer::
fi
if [[ "${BIG_TESTS_INSTANCE}" == TRUE ]]; then
test_threads="$((num_cpu_threads * 1 / 2))"
doctest_threads="${num_cpu_threads}"
else
if [[ "${FAST_TESTS}" != TRUE ]]; then
# block pbs are too slow for high params
# mul_crt_4_4 is extremely flaky (~80% failure)
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
# test_integer_smart_mul_param_message_4_carry_4_ks_pbs is too slow
# so is test_integer_default_add_sequence_multi_thread_param_message_4_carry_4_ks_pbs
filter_expression="""\
test_threads="${small_instance_n_threads}"
doctest_threads="${num_cpu_threads}"
fi
# block pbs are too slow for high params
# mul_crt_4_4 is extremely flaky (~80% failure)
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
# test_integer_smart_mul_param_message_4_carry_4_ks_pbs is too slow
# so is test_integer_default_add_sequence_multi_thread_param_message_4_carry_4_ks_pbs
# we skip smart_div, smart_rem which are already covered by the smar_div_rem test
# we similarly skip default_div, default_rem which are covered by default_div_rem
full_test_filter_expression="""\
test(/^integer::.*${multi_bit}/) \
${signed:+"and test(/^integer::.*${signed}/)"} \
${not_multi_bit:+"and not test(~${not_multi_bit})"} \
${not_signed:+"and not test(~${not_signed})"} \
and not test(/.*integer_smart_div_param/) \
and not test(/.*integer_smart_rem_param/) \
and not test(/.*integer_default_div_param/) \
and not test(/.*integer_default_rem_param/) \
and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]_ks_pbs$/) \
and not test(~mul_crt_param_message_4_carry_4_ks_pbs) \
and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]_ks_pbs$/) \
and not test(/.*test_integer_smart_mul_param_message_4_carry_4_ks_pbs$/) \
and not test(/.*test_integer_default_add_sequence_multi_thread_param_message_4_carry_4_ks_pbs$/)"""
else
# test only fast default operations with only two set of parameters
filter_expression="""\
# test only fast default operations with only two set of parameters
# we skip default_div, default_rem which are covered by default_div_rem
fast_test_filter_expression="""\
test(/^integer::.*${multi_bit}/) \
${signed:+"and test(/^integer::.*${signed}/)"} \
${not_multi_bit:+"and not test(~${not_multi_bit})"} \
${not_signed:+"and not test(~${not_signed})"} \
and test(/.*_default_.*?_param${multi_bit}_message_[2-3]_carry_[2-3]${multi_bit:+"_group_2"}_ks_pbs/) \
and not test(/.*integer_default_div_param/) \
and not test(/.*integer_default_rem_param/) \
and not test(/.*_param_message_[14]_carry_[14]_ks_pbs$/) \
and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3_ks_pbs$/)"""
fi
num_cpu_threads="$(${nproc_bin})"
num_threads=$((num_cpu_threads * 1 / 2))
cargo "${RUST_TOOLCHAIN}" nextest run \
--tests \
--cargo-profile "${cargo_profile}" \
if [[ "${FAST_TESTS}" == "TRUE" ]]; then
echo "Running 'fast' test set'"
filter_expression="${fast_test_filter_expression}"
else
echo "Running 'slow' test set"
filter_expression="${full_test_filter_expression}"
fi
cargo "${RUST_TOOLCHAIN}" nextest run \
--tests \
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",integer,internal-keycache,"${avx512_feature}" \
--test-threads "${test_threads}" \
-E "$filter_expression"
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--test-threads $num_threads \
-E "$filter_expression"
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--profile "${cargo_profile}" \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
-- --test-threads="$(${nproc_bin})" integer::
fi
--features="${ARCH_FEATURE}",integer,internal-keycache,"${avx512_feature}" \
--doc \
-- --test-threads="${doctest_threads}" integer::
fi
echo "Test ran in $SECONDS seconds"

View File

@@ -94,6 +94,7 @@ or test(/^shortint::.*_param${multi_bit}_message_2_carry_3${multi_bit:+"_group_[
or test(/^shortint::.*_param${multi_bit}_message_3_carry_1${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_2${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_3${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_ci_run_filter/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
else
@@ -159,6 +160,7 @@ or test(/^shortint::.*_param${multi_bit}_message_3_carry_1${multi_bit:+"_group_[
or test(/^shortint::.*_param${multi_bit}_message_3_carry_2${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_3${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_4_carry_4${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_ci_run_filter/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
else

View File

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
@@ -49,13 +49,13 @@ log = "0.4.19"
cbindgen = { version = "0.26.0", optional = true }
[dependencies]
concrete-csprng = { version = "0.4.0", path= "../concrete-csprng", features = [
concrete-csprng = { version = "0.4.0", path = "../concrete-csprng", features = [
"generator_fallback",
"parallel",
] }
lazy_static = { version = "1.4.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
rayon = { version = "1.5.0" }
rayon = { version = "1.5" }
bincode = { version = "1.3.3", optional = true }
concrete-fft = { version = "0.3.0", features = ["serde", "fft128"] }
pulp = "0.13"
@@ -82,7 +82,8 @@ bytemuck = "1.13.1"
boolean = ["dep:paste"]
shortint = ["dep:paste"]
integer = ["shortint", "dep:paste"]
internal-keycache = ["lazy_static", "dep:fs2", "dep:bincode", "dep:paste"]
float_wopbs = ["shortint", "dep:paste"]
internal-keycache = ["dep:lazy_static", "dep:fs2", "dep:bincode", "dep:paste"]
safe-deserialization = ["dep:bincode"]
# Experimental section
@@ -90,18 +91,19 @@ experimental = []
experimental-force_fft_algo_dif4 = []
# End experimental section
__c_api = ["cbindgen", "dep:bincode", "dep:paste"]
__c_api = ["dep:cbindgen", "dep:bincode", "dep:paste"]
# For the semver trick to skip the build.rs
__force_skip_cbindgen = []
boolean-c-api = ["boolean", "__c_api"]
shortint-c-api = ["shortint", "__c_api"]
high-level-c-api = ["boolean-c-api", "shortint-c-api", "integer", "__c_api"]
__wasm_api = [
"wasm-bindgen",
"js-sys",
"console_error_panic_hook",
"serde-wasm-bindgen",
"getrandom",
"getrandom/js",
"dep:wasm-bindgen",
"dep:js-sys",
"dep:console_error_panic_hook",
"dep:serde-wasm-bindgen",
"dep:getrandom",
"dep:bincode",
"safe-deserialization",
]
@@ -109,7 +111,7 @@ boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
integer-client-js-wasm-api = ["integer", "__wasm_api"]
high-level-client-js-wasm-api = ["boolean", "shortint", "integer", "__wasm_api"]
parallel-wasm-api = ["wasm-bindgen-rayon"]
parallel-wasm-api = ["dep:wasm-bindgen-rayon"]
nightly-avx512 = ["concrete-fft/nightly", "pulp/nightly"]
@@ -149,6 +151,12 @@ rustdoc-args = ["--html-in-header", "katex-header.html"]
# #
###########
[[bench]]
name = "ks-bench"
path = "benches/core_crypto/ks_bench.rs"
harness = false
required-features = ["shortint", "internal-keycache"]
[[bench]]
name = "pbs-bench"
path = "benches/core_crypto/pbs_bench.rs"
@@ -202,6 +210,12 @@ path = "benches/utilities.rs"
harness = false
required-features = ["boolean", "shortint", "integer", "internal-keycache"]
[[bench]]
name = "float-wopbs-bench"
path = "benches/float_wopbs/bench.rs"
harness = false
required-features = []
# Examples used as tools
[[example]]
@@ -212,7 +226,7 @@ required-features = ["shortint", "internal-keycache"]
[[example]]
name = "generates_test_keys"
path = "examples/utilities/generates_test_keys.rs"
required-features = ["shortint", "internal-keycache"]
required-features = ["boolean", "shortint", "internal-keycache"]
[[example]]
name = "boolean_key_sizes"
@@ -255,3 +269,7 @@ required-features = ["boolean"]
[lib]
crate-type = ["lib", "staticlib", "cdylib"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(bench)'] }

View File

@@ -130,7 +130,7 @@ fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Syn
);
let id = format!("Multi Bit PBS {}", Scalar::BITS);
#[allow(clippy::unit_arg)]
{
c.bench_function(&id, |b| {
b.iter(|| {

View File

@@ -0,0 +1,91 @@
use criterion::{criterion_group, criterion_main, Criterion};
use tfhe::core_crypto::prelude::*;
use tfhe::keycache::NamedParam;
use tfhe::shortint::prelude::*;
fn criterion_bench(criterion: &mut Criterion) {
type Scalar = u64;
let mut bench_group = criterion.benchmark_group("KS");
bench_group
.sample_size(15)
.measurement_time(std::time::Duration::from_secs(60));
for params in [
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
PARAM_MESSAGE_3_CARRY_3_KS_PBS,
PARAM_MESSAGE_4_CARRY_4_KS_PBS,
]
.into_iter()
{
let lwe_dimension = params.lwe_dimension;
let lwe_modular_std_dev = params.lwe_modular_std_dev;
let ciphertext_modulus = params.ciphertext_modulus;
let encoding_with_padding = if ciphertext_modulus.is_native_modulus() {
Scalar::ONE << (Scalar::BITS - 1)
} else {
Scalar::cast_from(ciphertext_modulus.get_custom_modulus() / 2)
};
let glwe_dimension = params.glwe_dimension;
let polynomial_size = params.polynomial_size;
let ks_decomp_base_log = params.ks_base_log;
let ks_decomp_level_count = params.ks_level;
let msg_modulus: Scalar = params.message_modulus.0.cast_into();
let total_modulus: Scalar = (params.message_modulus.0 * params.carry_modulus.0).cast_into();
let msg = msg_modulus - 1;
let delta: Scalar = encoding_with_padding / total_modulus;
// 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());
let lwe_sk =
allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
let glwe_sk = allocate_and_generate_new_binary_glwe_secret_key(
glwe_dimension,
polynomial_size,
&mut secret_generator,
);
let big_lwe_sk = glwe_sk.into_lwe_secret_key();
let ksk_big_to_small = allocate_and_generate_new_lwe_keyswitch_key(
&big_lwe_sk,
&lwe_sk,
ks_decomp_base_log,
ks_decomp_level_count,
lwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
let plaintext = Plaintext(msg * delta);
let ct = allocate_and_encrypt_new_lwe_ciphertext(
&big_lwe_sk,
plaintext,
lwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
let mut output_ct = LweCiphertext::new(
Scalar::ZERO,
lwe_sk.lwe_dimension().to_lwe_size(),
ciphertext_modulus,
);
bench_group.bench_function(&params.name(), |bencher| {
bencher.iter(|| {
keyswitch_lwe_ciphertext(&ksk_big_to_small, &ct, &mut output_ct);
})
});
}
}
criterion_group!(benches, criterion_bench);
criterion_main!(benches);

View File

@@ -0,0 +1,90 @@
#![allow(dead_code)]
use criterion::{criterion_group, criterion_main, Criterion};
use tfhe::float_wopbs::gen_keys;
#[allow(unused_imports)]
use tfhe::float_wopbs::parameters::{
PARAM_MESSAGE_2_16_BITS, PARAM_MESSAGE_4_16_BITS, PARAM_MESSAGE_8_16_BITS,
};
use tfhe::float_wopbs::parameters::{ PARAM_MESSAGE_2_4_8_BITS_BIV, PARAM_MESSAGE_4_2_8_BITS_BIV};
use tfhe::shortint::WopbsParameters;
macro_rules! named_param {
($param:ident) => {
(stringify!($param), $param)
};
}
struct Parameters {
parameters: WopbsParameters,
bit_mantissa: usize,
bit_exponent: usize,
}
const PARAM_4_BIT_LWE_8_BITS: Parameters = Parameters {
parameters: PARAM_MESSAGE_2_4_8_BITS_BIV,
bit_mantissa: 4,
bit_exponent: 3,
};
const PARAM_2_BIT_LWE_8_BITS: Parameters = Parameters {
parameters: PARAM_MESSAGE_4_2_8_BITS_BIV,
bit_mantissa: 4,
bit_exponent: 3,
};
const SERVER_KEY_BENCH_PARAMS: [(&str, Parameters); 2] =
[ named_param!(PARAM_4_BIT_LWE_8_BITS),
named_param!(PARAM_2_BIT_LWE_8_BITS)];
criterion_main!(float);
criterion_group!(float, float_wopbs_bivariate);
pub fn float_wopbs_mut_eval(c: &mut Criterion) {
for name_param in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(name_param.1.parameters);
let bit_mantissa = &name_param.1.bit_mantissa;
let bit_exponent = &name_param.1.bit_exponent;
let e_min = -2;
let msg_1 = 0.375;
// Encryption:
let mut ct_1 = cks.encrypt(msg_1, e_min, *bit_mantissa, *bit_exponent);
let lut = sks.create_lut(&mut ct_1, |x| x);
let bench_id = format!("8-bit floats WoP-PBS lut eval::{}", name_param.0);
c.bench_function(&bench_id, |b| {
b.iter(|| {
sks.wop_pbs(&sks, &mut ct_1, &lut);
})
});
}
}
pub fn float_wopbs_bivariate(c: &mut Criterion) {
for name_param in SERVER_KEY_BENCH_PARAMS {
let (cks, sks) = gen_keys(name_param.1.parameters);
let bit_mantissa = &name_param.1.bit_mantissa;
let bit_exponent = &name_param.1.bit_exponent;
let e_min = -2;
let msg_1 = 0.375;
// Encryption:
let mut ct_1 = cks.encrypt(msg_1, e_min, *bit_mantissa, *bit_exponent);
let msg_2 = -44.;
let mut ct_2 = cks.encrypt(msg_2, e_min, *bit_mantissa, *bit_exponent);
let lut = sks.create_bivariate_lut(&mut ct_1, |x, y| y * x);
let bench_id = format!("8-bit floats WoP-PBS bivariate::{}", name_param.0);
c.bench_function(&bench_id, |b| {
b.iter(|| {
sks.wop_pbs_bivariate(&sks, &mut ct_1, &mut ct_2, &lut);
})
});
}
}

View File

@@ -12,7 +12,7 @@ use rand::prelude::*;
use rand::Rng;
use std::vec::IntoIter;
use tfhe::integer::keycache::KEY_CACHE;
use tfhe::integer::{RadixCiphertext, ServerKey};
use tfhe::integer::{IntegerKeyKind, RadixCiphertext, ServerKey};
use tfhe::keycache::NamedParam;
use tfhe::integer::U256;
@@ -118,7 +118,7 @@ fn bench_server_key_binary_function_dirty_inputs<F>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_two_values = || {
let clear_0 = gen_random_u256(&mut rng);
@@ -186,7 +186,7 @@ fn bench_server_key_binary_function_clean_inputs<F>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_two_values = || {
let clear_0 = gen_random_u256(&mut rng);
@@ -243,7 +243,7 @@ fn bench_server_key_unary_function_dirty_inputs<F>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_one_value = || {
let clear_0 = gen_random_u256(&mut rng);
@@ -308,7 +308,7 @@ fn bench_server_key_unary_function_clean_inputs<F>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_one_value = || {
let clear_0 = gen_random_u256(&mut rng);
@@ -362,7 +362,7 @@ fn bench_server_key_binary_scalar_function_dirty_inputs<F, G>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_one_value = || {
let clear_0 = gen_random_u256(&mut rng);
@@ -435,7 +435,7 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F, G>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits_scalar_{bit_size}");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_one_value = || {
let clear_0 = gen_random_u256(&mut rng);
@@ -515,7 +515,7 @@ fn if_then_else_parallelized(c: &mut Criterion) {
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_tree_values = || {
let clear_0 = gen_random_u256(&mut rng);
@@ -524,7 +524,7 @@ fn if_then_else_parallelized(c: &mut Criterion) {
let clear_1 = gen_random_u256(&mut rng);
let ct_1 = cks.encrypt_radix(clear_1, num_block);
let cond = sks.create_trivial_radix(rng.gen_bool(0.5) as u64, num_block);
let cond = sks.create_trivial_boolean_block(rng.gen_bool(0.5));
(cond, ct_0, ct_1)
};

View File

@@ -10,7 +10,7 @@ use rand::prelude::*;
use rand::Rng;
use std::vec::IntoIter;
use tfhe::integer::keycache::KEY_CACHE;
use tfhe::integer::{RadixCiphertext, ServerKey, SignedRadixCiphertext, I256};
use tfhe::integer::{IntegerKeyKind, RadixCiphertext, ServerKey, SignedRadixCiphertext, I256};
use tfhe::keycache::NamedParam;
use tfhe::shortint::parameters::{
@@ -114,7 +114,7 @@ fn bench_server_key_signed_binary_function_clean_inputs<F>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_two_values = || {
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
@@ -167,7 +167,7 @@ fn bench_server_key_signed_shift_function_clean_inputs<F>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_two_values = || {
let clear_1 = rng.gen_range(0u128..bit_size as u128);
@@ -223,7 +223,7 @@ fn bench_server_key_unary_function_clean_inputs<F>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_one_value =
|| cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
@@ -266,13 +266,13 @@ fn signed_if_then_else_parallelized(c: &mut Criterion) {
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_tree_values = || {
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
let ct_1 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
let cond = sks.create_trivial_radix(rng.gen_bool(0.5) as u64, num_block);
let cond = sks.create_trivial_boolean_block(rng.gen_bool(0.5));
(cond, ct_0, ct_1)
};
@@ -348,6 +348,169 @@ macro_rules! define_server_key_bench_unary_signed_clean_input_fn (
};
);
define_server_key_bench_unary_signed_clean_input_fn!(
method_name: neg_parallelized,
display_name: negation
);
define_server_key_bench_unary_signed_clean_input_fn!(
method_name: abs_parallelized,
display_name: abs
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: add_parallelized,
display_name: add
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: sub_parallelized,
display_name: sub
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: mul_parallelized,
display_name: mul
);
// define_server_key_bench_binary_signed_clean_inputs_fn!(
// method_name: div_parallelized,
// display_name: div,
// sample_size:10
// );
// define_server_key_bench_binary_signed_clean_inputs_fn!(
// method_name: rem_parallelized,
// display_name: modulo,
// sample_size:10
// );
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: div_rem_parallelized,
display_name: div_mod,
sample_size:10
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: bitand_parallelized,
display_name: bitand
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: bitxor_parallelized,
display_name: bitxor
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: bitor_parallelized,
display_name: bitor
);
define_server_key_bench_unary_signed_clean_input_fn!(
method_name: bitnot_parallelized,
display_name: bitnot
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: max_parallelized,
display_name: max
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: min_parallelized,
display_name: min
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: eq_parallelized,
display_name: equal
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: ne_parallelized,
display_name: not_equal
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: lt_parallelized,
display_name: less_than
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: le_parallelized,
display_name: less_or_equal
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: gt_parallelized,
display_name: greater_than
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: ge_parallelized,
display_name: greater_or_equal
);
fn left_shift_parallelized(c: &mut Criterion) {
bench_server_key_signed_shift_function_clean_inputs(
c,
concat!("integer::signed::", "left_shift_parallelized"),
"left_shift",
|server_key, lhs, rhs| {
server_key.left_shift_parallelized(lhs, rhs);
},
)
}
fn right_shift_parallelized(c: &mut Criterion) {
bench_server_key_signed_shift_function_clean_inputs(
c,
concat!("integer::signed::", "right_shift_parallelized"),
"right_shift",
|server_key, lhs, rhs| {
server_key.right_shift_parallelized(lhs, rhs);
},
)
}
fn rotate_left_parallelized(c: &mut Criterion) {
bench_server_key_signed_shift_function_clean_inputs(
c,
concat!("integer::signed::", "rotate_left_parallelized"),
"rotate_left",
|server_key, lhs, rhs| {
server_key.rotate_left_parallelized(lhs, rhs);
},
)
}
fn rotate_right_parallelized(c: &mut Criterion) {
bench_server_key_signed_shift_function_clean_inputs(
c,
concat!("integer::signed::", "rotate_right_parallelized"),
"rotate_right",
|server_key, lhs, rhs| {
server_key.rotate_right_parallelized(lhs, rhs);
},
)
}
criterion_group!(
default_parallelized_ops,
neg_parallelized,
abs_parallelized,
add_parallelized,
sub_parallelized,
mul_parallelized,
// div_parallelized,
// rem_parallelized,
div_rem_parallelized, // For ciphertext div == rem == div_rem
bitand_parallelized,
bitnot_parallelized,
bitor_parallelized,
bitxor_parallelized,
left_shift_parallelized,
right_shift_parallelized,
rotate_left_parallelized,
rotate_right_parallelized,
);
criterion_group!(
default_parallelized_ops_comp,
max_parallelized,
min_parallelized,
eq_parallelized,
ne_parallelized,
lt_parallelized,
le_parallelized,
gt_parallelized,
ge_parallelized,
signed_if_then_else_parallelized,
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_mul_parallelized,
display_name: mul
@@ -370,32 +533,32 @@ define_server_key_bench_binary_signed_clean_inputs_fn!(
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_eq_parallelized,
display_name: eq
display_name: equal
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_ne_parallelized,
display_name: ne
display_name: not_equal
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_le_parallelized,
display_name: le
display_name: less_or_equal
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_lt_parallelized,
display_name: lt
display_name: less_than
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_ge_parallelized,
display_name: ge
display_name: greater_or_equal
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_gt_parallelized,
display_name: gt
display_name: greater_than
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
@@ -410,13 +573,13 @@ define_server_key_bench_binary_signed_clean_inputs_fn!(
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_div_rem_parallelized,
display_name: div_rem,
display_name: div_mod,
sample_size: 10,
);
define_server_key_bench_binary_signed_clean_inputs_fn!(
method_name: unchecked_div_rem_floor_parallelized,
display_name: div_rem_floor,
display_name: div_mod_floor,
sample_size: 10,
);
@@ -528,7 +691,7 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F, G>(
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits_scalar_{bit_size}");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let (cks, sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
let encrypt_one_value = || {
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
@@ -651,111 +814,248 @@ macro_rules! define_server_key_bench_binary_scalar_clean_inputs_fn (
}
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_add_parallelized,
display_name: add,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_sub_parallelized,
display_name: sub,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_mul_parallelized,
display_name: mul,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: signed_scalar_div_parallelized,
display_name: div,
rng_func: div_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: signed_scalar_rem_parallelized,
display_name: modulo,
rng_func: div_scalar
);
// define_server_key_bench_binary_scalar_clean_inputs_fn!(
// method_name: signed_scalar_div_rem_parallelized,
// display_name: div_mod,
// rng_func: div_scalar
// );
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_bitand_parallelized,
display_name: bitand,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_bitxor_parallelized,
display_name: bitxor,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_bitor_parallelized,
display_name: bitor,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_left_shift_parallelized,
display_name: left_shift,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_right_shift_parallelized,
display_name: right_shift,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_rotate_left_parallelized,
display_name: rotate_left,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_rotate_right_parallelized,
display_name: rotate_right,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_max_parallelized,
display_name: max,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_min_parallelized,
display_name: min,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_eq_parallelized,
display_name: equal,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_ne_parallelized,
display_name: not_equal,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_lt_parallelized,
display_name: less_than,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_le_parallelized,
display_name: less_or_equal,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_gt_parallelized,
display_name: greater_than,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: scalar_ge_parallelized,
display_name: greater_or_equal,
rng_func: default_scalar
);
criterion_group!(
default_scalar_parallelized_ops,
scalar_add_parallelized,
scalar_sub_parallelized,
scalar_mul_parallelized,
signed_scalar_div_parallelized,
signed_scalar_rem_parallelized, // For scalar rem == div_rem
// signed_scalar_div_rem_parallelized,
scalar_bitand_parallelized,
scalar_bitor_parallelized,
scalar_bitxor_parallelized,
scalar_left_shift_parallelized,
scalar_right_shift_parallelized,
scalar_rotate_left_parallelized,
scalar_rotate_right_parallelized,
);
criterion_group!(
default_scalar_parallelized_ops_comp,
scalar_max_parallelized,
scalar_min_parallelized,
scalar_eq_parallelized,
scalar_ne_parallelized,
scalar_lt_parallelized,
scalar_le_parallelized,
scalar_gt_parallelized,
scalar_ge_parallelized,
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_left_shift_parallelized,
display_name: scalar_left_shift,
display_name: left_shift,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_right_shift_parallelized,
display_name: scalar_right_shift,
display_name: right_shift,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_rotate_right_parallelized,
display_name: scalar_rotate_right,
display_name: rotate_right,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_rotate_left_parallelized,
display_name: scalar_rotate_left,
display_name: rotate_left,
rng_func: shift_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_mul_parallelized,
display_name: scalar_mul,
display_name: mul,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_bitand_parallelized,
display_name: scalar_bitand,
display_name: bitand,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_bitor_parallelized,
display_name: scalar_bitor,
display_name: bitor,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_bitxor_parallelized,
display_name: scalar_bitxor,
display_name: bitxor,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_eq_parallelized,
display_name: scalar_eq,
display_name: equal,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_ne_parallelized,
display_name: scalar_ne,
display_name: not_equal,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_le_parallelized,
display_name: scalar_le,
display_name: less_or_equal,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_lt_parallelized,
display_name: scalar_lt,
display_name: less_than,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_ge_parallelized,
display_name: scalar_ge,
display_name: greater_or_equal,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_gt_parallelized,
display_name: scalar_gt,
display_name: greater_than,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_max_parallelized,
display_name: scalar_max,
display_name: max,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_scalar_min_parallelized,
display_name: scalar_min,
display_name: min,
rng_func: default_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_signed_scalar_div_rem_parallelized,
display_name: scalar_div_rem,
display_name: div_mod,
rng_func: div_scalar
);
define_server_key_bench_binary_scalar_clean_inputs_fn!(
method_name: unchecked_signed_scalar_div_parallelized,
display_name: scalar_div,
display_name: div,
rng_func: div_scalar
);
@@ -786,12 +1086,14 @@ criterion_group!(
unchecked_scalar_min_parallelized,
);
criterion_group!(default_ops, signed_if_then_else_parallelized,);
fn main() {
match env::var("__TFHE_RS_BENCH_OP_FLAVOR") {
Ok(val) => {
match val.to_lowercase().as_str() {
"default" => default_parallelized_ops(),
"default_comp" => default_parallelized_ops_comp(),
"default_scalar" => default_scalar_parallelized_ops(),
"default_scalar_comp" => default_scalar_parallelized_ops_comp(),
"unchecked" => unchecked_ops(),
"unchecked_comp" => unchecked_ops_comp(),
"unchecked_scalar" => unchecked_scalar_ops(),
@@ -800,10 +1102,8 @@ fn main() {
};
}
Err(_) => {
unchecked_ops();
unchecked_ops_comp();
unchecked_scalar_ops();
unchecked_scalar_ops_comp();
default_parallelized_ops();
default_scalar_parallelized_ops();
}
};

View File

@@ -393,7 +393,7 @@ fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("programmable_bootstrap");
let param = WOPBS_PARAM_MESSAGE_4_NORM2_6_KS_PBS;
let param_set: ShortintParameterSet = param.try_into().unwrap();
let param_set: ShortintParameterSet = param.into();
let pbs_params = param_set.pbs_parameters().unwrap();
let keys = KEY_CACHE_WOPBS.get_from_param((pbs_params, param));
@@ -402,14 +402,14 @@ fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let clear = rng.gen::<usize>() % param.message_modulus.0;
let mut ct = cks.encrypt_without_padding(clear as u64);
let 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:?}");
bench_group.bench_function(&id, |b| {
b.iter(|| {
let _ = wopbs_key.programmable_bootstrapping_native_crt(&mut ct, &vec_lut);
let _ = wopbs_key.programmable_bootstrapping_native_crt(&ct, &vec_lut);
})
});

View File

@@ -1,4 +1,4 @@
#[cfg(feature = "__c_api")]
#[cfg(all(feature = "__c_api", not(feature = "__force_skip_cbindgen")))]
fn gen_c_api() {
use std::env;
use std::path::PathBuf;
@@ -32,14 +32,15 @@ fn gen_c_api() {
}
extern crate cbindgen;
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let crate_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into();
let package_name = env::var("CARGO_PKG_NAME").unwrap();
let output_file = target_dir()
.join(format!("{package_name}.h"))
.display()
.to_string();
let output_file = target_dir().join(format!("{package_name}.h"));
let parse_expand_features_vec = vec![
// Note that this list may not be complete, but as macro expansion is used mostly/only for
// the C API and the HL API, this is fine, if the C API build fails or generates invalid
// headers then you likely need to add other features that will be forwarded to Cargo
// expand
#[cfg(feature = "__c_api")]
"__c_api",
#[cfg(feature = "boolean-c-api")]
@@ -63,8 +64,8 @@ fn gen_c_api() {
};
cbindgen::Builder::new()
.with_crate(crate_dir.clone())
.with_config(cbindgen::Config::from_root_or_default(crate_dir))
.with_crate(crate_dir.as_path())
.with_config(cbindgen::Config::from_file(crate_dir.join("cbindgen.toml")).unwrap())
.with_parse_expand(&parse_expand_vec)
.with_parse_expand_features(&parse_expand_features_vec)
.generate()
@@ -73,6 +74,6 @@ fn gen_c_api() {
}
fn main() {
#[cfg(feature = "__c_api")]
#[cfg(all(feature = "__c_api", not(feature = "__force_skip_cbindgen")))]
gen_c_api()
}

View File

@@ -99,8 +99,7 @@ int main(void) {
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_integers_small(&builder);
config_builder_default(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;

View File

@@ -243,8 +243,7 @@ int main(void) {
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_integers_small(&builder);
config_builder_default(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;

View File

@@ -102,8 +102,7 @@ int main(void) {
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_bool(&builder);
config_builder_default(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;

View File

@@ -238,8 +238,8 @@ int main(void) {
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_custom_integers(&builder,
config_builder_default(&builder);
config_builder_use_custom_parameters(&builder,
SHORTINT_PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS);
config_builder_build(builder, &config);
@@ -266,8 +266,8 @@ int main(void) {
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_custom_integers(&builder,
config_builder_default(&builder);
config_builder_use_custom_parameters(&builder,
SHORTINT_PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS);
config_builder_build(builder, &config);

View File

@@ -203,9 +203,7 @@ int main(void) {
ConfigBuilder *builder;
Config *config;
ok = config_builder_all_disabled(&builder);
assert(ok == 0);
ok = config_builder_enable_default_integers(&builder);
ok = config_builder_default(&builder);
assert(ok == 0);
ok = config_builder_build(builder, &config);
assert(ok == 0);
@@ -242,9 +240,7 @@ int main(void) {
ConfigBuilder *builder;
Config *config;
ok = config_builder_all_disabled(&builder);
assert(ok == 0);
ok = config_builder_enable_default_integers_small(&builder);
ok = config_builder_default_with_small_encryption(&builder);
assert(ok == 0);
ok = config_builder_build(builder, &config);
assert(ok == 0);

View File

@@ -114,14 +114,15 @@ bitflags = false
############## Options for How Your Rust library Should Be Parsed ##############
[parse]
parse_deps = true
include = ["tfhe"]
parse_deps = false
include = []
exclude = []
clean = false
extra_bindings = []
[parse.expand]
# Managed by build.rs to programmatically select features when required
crates = []
all_features = false
default_features = true

View File

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

View File

@@ -35,7 +35,7 @@ fn main() {
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. To store the `server_key` in a binary file, you can use the `bincode` library:
```rust
use std::fs::File;
use std::fs::{File, create_dir_all};
use std::io::{Write, Read};
use tfhe::boolean::prelude::*;
@@ -49,7 +49,13 @@ fn main() {
// We serialize the server key to bytes, and store them in a file:
let encoded: Vec<u8> = bincode::serialize(&server_key).unwrap();
let server_key_file = "/tmp/tutorial_server_key.bin";
// Create a tmp dir with the current user name to avoid cluttering the /tmp dir
let user = std::env::var("USER").unwrap_or_else(|_| "unknown_user".to_string());
let tmp_dir_for_user = &format!("/tmp/{user}");
create_dir_all(tmp_dir_for_user).unwrap();
let server_key_file = &format!("{tmp_dir_for_user}/tutorial_server_key.bin");
// We write the server key to a file:
let mut file = File::create(server_key_file)

View File

@@ -8,7 +8,7 @@ Here is an example using the `bincode` serialization library, which serializes t
binary format:
```rust
use std::fs::File;
use std::fs::{File, create_dir_all};
use std::io::{Write, Read};
use tfhe::boolean::prelude::*;
@@ -20,8 +20,14 @@ fn main() {
let encoded_server_key: Vec<u8> = bincode::serialize(&server_key).unwrap();
let encoded_client_key: Vec<u8> = bincode::serialize(&client_key).unwrap();
let server_key_file = "/tmp/ser_example_server_key.bin";
let client_key_file = "/tmp/ser_example_client_key.bin";
// Create a tmp dir with the current user name to avoid cluttering the /tmp dir
let user = std::env::var("USER").unwrap_or_else(|_| "unknown_user".to_string());
let tmp_dir_for_user = &format!("/tmp/{user}");
create_dir_all(tmp_dir_for_user).unwrap();
let server_key_file = &format!("{tmp_dir_for_user}/ser_example_server_key.bin");
let client_key_file = &format!("{tmp_dir_for_user}/ser_example_client_key.bin");
// We write the keys to files:
let mut file = File::create(server_key_file)

View File

@@ -162,11 +162,9 @@ fn main() {
let ct_2 = client_key.encrypt(msg2);
let ct_3 = client_key.encrypt(msg3);
let result = server_key.checked_small_scalar_mul_assign(&mut ct_1, scalar);
assert!(result.is_ok());
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
assert!(result.is_ok());
server_key.checked_small_scalar_mul_assign(&mut ct_1, scalar).unwrap();
server_key.checked_sub_assign(&mut ct_1, &ct_2).unwrap();
let result = server_key.checked_add_assign(&mut ct_1, &ct_3);
assert!(result.is_err());

View File

@@ -40,8 +40,7 @@ use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
let config = ConfigBuilder::default()
.build();
let (client_key, server_key) = generate_keys(config);

View File

@@ -373,14 +373,14 @@ fn main() {
let modulus = client_key.parameters.message_modulus().0 as u64;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let mut ct_1 = client_key.encrypt(msg1);
let mut ct_2 = client_key.encrypt(msg2);
// Compute the lookup table for the bivariate functions
let acc = server_key.generate_lookup_table_bivariate(|x,y| (x.count_ones()
+ y.count_ones()) as u64 % modulus );
let ct_res = server_key.smart_apply_lookup_table_bivariate(&ct_1, &mut ct_2, &acc);
let ct_res = server_key.smart_apply_lookup_table_bivariate(&mut ct_1, &mut ct_2, &acc);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_res);

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