Compare commits

...

229 Commits

Author SHA1 Message Date
Arthur Meyre
58b4089524 chore(docs): add remarks on smart operations taking mutable inputs 2023-07-26 11:57:53 +02:00
Arthur Meyre
98db328de2 fix(integer): set proper MaxDegree for CompressedServerKey
- add shortint API to generate a CompressedServerKey with MaxDegree
- add non regression test based on the user issue
- factorize MaxDegree computation for integer server keys
2023-07-26 10:00:24 +02:00
David Testé
f5fab4db99 chore(bench): run groups of benchmarks using env variable 2023-07-26 09:36:29 +02:00
Arthur Meyre
95f2eef94f chore(doc): fix multiplication typo 2023-07-25 20:51:15 +02:00
David Testé
2c10a792a5 chore(ci): trigger benchmarks only if layers have changed
For example, if only shortint layer related files have changed,
only the shortint benchmarks would be run on push.
However, if any files changed in the common_benches group then
all the benchmarks would be run.
2023-07-25 09:13:41 +02:00
dependabot[bot]
96689443ef chore(deps): bump tj-actions/changed-files from 37.1.2 to 37.4.0
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 37.1.2 to 37.4.0.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](2a968ff601...de0eba3279)

---
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-07-24 13:04:38 +02:00
Arthur Meyre
514cb9e6af feat(core): add concrete-cpu tests for wopbs
- manage luts a bit differently to match TFHE-rs wopbs implementation
2023-07-24 10:56:49 +02:00
Pakorn Nathong
c79da46bb2 feat(integer): expose scalar mul and sub trait 2023-07-24 10:56:14 +02:00
tmontaigu
a8449f1ded feat(integer): allow scalar shift/rotate with more unsigned types
This is mainly for convenience.

Also, rust implements shift by u8, u16..u128 for each types.
(even shift by i8...i128 are implemented).
2023-07-21 13:31:29 +02:00
tmontaigu
11517703e6 fix(integer): remove incorrect bounds
In 35c6aea84b the bounds for
the scalar_div family of functions were changed.

However, the a few bounds `u64: From<T>` were
not removed meaning the functions which still
had these were still stuck with u64 as the max scalar value.

This commit removes the leftover bounds.
2023-07-20 17:10:26 +02:00
tmontaigu
35c6aea84b feat(integer): allow scalar_div/rem up to 256 bits 2023-07-20 13:55:21 +02:00
J-B Orfila
4e37f7e5bf docs(all): TFHE-rs v0.3.0 doc update 2023-07-19 11:08:36 +02:00
tmontaigu
a69a9c727b feat(integer): allow scalar_mul with U256
Same as scalar_sub, the UnsignedInteger bound was to strict, so we create a
`ScalarMultiplier` trait to allow using U256 as a scalar.
2023-07-17 16:30:17 +02:00
tmontaigu
bafb4f9e17 feat(integer): allow scalar sub with 256 bit scalar
The scalar (T) in scalar_sub could be at most a u128
because the bounds were `T: UnsignedInteger` and our
U256 does not implement this trait yet.

To make scalar_sub accept a U256 we create
a smaller trait.
2023-07-17 16:30:17 +02:00
Arthur Meyre
0663d7ca0e chore(ci): use aws ami with the latest updates
- Ubuntu 22.04 based
2023-07-17 15:57:58 +02:00
Arthur Meyre
8112684aae chore(ci): yet another bench fix 2023-07-17 15:51:07 +02:00
Arthur Meyre
f2f4cb7937 chore(ci): select benches to run 2023-07-17 14:16:28 +02:00
Arthur Meyre
e0e6aa845a chore(core): track caller on CiphertextModulus methods that can fail 2023-07-17 14:16:12 +02:00
Arthur Meyre
1455da273d chore(ci): remove auto retry for wasm tests
- the pipe was masking the potential error test
2023-07-17 14:16:00 +02:00
Arthur Meyre
763ad60ff9 chore(ci): fix m1 workflow run conidition 2023-07-17 11:07:32 +02:00
Arthur Meyre
6f4f923951 chore(ci): avoid removing labels when we are not on a PR 2023-07-17 11:07:32 +02:00
dependabot[bot]
5de984f7d6 chore(deps): bump tj-actions/changed-files from 37.0.5 to 37.1.2
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 37.0.5 to 37.1.2.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](54849deb96...2a968ff601)

---
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-07-17 10:32:28 +02:00
dependabot[bot]
640b849d4b chore(deps): bump JS-DevTools/npm-publish from 2.2.0 to 2.2.1
Bumps [JS-DevTools/npm-publish](https://github.com/js-devtools/npm-publish) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/js-devtools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](a25b4180b7...5a85faf05d)

---
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-07-17 10:32:07 +02:00
Arthur Meyre
7d484583ff chore(ci): add more triggers for re-running benches
- the bench "dependency" tree is bigger than first assumed
2023-07-17 09:36:54 +02:00
Arthur Meyre
68aa2ba25a chore(ci): fix workflow dispatch not triggering bench start 2023-07-17 09:36:54 +02:00
Arthur Meyre
228f85d843 chore(tfhe): remove dbg! macro calls and add a Makefile check for it 2023-07-13 19:45:15 +02:00
Arthur Meyre
f982c58538 chore(shortint): make shortint div behavior match integer on div by zero 2023-07-13 13:22:43 +02:00
Arthur Meyre
e2e901c220 chore(ci): fix usage of changed files 2023-07-12 17:27:06 +02:00
Arthur Meyre
507c569eee chore(shortint): add more convenience parameter aliases 2023-07-12 17:26:52 +02:00
Arthur Meyre
c37d9c590b chore(hlapi): remove leftover empty file 2023-07-12 14:55:12 +02:00
Arthur Meyre
549e9e70da chore(benches): need to checkout repo to check changed files for benchmarks 2023-07-10 13:05:17 +02:00
Arthur Meyre
d56afcd8c3 chore(integer): disable most sequence default add tests
- those are too slow and not the most optimized option to perform those
operations
2023-07-10 09:34:10 +02:00
Arthur Meyre
2019cd1708 chore(ci): M1 don't run multibit integer tests (too slow) 2023-07-10 09:34:10 +02:00
Arthur Meyre
3cfee104cb chore(ci): forward profile to shortint and integer test scripts 2023-07-10 09:34:10 +02:00
Arthur Meyre
4b174d552a chore(ci): run all M1 tests in FAST_TESTS=TRUE mode for better coverage 2023-07-10 09:34:10 +02:00
Arthur Meyre
1764c88de0 chore(ci): run schedule build only on public repo 2023-07-10 09:34:10 +02:00
Arthur Meyre
e4af2bad0f chore(test): fix wopbs only test which was using a wrong set of parameters 2023-07-10 09:34:10 +02:00
Arthur Meyre
59ef915095 chore(ci): fix C API build system to manage profiles other than release 2023-07-10 09:34:10 +02:00
Arthur Meyre
10f034171f chore(ci): LTO is causing issues in M1 CI tests use LTO off instead 2023-07-10 09:34:10 +02:00
Arthur Meyre
5e0aff616e chore(ci): run tests on M1 without integer as those are too long
- add a nightly trigger
2023-07-10 09:34:10 +02:00
Arthur Meyre
9687c55eb6 chore(ci): fix c_api_tests.sh to use threads on M1 properly 2023-07-10 09:34:10 +02:00
Arthur Meyre
222c5e1c19 chore(tfhe): misc fixes for error messages 2023-07-10 09:34:10 +02:00
Arthur Meyre
ea47265f15 chore(tfhe): remove unwarranted uses of unsafe when the code is not unsafe
- marking functions unsafe because the computations may be wrong due to a
bad choice of crypto parameters is not in line with the meaning of unsafe
in rust, so remove those uses
2023-07-10 09:33:18 +02:00
David Testé
465b79f42d chore(ci): trigger benchmarks only on specific file changes 2023-07-10 09:30:15 +02:00
tmontaigu
2557b29230 fix(shortint): Ciphertext::copy_from
Ciphertext::copy_from did not copy the degree
resulting in potential bad results for some operations.

This fixes it, and rewrites to use destructuring
in order to prevent such thing from happenning again
(with destructuring, if a member is not destructured,
a compile error is emited)

Also we move the implementation of copy_from into
clone_from.
2023-07-09 20:15:07 +02:00
tmontaigu
490bdaea30 fix(integer): fix U256::copy_to_be_byte_slice
There was a bug in to_be_bytes_slice, it was missing a
`slice.reverse()` (from_be_bytes correctly has it)

The from/to functions have been refactored to used
from_be_bytes / to_be_bytes, etc from the stdlib to
only have one layer of endianess to manage.

The test value used did not catch that so we change the value
used to expose the problem.
2023-07-07 15:29:35 +02:00
tmontaigu
936ac05e51 chore(core): fix typo in SignedInteger trait doc 2023-07-07 10:10:12 +02:00
tmontaigu
d496cfa431 feat(hlapi): bind scalar_bitwise/div/rem operations 2023-07-06 17:57:58 +02:00
Arthur Meyre
16be1c1c1d chore(bench): enable auto integer multi bit bench launch 2023-07-06 17:06:43 +02:00
Arthur Meyre
f2f4e397f1 chore(tfhe): bump version to 0.3.0 2023-06-30 23:10:26 +02:00
David Testé
facc2a162f test(integer): add unit and doc test for bitnot operator 2023-06-30 19:42:18 +02:00
Arthur Meyre
5981a886fd chore(bench): add multi bit key size measurements 2023-06-30 18:37:52 +02:00
tmontaigu
e98315fa60 feat(integer): add division by encrypted value
Adds a simple and slow algorithms for division/remainder
but at least it enables to use of this operators.

This also adds the same implementation in clear
so we will now be able to have u256 div.
2023-06-30 16:26:22 +02:00
Arthur Meyre
6b235f6fef chore(bench): fix issue due to overlapping merge 2023-06-30 13:15:36 +02:00
Arthur Meyre
4d376eea39 chore(bench): proper param name fix for WASM bench 2023-06-30 11:16:31 +02:00
tmontaigu
d93ddbe897 feat(integer): add scalar division/remainder 2023-06-30 09:46:47 +02:00
tmontaigu
189018ed05 feat(hlapi): allow use of multibit for integers 2023-06-30 09:45:14 +02:00
David Testé
fdae4e958c chore(ci): add bitnot operators to integer benchmarks 2023-06-30 09:32:42 +02:00
David Testé
d5ef359a04 chore(ci): use multi-bit params in shortint for pbs benchmarks
Use up-to-date crypto parameters for PBS benchmarks with multi-bit
instead of hardcoded ones.
2023-06-30 09:31:56 +02:00
J-B Orfila
a52cd6454d feat(shortint): add encrypt_message_and_carry 2023-06-29 17:34:36 +02:00
Arthur Meyre
142851792a chore(bench): fix param names 2023-06-29 16:20:58 +02:00
David Testé
e52bc09db5 chore(ci): add integer benchmarks with multi-bit parameters 2023-06-29 15:30:19 +02:00
Arthur Meyre
5bea1e0bc0 chore(ci): fix fast tests launching too many multi bit parameters 2023-06-28 19:14:20 +02:00
Arthur Meyre
224d81378a chore(docs): add information about the KS_PBS/PBS_KS naming "spec" 2023-06-28 19:14:20 +02:00
Arthur Meyre
011cb48ded chore(shortint): update exposed parameters 2023-06-28 19:14:20 +02:00
Arthur Meyre
da05f16c10 chore(shortint): add aliases for "old" parameter sets
- wopbs not included as it's due a heavy rewok
2023-06-28 19:14:20 +02:00
Arthur Meyre
ffc2472c95 chore(shortint): update keycache for CPK params, remove unusable params 2023-06-28 19:14:20 +02:00
Arthur Meyre
c0b82c77fb chore(shortint): plug cpk tests in scripts 2023-06-28 19:14:20 +02:00
Arthur Meyre
b09dc1f3ca chore(tfhe): rename params 2023-06-28 19:14:20 +02:00
J-B Orfila
a8e8a2e555 chore(shortint): update param compact key 2023-06-28 19:14:20 +02:00
David Testé
61819b2cea chore(ci): add ciphertext modulus for boolean crypto parameters 2023-06-28 18:14:27 +02:00
David Testé
1ee4440c0a chore(ci): put casting benchmarks into group to parse results
Fix usage of generics for crypto parameters in utilities.
2023-06-28 12:01:06 +02:00
David Testé
cbfaf63964 chore(ci): add pbs throughput benchmarks
This implies to add a conversion method to CiphertextModulus in
order to create the CryptoParametersRecord struct used as utils.
2023-06-27 18:16:27 +02:00
Arthur Meyre
6ac96bb46a chore(tfhe): dump non deterministic key and use deterministic when required 2023-06-27 16:10:22 +02:00
David Testé
f9b49eeb39 chore(ci): add feature gate for shortint benchmarks in utilities 2023-06-27 16:00:04 +02:00
Arthur Meyre
fdda5c56f2 feat(multibit): give the possibility to select deterministic execution
BREAKING CHANGE:
shortint ServerKey serialization has changed due to the additional info for
deterministic execution carried by the MultiBit variant
2023-06-27 13:21:23 +02:00
tmontaigu
cb20b4ad3a fix(integer): fix strict assert in add_paralellized 2023-06-27 12:54:50 +02:00
David Testé
fb653ef9b2 chore(ci): write shortint casting benchmarks to json file 2023-06-27 12:33:54 +02:00
Arthur Meyre
2e58fe36a4 test(core): add test on noise variance for lwe encryption 2023-06-26 14:27:09 +02:00
tmontaigu
2cbd8c9fd5 feat(integer): implement more U256 operators
This implements the following operators for U256
- BitXor
- Mul
- is_power_of_two
- ilog2
- SubAssign
2023-06-23 18:59:41 +02:00
twiby
11ac8e6cb9 feat(trivium): add bench for casting and packing 2023-06-23 16:01:40 +02:00
twiby
5f635e97fa feat(apps): add Trivium application of TFHE 2023-06-23 16:01:40 +02:00
twiby
7426e441ba feat(hlapi): keys can be derefed into their underlying keys 2023-06-23 16:01:40 +02:00
twiby
8ae799c477 feat(hlapi): impl TryFrom opeartors for GenericInteger: RadixCiphertext and Vec<Ciphertext> 2023-06-23 16:01:40 +02:00
tmontaigu
ee232ed81e feat(integer): add scalar bitwise operations
Nothing much interesting in terms of performance,
we only use the fact that we can 'inspect' the scalar
to avoid unnecessary work.
2023-06-23 15:57:21 +02:00
tmontaigu
16f4c721ab chore(wasm): re-enable tests which were wrongly disabled
Also fix a small typo, in HLAPI error message
2023-06-23 15:02:32 +02:00
Arthur Meyre
7ea13715ee chore(ci): run example tests 2023-06-22 11:57:27 +02:00
Arthur Meyre
a924b6ebde chore(ci): fix actions URL in a few workflows 2023-06-22 10:31:35 +02:00
Arthur Meyre
2b5d39c927 chore(ci): make release idiot proof 2023-06-22 10:31:35 +02:00
Morten Dahl
aafcbd0a3f chore(docs): fix typo 2023-06-21 18:09:12 +02:00
Arthur Meyre
e810b42eb6 chore(tfhe): remove wildcard deps 2023-06-21 16:24:22 +02:00
tmontaigu
352b282149 feat(hlapi): bind not operator (!) 2023-06-20 21:16:40 +02:00
tmontaigu
bc5f648c35 feat(hlapi): bind scalar comparisons 2023-06-20 21:16:40 +02:00
tmontaigu
7f83761fde feat(hlapi): bind not equal 2023-06-20 21:16:40 +02:00
tmontaigu
8c3993def2 feat(hlapi): bind bit rotations 2023-06-20 15:52:47 +02:00
tmontaigu
a361ad339d feat(hlapi): bind shift by encrypted amount
To keep things easy we have to drop the part
where the macro generated the docs.
2023-06-20 15:52:47 +02:00
sarah el kazdadi
aa82d9f19c feat(multibit): implement deterministic multibit pbs 2023-06-20 13:23:44 +02:00
tmontaigu
eae2d8137b refactor(shortint): rename generate_accumulator into generate_lookup_table
This renames the generate_accumulator* family of
functions into `generate_lookup_table`.

The reasoning is that we have `generate_accumulator`
which returns a `LookupTable` type, and you use
that with the `apply_lookup_table` function which is not
coherent.

Accumulator was the name we had originaly and consistently
for these, however lookup_table is probably easier to
understand / guess what it is about.
Also, it is the term used in concrete, the other
Zama product.
2023-06-19 18:09:28 +02:00
tmontaigu
ca127b2878 refactor(all): remove PBSOrder generic marker
The motivation for this change is that having the Big/Small
being a generic parameter of types, makes the code more complex
than it should be.

Also, It was not complete, not all structs had this generic parameter
making the whole thing a bit more clunky.
2023-06-19 15:16:09 +02:00
Arthur Meyre
80b5ce7f63 chore(core): remove Deref and DerefMut for Polynomial
- add indexing and iteration primitives
2023-06-19 13:21:39 +02:00
dependabot[bot]
2469aa0c2a chore(deps): bump actions/checkout from 3.5.2 to 3.5.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.5.2...c85c95e3d7251135ab7dc9ce3241c5835cc595a9)

---
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-06-19 11:47:07 +02:00
Arthur Meyre
48b307c627 chore(ci): fix wasm test docker 2023-06-19 11:46:52 +02:00
Arthur Meyre
8100b2d0de chore(ci): skip super slow integer tests in CI 2023-06-19 09:25:29 +02:00
Arthur Meyre
aab390470c chore(ci): add PR template 2023-06-19 09:25:16 +02:00
aquint-zama
f8f723f42d chore(readme): add citing tfhe-rs section 2023-06-16 14:14:13 +02:00
Arthur Meyre
1afdc71689 chore(tfhe): bump version for pre-release 2023-06-16 14:12:39 +02:00
tmontaigu
120f6b0304 feat(integer): add scalar comparisons
This adds scalar comparisons and min/max

- eq (==) and ne (!=) have similar performances
  to the non scalar version, only gaining a few milliseconds
  when the scalar value is smaller than the encrypted value

- orderings (<, >, <= ,>=) have more interesting
  performances gain the more the scalar value
  is small than the number of encrypted bits in the
  integer ciphertext.

  e.g:
  comparing an encrypted U256 to a value <= u128::MAX
  brings the comparison time from 234 ms to 194 ms

  comparing an encrypted U256 to a value <= u64::MAX
  brings the comparison time from 234 ms to 169 ms
2023-06-16 10:41:39 +02:00
Petar Ivanov
1d817c45d5 chore(makefile): add experimental deterministic FFT target for the C API
`Duration` is only needed when `experimental-force_fft_algo_dif4` is
set. We add a cfg directive to avoid a compiler warning.
2023-06-15 16:14:11 +03:00
David Testé
5690796da4 chore(ci): write boolean keys results file at the correct location 2023-06-14 18:47:32 +02:00
twiby
00c4eb417b fix(shortint): key switching key doctests 2023-06-14 18:45:54 +02:00
David Testé
42374db7cb chore(ci): use correct path to wasm pk generation result file 2023-06-14 15:57:59 +02:00
twiby
f98127498e feat(integer): add CastingKey struct to allow users to switch between integer parameter sets.
Possibilities are limited: you can only cast to a parameter set with the exact same representation: same message and carry size, same number of blocks in a radix representation, and same basis in crt representation.
2023-06-14 13:53:12 +02:00
twiby
d70729668e feat(shortint): add CastingKey struct to allow users to change server_key during a server circuit 2023-06-14 13:53:12 +02:00
twiby
8d339f2fbf feat(boolean): add CastingKey struct to allow users to change server_key during a server circuit 2023-06-14 13:53:12 +02:00
David Testé
c4a73f4f44 chore(ci): use correct relative path to wasm bench directory
Parsing program is using tfhe/ as working directory. Thus providing
a relative path starting with tfhe/ would result to an error while
trying to walk the directory.
2023-06-14 09:32:36 +02:00
tmontaigu
bd061dc85c refactor(hlpai): remove ServerKeyOp trait and macros
Since 8b3d31ae8a
integers use the same serve key, so
the `GenericIntegerServerKey<P>` type was removed.

Since `GenericIntegrServerKey<P>` does not exist,
there is much less need for the collection
of server key traits (ServerKeyAdd, SeverKeySub).

This commit removes them, as well as the macro layer
that was used to implement it
2023-06-13 12:57:03 +02:00
tmontaigu
2defb5a669 feat(c_api): safer destroys
This adds a null check in the different destroy function
of the C API. The performance impact of this should
be negligible / inexistant but should make usage more
ergonomic and safer.

Also this renames the detroy functions from being named
`destroy_shortint_*` | `destroy_boolean_*` to
`shortint_destroy_*` | `boolean_destroy_*` as it is more
coherent with the rest of the API where functions starts with `shortint`
or `boolean`.
2023-06-13 09:38:56 +02:00
David Testé
1b0f3631d4 chore(ci): fix wasm benchmarks and boolean keys measurement
Now use the CI version of the make recipe to run WASM client
benchmarks. In addition, boolean keys and wasm parsing is fixed
so that benchmarks_parameters directory is created and populated
under tfhe directory.
2023-06-12 18:30:36 +02:00
David Testé
06ddfe893a chore(ci): notify slack channel in case of benchmarks failure 2023-06-12 15:20:42 +02:00
David Testé
b69c9e7e7a chore(ci): create profile for wasm client bench and add in workflow 2023-06-12 15:20:13 +02:00
David Testé
18ed2e29a1 chore(ci): create fast feedback unit test profile
This is done to get quick feedback to developpers in a Pull Request.
It tests shorint level with only three sets of parameters. Integer
level is tested with only the default operations with two sets of
parameters.
This profile will be automatically triggered on each push in a
pull request. Conversely the full suite of test will also be
triggered automatically but once the review is approved.
2023-06-12 15:19:56 +02:00
Arthur Meyre
3a17ebd2fa feat(c_api): add entry point to generate LWE multi bit BSK 2023-06-12 14:18:13 +02:00
Agnes Leroy
dd15fd1b05 fix(core): fix multi bit bsk number of GGSWs check 2023-06-12 14:18:13 +02:00
dependabot[bot]
097ea6500c chore(deps): bump actions/checkout from 3.5.2 to 3.5.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8e5e7e5ab8...c85c95e3d7)

---
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-06-12 13:51:34 +02:00
Arthur Meyre
9e307a8945 chore(hlapi): add example to measure CPK and CCTL sizes
This also includes key generation time in WASM web client side
2023-06-12 11:41:21 +02:00
Arthur Meyre
f8b497a4b8 chore(ci): fix integer bench workflow uploads 2023-06-12 10:23:13 +02:00
tmontaigu
189f02b696 refactor(hlapi): simplify wrapping of booleans
The way the boolean type was
wrapped was done the same way
shortints and integers were wrapped.

This was so that the internal code was
consistent despite not needing the same complexity.

Now that integers are wrapped differently it make
sense to remove the consistency constraint and
simplify the way booleans are wrapped in the HLAPI
2023-06-09 17:04:29 +02:00
tmontaigu
5654fe7981 feat(integer): scalar_mul generic over UnsignedInteger
This makes the scalar_mul family of operation
accepts any scalar of type T that implements
the UnsignedInteger traits.

This unlocks scalar_multiplication with
scalar being a u128.
2023-06-09 15:21:24 +02:00
Arthur Meyre
2b83a1fec0 chore(ci): add a parser to output csv files for integer benchmarks
- will simplify "just to see" benchmarks output parsing to share when
iterating on performance work
2023-06-09 14:27:49 +02:00
tmontaigu
efac3c842f feat(c_api): add #[repr(C)] for boolean parameters
The same thing was done in 3508019cd2
for shortint.

This does it for booleans
2023-06-09 11:53:22 +02:00
Arthur Meyre
7dbb4485bc chore(shortint): use the right noise at encryption time 2023-06-09 10:49:17 +02:00
aquint-zama
a5906bb7cb chore(tfhe): add a Code of Conduct 2023-06-08 14:06:29 +02:00
Jeremy Shulman
90b7494acd chore(doc): attach tutorials to doc 2023-06-08 14:05:46 +02:00
Arthur Meyre
3508019cd2 feat(core): Add Compact Public Key
- Based on "TFHE Public-Key Encryption Revisited "
  https://eprint.iacr.org/2023/603.pdf

Co-authored-by: tmontaigu <thomas.montaigu@laposte.net>
2023-06-07 19:47:50 +02:00
Arthur Meyre
200c8a177a feat(core): add std multi-bit bootstrapping 2023-06-07 16:12:37 +02:00
Arthur Meyre
2f6c1cf0b5 chore(ci): add docs alias make target for doc 2023-06-07 14:18:49 +02:00
tmontaigu
b96027f417 feat(integer): improve default sub latency 2023-06-07 11:04:11 +02:00
tmontaigu
90c850ca0d feat(integer): improve scalar add,sub and negation
- scalar_add now uses the same parallel carry propagation algorithm
  as the add function.

- scalar_sub now uses the same parallel carry propagation algorithm
  as the sub function.

- the 'default' negation function uses the now improved scalar_add
  to be faster

- unchecked_scalar_add, smart_scalar_add, checked_scalar_add, scalar_add
  have been updated to work on generic scalar type so it should work
  on u32, u64, u128, U256, etc

- unchecked_scalar_sub, smart_scalar_sub, checked_scalar_sub, scalar_sub
  have been updated to work on generic scalar type so it should work
  on u32, u64, u128.
  As U256 does not yet implement the UnsignedInteger trait, its not
  usable yet as a scalar type for the sub operation.

- The HLAPI is still locked to u64 scalars, it will be updated
  when most / all scalar ops are ready
2023-06-06 19:56:56 +02:00
Arthur Meyre
c8d3008a8d chore(shortint): proper ThreadCount serialization for bootstrapping key
- skip thread_count on serialization, deserialize using the function to
properly populate thread_count
2023-06-06 16:58:23 +02:00
David Testé
08c264f193 chore(ci): put wasm tests in their own workflow
This is mostly done to avoid failure on AWS tests (core, boolean,
shortint, ...) workflow due to flaky tests in WASM.
2023-06-06 14:02:52 +02:00
twiby
4ae202d8a4 refactor(tfhe): provide CiphertextBase with functions to convert from a generic type OpOrder to a specific struct.
This allows removing all calls to std::mem::transmute in shortint/engine/server_side/mod.rs, isolating unsafe blocks in the conversion functions. This makes the code safer and more likely to panic! in case of an error.
2023-06-06 12:19:56 +02:00
dependabot[bot]
7eb8601540 chore(deps): bump JS-DevTools/npm-publish from 2.1.0 to 2.2.0
Bumps [JS-DevTools/npm-publish](https://github.com/JS-DevTools/npm-publish) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/JS-DevTools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](541aa6b21b...a25b4180b7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-06 10:23:30 +02:00
tmontaigu
8a1691c536 chore(wasm): remove serialization in web test
In the web wasm test we serialize the public key
to print its size (38_931_6265 bytes) this
means we hold the public key twice in ram.

I suspect this causes frequent out of
memory errors which then result in the
test timing out.

So we remove that hoping it has a positive impact
2023-06-02 17:19:04 +02:00
Arthur Meyre
d1cb55ba24 chore(tfhe): add multi bit shortint and integer tests
- default tests do not run multi bit PBS as it's not yet deterministic
- only radix parallel currently use multi bit pbs in integer
- remove determinism checks for some unchecked ops
- 4_4 multi bit parameters are disabled for now as they seem to introduce
too much noise
2023-06-02 16:00:28 +02:00
Arthur Meyre
2b9a49db87 chore(tfhe): switch to using Into for PBS parameters conversion
- it seems generally better for some "Self conversion" i.e. Into<A> for A
seems to work better than From<A> for A
2023-06-02 16:00:28 +02:00
Arthur Meyre
62ddb24f00 chore(ci): add multibit to key cache generation 2023-06-02 16:00:28 +02:00
Arthur Meyre
c6ae463b41 feat(shortint): add the possibility to use multi bit PBS 2023-06-02 16:00:28 +02:00
tmontaigu
4947eefad4 fix(u256): align with rust for shift behaviours 2023-06-02 12:00:42 +02:00
tmontaigu
71209e3927 feat(integer): make scalar shift match rust when shift >= bit size
When the scalar value denoting the shift was bigger or equal to
the total  number of bits in the ciphertext we would return zeros.

To match more the rust behaviour as well as the behaviour of
non scalar shift / rotate, the scalar shift will now remove
any higher bits of the clear shift value
2023-06-02 11:35:54 +02:00
tmontaigu
2a66ea3d16 feat(intger): add shifts and rotates on encrypted values
This implemantation is base on barrel shifters
which are used un hardware
2023-06-02 11:35:54 +02:00
tmontaigu
d4ff1f5595 feat(wasm): add parralellism in wasm API and add wasm for HLAPI
Co-authored-by: David Testé <david.teste@zama.ai>
2023-06-02 11:13:12 +02:00
Arthur Meyre
8ae92a960d chore(ci): add multibit workflow 2023-06-02 08:55:42 +02:00
tmontaigu
b042c2f7d6 refactor(integer): improve decomposition/recomposition into blocks
This new implementation should hopefully be a little bit easier to understand.

But more importantly it is more general/generic,
the previous implementation required the input type to be able to be described as u64 words,
the new one works for any type (as long as needed trait are implemented)

Also the new implementation is separated from the encryption code,
meaning it will be usable by scalar operation, which will allow us
to deduplicate code and start making scalar ops support scalar values
that are on more than 64-bits.
2023-06-01 18:13:34 +02:00
tmontaigu
e307da5c7f feat(integer): make eq (==) faster and add ne (!=) 2023-05-31 19:03:02 +02:00
Arthur Meyre
3d5b88d608 chore(core): encode the proper expectation wrt to ciphertext modulus
- we don't manage any non native moduli but rather native-compatible moduli
so update the asserts accordingly
2023-05-30 15:39:14 +02:00
Arthur Meyre
4fbf0691c5 chore(core): rename get_scaling_to_native_torus
- function now named get_power_of_two_scaling_to_native_torus to emphasize
it's reserved to power of 2 moduli
2023-05-30 15:39:14 +02:00
Arthur Meyre
5d277e85b9 feat(core): add non native decomposer 2023-05-30 15:39:14 +02:00
Arthur Meyre
778eea30e9 chore(tfhe): remove anyhow, just use Box<dyn std::error::Error> 2023-05-30 11:55:43 +02:00
tmontaigu
63247fa227 chore(sha256_example): use array_fn 2023-05-25 00:22:01 +02:00
David Testé
799291a1f0 docs(tfhe): format sha256_bool and add make recipes to run it 2023-05-25 00:22:01 +02:00
Sexosexosexo
509fe7a63e docs(tfhe): add boolean sha256 tutorial
Clap dev dependency added
2023-05-25 00:22:01 +02:00
tmontaigu
4eac45f0c6 fix(dark_market): fix change cwd logic 2023-05-24 23:30:26 +02:00
David Testé
ddb3451087 docs(tfhe): format dark market example add make recipe to run it 2023-05-24 23:30:26 +02:00
Yagiz Senal
e66a329e33 docs(tfhe): add dark market tutorial 2023-05-24 23:30:26 +02:00
David Testé
d79b1d9b19 docs(tfhe): format regex_engine and add make recipes to run it 2023-05-24 22:11:53 +02:00
Rick Klomp
b501cc078a docs(tfhe): add FHE Regex Pattern Matching Engine
this includes a tutorial and an example implementation for the regex bounty
2023-05-24 22:11:53 +02:00
tmontaigu
800878d89e feat(hlapi): add CompressedPublicKey decompression 2023-05-23 14:19:35 +02:00
tmontaigu
20d0e81bae feat(boolean): add CompressedPublicKey 2023-05-19 19:07:16 +02:00
tmontaigu
d3dbf4ecc9 feat(integer): allow decompressing CompressedPublicKey 2023-05-19 15:32:25 +02:00
tmontaigu
c20ca07cd3 chore(ci): reduce number of test-threads
Reduce number of test-threads being spawned
to reduce propability if tests getting killed due
to out of memory
2023-05-17 15:58:27 +02:00
tmontaigu
9f6c7e9139 feat(hlapi): add CompressedServerKey
Now that WopPBS key are optional in the hlapi
we can have a CompressedServerKey.
If a user tries to create a CompressedServerKey
but has enabled function evaluation on integers
(WopPBS) then it will panic as WopPBS are not yet compressible.
And 'stuffing' the non-compressed wopbs-key in the
compressed server key, would defeat the purpose of
compressed server key, as WopPBS key makes of for
the vast majority of the space used.

Also having CompressedServerKey is required to
be able to have wasm API of the hlapi
as wasm cannot generate normal server key.
2023-05-17 11:15:37 +02:00
David Testé
3c8d6a6f8b chore(ci): handle aws tests in pull request from forked repository 2023-05-17 08:42:19 +02:00
Arthur Meyre
1c837fa6f0 test(core): add normality test based on Shapiro-Francia 2023-05-16 10:12:28 +02:00
tmontaigu
1ec7e4762a feat(integer): make wopbs compile on wasm
The goal here is just to make the code compile
and not allow js api to generate wopbs key yet.
2023-05-15 22:06:36 +02:00
tmontaigu
20fb697d57 refactor(hlapi): disable WopPBS by default in hlapi
In the HLAPI, the WopPBS is enabled by default,
meaning the WopPBS key is generated when integers
are enabled.

This is not really good as the wopbs key is huge
(~700MB with PARAM_2_2) and only used for function evaluation
which does not scale for all types exposed by the halpi
and is still a bit experimental so not really advertised in the docs.

Also keys for wopbs are not compressible yet
(that is why the HLAPI does not yet have a CompressedServerKey).

So disabling wopbs by default will enable to have a compressed server
key that actually compresse things.
2023-05-15 19:01:53 +02:00
tmontaigu
0429d56cf3 chore(U256): add small tests 2023-05-15 11:40:44 +02:00
tmontaigu
509bf3e284 docs(bench): update results of benchmarks in the docs 2023-05-12 21:58:47 +02:00
Arthur Meyre
b2fc1d5266 refactor(shortint): make a difference between PBS and Wopbs parameters
- preparatory work to manage several PBS implementations and harmonize
parameters management

BREAKING CHANGE:
- parameters structures changed
- gen_keys for integer now takes parameters by value to uniformize with
shortint
2023-05-12 17:20:05 +02:00
Arthur Meyre
62d94dbee8 chore(tfhe): fix double Example heading in docstring 2023-05-12 17:20:05 +02:00
Agnes Leroy
fbe911d7db chore(tfhe): hard set number of threads to 10 for the multi-bit PBS
It's the optimal value measured on an m6i.metal instance where we run the benchmarks
2023-05-12 15:12:11 +02:00
tmontaigu
ba72faf828 chore(readme): remove non-needed mut in boolean example 2023-05-11 22:25:12 +02:00
tmontaigu
c387b9340f feat(integer): improve mul and scalar mul
This improves the mul and scalar_mul algorithms
to be faster

The improvement is made within the code
that was responsible for summing up all
the terms by making better use of carries
and avoiding uncessary propagations.

The scalar mul forwards the call to a right shift
when the scalar is a power of two as it just cost one
PBS so it will always be faster.

For 64-bits, target-cpu=native + avx512:
- mul before: 3.4s
- mul after: 900ms
2023-05-11 14:07:11 +02:00
Arthur Meyre
cbb7d30fb8 chore(core): avoid having branching depending on secret values in PKE 2023-05-11 11:12:43 +02:00
David Testé
6e4a707eff chore(ci): compute throughput as operations per second
Since most of the operations are over 1 ms, there is no point
to compute the number of operation per millisecond.
2023-05-11 08:58:45 +02:00
tmontaigu
06b700f904 feat(integer): improve parallel algorithms for add/sub
This adds fully parallel algorithms for the addition and subtraction

These algorithms take ciphertexts with clean carries and
return creates a sum ciphertext that also has clean carries.

The carries are propagated in parallel, using
parallel algorithms for prefix sum / cumulative sum.

The is one based on Hillis and Steele, it the fastest
but uses a lot of threads.

The other on Blelloch, it requires less threads but is a
bit slower.

256bits addition using param_2_2 goes down from ~2.7s to:
- 364ms using Hillis and Steele
- 474ms using Blelloch

The commit also adds bitwise not, as it it necessary for the
subtraction.
2023-05-10 14:43:40 +02:00
dependabot[bot]
cfbabf7480 chore(deps): bump JS-DevTools/npm-publish from 2.0.0 to 2.1.0
Bumps [JS-DevTools/npm-publish](https://github.com/JS-DevTools/npm-publish) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/JS-DevTools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](0be441d808...541aa6b21b)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-09 11:15:16 +02:00
tmontaigu
291ed9026f feat(hlapi): add casting between integer types
This adds the casting of integer types.

Downcasting truncates blocks.
Upcasting appends 0s

Casting is done via the introduced `cast_from` associated
function and the `cast_into` method. They are the equivalent of
the `From` and `Into` traits.

It was not possible to implement casting by implementing the
standard `From` and `Into` traits as initially planned because:

```rust
impl<P1, P2> From<GenericInteger<P1>> for GenericInteger<P2>
where P1: IntegerParameter,
      P2: Integer Parameter, {
    fn from(_: GenericInteger<P1>) -> Self {
        todo!()
    }
}
```

As it conflicts the blanket impl found in the stdlib
`impl<T> From<T> for T;` as P1, P2 may be the same and we have no way
of telling the compiler to consider this impl only when P1 != P2.
So we had to create our own stuff
2023-05-05 21:43:10 +02:00
tmontaigu
610f0010b8 refactor(hlapi): remove RefCell used to wrap integers
Our integer types are based on tfhe::integer.

Originally, operators (+, -, *, <<, etc) were mapped to "smart" operations
until commit "ee96a0ff185fedb9c4467a5b0c8195798c30b19f" where we swapped
to "default" ops.

The motivation of swapping from smart to default was that default ops
timing was always the same, so its easier to predict and reason about when
comparing with available benchmarks.
However, they may give worse performance depending
on the computations being done (addition/subtraction heavy or not).

In the High level API, we overloaded operators on const ref e.g. `&a + &b`
but as we initially mapped to smart operations we needed interior mutability.
RefCell was chosen, mainly because using Mutexes would have allowed
users to write "fake" parallel code, that is, the code compiles but is not
truly parallel due to the mutexes. RefCell makes writting parallel code
harder but If you manage to do it, its truly parallel.

After moving to default ops for the hlapi, we kept the inner RefCells
to allow time to decide to retract the change.

The final choice is to keep default ops as the default, so that means
we can remove the RefCells.

Entry points to smart operation will be added later to enable
'power users' to explicitely try them and see if they bring
improvement(s) for them.
Letting advanced user explicitely handle mutability of smart
operations and let them choose their synchronisation is likely a better
choice than making it for them as its an important choice.
2023-05-05 18:21:43 +02:00
tmontaigu
8b3d31ae8a refactor(hlapi): use one unique key for integers
This refactor of the inner workings of the High Level API
makes it so that all integer types share and use the same key
to encrypt their blocks

Before this commit, users that wanted to use integers via the
hlapi needed to select the type amongst the ones available
and enable it (eg i want to use 16 bits integers so I call
enable_default_uint16).

This meant that if users wanted to use many integer types
they would have to manually enable them. Since each type had its
own key, it meant they were completely separate (no casting possible)
and the different key type `ClientKey`, `ServerKey` would become very
big. So in practice you would stick to one and only integer type in your
program.

With this changes, users that wishes to use integers, will just need
to enable them (enable_default_integers()), and will get access to
all statically difined integer types (FheUint8, FheUint16, etc)
at the cost of one key for of these types.

This reduces complexity, memory footprint and,
will enable to introduce in commits later the ability to cast between
integer types.

BREAKING CHANGES:
 - Serialized keys are not backward compatible
 - enable_default_uint[8,16,etc] become enable_default_integers
2023-05-05 16:05:58 +02:00
sarah el kazdadi
98539aaa61 fix(pbs): fix bug in rounding code in f128 pbs 2023-05-05 15:32:59 +02:00
Arthur Meyre
9a80a01dc3 feat(integer): add trim/extend APIs for radix ciphertexts 2023-05-04 09:46:06 +02:00
tmontaigu
ecf9d50058 feat(integer): add parallelized scalar rotate_left/right
Like shifts, rotates are implemented by combining
a rotation of the block and bivariate PBSs in case
the rotation number `n` is not a multiple of the number
of bits in a block.

Since the behaviour of rotations is to 'cycle' bits
back the end/beginning of the 'bit slice' (i.e. no bits is ever lost like
it can with shifts), the performance is always the same when
(n % nb_bits_in block) != 0. However the implementation is simpler.

So assuming a machine where the number of threads
is >= to the ciphertext's number of block, the operation
cost one bivariate PBS.
2023-05-02 17:52:44 +02:00
Arthur Meyre
65e4aab38d chore(shortint): fix docstrings which were mixing big and small key params 2023-05-02 17:21:39 +02:00
Arthur Meyre
ac348870ba refactor(shortint): add encryption key choice in parameters
BREAKING CHANGE:
- Parameters layout change
- C API removal of SHORTINT_NATIVE_MODULUS which was a leftover from
a refactor
2023-05-02 17:21:39 +02:00
Arthur Meyre
6adfcaa5f7 chore(tfhe): bump version to 0.3.0 2023-05-02 11:10:14 +02:00
Arthur Meyre
bc6bbe66d9 chore(shortint): fix some wopbs function signature 2023-05-02 11:10:04 +02:00
Arthur Meyre
871d4aea17 refactor(core): refactor CiphertextModulus to be less error prone 2023-04-28 16:40:33 +02:00
Arthur Meyre
f81376b762 chore(ci): start benches only on our repo 2023-04-28 11:17:31 +02:00
Arthur Meyre
64813bae18 chore(tfhe): as seen there are uses of ilog2 which come from rust 1.67 2023-04-28 11:01:06 +02:00
Arthur Meyre
16ce2a8a3f refactor(wopbs): manage LUTs for wopbs to avoid copies 2023-04-28 09:45:11 +02:00
tmontaigu
f018987eac feat(integer): improve scalar shifts performances
This reworks the left/right scalar shift method.

This new implementation takes more advantage of the
radix representation property and use a combo
of shifting/moving blocks via a rotate_left/right
optionnaly followed by in-block shifting and propagation
to other blocks.

This new implementation requires the carries to be empty
(not sure what the preconditions were for the previous implementation)
and the output will also have clean carries.
Requiring empty carries allows to do the shift in a way
that scales well with the number of blocks as we can use truly parallel
operations.

This means that the time required to shift is dependent
in the shift value, which should not be a security problem
as its a clear value. (The previous implementation also
had timing that depended on the shift value)

There are two scenarios possible:
- The shift only require to move blocks -> its fast
- The shift requires moving + in-block shifting -> slower,
  but still faster than the previous implementation.

Worst case is when shift is less than the number of bits
in a block.

This also changes the type of the `shift` parameter from
`usize` to `u64` to be consistent with other scalar operations.

The follwing pseudo bench code on 64 bits
gives the following time ranges:

With changes:
```
unchecked_left: BenchStat {
    min: 16.743µs
    max: 526.518563ms
    mean: 150.77871ms
}
unchecked_left_parallelized: BenchStat {
    min: 17.291µs
    max: 94.408455ms
    mean: 30.092279ms
}
unchecked_right: BenchStat {
    min: 16.723µs
    max: 548.417332ms
    mean: 160.345234ms
}
unchecked_right_parallelized: BenchStat {
    min: 16.978µs
    max: 97.955322ms
    mean: 33.500562ms
}
Measured in 37.890296743s
```

Previous code:
```
unchecked_left: BenchStat {
    min: 1.055401595s
    max: 1.156574075s
    mean: 1.085648592s
}
unchecked_left_parallelized: BenchStat {
    min: 559.636545ms
    max: 630.83338ms
    mean: 584.747893ms
}
unchecked_right: BenchStat {
    min: 1.055041354s
    max: 2.314891255s
    mean: 1.644513996s
}
unchecked_right_parallelized: BenchStat {
    min: 562.017144ms
    max: 1.275945891s
    mean: 894.812286ms
}
Measured in 421.412913883s
```

```rust
use rand::Rng;
use std::time::Instant;
use tfhe::integer::{gen_keys, RadixClientKey};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;

const NB_CTXT: usize = 32;
const NB_TEST: usize = 100;

struct BenchStat {
    min: Option<std::time::Duration>,
    max: Option<std::time::Duration>,
    sum: std::time::Duration,
    count: u32,
}

impl BenchStat {
    fn update(&mut self, elapsed: std::time::Duration) {
        if self.min.is_none() {
            self.min = Some(elapsed);
        } else {
            self.min = self.min.map(|l| l.min(elapsed));
        }
        if self.max.is_none() {
            self.max = Some(elapsed);
        } else {
            self.max = self.max.map(|l| l.max(elapsed));
        }
        self.sum += elapsed;
        self.count += 1;
    }

    fn print(&self) {
        println!("BenchStat {{");
        println!("    min: {:?}", self.min.unwrap());
        println!("    max: {:?}", self.max.unwrap());
        println!("    mean: {:?}", self.sum / self.count);
        println!("}}");
    }
}

type ShiftType = u64;

fn main() {
    let mut unchecked_left_timing = BenchStat::default();
    let mut unchecked_left_parallelized_timing = BenchStat::default();
    let mut unchecked_right_timing = BenchStat::default();
    let mut unchecked_right_parallelized_timing = BenchStat::default();

    let total = Instant::now();

    let param = PARAM_MESSAGE_2_CARRY_2;
    let (cks, sks) = gen_keys(&param);
    let cks = RadixClientKey::from((cks, NB_CTXT));

    let mut rng = rand::thread_rng();

    //Nb of bits to shift
    let tmp_f64 = param.message_modulus.0 as f64;
    let nb_bits = tmp_f64.log2().floor() as usize * NB_CTXT;
    let modulus = (param.message_modulus.0 as u128).pow(NB_CTXT as u32);
    assert_eq!(nb_bits, 64);

    for i in 0..NB_TEST {
        println!("{} / {NB_TEST}", i + 1);
        let clear = rng.gen::<u128>() % modulus;
        let scalar = rng.gen::<u128>() % nb_bits as u128;

        println!("clear: {clear}, scalar: {scalar}");

        let ct = cks.encrypt(clear);

        {
            let before = Instant::now();
            let ct_res = sks.unchecked_scalar_left_shift(&ct, scalar as ShiftType);
            unchecked_left_timing.update(before.elapsed());
            //assert!(ct_res.block_carries_are_empty());
            let dec_res: u128 = cks.decrypt(&ct_res);
            assert_eq!((clear << scalar) % modulus, dec_res);

            let before = Instant::now();
            let ct_res = sks.unchecked_scalar_left_shift_parallelized(&ct, scalar as ShiftType);
            unchecked_left_parallelized_timing.update(before.elapsed());
            //assert!(ct_res.block_carries_are_empty());
            let dec_res: u128 = cks.decrypt(&ct_res);
            assert_eq!((clear << scalar) % modulus, dec_res);
        }

        {
            let before = Instant::now();
            let ct_res = sks.unchecked_scalar_right_shift(&ct, scalar as ShiftType);
            unchecked_right_timing.update(before.elapsed());
            // assert!(ct_res.block_carries_are_empty());
            let dec_res: u128 = cks.decrypt(&ct_res);
            assert_eq!((clear >> scalar) % modulus, dec_res);

            let before = Instant::now();
            let ct_res = sks.unchecked_scalar_right_shift_parallelized(&ct, scalar as ShiftType);
            unchecked_right_parallelized_timing.update(before.elapsed());
            //assert!(ct_res.block_carries_are_empty());
            let dec_res: u128 = cks.decrypt(&ct_res);
            assert_eq!((clear >> scalar) % modulus, dec_res);
        }
    }

    print!("unchecked_left: ");
    unchecked_left_timing.print();
    print!("unchecked_left_parallelized: ");
    unchecked_left_parallelized_timing.print();

    print!("unchecked_right: ");
    unchecked_right_timing.print();
    print!("unchecked_right_parallelized: ");
    unchecked_right_parallelized_timing.print();

    println!("Measured in {:?}", total.elapsed());
}
```

BREAKING CHANGE: parameter type changed from usize to u64
2023-04-27 17:18:55 +02:00
Arthur Meyre
20f6c5419b chore(core): re-enable split pbs for u128 2023-04-25 17:42:40 +02:00
Arthur Meyre
58b530f40b chore(doc): fix docstring ref 2023-04-25 17:42:40 +02:00
Arthur Meyre
689ad195f3 refactor(integer): remove usage of Mutex for determinism 2023-04-25 15:25:51 +02:00
sarah el kazdadi
4a1eda25d3 fix(split): fix split pbs backward conversion 2023-04-24 16:08:23 +02:00
Arthur Meyre
af936df064 chore(core): change rng tests to better avoid false failures
- we still check we generate non zero values but add retry conditions or
have less stringent checks, to allow some values to be zero for example as
it's a valid value that can be generated
- each test suite (test and doctest) for these tests ran 1000 times without
failure
2023-04-24 13:26:35 +02:00
dependabot[bot]
0233a69ea6 chore(deps): bump JS-DevTools/npm-publish from 1.4.3 to 2.0.0
Bumps [JS-DevTools/npm-publish](https://github.com/JS-DevTools/npm-publish) from 1.4.3 to 2.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](0f451a9417...0be441d808)

---
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-04-24 12:56:59 +02:00
Arthur Meyre
f72a6ec835 chore(doc): fix typo 2023-04-24 09:30:16 +02:00
David Testé
25a2586eae chore(ci): publish tfhe release on-demand
This will perform on-demand release publication.
It will publish on the following channels:
 * crates.io
 * web and node package on npmjs
2023-04-21 14:39:36 +02:00
Arthur Meyre
c112a43a63 chore(core): add more sanity checks on RNG 2023-04-21 14:36:14 +02:00
Arthur Meyre
2813812380 fix(core): fix rng 2023-04-21 14:36:14 +02:00
tmontaigu
84a6036789 feat(boolean): add BooleanEngine::replace_thread_local
This new associated function allows to replace
the engine used in the thread.
2023-04-20 15:16:13 +02:00
David Testé
658368d0b6 chore(ci): create dummy release workflow
This is done to be able to test the effective worklfow
implementation in a development branch.
2023-04-20 10:26:31 +02:00
Arthur Meyre
9368049adf chore(core): disable split pbs128 2023-04-19 18:38:22 +02:00
David Testé
5e8ca0b52c chore(ci): fix decomposition basis and add bit size to params
Decomposition basis wasn't correctly set to handle CRT. Now it uses
a Vec that would be displayed as a string in the database.
In addition the bit size has been added to ease comparison between
various of them in Grafana.
2023-04-19 17:58:22 +02:00
Arthur Meyre
605cd5b3b0 chore(doc): updated benchmarks for min to reflect the fix done to min/max 2023-04-19 16:56:46 +02:00
David Testé
4bfe9c22d4 chore(ci): remove unused env variable in boolean benchmarks 2023-04-19 16:28:13 +02:00
Arthur Meyre
1c0b36c672 chore(bench): only run avx512 benches 2023-04-19 09:23:01 +02:00
Arthur Meyre
7dccb01a8d fix(integer): fix mul correctness
- update benches accordingly
2023-04-19 09:23:01 +02:00
Arthur Meyre
7bff348367 chore(bench): more multi-bit bench params 2023-04-19 09:03:58 +02:00
tmontaigu
74a5a278b6 fix(hlapi): use correct number of blocks for FheUint32
The FheUint32 was wrongly defined as being 32 blocks of 2 bits
when it should have been 16 blocks.
2023-04-18 18:40:39 +02:00
tmontaigu
426ced3295 feat(hlapi): add trivial encryptions 2023-04-18 15:07:06 +02:00
tmontaigu
7af5fcc7eb feat(integer): add trivial encryption 2023-04-18 15:07:06 +02:00
Arthur Meyre
12d9947149 chore(integer): add non regression test for scalar mul fix 2023-04-18 10:46:19 +02:00
dependabot[bot]
7c54896e68 chore(deps): bump actions/checkout from 3.5.0 to 3.5.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 3.5.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8f4b7f8486...8e5e7e5ab8)

---
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-04-17 11:17:58 +02:00
J-B Orfila
04533dedfe chore(doc): fix typo 2023-04-14 18:30:30 +02:00
J-B Orfila
d01be35557 chore(doc): fix TOML 2023-04-14 18:30:30 +02:00
J-B Orfila
9fc32e2f52 chore(doc): fix dead links 2023-04-14 13:37:03 +02:00
Arthur Meyre
e1e78b8b9d chore(integer): restore empty carry check for default comparator tests
- only extract assign message instead of doing a full propagate as carries
are not supposed to be non zero (though the degree will have grown)
2023-04-14 13:34:35 +02:00
451 changed files with 57849 additions and 14941 deletions

View File

@@ -8,10 +8,10 @@ slow-timeout = "5m"
[[profile.ci.overrides]]
filter = 'test(/^.*param_message_1_carry_[567]$/) or test(/^.*param_message_4_carry_4$/)'
filter = 'test(/^.*param_message_1_carry_[567]_ks_pbs$/) or test(/^.*param_message_4_carry_4_ks_pbs$/)'
retries = 3
[[profile.ci.overrides]]
filter = 'test(/^.*param_message_[23]_carry_[23]$/)'
filter = 'test(/^.*param_message_[23]_carry_[23]_ks_pbs$/)'
retries = 1

13
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,13 @@
<!-- Feel free to delete the template if the PR (bumping a version e.g.) does not fit the template -->
closes: _please link all relevant issues_
### PR content/description
### Check-list:
* [ ] Tests for the changes have been added (for bug fixes / features)
* [ ] Docs have been added / updated (for bug fixes / features)
* [ ] Relevant issues are marked as resolved/closed, related issues are linked in the description
* [ ] Check for breaking changes (including serialization changes) and add them to commit message following the conventional commit [specification][conventional-breaking]
[conventional-breaking]: https://www.conventionalcommits.org/en/v1.0.0/#commit-message-with-description-and-breaking-change-footer

View File

@@ -0,0 +1,119 @@
# Run a small subset of shortint and integer tests to ensure quick feedback.
name: Fast AWS Tests on CPU
env:
CARGO_TERM_COLOR: always
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUSTFLAGS: "-C target-cpu=native"
on:
# Allows you to run this workflow manually from the Actions tab as an alternative.
workflow_dispatch:
# All the inputs are provided by Slab
inputs:
instance_id:
description: "AWS instance ID"
type: string
instance_image_id:
description: "AWS instance AMI ID"
type: string
instance_type:
description: "AWS instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
type: string
fork_repo:
description: 'Name of forked repo as user/repo'
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
type: string
jobs:
fast-tests:
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ inputs.runner_name }}
steps:
# Step used for log purpose.
- name: Instance configuration used
run: |
echo "ID: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Fork repo: ${{ inputs.fork_repo }}"
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: stable
default: true
- name: Run core tests
run: |
AVX512_SUPPORT=ON make test_core_crypto
- name: Run boolean tests
run: |
make test_boolean
- name: Run user docs tests
run: |
make test_user_doc
- name: Run js on wasm API tests
run: |
make test_nodejs_wasm_api_in_docker
- name: Gen Keys if required
run: |
make gen_key_cache
- name: Run shortint tests
run: |
BIG_TESTS_INSTANCE=TRUE FAST_TESTS=TRUE make test_shortint_ci
- name: Run integer tests
run: |
BIG_TESTS_INSTANCE=TRUE FAST_TESTS=TRUE make test_integer_ci
- name: Run shortint multi-bit tests
run: |
BIG_TESTS_INSTANCE=TRUE FAST_TESTS=TRUE make test_shortint_multi_bit_ci
- name: Run integer multi-bit tests
run: |
BIG_TESTS_INSTANCE=TRUE FAST_TESTS=TRUE make test_integer_multi_bit_ci
- name: Run high-level API tests
run: |
make test_high_level_api
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Fast AWS tests finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -25,26 +25,35 @@ on:
request_id:
description: 'Slab request ID'
type: string
matrix_item:
description: 'Build matrix item'
fork_repo:
description: 'Name of forked repo as user/repo'
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
type: string
jobs:
integer-tests:
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ github.event.inputs.runner_name }}
runs-on: ${{ inputs.runner_name }}
steps:
# Step used for log purpose.
- name: Instance configuration used
run: |
echo "ID: ${{ github.event.inputs.instance_id }}"
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
echo "Type: ${{ github.event.inputs.instance_type }}"
echo "Request ID: ${{ github.event.inputs.request_id }}"
echo "ID: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Fork repo: ${{ inputs.fork_repo }}"
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- name: Checkout tfhe-rs
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- name: Set up home
run: |

View File

@@ -0,0 +1,90 @@
name: AWS Multi Bit Tests on CPU
env:
CARGO_TERM_COLOR: always
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUSTFLAGS: "-C target-cpu=native"
on:
# Allows you to run this workflow manually from the Actions tab as an alternative.
workflow_dispatch:
# All the inputs are provided by Slab
inputs:
instance_id:
description: "AWS instance ID"
type: string
instance_image_id:
description: "AWS instance AMI ID"
type: string
instance_type:
description: "AWS instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
type: string
fork_repo:
description: 'Name of forked repo as user/repo'
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
type: string
jobs:
multi-bit-tests:
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ inputs.runner_name }}
steps:
# Step used for log purpose.
- name: Instance configuration used
run: |
echo "ID: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Fork repo: ${{ inputs.fork_repo }}"
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: stable
default: true
- name: Gen Keys if required
run: |
make GEN_KEY_CACHE_MULTI_BIT_ONLY=TRUE gen_key_cache
- name: Run shortint multi-bit tests
run: |
make test_shortint_multi_bit_ci
- name: Run integer multi-bit tests
run: |
make test_integer_multi_bit_ci
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Shortint tests finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -25,26 +25,35 @@ on:
request_id:
description: 'Slab request ID'
type: string
matrix_item:
description: 'Build matrix item'
fork_repo:
description: 'Name of forked repo as user/repo'
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
type: string
jobs:
shortint-tests:
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ github.event.inputs.runner_name }}
runs-on: ${{ inputs.runner_name }}
steps:
# Step used for log purpose.
- name: Instance configuration used
run: |
echo "ID: ${{ github.event.inputs.instance_id }}"
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
echo "Type: ${{ github.event.inputs.instance_type }}"
echo "Request ID: ${{ github.event.inputs.request_id }}"
echo "ID: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Fork repo: ${{ inputs.fork_repo }}"
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- name: Checkout tfhe-rs
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- name: Set up home
run: |
@@ -72,10 +81,6 @@ jobs:
run: |
make test_user_doc
- name: Run js on wasm API tests
run: |
make test_nodejs_wasm_api_in_docker
- name: Gen Keys if required
run: |
make gen_key_cache
@@ -88,6 +93,10 @@ jobs:
run: |
BIG_TESTS_INSTANCE=TRUE make test_high_level_api
- name: Run example tests
run: |
make test_examples
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true

View File

@@ -0,0 +1,87 @@
name: AWS WASM Tests on CPU
env:
CARGO_TERM_COLOR: always
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUSTFLAGS: "-C target-cpu=native"
on:
# Allows you to run this workflow manually from the Actions tab as an alternative.
workflow_dispatch:
# All the inputs are provided by Slab
inputs:
instance_id:
description: "AWS instance ID"
type: string
instance_image_id:
description: "AWS instance AMI ID"
type: string
instance_type:
description: "AWS instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
type: string
fork_repo:
description: 'Name of forked repo as user/repo'
type: string
fork_git_sha:
description: 'Git SHA to checkout from fork'
type: string
jobs:
wasm-tests:
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ inputs.runner_name }}
steps:
# Step used for log purpose.
- name: Instance configuration used
run: |
echo "ID: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Fork repo: ${{ inputs.fork_repo }}"
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
- name: Checkout tfhe-rs
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: stable
default: true
- name: Run js on wasm API tests
run: |
make test_nodejs_wasm_api_in_docker
- name: Run parallel wasm tests
run: |
make install_node
make ci_test_web_js_api_parallel
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "WASM tests finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -5,24 +5,25 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
description: "Instance ID"
type: string
instance_image_id:
description: 'Instance AMI ID'
description: "Instance AMI ID"
type: string
instance_type:
description: 'Instance product type'
description: "Instance product type"
type: string
runner_name:
description: 'Action runner name'
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
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:
run-boolean-benchmarks:
@@ -42,7 +43,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
@@ -57,9 +58,9 @@ jobs:
toolchain: nightly
override: true
- name: Run benchmarks
- name: Run benchmarks with AVX512
run: |
make bench_boolean
make AVX512_SUPPORT=ON bench_boolean
- name: Parse results
run: |
@@ -73,24 +74,8 @@ jobs:
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--throughput
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_boolean
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--hardware ${{ inputs.instance_type }} \
--name-suffix avx512 \
--walk-subdirs \
--throughput \
--append-results
--throughput
- name: Measure key sizes
run: |
@@ -101,7 +86,7 @@ jobs:
python3 ./ci/benchmark_parser.py tfhe/boolean_key_sizes.csv ${{ env.RESULTS_FILENAME }} \
--key-sizes \
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
@@ -109,7 +94,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: zama-ai/slab
path: slab
@@ -117,8 +102,6 @@ jobs:
- name: Send data to Slab
shell: bash
env:
COMPRESSED_RESULTS : ${{ env.RESULTS_FILENAME }}.gz
run: |
echo "Computing HMac on results file"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
@@ -130,3 +113,15 @@ jobs:
-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@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Boolean benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -21,7 +21,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
- name: Run pcc checks
run: |

View File

@@ -5,24 +5,26 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
description: "Instance ID"
type: string
instance_image_id:
description: 'Instance AMI ID'
description: "Instance AMI ID"
type: string
instance_type:
description: 'Instance product type'
description: "Instance product type"
type: string
runner_name:
description: 'Action runner name'
description: "Action runner name"
type: string
request_id:
description: 'Slab 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:
@@ -42,7 +44,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
@@ -57,9 +59,20 @@ jobs:
toolchain: nightly
override: true
- name: Run benchmarks
- name: Run benchmarks with AVX512
run: |
make bench_integer
make AVX512_SUPPORT=ON bench_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@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_csv_integer
path: ${{ env.PARSE_INTEGER_BENCH_CSV_FILE }}
- name: Parse results
run: |
@@ -73,24 +86,8 @@ jobs:
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--throughput
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_integer
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--hardware ${{ inputs.instance_type }} \
--walk-subdirs \
--name-suffix avx512 \
--throughput \
--append-results
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
@@ -99,7 +96,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: zama-ai/slab
path: slab
@@ -118,3 +115,15 @@ jobs:
-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@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Integer benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -0,0 +1,129 @@
# Run integer benchmarks with multi-bit cryptographic parameters on an AWS instance and return parsed results to Slab CI bot.
name: 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 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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
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 bench_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@0b7f8abb1508181956e8e162db84b466c27e18ce
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@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_integer
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
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@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Integer benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -4,11 +4,19 @@ on:
workflow_dispatch:
pull_request:
types: [labeled]
# Have a nightly build for M1 tests
schedule:
# * is a special character in YAML so you have to quote this string
# At 22:00 every day
# Timezone is UTC, so Paris time is +2 during the summer and +1 during winter
- cron: "0 22 * * *"
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:
group: ${{ github.workflow }}-${{ github.head_ref }}
@@ -16,11 +24,11 @@ concurrency:
jobs:
cargo-builds:
if: "github.event_name != 'pull_request' || contains(github.event.label.name, 'm1_test')"
if: ${{ (github.event_name == 'schedule' && github.repository == 'zama-ai/tfhe-rs') || github.event_name == 'workflow_dispatch' || contains(github.event.label.name, 'm1_test') }}
runs-on: ["self-hosted", "m1mac"]
steps:
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
@@ -87,6 +95,19 @@ jobs:
run: |
make test_integer_ci
- name: Gen Keys if required
run: |
make GEN_KEY_CACHE_MULTI_BIT_ONLY=TRUE gen_key_cache
- name: Run shortint multi bit tests
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
remove_label:
name: Remove m1_test label
runs-on: ubuntu-latest
@@ -95,6 +116,7 @@ jobs:
if: ${{ always() }}
steps:
- uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
if: ${{ github.event_name == 'pull_request' }}
with:
labels: m1_test
github_token: ${{ secrets.GITHUB_TOKEN }}

84
.github/workflows/make_release.yml vendored Normal file
View File

@@ -0,0 +1,84 @@
# Publish new release of tfhe-rs on various platform.
name: Publish release
on:
workflow_dispatch:
inputs:
dry_run:
description: "Dry-run"
type: boolean
default: true
push_to_crates:
description: "Push to crate"
type: boolean
default: true
push_web_package:
description: "Push web js package"
type: boolean
default: true
push_node_package:
description: "Push node js package"
type: boolean
default: true
env:
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
publish_release:
name: Publish Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
- name: Publish crate.io package
if: ${{ inputs.push_to_crates }}
env:
CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
DRY_RUN: ${{ inputs.dry_run && '--dry-run' || '' }}
run: |
cargo publish -p tfhe --token ${{ env.CRATES_TOKEN }} ${{ env.DRY_RUN }}
- name: Build web package
if: ${{ inputs.push_web_package }}
run: |
make build_web_js_api
- name: Publish web package
if: ${{ inputs.push_web_package }}
uses: JS-DevTools/npm-publish@5a85faf05d2ade2d5b6682bfe5359915d5159c6c
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json
dry-run: ${{ inputs.dry_run }}
- name: Build Node package
if: ${{ inputs.push_node_package }}
run: |
rm -rf tfhe/pkg
make build_node_js_api
sed -i 's/"tfhe"/"node-tfhe"/g' tfhe/pkg/package.json
- name: Publish Node package
if: ${{ inputs.push_node_package }}
uses: JS-DevTools/npm-publish@5a85faf05d2ade2d5b6682bfe5359915d5159c6c
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json
dry-run: ${{ inputs.dry_run }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Integer benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -5,25 +5,25 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
description: "Instance ID"
type: string
instance_image_id:
description: 'Instance AMI ID'
description: "Instance AMI ID"
type: string
instance_type:
description: 'Instance product type'
description: "Instance product type"
type: string
runner_name:
description: 'Action runner name'
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
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:
run-pbs-benchmarks:
@@ -43,7 +43,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
@@ -84,7 +84,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: zama-ai/slab
path: slab
@@ -103,3 +103,15 @@ jobs:
-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@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "PBS benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -5,25 +5,25 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
description: "Instance ID"
type: string
instance_image_id:
description: 'Instance AMI ID'
description: "Instance AMI ID"
type: string
instance_type:
description: 'Instance product type'
description: "Instance product type"
type: string
runner_name:
description: 'Action runner name'
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
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:
run-shortint-benchmarks:
@@ -43,7 +43,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
@@ -58,9 +58,9 @@ jobs:
toolchain: nightly
override: true
- name: Run benchmarks
- name: Run benchmarks with AVX512
run: |
make bench_shortint
make AVX512_SUPPORT=ON bench_shortint
- name: Parse results
run: |
@@ -74,24 +74,8 @@ jobs:
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--throughput
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_shortint
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--hardware ${{ inputs.instance_type }} \
--walk-subdirs \
--name-suffix avx512 \
--throughput \
--append-results
--throughput
- name: Measure key sizes
run: |
@@ -110,7 +94,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: zama-ai/slab
path: slab
@@ -129,3 +113,15 @@ jobs:
-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@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Shortint benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -4,24 +4,97 @@ name: Start all benchmarks
on:
push:
branches:
- 'main'
- "main"
workflow_dispatch:
inputs:
# The input name must be the name of the slab command to launch
boolean_bench:
description: "Run Boolean benches"
type: boolean
default: true
shortint_bench:
description: "Run shortint benches"
type: boolean
default: true
integer_bench:
description: "Run integer benches"
type: boolean
default: true
integer_multi_bit_bench:
description: "Run integer multi bit benches"
type: boolean
default: true
pbs_bench:
description: "Run PBS benches"
type: boolean
default: true
wasm_client_bench:
description: "Run WASM client benches"
type: boolean
default: true
jobs:
start-benchmarks:
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, pbs_bench]
command: [boolean_bench, shortint_bench, integer_bench, integer_multi_bit_bench, pbs_bench, wasm_client_bench]
runs-on: ubuntu-latest
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
- name: Check for file changes
id: changed-files
uses: tj-actions/changed-files@de0eba32790fb9bf87471b32855a30fc8f9d5fc6
with:
files_yaml: |
common_benches:
- toolchain.txt
- Makefile
- ci/slab.toml
- tfhe/Cargo.toml
- tfhe/src/core_crypto/**
- .github/workflows/start_benchmarks.yml
boolean_bench:
- tfhe/src/boolean/**
- tfhe/benches/boolean/**
- .github/workflows/boolean_benchmark.yml
shortint_bench:
- tfhe/src/shortint/**
- tfhe/benches/shortint/**
- .github/workflows/shortint_benchmark.yml
integer_bench:
- tfhe/src/shortint/**
- tfhe/src/integer/**
- tfhe/benches/integer/**
- .github/workflows/integer_benchmark.yml
integer_multi_bit_bench:
- tfhe/src/shortint/**
- tfhe/src/integer/**
- tfhe/benches/integer/**
- .github/workflows/integer_benchmark.yml
pbs_bench:
- tfhe/src/core_crypto/**
- tfhe/benches/core_crypto/**
- .github/workflows/pbs_benchmark.yml
wasm_client_bench:
- tfhe/web_wasm_parallel_tests/**
- .github/workflows/wasm_client_benchmark.yml
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Start AWS job in Slab
# If manually triggered check that the current bench has been requested
# Otherwise if it's on push check that files relevant to benchmarks have changed
if: (github.event_name == 'workflow_dispatch' && github.event.inputs[matrix.command] == 'true') || (github.event_name == 'push' && (steps.changed-files.outputs.common_benches_any_changed == 'true' || steps.changed-files.outputs[format('{0}_any_changed', matrix.command)] == 'true'))
shell: bash
run: |
echo -n '{"command": "${{ matrix.command }}", "git_ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' > command.json

View File

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

View File

@@ -3,16 +3,32 @@ name: PR AWS build trigger
on:
pull_request:
pull_request_review:
types: [submitted]
jobs:
test:
trigger-tests:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
- name: Launch fast tests
if: ${{ github.event_name == 'pull_request' }}
uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
with:
allow-repeats: true
message: |
@slab-ci cpu_fast_test
- name: Launch full tests suite
if: ${{ github.event_name == 'pull_request_review' && github.event.review.state == 'approved' }}
uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
with:
allow-repeats: true
message: |
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_wasm_test

View File

@@ -0,0 +1,128 @@
# Run WASM client benchmarks on an AWS instance and return parsed results to Slab CI bot.
name: WASM client 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
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
run-wasm-client-benchmarks:
name: Execute WASM client 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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
- name: Set up home
# "Install rust" step require root user to have a HOME directory which is not set.
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install rust
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: nightly
override: true
- name: Run benchmarks
run: |
make install_node
make ci_bench_web_js_api_parallel
- name: Parse results
run: |
make parse_wasm_benchmarks
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 tfhe/wasm_pk_gen.csv ${{ 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 }}" \
--key-gen
- name: Measure public key and ciphertext sizes in HL Api
run: |
make measure_hlapi_compact_pk_ct_sizes
- name: Parse key and ciphertext sizes results
run: |
python3 ./ci/benchmark_parser.py tfhe/hlapi_cpk_and_cctl_sizes.csv ${{ env.RESULTS_FILENAME }} \
--key-gen \
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_wasm
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
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@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "WASM benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ target/
# Some of our bench outputs
/tfhe/benchmarks_parameters
**/*.csv

131
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,131 @@
# Contributor Covenant Code of Conduct
## Our pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting us anonymously through [this form](https://forms.gle/569j3cZqGRFgrR3u9).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][mozilla coc].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][faq]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[faq]: https://www.contributor-covenant.org/faq
[homepage]: https://www.contributor-covenant.org
[mozilla coc]: https://github.com/mozilla/diversity
[translations]: https://www.contributor-covenant.org/translations
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html

View File

@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["tfhe", "tasks"]
members = ["tfhe", "tasks", "apps/trivium"]
[profile.bench]
lto = "fat"
@@ -8,6 +8,10 @@ lto = "fat"
[profile.release]
lto = "fat"
[profile.release_lto_off]
inherits = "release"
lto = "off"
# Compiles much faster for tests and allows reasonable performance for iterating
[profile.devo]
inherits = "dev"

238
Makefile
View File

@@ -7,10 +7,14 @@ RS_BUILD_TOOLCHAIN:=$(shell \
( (echo $(TARGET_ARCH_FEATURE) | grep -q x86) && echo stable) || echo $(RS_CHECK_TOOLCHAIN))
CARGO_RS_BUILD_TOOLCHAIN:=+$(RS_BUILD_TOOLCHAIN)
CARGO_PROFILE?=release
MIN_RUST_VERSION:=1.65
MIN_RUST_VERSION:=$(shell grep rust-version tfhe/Cargo.toml | cut -d '=' -f 2 | xargs)
AVX512_SUPPORT?=OFF
WASM_RUSTFLAGS:=
BIG_TESTS_INSTANCE?=FALSE
GEN_KEY_CACHE_MULTI_BIT_ONLY?=FALSE
PARSE_INTEGER_BENCH_CSV_FILE?=tfhe_rs_integer_benches.csv
FAST_TESTS?=FALSE
BENCH_OP_FLAVOR?=DEFAULT
# 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
@@ -21,6 +25,16 @@ else
AVX512_FEATURE=
endif
ifeq ($(GEN_KEY_CACHE_MULTI_BIT_ONLY),TRUE)
MULTI_BIT_ONLY=--multi-bit-only
else
MULTI_BIT_ONLY=
endif
# Variables used only for regex_engine example
REGEX_STRING?=''
REGEX_PATTERN?=''
.PHONY: rs_check_toolchain # Echo the rust toolchain used for checks
rs_check_toolchain:
@echo $(RS_CHECK_TOOLCHAIN)
@@ -52,6 +66,19 @@ install_cargo_nextest: install_rs_build_toolchain
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-nextest --locked || \
( echo "Unable to install cargo nextest, unknown error." && exit 1 )
.PHONY: install_wasm_pack # Install wasm-pack to build JS packages
install_wasm_pack: install_rs_build_toolchain
@wasm-pack --version > /dev/null 2>&1 || \
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install wasm-pack || \
( echo "Unable to install cargo wasm-pack, unknown error." && exit 1 )
.PHONY: install_node # Install last version of NodeJS via nvm
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' || \
( echo "Unable to install node, unknown error." && exit 1 )
.PHONY: fmt # Format rust code
fmt: install_rs_check_toolchain
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt
@@ -99,10 +126,10 @@ clippy_c_api: install_rs_check_toolchain
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint and the js wasm API
.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 \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_tasks # Run clippy lints on helper tasks crate.
@@ -127,7 +154,8 @@ clippy_fast: clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_ta
gen_key_cache: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example generates_test_keys \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- \
$(MULTI_BIT_ONLY)
.PHONY: build_core # Build core_crypto without experimental features
build_core: install_rs_build_toolchain install_rs_check_toolchain
@@ -167,25 +195,40 @@ 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
.PHONY: build_c_api # Build the C API for boolean and shortint
.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 \
-p tfhe
.PHONY: build_c_api_experimental_deterministic_fft # Build the C API for boolean, shortint and integer with experimental deterministic FFT
build_c_api_experimental_deterministic_fft: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,experimental-force_fft_algo_dif4 \
-p tfhe
.PHONY: build_web_js_api # Build the js API targeting the web browser
build_web_js_api: install_rs_build_toolchain
build_web_js_api: install_rs_build_toolchain install_wasm_pack
cd tfhe && \
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
wasm-pack build --release --target=web \
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api
.PHONY: build_web_js_api_parallel # Build the js API targeting the web browser with parallelism support
build_web_js_api_parallel: install_rs_check_toolchain install_wasm_pack
cd tfhe && \
rustup component add rust-src --toolchain $(RS_CHECK_TOOLCHAIN) && \
RUSTFLAGS="$(WASM_RUSTFLAGS) -C target-feature=+atomics,+bulk-memory,+mutable-globals" rustup run $(RS_CHECK_TOOLCHAIN) \
wasm-pack build --release --target=web \
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api,parallel-wasm-api \
-Z build-std=panic_abort,std
.PHONY: build_node_js_api # Build the js API targeting nodejs
build_node_js_api: install_rs_build_toolchain
build_node_js_api: install_rs_build_toolchain install_wasm_pack
cd tfhe && \
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
wasm-pack build --release --target=nodejs \
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api
.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
@@ -201,14 +244,33 @@ test_boolean: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
.PHONY: test_c_api # Run the tests for the C API
test_c_api: build_c_api
.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 \
-p tfhe \
c_api
.PHONY: test_c_api_c # Run the C tests for the C API
test_c_api_c: build_c_api
./scripts/c_api_tests.sh
.PHONY: test_c_api # Run all the tests for the C API
test_c_api: test_c_api_rs test_c_api_c
.PHONY: test_shortint_ci # Run the tests for shortint ci
test_shortint_ci: install_rs_build_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
./scripts/shortint-tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
FAST_TESTS="$(FAST_TESTS)" \
./scripts/shortint-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)"
.PHONY: test_shortint_multi_bit_ci # Run the tests for shortint ci running only multibit tests
test_shortint_multi_bit_ci: install_rs_build_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/shortint-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)" --multi-bit
.PHONY: test_shortint # Run all the tests for shortint
test_shortint: install_rs_build_toolchain
@@ -218,7 +280,16 @@ test_shortint: install_rs_build_toolchain
.PHONY: test_integer_ci # Run the tests for integer ci
test_integer_ci: install_rs_build_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
./scripts/integer-tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \
--cargo-profile "$(CARGO_PROFILE)"
.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
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
.PHONY: test_integer # Run all the tests for integer
test_integer: install_rs_build_toolchain
@@ -228,7 +299,8 @@ test_integer: install_rs_build_toolchain
.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 -- high_level_api::
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \
-- high_level_api::
.PHONY: test_user_doc # Run tests from the .md documentation
test_user_doc: install_rs_build_toolchain
@@ -236,12 +308,42 @@ test_user_doc: install_rs_build_toolchain
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \
-- test_user_docs::
.PHONY: test_regex_engine # Run tests for regex_engine example
test_regex_engine: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--example regex_engine \
--features=$(TARGET_ARCH_FEATURE),integer
.PHONY: test_sha256_bool # Run tests for sha256_bool example
test_sha256_bool: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--example sha256_bool \
--features=$(TARGET_ARCH_FEATURE),boolean
.PHONY: test_examples # Run tests for examples
test_examples: test_sha256_bool test_regex_engine
.PHONY: test_trivium # Run tests for trivium
test_trivium: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
trivium --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
-- --test-threads=1
.PHONY: test_kreyvium # Run tests for kreyvium
test_kreyvium: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
kreyvium --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
-- --test-threads=1
.PHONY: doc # Build rust doc
doc: install_rs_check_toolchain
RUSTDOCFLAGS="--html-in-header katex-header.html -Dwarnings" \
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" doc \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer --no-deps
.PHONY: docs # Build rust doc alias for doc
docs: doc
.PHONY: format_doc_latex # Format the documentation latex equations to avoid broken rendering.
format_doc_latex:
cargo xtask format_latex_doc
@@ -252,13 +354,15 @@ format_doc_latex:
@printf "\n===============================\n"
.PHONY: check_compile_tests # Build tests in debug without running them
check_compile_tests: build_c_api
check_compile_tests:
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --no-run \
--features=$(TARGET_ARCH_FEATURE),experimental,boolean,shortint,integer,internal-keycache \
-p tfhe
@if [[ "$(OS)" == "Linux" || "$(OS)" == "Darwin" ]]; then \
./scripts/c_api_tests.sh --build-only; \
fi
@if [[ "$(OS)" == "Linux" || "$(OS)" == "Darwin" ]]; then \
"$(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:
@@ -280,19 +384,46 @@ test_nodejs_wasm_api_in_docker: build_nodejs_test_docker
test_nodejs_wasm_api: build_node_js_api
cd tfhe && node --test js_on_wasm_tests
.PHONY: test_web_js_api_parallel # Run tests for the web wasm api
test_web_js_api_parallel: build_web_js_api_parallel
$(MAKE) -C tfhe/web_wasm_parallel_tests test
.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 && \
$(MAKE) -C tfhe/web_wasm_parallel_tests test-ci
.PHONY: no_tfhe_typo # Check we did not invert the h and f in tfhe
no_tfhe_typo:
@./scripts/no_tfhe_typo.sh
.PHONY: no_dbg_log # Check we did not leave dbg macro calls in the rust code
no_dbg_log:
@./scripts/no_dbg_calls.sh
#
# Benchmarks
#
.PHONY: bench_integer # Run benchmarks for integer
bench_integer: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) \
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 --
.PHONY: bench_integer_multi_bit # Run benchmarks for 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) \
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench integer-bench \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p tfhe --
.PHONY: bench_shortint # Run benchmarks for shortint
bench_shortint: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
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
@@ -308,23 +439,80 @@ bench_pbs: install_rs_check_toolchain
--bench pbs-bench \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
.PHONY: bench_web_js_api_parallel # Run benchmarks for the web wasm api
bench_web_js_api_parallel: build_web_js_api_parallel
$(MAKE) -C tfhe/web_wasm_parallel_tests bench
.PHONY: ci_bench_web_js_api_parallel # Run benchmarks for the web wasm api
ci_bench_web_js_api_parallel: build_web_js_api_parallel
source ~/.nvm/nvm.sh && \
nvm use node && \
$(MAKE) -C tfhe/web_wasm_parallel_tests bench-ci
#
# Utility tools
#
.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
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example hlapi_compact_pk_ct_sizes \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache
.PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint
measure_shortint_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example shortint_key_sizes \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache
.PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean
measure_boolean_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example boolean_key_sizes \
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache
.PHONY: parse_integer_benches # Run python parser to output a csv containing integer benches data
parse_integer_benches:
python3 ./ci/parse_integer_benches_to_csv.py \
--criterion-dir target/criterion \
--output-file "$(PARSE_INTEGER_BENCH_CSV_FILE)"
.PHONY: parse_wasm_benchmarks # Parse benchmarks performed with WASM web client into a CSV file
parse_wasm_benchmarks: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example wasm_benchmarks_parser \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache \
-- web_wasm_parallel_tests/test/benchmark_results
#
# Real use case examples
#
.PHONY: regex_engine # Run regex_engine example
regex_engine: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example regex_engine \
--features=$(TARGET_ARCH_FEATURE),integer \
-- $(REGEX_STRING) $(REGEX_PATTERN)
.PHONY: dark_market # Run dark market example
dark_market: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example dark_market \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache \
-- fhe-modified fhe-parallel plain fhe
.PHONY: sha256_bool # Run sha256_bool example
sha256_bool: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example sha256_bool \
--features=$(TARGET_ARCH_FEATURE),boolean
.PHONY: pcc # pcc stands for pre commit checks
pcc: no_tfhe_typo check_fmt doc clippy_all check_compile_tests
pcc: no_tfhe_typo no_dbg_log check_fmt doc clippy_all check_compile_tests
.PHONY: fpcc # pcc stands for pre commit checks, the f stands for fast
fpcc: no_tfhe_typo check_fmt doc clippy_fast check_compile_tests
fpcc: no_tfhe_typo no_dbg_log check_fmt doc clippy_fast check_compile_tests
.PHONY: conformance # Automatically fix problems that can be fixed
conformance: fmt

View File

@@ -58,7 +58,7 @@ tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64"]
Note: aarch64-based machines are not yet supported for Windows as it's currently missing an entropy source to be able to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs
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,
to run in release mode with cargo's `--release` flag to have the best performances possible,
eg: `cargo run --release`.
Here is a full example evaluating a Boolean circuit:
@@ -68,7 +68,7 @@ use tfhe::boolean::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (mut client_key, mut server_key) = gen_keys();
let (client_key, server_key) = gen_keys();
// We use the client secret key to encrypt two messages:
let ct_1 = client_key.encrypt(true);
@@ -95,7 +95,7 @@ use tfhe::shortint::prelude::*;
fn main() {
// Generate a set of client/server keys
// with 2 bits of message and 2 bits of carry
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let msg2 = 2;
@@ -111,8 +111,8 @@ fn main() {
// f: x -> sum of the bits of x
let f = |x:u64| x.count_ones() as u64;
// Generate the accumulator for the function
let acc = server_key.generate_accumulator(f);
// Generate the lookup table for the function
let acc = server_key.generate_lookup_table(f);
// Compute the function over the ciphertext using the PBS
let ct_res = server_key.apply_lookup_table(&ct_add, &acc);
@@ -127,12 +127,12 @@ An example using integer:
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
fn main() {
// We create keys to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, 8);
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, 8);
let clear_a = 2382u16;
let clear_b = 29374u16;
@@ -167,6 +167,19 @@ libraries.
<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,

24
apps/trivium/Cargo.toml Normal file
View File

@@ -0,0 +1,24 @@
[package]
name = "tfhe-trivium"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rayon = { version = "1.7.0"}
[target.'cfg(target_arch = "x86_64")'.dependencies.tfhe]
path = "../../tfhe"
features = [ "boolean", "shortint", "integer", "x86_64" ]
[target.'cfg(target_arch = "aarch64")'.dependencies.tfhe]
path = "../../tfhe"
features = [ "boolean", "shortint", "integer", "aarch64-unix" ]
[dev-dependencies]
criterion = { version = "0.4", features = [ "html_reports" ]}
[[bench]]
name = "trivium"
harness = false

204
apps/trivium/README.md Normal file
View File

@@ -0,0 +1,204 @@
# FHE boolean Trivium implementation using TFHE-rs
The cleartext boolean Trivium is available to be built using the function `TriviumStream::<bool>::new`.
This takes as input 2 arrays of 80 bool: the Trivium key and the IV. After initialization, it returns a TriviumStream on
which the user can call `next`, getting the next bit of the cipher stream, or `next_64`, which will compute 64 values at once,
using multithreading to accelerate the computation.
Quite similarly, the function `TriviumStream::<FheBool>::new` will return a very similar object running in FHE space. Its arguments are
2 arrays of 80 FheBool representing the encrypted Trivium key, and the encrypted IV. It also requires a reference to the the server key of the
current scheme. This means that any user of this feature must also have the `tfhe-rs` crate as a dependency.
Example of a Rust main below:
```rust
use tfhe::{ConfigBuilder, generate_keys, FheBool};
use tfhe::prelude::*;
use tfhe_trivium::TriviumStream;
fn get_hexadecimal_string_from_lsb_first_stream(a: Vec<bool>) -> String {
assert!(a.len() % 8 == 0);
let mut hexadecimal: String = "".to_string();
for test in a.chunks(8) {
// Encoding is bytes in LSB order
match test[4..8] {
[false, false, false, false] => hexadecimal.push('0'),
[true, false, false, false] => hexadecimal.push('1'),
[false, true, false, false] => hexadecimal.push('2'),
[true, true, false, false] => hexadecimal.push('3'),
[false, false, true, false] => hexadecimal.push('4'),
[true, false, true, false] => hexadecimal.push('5'),
[false, true, true, false] => hexadecimal.push('6'),
[true, true, true, false] => hexadecimal.push('7'),
[false, false, false, true] => hexadecimal.push('8'),
[true, false, false, true] => hexadecimal.push('9'),
[false, true, false, true] => hexadecimal.push('A'),
[true, true, false, true] => hexadecimal.push('B'),
[false, false, true, true] => hexadecimal.push('C'),
[true, false, true, true] => hexadecimal.push('D'),
[false, true, true, true] => hexadecimal.push('E'),
[true, true, true, true] => hexadecimal.push('F'),
_ => ()
};
match test[0..4] {
[false, false, false, false] => hexadecimal.push('0'),
[true, false, false, false] => hexadecimal.push('1'),
[false, true, false, false] => hexadecimal.push('2'),
[true, true, false, false] => hexadecimal.push('3'),
[false, false, true, false] => hexadecimal.push('4'),
[true, false, true, false] => hexadecimal.push('5'),
[false, true, true, false] => hexadecimal.push('6'),
[true, true, true, false] => hexadecimal.push('7'),
[false, false, false, true] => hexadecimal.push('8'),
[true, false, false, true] => hexadecimal.push('9'),
[false, true, false, true] => hexadecimal.push('A'),
[true, true, false, true] => hexadecimal.push('B'),
[false, false, true, true] => hexadecimal.push('C'),
[true, false, true, true] => hexadecimal.push('D'),
[false, true, true, true] => hexadecimal.push('E'),
[true, true, true, true] => hexadecimal.push('F'),
_ => ()
};
}
return hexadecimal;
}
fn main() {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [false; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i+2], 16).unwrap();
for j in 0..8 {
key[8*(i>>1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [false; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i+2], 16).unwrap();
for j in 0..8 {
iv[8*(i>>1) + j] = val % 2 == 1;
val >>= 1;
}
}
let output_0_63 = "F4CD954A717F26A7D6930830C4E7CF0819F80E03F25F342C64ADC66ABA7F8A8E6EAA49F23632AE3CD41A7BD290A0132F81C6D4043B6E397D7388F3A03B5FE358".to_string();
let cipher_key = key.map(|x| FheBool::encrypt(x, &client_key));
let cipher_iv = iv.map(|x| FheBool::encrypt(x, &client_key));
let mut trivium = TriviumStream::<FheBool>::new(cipher_key, cipher_iv, &server_key);
let mut vec = Vec::<bool>::with_capacity(64*8);
while vec.len() < 64*8 {
let cipher_outputs = trivium.next_64();
for c in cipher_outputs {
vec.push(c.decrypt(&client_key))
}
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output_0_63, hexadecimal[0..64*2]);
}
```
# FHE byte Trivium implementation
The same objects have also been implemented to stream bytes insead of booleans. They can be constructed and used in the same way via the functions `TriviumStreamByte::<u8>::new` and
`TriviumStreamByte::<FheUint8>::new` with the same arguments as before. The `FheUint8` version is significantly slower than the `FheBool` version, because not running
with the same cryptographic parameters. Its interest lie in its trans-ciphering capabilities: `TriviumStreamByte<FheUint8>` implements the trait `TransCiphering`,
meaning it implements the functions `trans_encrypt_64`. This function takes as input a `FheUint64` and outputs a `FheUint64`, the output being
encrypted via tfhe and trivium. For convenience we also provide `trans_decrypt_64`, but this is of course the exact same function.
Other sizes than 64 bit are expected to be available in the future.
# FHE shortint Trivium implementation
The same implementation is also available for generic Ciphertexts representing bits (meant to be used with parameters `PARAM_MESSAGE_1_CARRY_1_KS_PBS`). It uses a lower level API
of tfhe-rs, so the syntax is a little bit different. It also implements the `TransCiphering` trait. For optimization purposes, it does not internally run on the same
cryptographic parameters as the high level API of tfhe-rs. As such, it requires the usage of a casting key, to switch from one parameter space to another, which makes
its setup a little more intricate.
Example code:
```rust
use tfhe::shortint::prelude::*;
use tfhe::shortint::CastingKey;
use tfhe::{ConfigBuilder, generate_keys, FheUint64};
use tfhe::prelude::*;
use tfhe_trivium::TriviumStreamShortint;
fn test_shortint() {
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (hl_client_key, hl_server_key) = generate_keys(config);
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = CastingKey::new((&client_key, &server_key), (&hl_client_key, &hl_server_key));
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i+2], 16).unwrap();
for j in 0..8 {
key[8*(i>>1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i+2], 16).unwrap();
for j in 0..8 {
iv[8*(i>>1) + j] = val % 2;
val >>= 1;
}
}
let output_0_63 = "F4CD954A717F26A7D6930830C4E7CF0819F80E03F25F342C64ADC66ABA7F8A8E6EAA49F23632AE3CD41A7BD290A0132F81C6D4043B6E397D7388F3A03B5FE358".to_string();
let cipher_key = key.map(|x| client_key.encrypt(x));
let cipher_iv = iv.map(|x| client_key.encrypt(x));
let mut ciphered_message = vec![FheUint64::try_encrypt(0u64, &hl_client_key).unwrap(); 9];
let mut trivium = TriviumStreamShortint::new(cipher_key, cipher_iv, &server_key, &ksk);
let mut vec = Vec::<u64>::with_capacity(8);
while vec.len() < 8 {
let trans_ciphered_message = trivium.trans_encrypt_64(ciphered_message.pop().unwrap(), &hl_server_key);
vec.push(trans_ciphered_message.decrypt(&hl_client_key));
}
let hexadecimal = get_hexagonal_string_from_u64(vec);
assert_eq!(output_0_63, hexadecimal[0..64*2]);
}
```
# FHE Kreyvium implementation using tfhe-rs crate
This will work in exactly the same way as the Trivium implementation, except that the key and iv need to be 128 bits now. Available for the same internal types as Trivium, with similar syntax.
`KreyviumStreamByte<FheUint8>` and `KreyviumStreamShortint` also implement the `TransCiphering` trait.
# Testing
If you wish to run tests on this app, please run `cargo test -r trivium -- --test-threads=1` as multithreading provokes interferences between several running
Triviums at the same time.

View File

@@ -0,0 +1,75 @@
use tfhe::prelude::*;
use tfhe::{generate_keys, ConfigBuilder, FheBool};
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 (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [false; 128];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [false; 128];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let cipher_key = key.map(|x| FheBool::encrypt(x, &client_key));
let mut kreyvium = KreyviumStream::<FheBool>::new(cipher_key, iv, &server_key);
c.bench_function("kreyvium bool generate 64 bits", |b| {
b.iter(|| kreyvium.next_64())
});
}
pub fn kreyvium_bool_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [false; 128];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [false; 128];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
c.bench_function("kreyvium bool warmup", |b| {
b.iter(|| {
let cipher_key = key.map(|x| FheBool::encrypt(x, &client_key));
let _kreyvium = KreyviumStream::<FheBool>::new(cipher_key, iv, &server_key);
})
});
}

View File

@@ -0,0 +1,96 @@
use tfhe::prelude::*;
use tfhe::{generate_keys, ConfigBuilder, FheUint64, FheUint8};
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()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [0u8; 16];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [0u8; 16];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let mut kreyvium = KreyviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
c.bench_function("kreyvium byte generate 64 bits", |b| {
b.iter(|| kreyvium.next_64())
});
}
pub fn kreyvium_byte_trans(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [0u8; 16];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [0u8; 16];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let ciphered_message = FheUint64::try_encrypt(0u64, &client_key).unwrap();
let mut kreyvium = KreyviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
c.bench_function("kreyvium byte transencrypt 64 bits", |b| {
b.iter(|| kreyvium.trans_encrypt_64(ciphered_message.clone()))
});
}
pub fn kreyvium_byte_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [0u8; 16];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [0u8; 16];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
c.bench_function("kreyvium byte warmup", |b| {
b.iter(|| {
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let _kreyvium = KreyviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
})
});
}

View File

@@ -0,0 +1,155 @@
use tfhe::prelude::*;
use tfhe::shortint::prelude::*;
use tfhe::shortint::KeySwitchingKey;
use tfhe::{generate_keys, ConfigBuilder, FheUint64};
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 (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();
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = KeySwitchingKey::new(
(&client_key, &server_key),
(&underlying_ck, &underlying_sk),
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [0; 128];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [0; 128];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
c.bench_function("kreyvium 1_1 warmup", |b| {
b.iter(|| {
let cipher_key = key.map(|x| client_key.encrypt(x));
let _kreyvium = KreyviumStreamShortint::new(
cipher_key,
iv,
server_key.clone(),
ksk.clone(),
hl_server_key.clone(),
);
})
});
}
pub fn kreyvium_shortint_gen(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.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();
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = KeySwitchingKey::new(
(&client_key, &server_key),
(&underlying_ck, &underlying_sk),
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [0; 128];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [0; 128];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let cipher_key = key.map(|x| client_key.encrypt(x));
let mut kreyvium = KreyviumStreamShortint::new(cipher_key, iv, server_key, ksk, hl_server_key);
c.bench_function("kreyvium 1_1 generate 64 bits", |b| {
b.iter(|| kreyvium.next_64())
});
}
pub fn kreyvium_shortint_trans(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.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();
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = KeySwitchingKey::new(
(&client_key, &server_key),
(&underlying_ck, &underlying_sk),
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [0; 128];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [0; 128];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let cipher_key = key.map(|x| client_key.encrypt(x));
let ciphered_message = FheUint64::try_encrypt(0u64, &hl_client_key).unwrap();
let mut kreyvium = KreyviumStreamShortint::new(cipher_key, iv, server_key, ksk, hl_server_key);
c.bench_function("kreyvium 1_1 transencrypt 64 bits", |b| {
b.iter(|| kreyvium.trans_encrypt_64(ciphered_message.clone()))
});
}

View File

@@ -0,0 +1,53 @@
use criterion::{criterion_group, criterion_main};
mod trivium_bool;
criterion_group!(
trivium_bool,
trivium_bool::trivium_bool_gen,
trivium_bool::trivium_bool_warmup
);
mod kreyvium_bool;
criterion_group!(
kreyvium_bool,
kreyvium_bool::kreyvium_bool_gen,
kreyvium_bool::kreyvium_bool_warmup
);
mod trivium_shortint;
criterion_group!(
trivium_shortint,
trivium_shortint::trivium_shortint_gen,
trivium_shortint::trivium_shortint_warmup,
trivium_shortint::trivium_shortint_trans
);
mod kreyvium_shortint;
criterion_group!(
kreyvium_shortint,
kreyvium_shortint::kreyvium_shortint_gen,
kreyvium_shortint::kreyvium_shortint_warmup,
kreyvium_shortint::kreyvium_shortint_trans
);
mod trivium_byte;
criterion_group!(
trivium_byte,
trivium_byte::trivium_byte_gen,
trivium_byte::trivium_byte_trans,
trivium_byte::trivium_byte_warmup
);
mod kreyvium_byte;
criterion_group!(
kreyvium_byte,
kreyvium_byte::kreyvium_byte_gen,
kreyvium_byte::kreyvium_byte_trans,
kreyvium_byte::kreyvium_byte_warmup
);
criterion_main!(
trivium_bool,
trivium_shortint,
trivium_byte,
kreyvium_bool,
kreyvium_shortint,
kreyvium_byte,
);

View File

@@ -0,0 +1,75 @@
use tfhe::prelude::*;
use tfhe::{generate_keys, ConfigBuilder, FheBool};
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 (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [false; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [false; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let cipher_key = key.map(|x| FheBool::encrypt(x, &client_key));
let mut trivium = TriviumStream::<FheBool>::new(cipher_key, iv, &server_key);
c.bench_function("trivium bool generate 64 bits", |b| {
b.iter(|| trivium.next_64())
});
}
pub fn trivium_bool_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [false; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [false; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
c.bench_function("trivium bool warmup", |b| {
b.iter(|| {
let cipher_key = key.map(|x| FheBool::encrypt(x, &client_key));
let _trivium = TriviumStream::<FheBool>::new(cipher_key, iv, &server_key);
})
});
}

View File

@@ -0,0 +1,93 @@
use tfhe::prelude::*;
use tfhe::{generate_keys, ConfigBuilder, FheUint64, FheUint8};
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 (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0u8; 10];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0u8; 10];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let mut trivium = TriviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
c.bench_function("trivium byte generate 64 bits", |b| {
b.iter(|| trivium.next_64())
});
}
pub fn trivium_byte_trans(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0u8; 10];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0u8; 10];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let ciphered_message = FheUint64::try_encrypt(0u64, &client_key).unwrap();
let mut trivium = TriviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
c.bench_function("trivium byte transencrypt 64 bits", |b| {
b.iter(|| trivium.trans_encrypt_64(ciphered_message.clone()))
});
}
pub fn trivium_byte_warmup(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0u8; 10];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0u8; 10];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
c.bench_function("trivium byte warmup", |b| {
b.iter(|| {
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let _trivium = TriviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
})
});
}

View File

@@ -0,0 +1,155 @@
use tfhe::prelude::*;
use tfhe::shortint::prelude::*;
use tfhe::shortint::KeySwitchingKey;
use tfhe::{generate_keys, ConfigBuilder, FheUint64};
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 (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();
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = KeySwitchingKey::new(
(&client_key, &server_key),
(&underlying_ck, &underlying_sk),
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
c.bench_function("trivium 1_1 warmup", |b| {
b.iter(|| {
let cipher_key = key.map(|x| client_key.encrypt(x));
let _trivium = TriviumStreamShortint::new(
cipher_key,
iv,
server_key.clone(),
ksk.clone(),
hl_server_key.clone(),
);
})
});
}
pub fn trivium_shortint_gen(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.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();
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = KeySwitchingKey::new(
(&client_key, &server_key),
(&underlying_ck, &underlying_sk),
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let cipher_key = key.map(|x| client_key.encrypt(x));
let mut trivium = TriviumStreamShortint::new(cipher_key, iv, server_key, ksk, hl_server_key);
c.bench_function("trivium 1_1 generate 64 bits", |b| {
b.iter(|| trivium.next_64())
});
}
pub fn trivium_shortint_trans(c: &mut Criterion) {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.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();
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = KeySwitchingKey::new(
(&client_key, &server_key),
(&underlying_ck, &underlying_sk),
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let cipher_key = key.map(|x| client_key.encrypt(x));
let ciphered_message = FheUint64::try_encrypt(0u64, &hl_client_key).unwrap();
let mut trivium = TriviumStreamShortint::new(cipher_key, iv, server_key, ksk, hl_server_key);
c.bench_function("trivium 1_1 transencrypt 64 bits", |b| {
b.iter(|| trivium.trans_encrypt_64(ciphered_message.clone()))
});
}

View File

@@ -0,0 +1,257 @@
//! This module implements the Kreyvium stream cipher, using booleans or FheBool
//! for the representaion of the inner bits.
use crate::static_deque::StaticDeque;
use tfhe::prelude::*;
use tfhe::{set_server_key, unset_server_key, FheBool, ServerKey};
use rayon::prelude::*;
/// Internal trait specifying which operations are necessary for KreyviumStream generic type
pub trait KreyviumBoolInput<OpOutput>:
Sized
+ Clone
+ std::ops::BitXor<Output = OpOutput>
+ std::ops::BitAnd<Output = OpOutput>
+ std::ops::Not<Output = OpOutput>
{
}
impl KreyviumBoolInput<bool> for bool {}
impl KreyviumBoolInput<bool> for &bool {}
impl KreyviumBoolInput<FheBool> for FheBool {}
impl KreyviumBoolInput<FheBool> for &FheBool {}
/// KreyviumStream: a struct implementing the Kreyvium stream cipher, using T for the internal
/// representation of bits (bool or FheBool). To be able to compute FHE operations, it also owns
/// an Option for a ServerKey.
pub struct KreyviumStream<T> {
a: StaticDeque<93, T>,
b: StaticDeque<84, T>,
c: StaticDeque<111, T>,
k: StaticDeque<128, T>,
iv: StaticDeque<128, T>,
fhe_key: Option<ServerKey>,
}
impl KreyviumStream<bool> {
/// Contructor for `KreyviumStream<bool>`: arguments are the secret key and the input vector.
/// Outputs a KreyviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(mut key: [bool; 128], mut iv: [bool; 128]) -> KreyviumStream<bool> {
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_register = [false; 93];
let mut b_register = [false; 84];
let mut c_register = [false; 111];
for i in 0..93 {
a_register[i] = key[128 - 93 + i];
}
for i in 0..84 {
b_register[i] = iv[128 - 84 + i];
}
for i in 0..44 {
c_register[111 - 44 + i] = iv[i];
}
for i in 0..66 {
c_register[i + 1] = true;
}
key.reverse();
iv.reverse();
KreyviumStream::<bool>::new_from_registers(
a_register, b_register, c_register, key, iv, None,
)
}
}
impl KreyviumStream<FheBool> {
/// Constructor for `KreyviumStream<FheBool>`: arguments are the encrypted secret key and input
/// vector, and the FHE server key.
/// Outputs a KreyviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(
mut key: [FheBool; 128],
mut iv: [bool; 128],
sk: &ServerKey,
) -> KreyviumStream<FheBool> {
set_server_key(sk.clone());
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_register = [false; 93].map(|x| FheBool::encrypt_trivial(x));
let mut b_register = [false; 84].map(|x| FheBool::encrypt_trivial(x));
let mut c_register = [false; 111].map(|x| FheBool::encrypt_trivial(x));
for i in 0..93 {
a_register[i] = key[128 - 93 + i].clone();
}
for i in 0..84 {
b_register[i] = FheBool::encrypt_trivial(iv[128 - 84 + i]);
}
for i in 0..44 {
c_register[111 - 44 + i] = FheBool::encrypt_trivial(iv[i]);
}
for i in 0..66 {
c_register[i + 1] = FheBool::encrypt_trivial(true);
}
key.reverse();
iv.reverse();
let iv = iv.map(|x| FheBool::encrypt_trivial(x));
unset_server_key();
KreyviumStream::<FheBool>::new_from_registers(
a_register,
b_register,
c_register,
key,
iv,
Some(sk.clone()),
)
}
}
impl<T> KreyviumStream<T>
where
T: KreyviumBoolInput<T> + std::marker::Send + std::marker::Sync,
for<'a> &'a T: KreyviumBoolInput<T>,
{
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
/// server key
fn new_from_registers(
a_register: [T; 93],
b_register: [T; 84],
c_register: [T; 111],
k_register: [T; 128],
iv_register: [T; 128],
key: Option<ServerKey>,
) -> Self {
let mut ret = Self {
a: StaticDeque::<93, T>::new(a_register),
b: StaticDeque::<84, T>::new(b_register),
c: StaticDeque::<111, T>::new(c_register),
k: StaticDeque::<128, T>::new(k_register),
iv: StaticDeque::<128, T>::new(iv_register),
fhe_key: key,
};
ret.init();
ret
}
/// The specification of Kreyvium includes running 1152 (= 18*64) unused steps to mix up the
/// registers, before starting the proper stream
fn init(&mut self) {
for _ in 0..18 {
self.next_64();
}
}
/// Computes one turn of the stream, updating registers and outputting the new bit.
pub fn next(&mut self) -> T {
match &self.fhe_key {
Some(sk) => set_server_key(sk.clone()),
None => (),
};
let [o, a, b, c] = self.get_output_and_values(0);
self.a.push(a);
self.b.push(b);
self.c.push(c);
self.k.shift();
self.iv.shift();
o
}
/// Computes a potential future step of Kreyvium, n terms in the future. This does not update
/// registers, but rather returns with the output, the three values that will be used to
/// update the registers, when the time is right. This function is meant to be used in
/// parallel.
fn get_output_and_values(&self, n: usize) -> [T; 4] {
assert!(n < 65);
let (((temp_a, temp_b), (temp_c, a_and)), (b_and, c_and)) = rayon::join(
|| {
rayon::join(
|| {
rayon::join(
|| &self.a[65 - n] ^ &self.a[92 - n],
|| &self.b[68 - n] ^ &self.b[83 - n],
)
},
|| {
rayon::join(
|| &(&self.c[65 - n] ^ &self.c[110 - n]) ^ &self.k[127 - n],
|| &(&self.a[91 - n] & &self.a[90 - n]) ^ &self.iv[127 - n],
)
},
)
},
|| {
rayon::join(
|| &self.b[82 - n] & &self.b[81 - n],
|| &self.c[109 - n] & &self.c[108 - n],
)
},
);
let ((o, a), (b, c)) = rayon::join(
|| {
rayon::join(
|| &(&temp_a ^ &temp_b) ^ &temp_c,
|| &temp_c ^ &(&c_and ^ &self.a[68 - n]),
)
},
|| {
rayon::join(
|| &temp_a ^ &(&a_and ^ &self.b[77 - n]),
|| &temp_b ^ &(&b_and ^ &self.c[86 - n]),
)
},
);
[o, a, b, c]
}
/// This calls `get_output_and_values` in parallel 64 times, and stores all results in a Vec.
fn get_64_output_and_values(&self) -> Vec<[T; 4]> {
(0..64)
.into_par_iter()
.map(|x| self.get_output_and_values(x))
.rev()
.collect()
}
/// Computes 64 turns of the stream, outputting the 64 bits all at once in a
/// Vec (first value is oldest, last is newest)
pub fn next_64(&mut self) -> Vec<T> {
match &self.fhe_key {
Some(sk) => {
rayon::broadcast(|_| set_server_key(sk.clone()));
}
None => (),
}
let mut values = self.get_64_output_and_values();
match &self.fhe_key {
Some(_) => {
rayon::broadcast(|_| unset_server_key());
}
None => (),
}
let mut ret = Vec::<T>::with_capacity(64);
while let Some([o, a, b, c]) = values.pop() {
ret.push(o);
self.a.push(a);
self.b.push(b);
self.c.push(c);
}
self.k.n_shifts(64);
self.iv.n_shifts(64);
ret
}
}

View File

@@ -0,0 +1,297 @@
//! This module implements the Kreyvium stream cipher, using u8 or FheUint8
//! for the representaion of the inner bits.
use crate::static_deque::{StaticByteDeque, StaticByteDequeInput};
use tfhe::prelude::*;
use tfhe::{set_server_key, unset_server_key, FheUint8, ServerKey};
use rayon::prelude::*;
/// Internal trait specifying which operations are necessary for KreyviumStreamByte generic type
pub trait KreyviumByteInput<OpOutput>:
Sized
+ Send
+ Sync
+ Clone
+ StaticByteDequeInput<OpOutput>
+ std::ops::BitXor<Output = OpOutput>
+ std::ops::BitAnd<Output = OpOutput>
+ std::ops::Shr<u8, Output = OpOutput>
+ std::ops::Shl<u8, Output = OpOutput>
+ std::ops::Add<Output = OpOutput>
{
}
impl KreyviumByteInput<u8> for u8 {}
impl KreyviumByteInput<u8> for &u8 {}
impl KreyviumByteInput<FheUint8> for FheUint8 {}
impl KreyviumByteInput<FheUint8> for &FheUint8 {}
/// KreyviumStreamByte: a struct implementing the Kreyvium stream cipher, using T for the internal
/// representation of bits (u8 or FheUint8). To be able to compute FHE operations, it also owns
/// an Option for a ServerKey.
/// Since the original Kreyvium registers' sizes are not a multiple of 8, these registers (which
/// store byte-like objects) have a size that is the eigth of the closest multiple of 8 above the
/// originals' sizes.
pub struct KreyviumStreamByte<T> {
a_byte: StaticByteDeque<12, T>,
b_byte: StaticByteDeque<11, T>,
c_byte: StaticByteDeque<14, T>,
k_byte: StaticByteDeque<16, T>,
iv_byte: StaticByteDeque<16, T>,
fhe_key: Option<ServerKey>,
}
impl KreyviumStreamByte<u8> {
/// Contructor for `KreyviumStreamByte<u8>`: arguments are the secret key and the input vector.
/// Outputs a KreyviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(key_bytes: [u8; 16], iv_bytes: [u8; 16]) -> KreyviumStreamByte<u8> {
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_byte_reg = [0u8; 12];
let mut b_byte_reg = [0u8; 11];
let mut c_byte_reg = [0u8; 14];
// Copy key bits into a register
for b in 0..12 {
a_byte_reg[b] = key_bytes[b + 4];
}
// Copy iv bits into a register
for b in 0..11 {
b_byte_reg[b] = iv_bytes[b + 5];
}
// Copy a lot of ones in the c register
c_byte_reg[0] = 252;
for b in 1..8 {
c_byte_reg[b] = 255;
}
// Copy iv bits in the c register
c_byte_reg[8] = (iv_bytes[0] << 4) | 31;
for b in 9..14 {
c_byte_reg[b] = (iv_bytes[b - 9] >> 4) | (iv_bytes[b - 8] << 4);
}
// Key and iv are stored in reverse in their shift registers
let mut key = key_bytes.map(|b| b.reverse_bits());
let mut iv = iv_bytes.map(|b| b.reverse_bits());
key.reverse();
iv.reverse();
let mut ret = KreyviumStreamByte::<u8>::new_from_registers(
a_byte_reg, b_byte_reg, c_byte_reg, key, iv, None,
);
ret.init();
ret
}
}
impl KreyviumStreamByte<FheUint8> {
/// Constructor for `KreyviumStream<FheUint8>`: arguments are the encrypted secret key and input
/// vector, and the FHE server key.
/// Outputs a KreyviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(
key_bytes: [FheUint8; 16],
iv_bytes: [u8; 16],
server_key: &ServerKey,
) -> KreyviumStreamByte<FheUint8> {
set_server_key(server_key.clone());
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_byte_reg = [0u8; 12].map(|x| FheUint8::encrypt_trivial(x));
let mut b_byte_reg = [0u8; 11].map(|x| FheUint8::encrypt_trivial(x));
let mut c_byte_reg = [0u8; 14].map(|x| FheUint8::encrypt_trivial(x));
// Copy key bits into a register
for b in 0..12 {
a_byte_reg[b] = key_bytes[b + 4].clone();
}
// Copy iv bits into a register
for b in 0..11 {
b_byte_reg[b] = FheUint8::encrypt_trivial(iv_bytes[b + 5]);
}
// Copy a lot of ones in the c register
c_byte_reg[0] = FheUint8::encrypt_trivial(252u8);
for b in 1..8 {
c_byte_reg[b] = FheUint8::encrypt_trivial(255u8);
}
// Copy iv bits in the c register
c_byte_reg[8] = FheUint8::encrypt_trivial((&iv_bytes[0] << 4u8) | 31u8);
for b in 9..14 {
c_byte_reg[b] =
FheUint8::encrypt_trivial((&iv_bytes[b - 9] >> 4u8) | (&iv_bytes[b - 8] << 4u8));
}
// Key and iv are stored in reverse in their shift registers
let mut key = key_bytes.map(|b| b.map(|x| (x as u8).reverse_bits() as u64));
let mut iv = iv_bytes.map(|x| FheUint8::encrypt_trivial(x.reverse_bits()));
key.reverse();
iv.reverse();
unset_server_key();
let mut ret = KreyviumStreamByte::<FheUint8>::new_from_registers(
a_byte_reg,
b_byte_reg,
c_byte_reg,
key,
iv,
Some(server_key.clone()),
);
ret.init();
ret
}
}
impl<T> KreyviumStreamByte<T>
where
T: KreyviumByteInput<T> + Send,
for<'a> &'a T: KreyviumByteInput<T>,
{
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
/// server key
fn new_from_registers(
a_register: [T; 12],
b_register: [T; 11],
c_register: [T; 14],
k_register: [T; 16],
iv_register: [T; 16],
sk: Option<ServerKey>,
) -> Self {
Self {
a_byte: StaticByteDeque::<12, T>::new(a_register),
b_byte: StaticByteDeque::<11, T>::new(b_register),
c_byte: StaticByteDeque::<14, T>::new(c_register),
k_byte: StaticByteDeque::<16, T>::new(k_register),
iv_byte: StaticByteDeque::<16, T>::new(iv_register),
fhe_key: sk,
}
}
/// The specification of Kreyvium includes running 1152 (= 18*64) unused steps to mix up the
/// registers, before starting the proper stream
fn init(&mut self) {
for _ in 0..18 {
self.next_64();
}
}
/// Computes 8 potential future step of Kreyvium, b*8 terms in the future. This does not update
/// registers, but rather returns with the output, the three values that will be used to
/// update the registers, when the time is right. This function is meant to be used in
/// parallel.
fn get_output_and_values(&self, b: usize) -> [T; 4] {
let n = b * 8 + 7;
assert!(n < 65);
let (((k, iv), (a1, a2, a3, a4, a5)), ((b1, b2, b3, b4, b5), (c1, c2, c3, c4, c5))) =
rayon::join(
|| {
rayon::join(
|| (self.k_byte.byte(127 - n), self.iv_byte.byte(127 - n)),
|| Self::get_bytes(&self.a_byte, [91 - n, 90 - n, 68 - n, 65 - n, 92 - n]),
)
},
|| {
rayon::join(
|| Self::get_bytes(&self.b_byte, [82 - n, 81 - n, 77 - n, 68 - n, 83 - n]),
|| {
Self::get_bytes(
&self.c_byte,
[109 - n, 108 - n, 86 - n, 65 - n, 110 - n],
)
},
)
},
);
let (((temp_a, temp_b), (temp_c, a_and)), (b_and, c_and)) = rayon::join(
|| {
rayon::join(
|| rayon::join(|| a4 ^ a5, || b4 ^ b5),
|| rayon::join(|| c4 ^ c5 ^ k, || a1 & a2 ^ iv),
)
},
|| rayon::join(|| b1 & b2, || c1 & c2),
);
let (temp_a_2, temp_b_2, temp_c_2) = (temp_a.clone(), temp_b.clone(), temp_c.clone());
let ((o, a), (b, c)) = rayon::join(
|| {
rayon::join(
|| (temp_a_2 ^ temp_b_2) ^ temp_c_2,
|| temp_c ^ ((c_and) ^ a3),
)
},
|| rayon::join(|| temp_a ^ (a_and ^ b3), || temp_b ^ (b_and ^ c3)),
);
[o, a, b, c]
}
/// This calls `get_output_and_values` in parallel 8 times, and stores all results in a Vec.
fn get_64_output_and_values(&self) -> Vec<[T; 4]> {
(0..8)
.into_par_iter()
.map(|i| self.get_output_and_values(i))
.collect()
}
/// Computes 64 turns of the stream, outputting the 64 bits (in 8 bytes) all at once in a
/// Vec (first value is oldest, last is newest)
pub fn next_64(&mut self) -> Vec<T> {
match &self.fhe_key {
Some(sk) => {
rayon::broadcast(|_| set_server_key(sk.clone()));
}
None => (),
}
let values = self.get_64_output_and_values();
match &self.fhe_key {
Some(_) => {
rayon::broadcast(|_| unset_server_key());
}
None => (),
}
let mut bytes = Vec::<T>::with_capacity(8);
for [o, a, b, c] in values {
self.a_byte.push(a);
self.b_byte.push(b);
self.c_byte.push(c);
bytes.push(o);
}
self.k_byte.n_shifts(8);
self.iv_byte.n_shifts(8);
bytes
}
/// Reconstructs a bunch of 5 bytes in a parallel fashion.
fn get_bytes<const N: usize>(
reg: &StaticByteDeque<N, T>,
offsets: [usize; 5],
) -> (T, T, T, T, T) {
let mut ret = offsets
.par_iter()
.rev()
.map(|&i| reg.byte(i))
.collect::<Vec<_>>();
(
ret.pop().unwrap(),
ret.pop().unwrap(),
ret.pop().unwrap(),
ret.pop().unwrap(),
ret.pop().unwrap(),
)
}
}
impl KreyviumStreamByte<FheUint8> {
pub fn get_server_key(&self) -> &ServerKey {
&self.fhe_key.as_ref().unwrap()
}
}

View File

@@ -0,0 +1,205 @@
use crate::static_deque::StaticDeque;
use tfhe::shortint::prelude::*;
use rayon::prelude::*;
/// KreyviumStreamShortint: a struct implementing the Kreyvium stream cipher, using a generic
/// Ciphertext for the internal representation of bits (intended to represent a single bit). To be
/// able to compute FHE operations, it also owns a ServerKey.
pub struct KreyviumStreamShortint {
a: StaticDeque<93, Ciphertext>,
b: StaticDeque<84, Ciphertext>,
c: StaticDeque<111, Ciphertext>,
k: StaticDeque<128, Ciphertext>,
iv: StaticDeque<128, Ciphertext>,
internal_server_key: ServerKey,
transciphering_casting_key: KeySwitchingKey,
hl_server_key: tfhe::ServerKey,
}
impl KreyviumStreamShortint {
/// Contructor for KreyviumStreamShortint: arguments are the secret key and the input vector,
/// and a ServerKey reference. Outputs a KreyviumStream object already initialized (1152
/// steps have been run before returning)
pub fn new(
mut key: [Ciphertext; 128],
mut iv: [u64; 128],
sk: ServerKey,
ksk: KeySwitchingKey,
hl_sk: tfhe::ServerKey,
) -> Self {
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_register: [Ciphertext; 93] = [0; 93].map(|x| sk.create_trivial(x));
let mut b_register: [Ciphertext; 84] = [0; 84].map(|x| sk.create_trivial(x));
let mut c_register: [Ciphertext; 111] = [0; 111].map(|x| sk.create_trivial(x));
for i in 0..93 {
a_register[i] = key[128 - 93 + i].clone();
}
for i in 0..84 {
b_register[i] = sk.create_trivial(iv[128 - 84 + i]);
}
for i in 0..44 {
c_register[111 - 44 + i] = sk.create_trivial(iv[i]);
}
for i in 0..66 {
c_register[i + 1] = sk.create_trivial(1);
}
key.reverse();
iv.reverse();
let iv = iv.map(|x| sk.create_trivial(x));
let mut ret = Self {
a: StaticDeque::<93, Ciphertext>::new(a_register),
b: StaticDeque::<84, Ciphertext>::new(b_register),
c: StaticDeque::<111, Ciphertext>::new(c_register),
k: StaticDeque::<128, Ciphertext>::new(key),
iv: StaticDeque::<128, Ciphertext>::new(iv),
internal_server_key: sk,
transciphering_casting_key: ksk,
hl_server_key: hl_sk,
};
ret.init();
ret
}
/// The specification of Kreyvium includes running 1152 (= 18*64) unused steps to mix up the
/// registers, before starting the proper stream
fn init(&mut self) {
for _ in 0..18 {
self.next_64();
}
}
/// Computes one turn of the stream, updating registers and outputting the new bit.
pub fn next(&mut self) -> Ciphertext {
let [o, a, b, c] = self.get_output_and_values(0);
self.a.push(a);
self.b.push(b);
self.c.push(c);
o
}
/// Computes a potential future step of Kreyvium, n terms in the future. This does not update
/// registers, but rather returns with the output, the three values that will be used to
/// update the registers, when the time is right. This function is meant to be used in
/// parallel.
fn get_output_and_values(&self, n: usize) -> [Ciphertext; 4] {
let (k, iv) = (&self.k[127 - n], &self.iv[127 - n]);
let (a1, a2, a3, a4, a5) = (
&self.a[65 - n],
&self.a[92 - n],
&self.a[91 - n],
&self.a[90 - n],
&self.a[68 - n],
);
let (b1, b2, b3, b4, b5) = (
&self.b[68 - n],
&self.b[83 - n],
&self.b[82 - n],
&self.b[81 - n],
&self.b[77 - n],
);
let (c1, c2, c3, c4, c5) = (
&self.c[65 - n],
&self.c[110 - n],
&self.c[109 - n],
&self.c[108 - n],
&self.c[86 - n],
);
let temp_a = self.internal_server_key.unchecked_add(a1, a2);
let temp_b = self.internal_server_key.unchecked_add(b1, b2);
let mut temp_c = self.internal_server_key.unchecked_add(c1, c2);
self.internal_server_key
.unchecked_add_assign(&mut temp_c, k);
let ((new_a, new_b), (new_c, o)) = rayon::join(
|| {
rayon::join(
|| {
let mut new_a = self.internal_server_key.unchecked_bitand(c3, c4);
self.internal_server_key
.unchecked_add_assign(&mut new_a, a5);
self.internal_server_key.add_assign(&mut new_a, &temp_c);
new_a
},
|| {
let mut new_b = self.internal_server_key.unchecked_bitand(a3, a4);
self.internal_server_key
.unchecked_add_assign(&mut new_b, b5);
self.internal_server_key
.unchecked_add_assign(&mut new_b, &temp_a);
self.internal_server_key.add_assign(&mut new_b, iv);
new_b
},
)
},
|| {
rayon::join(
|| {
let mut new_c = self.internal_server_key.unchecked_bitand(b3, b4);
self.internal_server_key
.unchecked_add_assign(&mut new_c, c5);
self.internal_server_key
.unchecked_add_assign(&mut new_c, &temp_b);
self.internal_server_key.clear_carry_assign(&mut new_c);
new_c
},
|| {
self.internal_server_key.bitxor(
&self.internal_server_key.unchecked_add(&temp_a, &temp_b),
&temp_c,
)
},
)
},
);
[o, new_a, new_b, new_c]
}
/// This calls `get_output_and_values` in parallel 64 times, and stores all results in a Vec.
fn get_64_output_and_values(&self) -> Vec<[Ciphertext; 4]> {
(0..64)
.into_par_iter()
.map(|x| self.get_output_and_values(x))
.rev()
.collect()
}
/// Computes 64 turns of the stream, outputting the 64 bits all at once in a
/// Vec (first value is oldest, last is newest)
pub fn next_64(&mut self) -> Vec<Ciphertext> {
let mut values = self.get_64_output_and_values();
let mut ret = Vec::<Ciphertext>::with_capacity(64);
while let Some([o, a, b, c]) = values.pop() {
ret.push(o);
self.a.push(a);
self.b.push(b);
self.c.push(c);
}
self.k.n_shifts(64);
self.iv.n_shifts(64);
ret
}
pub fn get_internal_server_key(&self) -> &ServerKey {
&self.internal_server_key
}
pub fn get_casting_key(&self) -> &KeySwitchingKey {
&self.transciphering_casting_key
}
pub fn get_hl_server_key(&self) -> &tfhe::ServerKey {
&self.hl_server_key
}
}

View File

@@ -0,0 +1,11 @@
mod kreyvium;
pub use kreyvium::KreyviumStream;
mod kreyvium_byte;
pub use kreyvium_byte::KreyviumStreamByte;
mod kreyvium_shortint;
pub use kreyvium_shortint::KreyviumStreamShortint;
#[cfg(test)]
mod test;

View File

@@ -0,0 +1,378 @@
use tfhe::prelude::*;
use tfhe::{generate_keys, ConfigBuilder, FheBool, FheUint64, FheUint8};
use crate::{KreyviumStream, KreyviumStreamByte, KreyviumStreamShortint, TransCiphering};
// Values for these tests come from the github repo renaud1239/Kreyvium,
// commit fd6828f68711276c25f55e605935028f5e843f43
fn get_hexadecimal_string_from_lsb_first_stream(a: Vec<bool>) -> String {
assert!(a.len() % 8 == 0);
let mut hexadecimal: String = "".to_string();
for test in a.chunks(8) {
// Encoding is bytes in LSB order
match test[4..8] {
[false, false, false, false] => hexadecimal.push('0'),
[true, false, false, false] => hexadecimal.push('1'),
[false, true, false, false] => hexadecimal.push('2'),
[true, true, false, false] => hexadecimal.push('3'),
[false, false, true, false] => hexadecimal.push('4'),
[true, false, true, false] => hexadecimal.push('5'),
[false, true, true, false] => hexadecimal.push('6'),
[true, true, true, false] => hexadecimal.push('7'),
[false, false, false, true] => hexadecimal.push('8'),
[true, false, false, true] => hexadecimal.push('9'),
[false, true, false, true] => hexadecimal.push('A'),
[true, true, false, true] => hexadecimal.push('B'),
[false, false, true, true] => hexadecimal.push('C'),
[true, false, true, true] => hexadecimal.push('D'),
[false, true, true, true] => hexadecimal.push('E'),
[true, true, true, true] => hexadecimal.push('F'),
_ => (),
};
match test[0..4] {
[false, false, false, false] => hexadecimal.push('0'),
[true, false, false, false] => hexadecimal.push('1'),
[false, true, false, false] => hexadecimal.push('2'),
[true, true, false, false] => hexadecimal.push('3'),
[false, false, true, false] => hexadecimal.push('4'),
[true, false, true, false] => hexadecimal.push('5'),
[false, true, true, false] => hexadecimal.push('6'),
[true, true, true, false] => hexadecimal.push('7'),
[false, false, false, true] => hexadecimal.push('8'),
[true, false, false, true] => hexadecimal.push('9'),
[false, true, false, true] => hexadecimal.push('A'),
[true, true, false, true] => hexadecimal.push('B'),
[false, false, true, true] => hexadecimal.push('C'),
[true, false, true, true] => hexadecimal.push('D'),
[false, true, true, true] => hexadecimal.push('E'),
[true, true, true, true] => hexadecimal.push('F'),
_ => (),
};
}
return hexadecimal;
}
fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
assert!(a.len() % 8 == 0);
let mut hexadecimal: String = "".to_string();
for test in a {
hexadecimal.push_str(&format!("{:02X?}", test));
}
return hexadecimal;
}
fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
let mut hexadecimal: String = "".to_string();
for test in a {
hexadecimal.push_str(&format!("{:016X?}", test));
}
return hexadecimal;
}
#[test]
fn kreyvium_test_1() {
let key = [false; 128];
let iv = [false; 128];
let output = "26DCF1F4BC0F1922";
let mut kreyvium = KreyviumStream::<bool>::new(key, iv);
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
vec.push(kreyvium.next());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output, hexadecimal);
}
#[test]
fn kreyvium_test_2() {
let mut key = [false; 128];
let iv = [false; 128];
key[0] = true;
let output = "4FD421D4DA3D2C8A";
let mut kreyvium = KreyviumStream::<bool>::new(key, iv);
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
vec.push(kreyvium.next());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output, hexadecimal);
}
#[test]
fn kreyvium_test_3() {
let key = [false; 128];
let mut iv = [false; 128];
iv[0] = true;
let output = "C9217BA0D762ACA1";
let mut kreyvium = KreyviumStream::<bool>::new(key, iv);
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
vec.push(kreyvium.next());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output, hexadecimal);
}
#[test]
fn kreyvium_test_4() {
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [false; 128];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [false; 128];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let output = "D1F0303482061111";
let mut kreyvium = KreyviumStream::<bool>::new(key, iv);
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
vec.push(kreyvium.next());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(hexadecimal, output);
}
#[test]
fn kreyvium_test_fhe_long() {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [false; 128];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [false; 128];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let output = "D1F0303482061111";
let cipher_key = key.map(|x| FheBool::encrypt(x, &client_key));
let mut kreyvium = KreyviumStream::<FheBool>::new(cipher_key, iv, &server_key);
let mut vec = Vec::<bool>::with_capacity(64);
while vec.len() < 64 {
let cipher_outputs = kreyvium.next_64();
for c in cipher_outputs {
vec.push(c.decrypt(&client_key))
}
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output, hexadecimal);
}
use tfhe::shortint::prelude::*;
#[test]
fn kreyvium_test_shortint_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.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();
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = KeySwitchingKey::new(
(&client_key, &server_key),
(&underlying_ck, &underlying_sk),
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [0; 128];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [0; 128];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let output = "D1F0303482061111".to_string();
let cipher_key = key.map(|x| client_key.encrypt(x));
let ciphered_message = FheUint64::try_encrypt(0u64, &hl_client_key).unwrap();
let mut kreyvium = KreyviumStreamShortint::new(cipher_key, iv, server_key, ksk, hl_server_key);
let trans_ciphered_message = kreyvium.trans_encrypt_64(ciphered_message);
let ciphered_message = trans_ciphered_message.decrypt(&hl_client_key);
let hexadecimal = get_hexagonal_string_from_u64(vec![ciphered_message]);
assert_eq!(output, hexadecimal);
}
#[test]
fn kreyvium_test_clear_byte() {
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key_bytes = [0u8; 16];
for i in (0..key_string.len()).step_by(2) {
key_bytes[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv_bytes = [0u8; 16];
for i in (0..iv_string.len()).step_by(2) {
iv_bytes[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let output = "D1F0303482061111".to_string();
let mut kreyvium = KreyviumStreamByte::<u8>::new(key_bytes, iv_bytes);
let mut vec = Vec::<u8>::with_capacity(8);
while vec.len() < 8 {
let outputs = kreyvium.next_64();
for c in outputs {
vec.push(c)
}
}
let hexadecimal = get_hexagonal_string_from_bytes(vec);
assert_eq!(output, hexadecimal);
}
#[test]
fn kreyvium_test_byte_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key_bytes = [0u8; 16];
for i in (0..key_string.len()).step_by(2) {
key_bytes[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv_bytes = [0u8; 16];
for i in (0..iv_string.len()).step_by(2) {
iv_bytes[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let cipher_key = key_bytes.map(|x| FheUint8::encrypt(x, &client_key));
let output = "D1F0303482061111".to_string();
let mut kreyvium = KreyviumStreamByte::<FheUint8>::new(cipher_key, iv_bytes, &server_key);
let mut vec = Vec::<u8>::with_capacity(8);
while vec.len() < 8 {
let cipher_outputs = kreyvium.next_64();
for c in cipher_outputs {
vec.push(c.decrypt(&client_key))
}
}
let hexadecimal = get_hexagonal_string_from_bytes(vec);
assert_eq!(output, hexadecimal);
}
#[test]
fn kreyvium_test_fhe_byte_transciphering_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.enable_function_evaluation_integers()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB000000000000".to_string();
let mut key = [0u8; 16];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC000000000000".to_string();
let mut iv = [0u8; 16];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let output = "D1F0303482061111".to_string();
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let ciphered_message = FheUint64::try_encrypt(0u64, &client_key).unwrap();
let mut kreyvium = KreyviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
let trans_ciphered_message = kreyvium.trans_encrypt_64(ciphered_message);
let ciphered_message = trans_ciphered_message.decrypt(&client_key);
let hexadecimal = get_hexagonal_string_from_u64(vec![ciphered_message]);
assert_eq!(output, hexadecimal);
}

10
apps/trivium/src/lib.rs Normal file
View File

@@ -0,0 +1,10 @@
mod static_deque;
mod kreyvium;
pub use kreyvium::{KreyviumStream, KreyviumStreamByte, KreyviumStreamShortint};
mod trivium;
pub use trivium::{TriviumStream, TriviumStreamByte, TriviumStreamShortint};
mod trans_ciphering;
pub use trans_ciphering::TransCiphering;

View File

@@ -0,0 +1,4 @@
mod static_deque;
pub use static_deque::StaticDeque;
mod static_byte_deque;
pub use static_byte_deque::{StaticByteDeque, StaticByteDequeInput};

View File

@@ -0,0 +1,141 @@
//! This module implements the StaticByteDeque struct: a deque of bytes. The idea
//! is that this is a wrapper around StaticDeque, but StaticByteDeque has an additional
//! functionnality: it can construct the "intermediate" bytes, made of parts of other bytes.
//! This is pretending to store bits, and allows accessing bits in chunks of 8 consecutive.
use crate::static_deque::StaticDeque;
use tfhe::FheUint8;
/// Internal trait specifying which operations are needed by StaticByteDeque
pub trait StaticByteDequeInput<OpOutput>:
Clone
+ std::ops::Shr<u8, Output = OpOutput>
+ std::ops::Shl<u8, Output = OpOutput>
+ std::ops::BitOr<Output = OpOutput>
{
}
impl StaticByteDequeInput<u8> for u8 {}
impl StaticByteDequeInput<u8> for &u8 {}
impl StaticByteDequeInput<FheUint8> for FheUint8 {}
impl StaticByteDequeInput<FheUint8> for &FheUint8 {}
/// Here T must represent a type covering a byte, like u8 or FheUint8.
#[derive(Clone)]
pub struct StaticByteDeque<const N: usize, T> {
deque: StaticDeque<N, T>,
}
impl<const N: usize, T> StaticByteDeque<N, T>
where
T: StaticByteDequeInput<T>,
for<'a> &'a T: StaticByteDequeInput<T>,
{
/// Constructor always uses a fully initialized array, the first element of
/// which is oldest, the last is newest
pub fn new(_arr: [T; N]) -> Self {
Self {
deque: StaticDeque::<N, T>::new(_arr),
}
}
/// Elements are pushed via a byte element (covering 8 underlying bits)
pub fn push(&mut self, val: T) {
self.deque.push(val)
}
/// computes n shift in a row
pub fn n_shifts(&mut self, n: usize) {
self.deque.n_shifts(n);
}
/// Getter for the internal memory
#[allow(dead_code)]
fn get_arr(&self) -> &[T; N] {
self.deque.get_arr()
}
/// This returns a byte full of zeros, except maybe a one
/// at the specified location, if it is present in the deque
#[allow(dead_code)]
fn bit(&self, i: usize) -> T
where
for<'a> &'a T: std::ops::BitAnd<u8, Output = T>,
{
let byte: &T = &self.deque[i / 8];
let bit_selector: u8 = 1u8 << (i % 8);
byte & bit_selector
}
/// This function reconstructs an intermediate byte if necessary
pub fn byte(&self, i: usize) -> T {
let byte: &T = &self.deque[i / 8];
let bit_idx: u8 = (i % 8) as u8;
if bit_idx == 0 {
return byte.clone();
}
let byte_next: &T = &self.deque[i / 8 + 1];
return (byte << bit_idx) | (byte_next >> (8 - bit_idx as u8));
}
}
#[cfg(test)]
mod tests {
use crate::static_deque::StaticByteDeque;
#[test]
fn byte_deque_test() {
let mut deque = StaticByteDeque::<3, u8>::new([2, 64, 128]);
deque.push(4);
// Youngest: 4
assert!(deque.bit(0) == 0);
assert!(deque.bit(1) == 0);
assert!(deque.bit(2) > 0);
assert!(deque.bit(3) == 0);
assert!(deque.bit(4) == 0);
assert!(deque.bit(5) == 0);
assert!(deque.bit(6) == 0);
assert!(deque.bit(7) == 0);
// second youngest: 128
assert!(deque.bit(8 + 0) == 0);
assert!(deque.bit(8 + 1) == 0);
assert!(deque.bit(8 + 2) == 0);
assert!(deque.bit(8 + 3) == 0);
assert!(deque.bit(8 + 4) == 0);
assert!(deque.bit(8 + 5) == 0);
assert!(deque.bit(8 + 6) == 0);
assert!(deque.bit(8 + 7) > 0);
// oldest: 64
assert!(deque.bit(16 + 0) == 0);
assert!(deque.bit(16 + 1) == 0);
assert!(deque.bit(16 + 2) == 0);
assert!(deque.bit(16 + 3) == 0);
assert!(deque.bit(16 + 4) == 0);
assert!(deque.bit(16 + 5) == 0);
assert!(deque.bit(16 + 6) > 0);
assert!(deque.bit(16 + 7) == 0);
assert_eq!(deque.byte(0), 4u8);
assert_eq!(deque.byte(1), 9u8);
assert_eq!(deque.byte(2), 18u8);
assert_eq!(deque.byte(3), 36u8);
assert_eq!(deque.byte(4), 72u8);
assert_eq!(deque.byte(5), 144u8);
assert_eq!(deque.byte(6), 32u8);
assert_eq!(deque.byte(7), 64u8);
assert_eq!(deque.byte(8), 128u8);
assert_eq!(deque.byte(9), 0u8);
assert_eq!(deque.byte(10), 1u8);
assert_eq!(deque.byte(11), 2u8);
assert_eq!(deque.byte(12), 4u8);
assert_eq!(deque.byte(13), 8u8);
assert_eq!(deque.byte(14), 16u8);
assert_eq!(deque.byte(15), 32u8);
assert_eq!(deque.byte(16), 64u8);
}
}

View File

@@ -0,0 +1,135 @@
//! This module implements the StaticDeque struct: a deque utility whose size
//! is known at compile time. Construction, push, and indexing are publicly
//! available.
use core::ops::{Index, IndexMut};
/// StaticDeque: a struct implementing a deque whose size is known at compile time.
/// It has 2 members: the static array conatining the data (never empty), and a cursor
/// equal to the index of the oldest element (and the next one to be overwritten).
#[derive(Clone)]
pub struct StaticDeque<const N: usize, T> {
arr: [T; N],
cursor: usize,
}
impl<const N: usize, T> StaticDeque<N, T> {
/// Constructor always uses a fully initialized array, the first element of
/// which is oldest, the last is newest
pub fn new(_arr: [T; N]) -> Self {
Self {
arr: _arr,
cursor: 0,
}
}
/// Push a new element to the deque, overwriting the oldest at the same time.
pub fn push(&mut self, val: T) {
self.arr[self.cursor] = val;
self.shift();
}
/// Shift: equivalent to pushing the oldest element
pub fn shift(&mut self) {
self.n_shifts(1);
}
/// computes n shift in a row
pub fn n_shifts(&mut self, n: usize) {
self.cursor += n;
self.cursor %= N;
}
/// Getter for the internal memory
#[allow(dead_code)]
pub fn get_arr(&self) -> &[T; N] {
&self.arr
}
}
/// Index trait for the StaticDeque: 0 is the youngest element, N-1 is the oldest,
/// and above N will panic.
impl<const N: usize, T> Index<usize> for StaticDeque<N, T> {
type Output = T;
/// 0 is youngest
fn index(&self, i: usize) -> &T {
if i >= N {
panic!("Index {:?} too high for size {:?}", i, N);
}
&self.arr[(N + self.cursor - i - 1) % N]
}
}
/// IndexMut trait for the StaticDeque: 0 is the youngest element, N-1 is the oldest,
/// and above N will panic.
impl<const N: usize, T> IndexMut<usize> for StaticDeque<N, T> {
/// 0 is youngest
fn index_mut(&mut self, i: usize) -> &mut T {
if i >= N {
panic!("Index {:?} too high for size {:?}", i, N);
}
&mut self.arr[(N + self.cursor - i - 1) % N]
}
}
#[cfg(test)]
mod tests {
use crate::static_deque::StaticDeque;
#[test]
fn test_static_deque() {
let a = [1, 2, 3, 4, 5, 6];
let mut static_deque = StaticDeque::new(a);
for i in 7..11 {
static_deque.push(i);
}
assert_eq!(*static_deque.get_arr(), [7, 8, 9, 10, 5, 6]);
for i in 11..15 {
static_deque.push(i);
}
assert_eq!(*static_deque.get_arr(), [13, 14, 9, 10, 11, 12]);
assert_eq!(static_deque[0], 14);
assert_eq!(static_deque[1], 13);
assert_eq!(static_deque[2], 12);
assert_eq!(static_deque[3], 11);
assert_eq!(static_deque[4], 10);
assert_eq!(static_deque[5], 9);
}
#[test]
fn test_static_deque_indexmut() {
let a = [1, 2, 3, 4, 5, 6];
let mut static_deque = StaticDeque::new(a);
for i in 7..11 {
static_deque.push(i);
}
assert_eq!(*static_deque.get_arr(), [7, 8, 9, 10, 5, 6]);
for i in 11..15 {
static_deque.push(i);
}
assert_eq!(*static_deque.get_arr(), [13, 14, 9, 10, 11, 12]);
static_deque[1] = 100;
assert_eq!(static_deque[0], 14);
assert_eq!(static_deque[1], 100);
assert_eq!(static_deque[2], 12);
assert_eq!(static_deque[3], 11);
assert_eq!(static_deque[4], 10);
assert_eq!(static_deque[5], 9);
}
#[test]
#[should_panic]
fn test_static_deque_index_fail() {
let a = [1, 2, 3, 4, 5, 6];
let static_deque = StaticDeque::new(a);
let _ = static_deque[6];
}
}

View File

@@ -0,0 +1,118 @@
//! This module will contain extensions of some TriviumStream of KreyviumStream objects,
//! when trans ciphering is available to them.
use crate::{KreyviumStreamByte, KreyviumStreamShortint, TriviumStreamByte, TriviumStreamShortint};
use tfhe::shortint::Ciphertext;
use tfhe::{set_server_key, unset_server_key, FheUint64, FheUint8, ServerKey};
use rayon::prelude::*;
/// Triat specifying the interface for trans ciphering a FheUint64 object. Since it is meant
/// to be used with stream ciphers, encryption and decryption are by default the same.
pub trait TransCiphering {
fn trans_encrypt_64(&mut self, cipher: FheUint64) -> FheUint64;
fn trans_decrypt_64(&mut self, cipher: FheUint64) -> FheUint64 {
self.trans_encrypt_64(cipher)
}
}
fn transcipher_from_fheu8_stream(
stream: Vec<FheUint8>,
cipher: FheUint64,
fhe_server_key: &ServerKey,
) -> FheUint64 {
assert_eq!(stream.len(), 8);
set_server_key(fhe_server_key.clone());
rayon::broadcast(|_| set_server_key(fhe_server_key.clone()));
let ret: FheUint64 = stream
.into_par_iter()
.enumerate()
.map(|(i, x)| &cipher ^ &(FheUint64::cast_from(x) << (8 * (7 - i) as u8)))
.reduce_with(|a, b| a | b)
.unwrap();
unset_server_key();
rayon::broadcast(|_| unset_server_key());
ret
}
fn transcipher_from_1_1_stream(
stream: Vec<Ciphertext>,
cipher: FheUint64,
hl_server_key: &ServerKey,
internal_server_key: &tfhe::shortint::ServerKey,
casting_key: &tfhe::shortint::KeySwitchingKey,
) -> FheUint64 {
assert_eq!(stream.len(), 64);
let pairs = (0..32)
.into_par_iter()
.map(|i| {
let byte_idx = 7 - i / 4;
let pair_idx = i % 4;
let b0 = &stream[8 * byte_idx + 2 * pair_idx];
let b1 = &stream[8 * byte_idx + 2 * pair_idx + 1];
casting_key.cast(
&internal_server_key
.unchecked_add(b0, &internal_server_key.unchecked_scalar_mul(b1, 2)),
)
})
.collect::<Vec<_>>();
set_server_key(hl_server_key.clone());
let ret = &cipher ^ &FheUint64::try_from(pairs).unwrap();
unset_server_key();
ret
}
impl TransCiphering for TriviumStreamByte<FheUint8> {
/// `TriviumStreamByte<FheUint8>`: since a full step outputs 8 bytes, these bytes
/// are each shifted by a number in [0, 8), and XORed with the input cipher
fn trans_encrypt_64(&mut self, cipher: FheUint64) -> FheUint64 {
transcipher_from_fheu8_stream(self.next_64(), cipher, self.get_server_key())
}
}
impl TransCiphering for KreyviumStreamByte<FheUint8> {
/// `KreyviumStreamByte<FheUint8>`: since a full step outputs 8 bytes, these bytes
/// are each shifted by a number in [0, 8), and XORed with the input cipher
fn trans_encrypt_64(&mut self, cipher: FheUint64) -> FheUint64 {
transcipher_from_fheu8_stream(self.next_64(), cipher, self.get_server_key())
}
}
impl TransCiphering for TriviumStreamShortint {
/// TriviumStreamShortint: since a full step outputs 64 shortints, these bits
/// are paired 2 by 2 in the HL parameter space and packed in a full word,
/// and XORed with the input cipher
fn trans_encrypt_64(&mut self, cipher: FheUint64) -> FheUint64 {
transcipher_from_1_1_stream(
self.next_64(),
cipher,
self.get_hl_server_key(),
self.get_internal_server_key(),
self.get_casting_key(),
)
}
}
impl TransCiphering for KreyviumStreamShortint {
/// KreyviumStreamShortint: since a full step outputs 64 shortints, these bits
/// are paired 2 by 2 in the HL parameter space and packed in a full word,
/// and XORed with the input cipher
fn trans_encrypt_64(&mut self, cipher: FheUint64) -> FheUint64 {
transcipher_from_1_1_stream(
self.next_64(),
cipher,
self.get_hl_server_key(),
self.get_internal_server_key(),
self.get_casting_key(),
)
}
}

View File

@@ -0,0 +1,11 @@
mod trivium;
pub use trivium::TriviumStream;
mod trivium_byte;
pub use trivium_byte::TriviumStreamByte;
mod trivium_shortint;
pub use trivium_shortint::TriviumStreamShortint;
#[cfg(test)]
mod test;

View File

@@ -0,0 +1,412 @@
use tfhe::prelude::*;
use tfhe::{generate_keys, ConfigBuilder, FheBool, FheUint64, FheUint8};
use crate::{TransCiphering, TriviumStream, TriviumStreamByte, TriviumStreamShortint};
// Values for these tests come from the github repo cantora/avr-crypto-lib, commit 2a5b018,
// file testvectors/trivium-80.80.test-vectors
fn get_hexadecimal_string_from_lsb_first_stream(a: Vec<bool>) -> String {
assert!(a.len() % 8 == 0);
let mut hexadecimal: String = "".to_string();
for test in a.chunks(8) {
// Encoding is bytes in LSB order
match test[4..8] {
[false, false, false, false] => hexadecimal.push('0'),
[true, false, false, false] => hexadecimal.push('1'),
[false, true, false, false] => hexadecimal.push('2'),
[true, true, false, false] => hexadecimal.push('3'),
[false, false, true, false] => hexadecimal.push('4'),
[true, false, true, false] => hexadecimal.push('5'),
[false, true, true, false] => hexadecimal.push('6'),
[true, true, true, false] => hexadecimal.push('7'),
[false, false, false, true] => hexadecimal.push('8'),
[true, false, false, true] => hexadecimal.push('9'),
[false, true, false, true] => hexadecimal.push('A'),
[true, true, false, true] => hexadecimal.push('B'),
[false, false, true, true] => hexadecimal.push('C'),
[true, false, true, true] => hexadecimal.push('D'),
[false, true, true, true] => hexadecimal.push('E'),
[true, true, true, true] => hexadecimal.push('F'),
_ => (),
};
match test[0..4] {
[false, false, false, false] => hexadecimal.push('0'),
[true, false, false, false] => hexadecimal.push('1'),
[false, true, false, false] => hexadecimal.push('2'),
[true, true, false, false] => hexadecimal.push('3'),
[false, false, true, false] => hexadecimal.push('4'),
[true, false, true, false] => hexadecimal.push('5'),
[false, true, true, false] => hexadecimal.push('6'),
[true, true, true, false] => hexadecimal.push('7'),
[false, false, false, true] => hexadecimal.push('8'),
[true, false, false, true] => hexadecimal.push('9'),
[false, true, false, true] => hexadecimal.push('A'),
[true, true, false, true] => hexadecimal.push('B'),
[false, false, true, true] => hexadecimal.push('C'),
[true, false, true, true] => hexadecimal.push('D'),
[false, true, true, true] => hexadecimal.push('E'),
[true, true, true, true] => hexadecimal.push('F'),
_ => (),
};
}
return hexadecimal;
}
fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
assert!(a.len() % 8 == 0);
let mut hexadecimal: String = "".to_string();
for test in a {
hexadecimal.push_str(&format!("{:02X?}", test));
}
return hexadecimal;
}
fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
let mut hexadecimal: String = "".to_string();
for test in a {
hexadecimal.push_str(&format!("{:016X?}", test));
}
return hexadecimal;
}
#[test]
fn trivium_test_1() {
let key = [false; 80];
let iv = [false; 80];
let output_0_63 = "FBE0BF265859051B517A2E4E239FC97F563203161907CF2DE7A8790FA1B2E9CDF75292030268B7382B4C1A759AA2599A285549986E74805903801A4CB5A5D4F2".to_string();
let output_192_255 = "0F1BE95091B8EA857B062AD52BADF47784AC6D9B2E3F85A9D79995043302F0FDF8B76E5BC8B7B4F0AA46CD20DDA04FDD197BC5E1635496828F2DBFB23F6BD5D0".to_string();
let output_256_319 = "80F9075437BAC73F696D0ABE3972F5FCE2192E5FCC13C0CB77D0ABA09126838D31A2D38A2087C46304C8A63B54109F679B0B1BC71E72A58D6DD3E0A3FF890D4A".to_string();
let output_448_511 = "68450EB0910A98EF1853E0FC1BED8AB6BB08DF5F167D34008C2A85284D4B886DD56883EE92BF18E69121670B4C81A5689C9B0538373D22EB923A28A2DB44C0EB".to_string();
let mut trivium = TriviumStream::<bool>::new(key, iv);
let mut vec = Vec::<bool>::with_capacity(512 * 8);
while vec.len() < 512 * 8 {
vec.push(trivium.next());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
assert_eq!(output_192_255, hexadecimal[192 * 2..256 * 2]);
assert_eq!(output_256_319, hexadecimal[256 * 2..320 * 2]);
assert_eq!(output_448_511, hexadecimal[448 * 2..512 * 2]);
}
#[test]
fn trivium_test_2() {
let mut key = [false; 80];
let iv = [false; 80];
key[7] = true;
let output_0_63 = "38EB86FF730D7A9CAF8DF13A4420540DBB7B651464C87501552041C249F29A64D2FBF515610921EBE06C8F92CECF7F8098FF20CCCC6A62B97BE8EF7454FC80F9".to_string();
let output_192_255 = "EAF2625D411F61E41F6BAEEDDD5FE202600BD472F6C9CD1E9134A745D900EF6C023E4486538F09930CFD37157C0EB57C3EF6C954C42E707D52B743AD83CFF297".to_string();
let output_256_319 = "9A203CF7B2F3F09C43D188AA13A5A2021EE998C42F777E9B67C3FA221A0AA1B041AA9E86BC2F5C52AFF11F7D9EE480CB1187B20EB46D582743A52D7CD080A24A".to_string();
let output_448_511 = "EBF14772061C210843C18CEA2D2A275AE02FCB18E5D7942455FF77524E8A4CA51E369A847D1AEEFB9002FCD02342983CEAFA9D487CC2032B10192CD416310FA4".to_string();
let mut trivium = TriviumStream::<bool>::new(key, iv);
let mut vec = Vec::<bool>::with_capacity(512 * 8);
while vec.len() < 512 * 8 {
vec.push(trivium.next());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
assert_eq!(output_192_255, hexadecimal[192 * 2..256 * 2]);
assert_eq!(output_256_319, hexadecimal[256 * 2..320 * 2]);
assert_eq!(output_448_511, hexadecimal[448 * 2..512 * 2]);
}
#[test]
fn trivium_test_3() {
let key = [false; 80];
let mut iv = [false; 80];
iv[7] = true;
let output_0_63 = "F8901736640549E3BA7D42EA2D07B9F49233C18D773008BD755585B1A8CBAB86C1E9A9B91F1AD33483FD6EE3696D659C9374260456A36AAE11F033A519CBD5D7".to_string();
let output_192_255 = "87423582AF64475C3A9C092E32A53C5FE07D35B4C9CA288A89A43DEF3913EA9237CA43342F3F8E83AD3A5C38D463516F94E3724455656A36279E3E924D442F06".to_string();
let output_256_319 = "D94389A90E6F3BF2BB4C8B057339AAD8AA2FEA238C29FCAC0D1FF1CB2535A07058BA995DD44CFC54CCEC54A5405B944C532D74E50EA370CDF1BA1CBAE93FC0B5".to_string();
let output_448_511 = "4844151714E56A3A2BBFBA426A1D60F9A4F265210A91EC29259AE2035234091C49FFB1893FA102D425C57C39EB4916F6D148DC83EBF7DE51EEB9ABFE045FB282".to_string();
let mut trivium = TriviumStream::<bool>::new(key, iv);
let mut vec = Vec::<bool>::with_capacity(512 * 8);
while vec.len() < 512 * 8 {
vec.push(trivium.next());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
assert_eq!(output_192_255, hexadecimal[192 * 2..256 * 2]);
assert_eq!(output_256_319, hexadecimal[256 * 2..320 * 2]);
assert_eq!(output_448_511, hexadecimal[448 * 2..512 * 2]);
}
#[test]
fn trivium_test_4() {
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [false; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [false; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let output_0_63 = "F4CD954A717F26A7D6930830C4E7CF0819F80E03F25F342C64ADC66ABA7F8A8E6EAA49F23632AE3CD41A7BD290A0132F81C6D4043B6E397D7388F3A03B5FE358".to_string();
let output_65472_65535 = "C04C24A6938C8AF8A491D5E481271E0E601338F01067A86A795CA493AA4FF265619B8D448B706B7C88EE8395FC79E5B51AB40245BBF7773AE67DF86FCFB71F30".to_string();
let output_65536_65599 = "011A0D7EC32FA102C66C164CFCB189AED9F6982E8C7370A6A37414781192CEB155C534C1C8C9E53FDEADF2D3D0577DAD3A8EB2F6E5265F1E831C86844670BC69".to_string();
let output_131008_131071 = "48107374A9CE3AAF78221AE77789247CF6896A249ED75DCE0CF2D30EB9D889A0C61C9F480E5C07381DED9FAB2AD54333E82C89BA92E6E47FD828F1A66A8656E0".to_string();
let mut trivium = TriviumStream::<bool>::new(key, iv);
let mut vec = Vec::<bool>::with_capacity(131072 * 8);
while vec.len() < 131072 * 8 {
vec.push(trivium.next());
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
assert_eq!(output_65472_65535, hexadecimal[65472 * 2..65536 * 2]);
assert_eq!(output_65536_65599, hexadecimal[65536 * 2..65600 * 2]);
assert_eq!(output_131008_131071, hexadecimal[131008 * 2..131072 * 2]);
}
#[test]
fn trivium_test_clear_byte() {
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0u8; 10];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0u8; 10];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let output_0_63 = "F4CD954A717F26A7D6930830C4E7CF0819F80E03F25F342C64ADC66ABA7F8A8E6EAA49F23632AE3CD41A7BD290A0132F81C6D4043B6E397D7388F3A03B5FE358".to_string();
let output_65472_65535 = "C04C24A6938C8AF8A491D5E481271E0E601338F01067A86A795CA493AA4FF265619B8D448B706B7C88EE8395FC79E5B51AB40245BBF7773AE67DF86FCFB71F30".to_string();
let output_65536_65599 = "011A0D7EC32FA102C66C164CFCB189AED9F6982E8C7370A6A37414781192CEB155C534C1C8C9E53FDEADF2D3D0577DAD3A8EB2F6E5265F1E831C86844670BC69".to_string();
let output_131008_131071 = "48107374A9CE3AAF78221AE77789247CF6896A249ED75DCE0CF2D30EB9D889A0C61C9F480E5C07381DED9FAB2AD54333E82C89BA92E6E47FD828F1A66A8656E0".to_string();
let mut trivium = TriviumStreamByte::<u8>::new(key, iv);
let mut vec = Vec::<u8>::with_capacity(131072);
while vec.len() < 131072 {
let outputs = trivium.next_64();
for c in outputs {
vec.push(c)
}
}
let hexadecimal = get_hexagonal_string_from_bytes(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
assert_eq!(output_65472_65535, hexadecimal[65472 * 2..65536 * 2]);
assert_eq!(output_65536_65599, hexadecimal[65536 * 2..65600 * 2]);
assert_eq!(output_131008_131071, hexadecimal[131008 * 2..131072 * 2]);
}
#[test]
fn trivium_test_fhe_long() {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [false; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [false; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val: u8 = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2 == 1;
val >>= 1;
}
}
let output_0_63 = "F4CD954A717F26A7D6930830C4E7CF0819F80E03F25F342C64ADC66ABA7F8A8E6EAA49F23632AE3CD41A7BD290A0132F81C6D4043B6E397D7388F3A03B5FE358".to_string();
let cipher_key = key.map(|x| FheBool::encrypt(x, &client_key));
let mut trivium = TriviumStream::<FheBool>::new(cipher_key, iv, &server_key);
let mut vec = Vec::<bool>::with_capacity(64 * 8);
while vec.len() < 64 * 8 {
let cipher_outputs = trivium.next_64();
for c in cipher_outputs {
vec.push(c.decrypt(&client_key))
}
}
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
}
#[test]
fn trivium_test_fhe_byte_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0u8; 10];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0u8; 10];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let output_0_63 = "F4CD954A717F26A7D6930830C4E7CF0819F80E03F25F342C64ADC66ABA7F8A8E6EAA49F23632AE3CD41A7BD290A0132F81C6D4043B6E397D7388F3A03B5FE358".to_string();
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let mut trivium = TriviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
let mut vec = Vec::<u8>::with_capacity(64);
while vec.len() < 64 {
let cipher_outputs = trivium.next_64();
for c in cipher_outputs {
vec.push(c.decrypt(&client_key))
}
}
let hexadecimal = get_hexagonal_string_from_bytes(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
}
#[test]
fn trivium_test_fhe_byte_transciphering_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0u8; 10];
for i in (0..key_string.len()).step_by(2) {
key[i >> 1] = u8::from_str_radix(&key_string[i..i + 2], 16).unwrap();
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0u8; 10];
for i in (0..iv_string.len()).step_by(2) {
iv[i >> 1] = u8::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
}
let output_0_63 = "F4CD954A717F26A7D6930830C4E7CF0819F80E03F25F342C64ADC66ABA7F8A8E6EAA49F23632AE3CD41A7BD290A0132F81C6D4043B6E397D7388F3A03B5FE358".to_string();
let cipher_key = key.map(|x| FheUint8::encrypt(x, &client_key));
let mut ciphered_message = vec![FheUint64::try_encrypt(0u64, &client_key).unwrap(); 9];
let mut trivium = TriviumStreamByte::<FheUint8>::new(cipher_key, iv, &server_key);
let mut vec = Vec::<u64>::with_capacity(8);
while vec.len() < 8 {
let trans_ciphered_message = trivium.trans_encrypt_64(ciphered_message.pop().unwrap());
vec.push(trans_ciphered_message.decrypt(&client_key));
}
let hexadecimal = get_hexagonal_string_from_u64(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
}
use tfhe::shortint::prelude::*;
#[test]
fn trivium_test_shortint_long() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.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();
let (client_key, server_key): (ClientKey, ServerKey) = gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let ksk = KeySwitchingKey::new(
(&client_key, &server_key),
(&underlying_ck, &underlying_sk),
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
);
let key_string = "0053A6F94C9FF24598EB".to_string();
let mut key = [0; 80];
for i in (0..key_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&key_string[i..i + 2], 16).unwrap();
for j in 0..8 {
key[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let iv_string = "0D74DB42A91077DE45AC".to_string();
let mut iv = [0; 80];
for i in (0..iv_string.len()).step_by(2) {
let mut val = u64::from_str_radix(&iv_string[i..i + 2], 16).unwrap();
for j in 0..8 {
iv[8 * (i >> 1) + j] = val % 2;
val >>= 1;
}
}
let output_0_63 = "F4CD954A717F26A7D6930830C4E7CF0819F80E03F25F342C64ADC66ABA7F8A8E6EAA49F23632AE3CD41A7BD290A0132F81C6D4043B6E397D7388F3A03B5FE358".to_string();
let cipher_key = key.map(|x| client_key.encrypt(x));
let mut ciphered_message = vec![FheUint64::try_encrypt(0u64, &hl_client_key).unwrap(); 9];
let mut trivium = TriviumStreamShortint::new(cipher_key, iv, server_key, ksk, hl_server_key);
let mut vec = Vec::<u64>::with_capacity(8);
while vec.len() < 8 {
let trans_ciphered_message = trivium.trans_encrypt_64(ciphered_message.pop().unwrap());
vec.push(trans_ciphered_message.decrypt(&hl_client_key));
}
let hexadecimal = get_hexagonal_string_from_u64(vec);
assert_eq!(output_0_63, hexadecimal[0..64 * 2]);
}

View File

@@ -0,0 +1,225 @@
//! This module implements the Trivium stream cipher, using booleans or FheBool
//! for the representaion of the inner bits.
use crate::static_deque::StaticDeque;
use tfhe::prelude::*;
use tfhe::{set_server_key, unset_server_key, FheBool, ServerKey};
use rayon::prelude::*;
/// Internal trait specifying which operations are necessary for TriviumStream generic type
pub trait TriviumBoolInput<OpOutput>:
Sized
+ Clone
+ std::ops::BitXor<Output = OpOutput>
+ std::ops::BitAnd<Output = OpOutput>
+ std::ops::Not<Output = OpOutput>
{
}
impl TriviumBoolInput<bool> for bool {}
impl TriviumBoolInput<bool> for &bool {}
impl TriviumBoolInput<FheBool> for FheBool {}
impl TriviumBoolInput<FheBool> for &FheBool {}
/// TriviumStream: a struct implementing the Trivium stream cipher, using T for the internal
/// representation of bits (bool or FheBool). To be able to compute FHE operations, it also owns
/// an Option for a ServerKey.
pub struct TriviumStream<T> {
a: StaticDeque<93, T>,
b: StaticDeque<84, T>,
c: StaticDeque<111, T>,
fhe_key: Option<ServerKey>,
}
impl TriviumStream<bool> {
/// Contructor for `TriviumStream<bool>`: arguments are the secret key and the input vector.
/// Outputs a TriviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(key: [bool; 80], iv: [bool; 80]) -> TriviumStream<bool> {
// Initialization of Trivium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_register = [false; 93];
let mut b_register = [false; 84];
let mut c_register = [false; 111];
for i in 0..80 {
a_register[93 - 80 + i] = key[i];
b_register[84 - 80 + i] = iv[i];
}
c_register[0] = true;
c_register[1] = true;
c_register[2] = true;
TriviumStream::<bool>::new_from_registers(a_register, b_register, c_register, None)
}
}
impl TriviumStream<FheBool> {
/// Constructor for `TriviumStream<FheBool>`: arguments are the encrypted secret key and input
/// vector, and the FHE server key.
/// Outputs a TriviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(key: [FheBool; 80], iv: [bool; 80], sk: &ServerKey) -> TriviumStream<FheBool> {
set_server_key(sk.clone());
// Initialization of Trivium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_register = [false; 93].map(|x| FheBool::encrypt_trivial(x));
let mut b_register = [false; 84].map(|x| FheBool::encrypt_trivial(x));
let mut c_register = [false; 111].map(|x| FheBool::encrypt_trivial(x));
for i in 0..80 {
a_register[93 - 80 + i] = key[i].clone();
b_register[84 - 80 + i] = FheBool::encrypt_trivial(iv[i]);
}
c_register[0] = FheBool::try_encrypt_trivial(true).unwrap();
c_register[1] = FheBool::try_encrypt_trivial(true).unwrap();
c_register[2] = FheBool::try_encrypt_trivial(true).unwrap();
unset_server_key();
TriviumStream::<FheBool>::new_from_registers(
a_register,
b_register,
c_register,
Some(sk.clone()),
)
}
}
impl<T> TriviumStream<T>
where
T: TriviumBoolInput<T> + std::marker::Send + std::marker::Sync,
for<'a> &'a T: TriviumBoolInput<T>,
{
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
/// server key
fn new_from_registers(
a_register: [T; 93],
b_register: [T; 84],
c_register: [T; 111],
key: Option<ServerKey>,
) -> Self {
let mut ret = Self {
a: StaticDeque::<93, T>::new(a_register),
b: StaticDeque::<84, T>::new(b_register),
c: StaticDeque::<111, T>::new(c_register),
fhe_key: key,
};
ret.init();
ret
}
/// The specification of Trivium includes running 1152 (= 18*64) unused steps to mix up the
/// registers, before starting the proper stream
fn init(&mut self) {
for _ in 0..18 {
self.next_64();
}
}
/// Computes one turn of the stream, updating registers and outputting the new bit.
pub fn next(&mut self) -> T {
match &self.fhe_key {
Some(sk) => set_server_key(sk.clone()),
None => (),
};
let [o, a, b, c] = self.get_output_and_values(0);
self.a.push(a);
self.b.push(b);
self.c.push(c);
o
}
/// Computes a potential future step of Trivium, n terms in the future. This does not update
/// registers, but rather returns with the output, the three values that will be used to
/// update the registers, when the time is right. This function is meant to be used in
/// parallel.
fn get_output_and_values(&self, n: usize) -> [T; 4] {
assert!(n < 65);
let (((temp_a, temp_b), (temp_c, a_and)), (b_and, c_and)) = rayon::join(
|| {
rayon::join(
|| {
rayon::join(
|| &self.a[65 - n] ^ &self.a[92 - n],
|| &self.b[68 - n] ^ &self.b[83 - n],
)
},
|| {
rayon::join(
|| &self.c[65 - n] ^ &self.c[110 - n],
|| &self.a[91 - n] & &self.a[90 - n],
)
},
)
},
|| {
rayon::join(
|| &self.b[82 - n] & &self.b[81 - n],
|| &self.c[109 - n] & &self.c[108 - n],
)
},
);
let ((o, a), (b, c)) = rayon::join(
|| {
rayon::join(
|| &(&temp_a ^ &temp_b) ^ &temp_c,
|| &temp_c ^ &(&c_and ^ &self.a[68 - n]),
)
},
|| {
rayon::join(
|| &temp_a ^ &(&a_and ^ &self.b[77 - n]),
|| &temp_b ^ &(&b_and ^ &self.c[86 - n]),
)
},
);
[o, a, b, c]
}
/// This calls `get_output_and_values` in parallel 64 times, and stores all results in a Vec.
fn get_64_output_and_values(&self) -> Vec<[T; 4]> {
(0..64)
.into_par_iter()
.map(|x| self.get_output_and_values(x))
.rev()
.collect()
}
/// Computes 64 turns of the stream, outputting the 64 bits all at once in a
/// Vec (first value is oldest, last is newest)
pub fn next_64(&mut self) -> Vec<T> {
match &self.fhe_key {
Some(sk) => {
rayon::broadcast(|_| set_server_key(sk.clone()));
}
None => (),
}
let mut values = self.get_64_output_and_values();
match &self.fhe_key {
Some(_) => {
rayon::broadcast(|_| unset_server_key());
}
None => (),
}
let mut ret = Vec::<T>::with_capacity(64);
while let Some([o, a, b, c]) = values.pop() {
ret.push(o);
self.a.push(a);
self.b.push(b);
self.c.push(c);
}
ret
}
}

View File

@@ -0,0 +1,241 @@
//! This module implements the Trivium stream cipher, using u8 or FheUint8
//! for the representaion of the inner bits.
use crate::static_deque::{StaticByteDeque, StaticByteDequeInput};
use tfhe::prelude::*;
use tfhe::{set_server_key, unset_server_key, FheUint8, ServerKey};
use rayon::prelude::*;
/// Internal trait specifying which operations are necessary for TriviumStreamByte generic type
pub trait TriviumByteInput<OpOutput>:
Sized
+ Clone
+ Send
+ Sync
+ StaticByteDequeInput<OpOutput>
+ std::ops::BitXor<Output = OpOutput>
+ std::ops::BitAnd<Output = OpOutput>
+ std::ops::Shr<u8, Output = OpOutput>
+ std::ops::Shl<u8, Output = OpOutput>
+ std::ops::Add<Output = OpOutput>
{
}
impl TriviumByteInput<u8> for u8 {}
impl TriviumByteInput<u8> for &u8 {}
impl TriviumByteInput<FheUint8> for FheUint8 {}
impl TriviumByteInput<FheUint8> for &FheUint8 {}
/// TriviumStreamByte: a struct implementing the Trivium stream cipher, using T for the internal
/// representation of bits (u8 or FheUint8). To be able to compute FHE operations, it also owns
/// an Option for a ServerKey.
/// Since the original Trivium registers' sizes are not a multiple of 8, these registers (which
/// store byte-like objects) have a size that is the eigth of the closest multiple of 8 above the
/// originals' sizes.
pub struct TriviumStreamByte<T> {
a_byte: StaticByteDeque<12, T>,
b_byte: StaticByteDeque<11, T>,
c_byte: StaticByteDeque<14, T>,
fhe_key: Option<ServerKey>,
}
impl TriviumStreamByte<u8> {
/// Contructor for `TriviumStreamByte<u8>`: arguments are the secret key and the input vector.
/// Outputs a TriviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(key: [u8; 10], iv: [u8; 10]) -> TriviumStreamByte<u8> {
// Initialization of Trivium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_byte_reg = [0u8; 12];
let mut b_byte_reg = [0u8; 11];
let mut c_byte_reg = [0u8; 14];
for i in 0..10 {
a_byte_reg[12 - 10 + i] = key[i];
b_byte_reg[11 - 10 + i] = iv[i];
}
// Magic number 14, aka 00001110: this represents the 3 ones at the beginning of the c
// registers, with additional zeros to make the register's size a multiple of 8.
c_byte_reg[0] = 14;
let mut ret =
TriviumStreamByte::<u8>::new_from_registers(a_byte_reg, b_byte_reg, c_byte_reg, None);
ret.init();
ret
}
}
impl TriviumStreamByte<FheUint8> {
/// Constructor for `TriviumStream<FheUint8>`: arguments are the encrypted secret key and input
/// vector, and the FHE server key.
/// Outputs a TriviumStream object already initialized (1152 steps have been run before
/// returning)
pub fn new(
key: [FheUint8; 10],
iv: [u8; 10],
server_key: &ServerKey,
) -> TriviumStreamByte<FheUint8> {
set_server_key(server_key.clone());
// Initialization of Trivium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_byte_reg = [0u8; 12].map(|x| FheUint8::encrypt_trivial(x));
let mut b_byte_reg = [0u8; 11].map(|x| FheUint8::encrypt_trivial(x));
let mut c_byte_reg = [0u8; 14].map(|x| FheUint8::encrypt_trivial(x));
for i in 0..10 {
a_byte_reg[12 - 10 + i] = key[i].clone();
b_byte_reg[11 - 10 + i] = FheUint8::encrypt_trivial(iv[i]);
}
// Magic number 14, aka 00001110: this represents the 3 ones at the beginning of the c
// registers, with additional zeros to make the register's size a multiple of 8.
c_byte_reg[0] = FheUint8::encrypt_trivial(14u8);
unset_server_key();
let mut ret = TriviumStreamByte::<FheUint8>::new_from_registers(
a_byte_reg,
b_byte_reg,
c_byte_reg,
Some(server_key.clone()),
);
ret.init();
ret
}
}
impl<T> TriviumStreamByte<T>
where
T: TriviumByteInput<T> + Send,
for<'a> &'a T: TriviumByteInput<T>,
{
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
/// server key
fn new_from_registers(
a_register: [T; 12],
b_register: [T; 11],
c_register: [T; 14],
sk: Option<ServerKey>,
) -> Self {
Self {
a_byte: StaticByteDeque::<12, T>::new(a_register),
b_byte: StaticByteDeque::<11, T>::new(b_register),
c_byte: StaticByteDeque::<14, T>::new(c_register),
fhe_key: sk,
}
}
/// The specification of Trivium includes running 1152 (= 18*64) unused steps to mix up the
/// registers, before starting the proper stream
fn init(&mut self) {
for _ in 0..18 {
self.next_64();
}
}
/// Computes 8 potential future step of Trivium, b*8 terms in the future. This does not update
/// registers, but rather returns with the output, the three values that will be used to
/// update the registers, when the time is right. This function is meant to be used in
/// parallel.
fn get_output_and_values(&self, b: usize) -> [T; 4] {
let n = b * 8 + 7;
assert!(n < 65);
let ((a1, a2, a3, a4, a5), ((b1, b2, b3, b4, b5), (c1, c2, c3, c4, c5))) = rayon::join(
|| Self::get_bytes(&self.a_byte, [91 - n, 90 - n, 68 - n, 65 - n, 92 - n]),
|| {
rayon::join(
|| Self::get_bytes(&self.b_byte, [82 - n, 81 - n, 77 - n, 68 - n, 83 - n]),
|| Self::get_bytes(&self.c_byte, [109 - n, 108 - n, 86 - n, 65 - n, 110 - n]),
)
},
);
let (((temp_a, temp_b), (temp_c, a_and)), (b_and, c_and)) = rayon::join(
|| {
rayon::join(
|| rayon::join(|| a4 ^ a5, || b4 ^ b5),
|| rayon::join(|| c4 ^ c5, || a1 & a2),
)
},
|| rayon::join(|| b1 & b2, || c1 & c2),
);
let (temp_a_2, temp_b_2, temp_c_2) = (temp_a.clone(), temp_b.clone(), temp_c.clone());
let ((o, a), (b, c)) = rayon::join(
|| {
rayon::join(
|| (temp_a_2 ^ temp_b_2) ^ temp_c_2,
|| temp_c ^ ((c_and) ^ a3),
)
},
|| rayon::join(|| temp_a ^ (a_and ^ b3), || temp_b ^ (b_and ^ c3)),
);
[o, a, b, c]
}
/// This calls `get_output_and_values` in parallel 8 times, and stores all results in a Vec.
fn get_64_output_and_values(&self) -> Vec<[T; 4]> {
(0..8)
.into_par_iter()
.map(|i| self.get_output_and_values(i))
.collect()
}
/// Computes 64 turns of the stream, outputting the 64 bits (in 8 bytes) all at once in a
/// Vec (first value is oldest, last is newest)
pub fn next_64(&mut self) -> Vec<T> {
match &self.fhe_key {
Some(sk) => {
rayon::broadcast(|_| set_server_key(sk.clone()));
}
None => (),
}
let values = self.get_64_output_and_values();
match &self.fhe_key {
Some(_) => {
rayon::broadcast(|_| unset_server_key());
}
None => (),
}
let mut bytes = Vec::<T>::with_capacity(8);
for [o, a, b, c] in values {
self.a_byte.push(a);
self.b_byte.push(b);
self.c_byte.push(c);
bytes.push(o);
}
bytes
}
/// Reconstructs a bunch of 5 bytes in a parallel fashion.
fn get_bytes<const N: usize>(
reg: &StaticByteDeque<N, T>,
offsets: [usize; 5],
) -> (T, T, T, T, T) {
let mut ret = offsets
.par_iter()
.rev()
.map(|&i| reg.byte(i))
.collect::<Vec<_>>();
(
ret.pop().unwrap(),
ret.pop().unwrap(),
ret.pop().unwrap(),
ret.pop().unwrap(),
ret.pop().unwrap(),
)
}
}
impl TriviumStreamByte<FheUint8> {
pub fn get_server_key(&self) -> &ServerKey {
&self.fhe_key.as_ref().unwrap()
}
}

View File

@@ -0,0 +1,189 @@
use crate::static_deque::StaticDeque;
use tfhe::shortint::prelude::*;
use rayon::prelude::*;
/// TriviumStreamShortint: a struct implementing the Trivium stream cipher, using a generic
/// Ciphertext for the internal representation of bits (intended to represent a single bit). To be
/// able to compute FHE operations, it also owns a ServerKey.
pub struct TriviumStreamShortint {
a: StaticDeque<93, Ciphertext>,
b: StaticDeque<84, Ciphertext>,
c: StaticDeque<111, Ciphertext>,
internal_server_key: ServerKey,
transciphering_casting_key: KeySwitchingKey,
hl_server_key: tfhe::ServerKey,
}
impl TriviumStreamShortint {
/// Contructor for TriviumStreamShortint: arguments are the secret key and the input vector, and
/// a ServerKey reference. Outputs a TriviumStream object already initialized (1152 steps
/// have been run before returning)
pub fn new(
key: [Ciphertext; 80],
iv: [u64; 80],
sk: ServerKey,
ksk: KeySwitchingKey,
hl_sk: tfhe::ServerKey,
) -> Self {
// Initialization of Trivium registers: a has the secret key, b the input vector,
// and c a few ones.
let mut a_register: [Ciphertext; 93] = [0; 93].map(|x| sk.create_trivial(x));
let mut b_register: [Ciphertext; 84] = [0; 84].map(|x| sk.create_trivial(x));
let mut c_register: [Ciphertext; 111] = [0; 111].map(|x| sk.create_trivial(x));
for i in 0..80 {
a_register[93 - 80 + i] = key[i].clone();
b_register[84 - 80 + i] = sk.create_trivial(iv[i]);
}
c_register[0] = sk.create_trivial(1);
c_register[1] = sk.create_trivial(1);
c_register[2] = sk.create_trivial(1);
let mut ret = Self {
a: StaticDeque::<93, Ciphertext>::new(a_register),
b: StaticDeque::<84, Ciphertext>::new(b_register),
c: StaticDeque::<111, Ciphertext>::new(c_register),
internal_server_key: sk,
transciphering_casting_key: ksk,
hl_server_key: hl_sk,
};
ret.init();
ret
}
/// The specification of Trivium includes running 1152 (= 18*64) unused steps to mix up the
/// registers, before starting the proper stream
fn init(&mut self) {
for _ in 0..18 {
self.next_64();
}
}
/// Computes one turn of the stream, updating registers and outputting the new bit.
pub fn next(&mut self) -> Ciphertext {
let [o, a, b, c] = self.get_output_and_values(0);
self.a.push(a);
self.b.push(b);
self.c.push(c);
o
}
/// Computes a potential future step of Trivium, n terms in the future. This does not update
/// registers, but rather returns with the output, the three values that will be used to
/// update the registers, when the time is right. This function is meant to be used in
/// parallel.
fn get_output_and_values(&self, n: usize) -> [Ciphertext; 4] {
let (a1, a2, a3, a4, a5) = (
&self.a[65 - n],
&self.a[92 - n],
&self.a[91 - n],
&self.a[90 - n],
&self.a[68 - n],
);
let (b1, b2, b3, b4, b5) = (
&self.b[68 - n],
&self.b[83 - n],
&self.b[82 - n],
&self.b[81 - n],
&self.b[77 - n],
);
let (c1, c2, c3, c4, c5) = (
&self.c[65 - n],
&self.c[110 - n],
&self.c[109 - n],
&self.c[108 - n],
&self.c[86 - n],
);
let temp_a = self.internal_server_key.unchecked_add(a1, a2);
let temp_b = self.internal_server_key.unchecked_add(b1, b2);
let temp_c = self.internal_server_key.unchecked_add(c1, c2);
let ((new_a, new_b), (new_c, o)) = rayon::join(
|| {
rayon::join(
|| {
let mut new_a = self.internal_server_key.unchecked_bitand(c3, c4);
self.internal_server_key
.unchecked_add_assign(&mut new_a, a5);
self.internal_server_key
.unchecked_add_assign(&mut new_a, &temp_c);
self.internal_server_key.clear_carry_assign(&mut new_a);
new_a
},
|| {
let mut new_b = self.internal_server_key.unchecked_bitand(a3, a4);
self.internal_server_key
.unchecked_add_assign(&mut new_b, b5);
self.internal_server_key
.unchecked_add_assign(&mut new_b, &temp_a);
self.internal_server_key.clear_carry_assign(&mut new_b);
new_b
},
)
},
|| {
rayon::join(
|| {
let mut new_c = self.internal_server_key.unchecked_bitand(b3, b4);
self.internal_server_key
.unchecked_add_assign(&mut new_c, c5);
self.internal_server_key
.unchecked_add_assign(&mut new_c, &temp_b);
self.internal_server_key.clear_carry_assign(&mut new_c);
new_c
},
|| {
self.internal_server_key.bitxor(
&self.internal_server_key.unchecked_add(&temp_a, &temp_b),
&temp_c,
)
},
)
},
);
[o, new_a, new_b, new_c]
}
/// This calls `get_output_and_values` in parallel 64 times, and stores all results in a Vec.
fn get_64_output_and_values(&self) -> Vec<[Ciphertext; 4]> {
(0..64)
.into_par_iter()
.map(|x| self.get_output_and_values(x))
.rev()
.collect()
}
/// Computes 64 turns of the stream, outputting the 64 bits all at once in a
/// Vec (first value is oldest, last is newest)
pub fn next_64(&mut self) -> Vec<Ciphertext> {
let mut values = self.get_64_output_and_values();
let mut ret = Vec::<Ciphertext>::with_capacity(64);
while let Some([o, a, b, c]) = values.pop() {
ret.push(o);
self.a.push(a);
self.b.push(b);
self.c.push(c);
}
ret
}
pub fn get_internal_server_key(&self) -> &ServerKey {
&self.internal_server_key
}
pub fn get_casting_key(&self) -> &KeySwitchingKey {
&self.transciphering_casting_key
}
pub fn get_hl_server_key(&self) -> &tfhe::ServerKey {
&self.hl_server_key
}
}

View File

@@ -39,8 +39,10 @@ parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true',
help='Check for results in subdirectories')
parser.add_argument('--key-sizes', dest='key_sizes', action='store_true',
help='Parse only the results regarding keys size measurements')
parser.add_argument('--key-gen', dest='key_gen', action='store_true',
help='Parse only the results regarding keys generation time measurements')
parser.add_argument('--throughput', dest='throughput', action='store_true',
help='Compute and append number of operations per millisecond and'
help='Compute and append number of operations per second and'
'operations per dollar')
parser.add_argument('--backend', dest='backend', default='cpu',
help='Backend on which benchmarks have run')
@@ -55,7 +57,7 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
:param directory: path to directory that contains raw results as :class:`pathlib.Path`
:param walk_subdirs: traverse results subdirectories if parameters changes for benchmark case.
:param name_suffix: a :class:`str` suffix to apply to each test name found
:param compute_throughput: compute number of operations per millisecond and operations per
:param compute_throughput: compute number of operations per second and operations per
dollar
:param hardware_hourly_cost: hourly cost of the hardware used in dollar
@@ -93,6 +95,7 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
for stat_name, value in parse_estimate_file(subdir).items():
test_name_parts = list(filter(None, [test_name, stat_name, name_suffix]))
result_values.append(
_create_point(
value,
@@ -106,11 +109,11 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
)
if stat_name == "mean" and compute_throughput:
test_suffix = "ops-per-ms"
test_suffix = "ops-per-sec"
test_name_parts.append(test_suffix)
result_values.append(
_create_point(
compute_ops_per_millisecond(value),
compute_ops_per_second(value),
"_".join(test_name_parts),
bench_class,
"throughput",
@@ -177,9 +180,9 @@ def parse_estimate_file(directory):
}
def parse_key_sizes(result_file):
def _parse_key_results(result_file, bench_type):
"""
Parse file containing key sizes results. The file must be formatted as CSV.
Parse file containing results about operation on keys. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
@@ -202,13 +205,35 @@ def parse_key_sizes(result_file):
"test": test_name,
"name": display_name,
"class": "keygen",
"type": "keysize",
"type": bench_type,
"operator": operator,
"params": params})
return result_values, parsing_failures
def parse_key_sizes(result_file):
"""
Parse file containing key sizes results. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
:return: tuple of :class:`list` as (data points, parsing failures)
"""
return _parse_key_results(result_file, "keysize")
def parse_key_gen_time(result_file):
"""
Parse file containing key generation time results. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
:return: tuple of :class:`list` as (data points, parsing failures)
"""
return _parse_key_results(result_file, "latency")
def get_parameters(bench_id):
"""
Get benchmarks parameters recorded for a given benchmark case.
@@ -242,15 +267,15 @@ def compute_ops_per_dollar(data_point, product_hourly_cost):
return ONE_HOUR_IN_NANOSECONDS / (product_hourly_cost * data_point)
def compute_ops_per_millisecond(data_point):
def compute_ops_per_second(data_point):
"""
Compute numbers of operations per millisecond for a given ``data_point``.
Compute numbers of operations per second for a given ``data_point``.
:param data_point: timing value measured during benchmark in nanoseconds
:return: number of operations per millisecond
:return: number of operations per second
"""
return 1E6 / data_point
return 1E9 / data_point
def _parse_file_to_json(directory, filename):
@@ -301,7 +326,7 @@ def check_mandatory_args(input_args):
for arg_name in vars(input_args):
if arg_name in ["results_dir", "output_file", "name_suffix",
"append_results", "walk_subdirs", "key_sizes",
"throughput"]:
"key_gen", "throughput"]:
continue
if not getattr(input_args, arg_name):
missing_args.append(arg_name)
@@ -318,7 +343,15 @@ if __name__ == "__main__":
#failures = []
raw_results = pathlib.Path(args.results)
if not args.key_sizes:
if args.key_sizes or args.key_gen:
if args.key_sizes:
print("Parsing key sizes results... ")
results, failures = parse_key_sizes(raw_results)
if args.key_gen:
print("Parsing key generation time results... ")
results, failures = parse_key_gen_time(raw_results)
else:
print("Parsing benchmark results... ")
hardware_cost = None
if args.throughput:
@@ -334,9 +367,7 @@ if __name__ == "__main__":
results, failures = recursive_parse(raw_results, args.walk_subdirs, args.name_suffix,
args.throughput, hardware_cost)
else:
print("Parsing key sizes results... ")
results, failures = parse_key_sizes(raw_results)
print("Parsing results done")
output_file = pathlib.Path(args.output_file)

View File

@@ -0,0 +1,90 @@
import argparse
from pathlib import Path
import json
def main(args):
criterion_dir = Path(args.criterion_dir)
output_file = Path(args.output_file)
data = []
for json_file in sorted(criterion_dir.glob("**/*.json")):
if json_file.parent.name == "base" or json_file.name != "benchmark.json":
continue
try:
bench_data = json.loads(json_file.read_text())
estimate_file = json_file.with_name("estimates.json")
estimate_data = json.loads(estimate_file.read_text())
bench_function_id = bench_data["function_id"]
split = bench_function_id.split("::")
(_, function_name, parameter_set, bits) = split
(bits, _) = bits.split("_")
bits = int(bits)
estimate_mean_ms = estimate_data["mean"]["point_estimate"] / 1000000
estimate_lower_bound_ms = (
estimate_data["mean"]["confidence_interval"]["lower_bound"] / 1000000
)
estimate_upper_bound_ms = (
estimate_data["mean"]["confidence_interval"]["upper_bound"] / 1000000
)
data.append(
(
function_name,
parameter_set,
bits,
estimate_mean_ms,
estimate_lower_bound_ms,
estimate_upper_bound_ms,
)
)
except:
pass
if len(data) == 0:
print("No integer bench found, skipping writing output file")
return
with open(output_file, "w", encoding="utf-8") as output:
output.write(
"function_name,parameter_set,bits,mean_ms,"
"confidence_interval_lower_bound_ms,confidence_interval_upper_bound_ms\n"
)
# Sort by func_name, bit width and then parameters
data.sort(key=lambda x: (x[0], x[2], x[1]))
for dat in data:
(
function_name,
parameter_set,
bits,
estimate_mean_ms,
estimate_lower_bound_ms,
estimate_upper_bound_ms,
) = dat
output.write(
f"{function_name},{parameter_set},{bits},{estimate_mean_ms},"
f"{estimate_lower_bound_ms},{estimate_upper_bound_ms}\n"
)
if __name__ == "__main__":
parser = argparse.ArgumentParser("Parse criterion results to csv file")
parser.add_argument(
"--criterion-dir",
type=str,
default="target/criterion",
help="Where to look for criterion result json files",
)
parser.add_argument(
"--output-file",
type=str,
default="parsed_benches.csv",
help="Path of the output file, will be csv formatted",
)
main(parser.parse_args())

View File

@@ -1,11 +1,16 @@
[profile.cpu-big]
region = "eu-west-3"
image_id = "ami-04deffe45b5b236fd"
image_id = "ami-0ab73f5bd11708a85"
instance_type = "m6i.32xlarge"
[profile.cpu-small]
region = "eu-west-3"
image_id = "ami-0ab73f5bd11708a85"
instance_type = "m6i.4xlarge"
[profile.bench]
region = "eu-west-3"
image_id = "ami-04deffe45b5b236fd"
image_id = "ami-0ab73f5bd11708a85"
instance_type = "m6i.metal"
[command.cpu_test]
@@ -18,11 +23,31 @@ workflow = "aws_tfhe_integer_tests.yml"
profile = "cpu-big"
check_run_name = "CPU Integer AWS Tests"
[command.cpu_multi_bit_test]
workflow = "aws_tfhe_multi_bit_tests.yml"
profile = "cpu-big"
check_run_name = "CPU AWS Multi Bit Tests"
[command.cpu_wasm_test]
workflow = "aws_tfhe_wasm_tests.yml"
profile = "cpu-small"
check_run_name = "CPU AWS WASM Tests"
[command.cpu_fast_test]
workflow = "aws_tfhe_fast_tests.yml"
profile = "cpu-big"
check_run_name = "CPU AWS Fast Tests"
[command.integer_bench]
workflow = "integer_benchmark.yml"
profile = "bench"
check_run_name = "Integer CPU AWS Benchmarks"
[command.integer_multi_bit_bench]
workflow = "integer_multi_bit_benchmark.yml"
profile = "bench"
check_run_name = "Integer multi bit CPU AWS Benchmarks"
[command.shortint_bench]
workflow = "shortint_benchmark.yml"
profile = "bench"
@@ -37,3 +62,8 @@ check_run_name = "Boolean CPU AWS Benchmarks"
workflow = "pbs_benchmark.yml"
profile = "bench"
check_run_name = "PBS CPU AWS Benchmarks"
[command.wasm_client_bench]
workflow = "wasm_client_benchmark.yml"
profile = "cpu-small"
check_run_name = "WASM Client AWS Benchmarks"

View File

@@ -28,10 +28,8 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > install-rustup.s
chmod +x install-rustup.sh && \
./install-rustup.sh -y --default-toolchain "${RUST_TOOLCHAIN}" \
-c rust-src -t wasm32-unknown-unknown && \
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf > install-wasm-pack.sh && \
chmod +x install-wasm-pack.sh && \
. "$HOME/.cargo/env" && \
./install-wasm-pack.sh -y && \
cargo install wasm-pack && \
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh > install-node.sh && \
chmod +x install-node.sh && \
./install-node.sh && \

View File

@@ -40,7 +40,7 @@ mkdir -p "${TFHE_BUILD_DIR}"
cd "${TFHE_BUILD_DIR}"
cmake .. -DCMAKE_BUILD_TYPE=RELEASE
cmake .. -DCMAKE_BUILD_TYPE=RELEASE -DCARGO_PROFILE="${CARGO_PROFILE}"
make -j
@@ -56,4 +56,4 @@ if [[ $(uname) == "Darwin" ]]; then
fi
# Let's go parallel
ARGS="-j$("${nproc_bin}")" make test
ARGS="-j$(${nproc_bin})" make test

View File

@@ -2,18 +2,20 @@
set -e
CURR_DIR="$(dirname "$0")"
REL_CARGO_TOML_PATH="${CURR_DIR}/../tfhe/Cargo.toml"
MIN_RUST_VERSION="$(grep rust-version "${REL_CARGO_TOML_PATH}" | cut -d '=' -f 2 | xargs)"
function usage() {
echo "$0: check minimum cargo version"
echo
echo "--help Print this message"
echo "--rust-toolchain The toolchain to check the version for with leading"
echo "--min-rust-version Check toolchain version is >= to this version, default is 1.65"
echo "--min-rust-version Check toolchain version is >= to this version, default is ${MIN_RUST_VERSION}"
echo
}
RUST_TOOLCHAIN=""
# We set the default rust version 1.65 which is the minimum version required for stable GATs
MIN_RUST_VERSION="1.65"
while [ -n "$1" ]
do

View File

@@ -2,6 +2,56 @@
set -e
function usage() {
echo "$0: shortint test runner"
echo
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 "--cargo-profile The cargo profile used to build tests"
echo
}
RUST_TOOLCHAIN="+stable"
multi_bit=""
not_multi_bit="_multi_bit"
cargo_profile="release"
while [ -n "$1" ]
do
case "$1" in
"--help" | "-h" )
usage
exit 0
;;
"--rust-toolchain" )
shift
RUST_TOOLCHAIN="$1"
;;
"--multi-bit" )
multi_bit="_multi_bit"
not_multi_bit=""
;;
"--cargo-profile" )
shift
cargo_profile="$1"
;;
*)
echo "Unknown param : $1"
exit 1
;;
esac
shift
done
if [[ "${RUST_TOOLCHAIN::1}" != "+" ]]; then
RUST_TOOLCHAIN="+${RUST_TOOLCHAIN}"
fi
CURR_DIR="$(dirname "$0")"
ARCH_FEATURE="$("${CURR_DIR}/get_arch_feature.sh")"
@@ -25,59 +75,91 @@ else
fi
if [[ "${BIG_TESTS_INSTANCE}" != 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 is too slow
filter_expression=''\
'test(/^integer::.*$/)'\
'and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]$/)'\
'and not test(~mul_crt_param_message_4_carry_4)'\
'and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]$/)'\
'and not test(/.*test_integer_smart_mul_param_message_4_carry_4$/)'
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 ${1:+"${1}"} nextest run \
cargo "${RUST_TOOLCHAIN}" nextest run \
--tests \
--release \
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--test-threads "${n_threads}" \
-E "$filter_expression"
cargo ${1:+"${1}"} test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
integer::
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--profile "${cargo_profile}" \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
-- integer::
fi
else
# 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 is too slow
filter_expression=''\
'test(/^integer::.*$/)'\
'and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]$/)'\
'and not test(~mul_crt_param_message_4_carry_4)'\
'and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]$/)'\
'and not test(/.*test_integer_smart_mul_param_message_4_carry_4$/)'
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 ${1:+"${1}"} nextest run \
num_cpu_threads="$(${nproc_bin})"
num_threads=$((num_cpu_threads * 2 / 3))
cargo "${RUST_TOOLCHAIN}" nextest run \
--tests \
--release \
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--test-threads "$(${nproc_bin})" \
--test-threads $num_threads \
-E "$filter_expression"
cargo ${1:+"${1}"} test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
integer:: -- --test-threads="$(${nproc_bin})"
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
fi
echo "Test ran in $SECONDS seconds"

20
scripts/no_dbg_calls.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -e
THIS_SCRIPT_NAME="$(basename "$0")"
TMP_FILE="$(mktemp)"
COUNT="$(git grep -rniI "dbg!" . | grep -v "${THIS_SCRIPT_NAME}" | \
tee "${TMP_FILE}" | wc -l | tr -d '[:space:]')"
cat "${TMP_FILE}"
rm -rf "${TMP_FILE}"
if [[ "${COUNT}" == "0" ]]; then
exit 0
else
echo "dbg macro calls detected, see output log above"
exit 1
fi

View File

@@ -6,7 +6,7 @@ THIS_SCRIPT_NAME="$(basename "$0")"
TMP_FILE="$(mktemp)"
COUNT="$(git grep -rniI "thfe" . | grep -v "${THIS_SCRIPT_NAME}" | \
COUNT="$(git grep -rniI "thfe\|tfhr\|thfr" . | grep -v "${THIS_SCRIPT_NAME}" | \
tee "${TMP_FILE}" | wc -l | tr -d '[:space:]')"
cat "${TMP_FILE}"

View File

@@ -2,6 +2,54 @@
set -e
function usage() {
echo "$0: shortint test runner"
echo
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 "--cargo-profile The cargo profile used to build tests"
echo
}
RUST_TOOLCHAIN="+stable"
multi_bit=""
cargo_profile="release"
while [ -n "$1" ]
do
case "$1" in
"--help" | "-h" )
usage
exit 0
;;
"--rust-toolchain" )
shift
RUST_TOOLCHAIN="$1"
;;
"--multi-bit" )
multi_bit="_multi_bit"
;;
"--cargo-profile" )
shift
cargo_profile="$1"
;;
*)
echo "Unknown param : $1"
exit 1
;;
esac
shift
done
if [[ "${RUST_TOOLCHAIN::1}" != "+" ]]; then
RUST_TOOLCHAIN="+${RUST_TOOLCHAIN}"
fi
CURR_DIR="$(dirname "$0")"
ARCH_FEATURE="$("${CURR_DIR}/get_arch_feature.sh")"
@@ -31,90 +79,116 @@ else
fi
if [[ "${BIG_TESTS_INSTANCE}" != TRUE ]]; then
filter_expression_small_params=''\
'('\
' test(/^shortint::.*_param_message_1_carry_1$/)'\
'or test(/^shortint::.*_param_message_1_carry_2$/)'\
'or test(/^shortint::.*_param_message_1_carry_3$/)'\
'or test(/^shortint::.*_param_message_1_carry_4$/)'\
'or test(/^shortint::.*_param_message_1_carry_5$/)'\
'or test(/^shortint::.*_param_message_1_carry_6$/)'\
'or test(/^shortint::.*_param_message_2_carry_1$/)'\
'or test(/^shortint::.*_param_message_2_carry_2$/)'\
'or test(/^shortint::.*_param_message_2_carry_3$/)'\
'or test(/^shortint::.*_param_message_3_carry_1$/)'\
'or test(/^shortint::.*_param_message_3_carry_2$/)'\
'or test(/^shortint::.*_param_message_3_carry_3$/)'\
')'\
'and not test(~smart_add_and_mul)' # This test is too slow
if [[ "${FAST_TESTS}" != TRUE ]]; then
filter_expression_small_params="""\
(\
test(/^shortint::.*_param${multi_bit}_message_1_carry_1${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_2${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_3${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_4${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_5${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_6${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_1${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_2${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_3${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
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/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
else
filter_expression_small_params="""\
(\
test(/^shortint::.*_param${multi_bit}_message_2_carry_1${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_2${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_3${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
fi
# Run tests only no examples or benches with small params and more threads
cargo ${1:+"${1}"} nextest run \
cargo "${RUST_TOOLCHAIN}" nextest run \
--tests \
--release \
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--test-threads "${n_threads_small}" \
-E "${filter_expression_small_params}"
filter_expression_big_params=''\
'('\
' test(/^shortint::.*_param_message_4_carry_4$/)'\
')'\
'and not test(~smart_add_and_mul)'
if [[ "${FAST_TESTS}" != TRUE ]]; then
filter_expression_big_params="""\
(\
test(/^shortint::.*_param${multi_bit}_message_4_carry_4${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
) \
and not test(~smart_add_and_mul)"""
# Run tests only no examples or benches with big params and less threads
cargo ${1:+"${1}"} nextest run \
cargo "${RUST_TOOLCHAIN}" nextest run \
--tests \
--release \
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--test-threads "${n_threads_big}" \
-E "${filter_expression_big_params}"
cargo ${1:+"${1}"} test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--doc \
shortint::
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--profile "${cargo_profile}" \
--package tfhe \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--doc \
-- shortint::
fi
fi
else
filter_expression=''\
'('\
' test(/^shortint::.*_param_message_1_carry_1$/)'\
'or test(/^shortint::.*_param_message_1_carry_2$/)'\
'or test(/^shortint::.*_param_message_1_carry_3$/)'\
'or test(/^shortint::.*_param_message_1_carry_4$/)'\
'or test(/^shortint::.*_param_message_1_carry_5$/)'\
'or test(/^shortint::.*_param_message_1_carry_6$/)'\
'or test(/^shortint::.*_param_message_2_carry_1$/)'\
'or test(/^shortint::.*_param_message_2_carry_2$/)'\
'or test(/^shortint::.*_param_message_2_carry_3$/)'\
'or test(/^shortint::.*_param_message_3_carry_1$/)'\
'or test(/^shortint::.*_param_message_3_carry_2$/)'\
'or test(/^shortint::.*_param_message_3_carry_3$/)'\
'or test(/^shortint::.*_param_message_4_carry_4$/)'\
')'\
'and not test(~smart_add_and_mul)' # This test is too slow
if [[ "${FAST_TESTS}" != TRUE ]]; then
filter_expression="""\
(\
test(/^shortint::.*_param${multi_bit}_message_1_carry_1${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_2${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_3${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_4${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_5${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_6${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_1${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_2${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_3${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
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::.*_param${multi_bit}_message_4_carry_4${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
else
filter_expression="""\
(\
test(/^shortint::.*_param${multi_bit}_message_2_carry_1${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_2${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_3${multi_bit:+"_group_[0-9]"}(_compact_pk)?_ks_pbs/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
fi
# Run tests only no examples or benches with small params and more threads
cargo ${1:+"${1}"} nextest run \
cargo "${RUST_TOOLCHAIN}" nextest run \
--tests \
--release \
--cargo-profile "${cargo_profile}" \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--test-threads "$(${nproc_bin})" \
-E "${filter_expression}"
cargo ${1:+"${1}"} test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--doc \
shortint:: -- --test-threads="$(${nproc_bin})"
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--profile "${cargo_profile}" \
--package tfhe \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--doc \
-- --test-threads="$(${nproc_bin})" shortint::
fi
fi
echo "Test ran in $SECONDS seconds"

View File

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
@@ -10,25 +10,37 @@ repository = "https://github.com/zama-ai/tfhe-rs"
license = "BSD-3-Clause-Clear"
description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
build = "build.rs"
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt", "/js_on_wasm_tests/"]
rust-version = "1.65"
exclude = [
"/docs/",
"/c_api_tests/",
"/CMakeLists.txt",
"/js_on_wasm_tests/",
"/web_wasm_parallel_tests/",
]
rust-version = "1.67"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
rand = "0.8.5"
rand_distr = "0.4.3"
kolmogorov_smirnov = "1.1.0"
paste = "1.0.7"
lazy_static = { version = "1.4.0" }
criterion = "0.4.0"
doc-comment = "0.3.3"
serde_json = "1.0.94"
clap = { version = "4.2.7", features = ["derive"] }
# Used in user documentation
bincode = "1.3.3"
fs2 = { version = "0.4.3" }
itertools = "0.10.5"
num_cpus = "1.15"
# For erf and normality test
libm = "0.2.6"
test-case = "3.1.0"
combine = "4.6.6"
env_logger = "0.10.0"
log = "0.4.19"
[build-dependencies]
cbindgen = { version = "0.24.3", optional = true }
@@ -53,9 +65,10 @@ fs2 = { version = "0.4.3", optional = true }
itertools = "0.10.5"
# wasm deps
wasm-bindgen = { version = "0.2.63", features = [
wasm-bindgen = { version = "0.2.86", features = [
"serde-serialize",
], optional = true }
wasm-bindgen-rayon = { version = "1.0", optional = true }
js-sys = { version = "0.3", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
serde-wasm-bindgen = { version = "0.4", optional = true }
@@ -76,7 +89,7 @@ experimental-force_fft_algo_dif4 = []
__c_api = ["cbindgen", "bincode"]
boolean-c-api = ["boolean", "__c_api"]
shortint-c-api = ["shortint", "__c_api"]
high-level-c-api = ["boolean", "shortint", "integer", "__c_api"]
high-level-c-api = ["boolean-c-api", "shortint-c-api", "integer", "__c_api"]
__wasm_api = [
"wasm-bindgen",
@@ -89,6 +102,9 @@ __wasm_api = [
]
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"]
nightly-avx512 = ["concrete-fft/nightly", "pulp/nightly"]
@@ -174,20 +190,50 @@ path = "benches/utilities.rs"
harness = false
required-features = ["boolean", "shortint", "integer", "internal-keycache"]
# Examples used as tools
[[example]]
name = "wasm_benchmarks_parser"
path = "examples/utilities/wasm_benchmarks_parser.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "generates_test_keys"
path = "examples/utilities/generates_test_keys.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "boolean_key_sizes"
path = "examples/utilities/boolean_key_sizes.rs"
required-features = ["boolean", "internal-keycache"]
[[example]]
name = "shortint_key_sizes"
path = "examples/utilities/shortint_key_sizes.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "hlapi_compact_pk_ct_sizes"
path = "examples/utilities/hlapi_compact_pk_ct_sizes.rs"
required-features = ["integer", "internal-keycache"]
[[example]]
name = "micro_bench_and"
path = "examples/utilities/micro_bench_and.rs"
required-features = ["boolean"]
# Real use-case examples
[[example]]
name = "dark_market"
required-features = ["integer", "internal-keycache"]
[[example]]
name = "regex_engine"
required-features = ["integer"]
[[example]]
name = "sha256_bool"
required-features = ["boolean"]
[lib]

View File

@@ -1,6 +1,6 @@
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tfhe::boolean::client_key::ClientKey;
@@ -16,6 +16,24 @@ criterion_group!(
criterion_main!(gates_benches);
/// Helper function to write boolean benchmarks parameters to disk in JSON format.
pub fn write_to_json_boolean<T: Into<CryptoParametersRecord<u32>>>(
bench_id: &str,
params: T,
params_alias: impl Into<String>,
display_name: impl Into<String>,
) {
write_to_json(
bench_id,
params,
params_alias,
display_name,
&OperatorType::Atomic,
1,
vec![1],
);
}
// Put all `bench_function` in one place
// so the keygen is only run once per parameters saving time.
fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
@@ -28,35 +46,33 @@ fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
let ct2 = cks.encrypt(false);
let ct3 = cks.encrypt(true);
let operator = OperatorType::Atomic;
let id = format!("AND::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.and(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "and", &operator);
write_to_json_boolean(&id, params, parameter_name, "and");
let id = format!("NAND::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.nand(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "nand", &operator);
write_to_json_boolean(&id, params, parameter_name, "nand");
let id = format!("OR::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.or(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "or", &operator);
write_to_json_boolean(&id, params, parameter_name, "or");
let id = format!("XOR::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.xor(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "xor", &operator);
write_to_json_boolean(&id, params, parameter_name, "xor");
let id = format!("XNOR::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.xnor(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "xnor", &operator);
write_to_json_boolean(&id, params, parameter_name, "xnor");
let id = format!("NOT::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.not(&ct1))));
write_to_json(&id, params, parameter_name, "not", &operator);
write_to_json_boolean(&id, params, parameter_name, "not");
let id = format!("MUX::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.mux(&ct1, &ct2, &ct3))));
write_to_json(&id, params, parameter_name, "mux", &operator);
write_to_json_boolean(&id, params, parameter_name, "mux");
}
fn bench_default_parameters(c: &mut Criterion) {

View File

@@ -1,30 +1,32 @@
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
use rayon::prelude::*;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use serde::Serialize;
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
use tfhe::core_crypto::prelude::*;
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::*;
use tfhe::shortint::Parameters;
use tfhe::shortint::ClassicPBSParameters;
const SHORTINT_BENCH_PARAMS: [Parameters; 15] = [
PARAM_MESSAGE_1_CARRY_0,
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_0,
PARAM_MESSAGE_2_CARRY_1,
PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_0,
PARAM_MESSAGE_3_CARRY_2,
PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_0,
PARAM_MESSAGE_4_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
PARAM_MESSAGE_5_CARRY_0,
PARAM_MESSAGE_6_CARRY_0,
PARAM_MESSAGE_7_CARRY_0,
PARAM_MESSAGE_8_CARRY_0,
const SHORTINT_BENCH_PARAMS: [ClassicPBSParameters; 15] = [
PARAM_MESSAGE_1_CARRY_0_KS_PBS,
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
PARAM_MESSAGE_2_CARRY_0_KS_PBS,
PARAM_MESSAGE_2_CARRY_1_KS_PBS,
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
PARAM_MESSAGE_3_CARRY_0_KS_PBS,
PARAM_MESSAGE_3_CARRY_2_KS_PBS,
PARAM_MESSAGE_3_CARRY_3_KS_PBS,
PARAM_MESSAGE_4_CARRY_0_KS_PBS,
PARAM_MESSAGE_4_CARRY_3_KS_PBS,
PARAM_MESSAGE_4_CARRY_4_KS_PBS,
PARAM_MESSAGE_5_CARRY_0_KS_PBS,
PARAM_MESSAGE_6_CARRY_0_KS_PBS,
PARAM_MESSAGE_7_CARRY_0_KS_PBS,
PARAM_MESSAGE_8_CARRY_0_KS_PBS,
];
const BOOLEAN_BENCH_PARAMS: [(&str, BooleanParameters); 2] = [
@@ -41,71 +43,104 @@ criterion_group!(
criterion_group!(
name = multi_bit_pbs_group;
config = Criterion::default().sample_size(2000);
targets = multi_bit_pbs::<u64>, multi_bit_pbs::<u32>
targets = multi_bit_pbs::<u64>,
multi_bit_pbs::<u32>,
multi_bit_deterministic_pbs::<u64>,
multi_bit_deterministic_pbs::<u32>,
);
criterion_main!(pbs_group, multi_bit_pbs_group);
criterion_group!(
name = pbs_throughput_group;
config = Criterion::default().sample_size(100);
targets = pbs_throughput::<u64>, pbs_throughput::<u32>
);
fn benchmark_parameters<Scalar: Numeric>() -> Vec<(String, CryptoParametersRecord)> {
criterion_main!(pbs_group, multi_bit_pbs_group, pbs_throughput_group);
fn benchmark_parameters<Scalar: UnsignedInteger>(
) -> Vec<(&'static str, CryptoParametersRecord<Scalar>)> {
if Scalar::BITS == 64 {
SHORTINT_BENCH_PARAMS
.iter()
.map(|params| (params.name(), params.to_owned().into()))
.map(|params| {
(
params.name(),
<ClassicPBSParameters as Into<PBSParameters>>::into(*params)
.to_owned()
.into(),
)
})
.collect()
} else if Scalar::BITS == 32 {
BOOLEAN_BENCH_PARAMS
.iter()
.map(|(name, params)| (name.to_string(), params.to_owned().into()))
.map(|(name, params)| (*name, params.to_owned().into()))
.collect()
} else {
vec![]
}
}
fn multi_bit_benchmark_parameters<Scalar: Numeric>(
) -> Vec<(String, (CryptoParametersRecord, LweBskGroupingFactor))> {
fn throughput_benchmark_parameters<Scalar: UnsignedInteger>(
) -> Vec<(&'static str, CryptoParametersRecord<Scalar>)> {
if Scalar::BITS == 64 {
vec![
(
"4_bits_multi_bit_group_2".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(788)),
lwe_modular_std_dev: Some(StandardDev(0.000003871078133364534)),
pbs_base_log: Some(DecompositionBaseLog(22)),
pbs_level: Some(DecompositionLevelCount(1)),
glwe_dimension: Some(GlweDimension(2)),
glwe_modular_std_dev: Some(StandardDev(0.0000000000000003152931493498455)),
polynomial_size: Some(PolynomialSize(1024)),
..Default::default()
},
LweBskGroupingFactor(2),
),
),
(
"4_bits_multi_bit_group_3".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(789)),
lwe_modular_std_dev: Some(StandardDev(0.0000038003596741624174)),
pbs_base_log: Some(DecompositionBaseLog(22)),
pbs_level: Some(DecompositionLevelCount(1)),
glwe_dimension: Some(GlweDimension(2)),
glwe_modular_std_dev: Some(StandardDev(0.0000000000000003152931493498455)),
polynomial_size: Some(PolynomialSize(1024)),
..Default::default()
},
LweBskGroupingFactor(3),
),
),
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
PARAM_MESSAGE_3_CARRY_3_KS_PBS,
]
.iter()
.map(|params| {
(
params.name(),
<ClassicPBSParameters as Into<PBSParameters>>::into(*params)
.to_owned()
.into(),
)
})
.collect()
} else if Scalar::BITS == 32 {
BOOLEAN_BENCH_PARAMS
.iter()
.map(|(name, params)| (*name, params.to_owned().into()))
.collect()
} else {
vec![]
}
}
fn multi_bit_benchmark_parameters<Scalar: UnsignedInteger + Default>() -> Vec<(
&'static str,
CryptoParametersRecord<Scalar>,
LweBskGroupingFactor,
)> {
if Scalar::BITS == 64 {
vec![
PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_3_CARRY_3_GROUP_2_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_3_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
PARAM_MULTI_BIT_MESSAGE_3_CARRY_3_GROUP_3_KS_PBS,
]
.iter()
.map(|params| {
(
params.name(),
<MultiBitPBSParameters as Into<PBSParameters>>::into(*params)
.to_owned()
.into(),
params.grouping_factor,
)
})
.collect()
} else {
// For now there are no parameters available to test multi bit PBS on 32 bits.
vec![]
}
}
fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize> + Serialize>(c: &mut Criterion) {
let bench_name = "PBS_mem-optimized";
let mut bench_group = c.benchmark_group(bench_name);
@@ -195,17 +230,24 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
});
}
write_to_json(&id, *params, name, "pbs", &OperatorType::Atomic);
let bit_size = (params.message_modulus.unwrap_or(2) as u32).ilog2();
write_to_json(
&id,
*params,
*name,
"pbs",
&OperatorType::Atomic,
bit_size,
vec![bit_size],
);
}
}
fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Sync>(
fn multi_bit_pbs<
Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Default + Sync + Serialize,
>(
c: &mut Criterion,
) {
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
// computations
// Define parameters for LweBootstrapKey creation
let bench_name = "multi_bits_PBS";
let mut bench_group = c.benchmark_group(bench_name);
@@ -217,7 +259,7 @@ fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Syn
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
for (name, (params, grouping_factor)) in multi_bit_benchmark_parameters::<Scalar>().iter() {
for (name, params, grouping_factor) in multi_bit_benchmark_parameters::<Scalar>().iter() {
// Create the LweSecretKey
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
params.lwe_dimension.unwrap(),
@@ -271,13 +313,233 @@ fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Syn
&mut out_pbs_ct,
&accumulator.as_view(),
&multi_bit_bsk,
// Leave one thread to the OS and one for the ext product loop
ThreadCount(2.max(num_cpus::get_physical() - 2)),
ThreadCount(10),
);
black_box(&mut out_pbs_ct);
})
});
write_to_json(&id, *params, name, "pbs", &OperatorType::Atomic);
let bit_size = params.message_modulus.unwrap().ilog2();
write_to_json(
&id,
*params,
*name,
"pbs",
&OperatorType::Atomic,
bit_size,
vec![bit_size],
);
}
}
fn multi_bit_deterministic_pbs<
Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Default + Serialize + Sync,
>(
c: &mut Criterion,
) {
let bench_name = "multi_bits_deterministic_PBS";
let mut bench_group = c.benchmark_group(bench_name);
// 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());
for (name, params, grouping_factor) in multi_bit_benchmark_parameters::<Scalar>().iter() {
// Create the LweSecretKey
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
params.lwe_dimension.unwrap(),
&mut secret_generator,
);
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
allocate_and_generate_new_binary_glwe_secret_key(
params.glwe_dimension.unwrap(),
params.polynomial_size.unwrap(),
&mut secret_generator,
);
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
let multi_bit_bsk = FourierLweMultiBitBootstrapKey::new(
params.lwe_dimension.unwrap(),
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
params.pbs_base_log.unwrap(),
params.pbs_level.unwrap(),
*grouping_factor,
);
// Allocate a new LweCiphertext and encrypt our plaintext
let lwe_ciphertext_in = allocate_and_encrypt_new_lwe_ciphertext(
&input_lwe_secret_key,
Plaintext(Scalar::ZERO),
params.lwe_modular_std_dev.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
&mut encryption_generator,
);
let accumulator = GlweCiphertext::new(
Scalar::ZERO,
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
);
// Allocate the LweCiphertext to store the result of the PBS
let mut out_pbs_ct = LweCiphertext::new(
Scalar::ZERO,
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
);
let id = format!("{bench_name}_{name}_parallelized");
bench_group.bench_function(&id, |b| {
b.iter(|| {
multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext(
&lwe_ciphertext_in,
&mut out_pbs_ct,
&accumulator.as_view(),
&multi_bit_bsk,
ThreadCount(10),
);
black_box(&mut out_pbs_ct);
})
});
let bit_size = params.message_modulus.unwrap().ilog2();
write_to_json(
&id,
*params,
*name,
"pbs",
&OperatorType::Atomic,
bit_size,
vec![bit_size],
);
}
}
fn pbs_throughput<Scalar: UnsignedTorus + CastInto<usize> + Sync + Send + Serialize>(
c: &mut Criterion,
) {
let bench_name = "PBS_throughput";
let mut bench_group = c.benchmark_group(bench_name);
// 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());
for (name, params) in throughput_benchmark_parameters::<Scalar>().iter() {
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
params.lwe_dimension.unwrap(),
&mut secret_generator,
);
let glwe_secret_key = GlweSecretKey::new_empty_key(
Scalar::ZERO,
params.glwe_dimension.unwrap(),
params.polynomial_size.unwrap(),
);
let big_lwe_sk = glwe_secret_key.into_lwe_secret_key();
let big_lwe_dimension = big_lwe_sk.lwe_dimension();
const NUM_CTS: usize = 512;
let lwe_vec: Vec<_> = (0..NUM_CTS)
.map(|_| {
allocate_and_encrypt_new_lwe_ciphertext(
&input_lwe_secret_key,
Plaintext(Scalar::ZERO),
params.lwe_modular_std_dev.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
&mut encryption_generator,
)
})
.collect();
let mut output_lwe_list = LweCiphertextList::new(
Scalar::ZERO,
big_lwe_dimension.to_lwe_size(),
LweCiphertextCount(NUM_CTS),
params.ciphertext_modulus.unwrap(),
);
let lwe_vec = lwe_vec;
let fft = Fft::new(params.polynomial_size.unwrap());
let fft = fft.as_view();
let mut vec_buffers: Vec<_> = (0..NUM_CTS)
.map(|_| {
let mut buffers = ComputationBuffers::new();
buffers.resize(
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<Scalar>(
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
fft,
)
.unwrap()
.unaligned_bytes_required(),
);
buffers
})
.collect();
let glwe = GlweCiphertext::new(
Scalar::ONE << 60,
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
params.ciphertext_modulus.unwrap(),
);
let fbsk = FourierLweBootstrapKey::new(
params.lwe_dimension.unwrap(),
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
params.pbs_base_log.unwrap(),
params.pbs_level.unwrap(),
);
for chunk_size in [1, 16, 32, 64, 128, 256, 512] {
let id = format!("{bench_name}_{name}_{chunk_size}chunk");
{
bench_group.bench_function(&id, |b| {
b.iter(|| {
lwe_vec
.par_iter()
.zip(output_lwe_list.par_iter_mut())
.zip(vec_buffers.par_iter_mut())
.take(chunk_size)
.for_each(|((input_lwe, mut out_lwe), buffer)| {
programmable_bootstrap_lwe_ciphertext_mem_optimized(
input_lwe,
&mut out_lwe,
&glwe,
&fbsk,
fft,
buffer.stack(),
);
});
black_box(&mut output_lwe_list);
})
});
}
let bit_size = (params.message_modulus.unwrap_or(2) as u32).ilog2();
write_to_json(
&id,
*params,
*name,
"pbs",
&OperatorType::Atomic,
bit_size,
vec![bit_size],
);
}
}
}

View File

@@ -2,20 +2,22 @@
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use criterion::{criterion_group, criterion_main, Criterion};
use crate::utilities::{write_to_json, OperatorType};
use std::env;
use criterion::{criterion_group, Criterion};
use itertools::iproduct;
use rand::Rng;
use std::array::IntoIter;
use std::vec::IntoIter;
use tfhe::integer::keycache::KEY_CACHE;
use tfhe::integer::{RadixCiphertextBig, ServerKey};
use tfhe::integer::{RadixCiphertext, ServerKey};
use tfhe::shortint::keycache::NamedParam;
#[allow(unused_imports)]
use tfhe::shortint::parameters::{
PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2, PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
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, PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
};
/// An iterator that yields a succession of combinations
@@ -23,31 +25,47 @@ use tfhe::shortint::parameters::{
/// in radix decomposition
struct ParamsAndNumBlocksIter {
params_and_bit_sizes:
itertools::Product<IntoIter<tfhe::shortint::Parameters, 1>, IntoIter<usize, 7>>,
itertools::Product<IntoIter<tfhe::shortint::PBSParameters>, IntoIter<usize>>,
}
impl Default for ParamsAndNumBlocksIter {
fn default() -> Self {
// FIXME One set of parameter is tested since we want to benchmark only quickest operations.
const PARAMS: [tfhe::shortint::Parameters; 1] = [
PARAM_MESSAGE_2_CARRY_2,
// PARAM_MESSAGE_3_CARRY_3,
// PARAM_MESSAGE_4_CARRY_4,
];
const BIT_SIZES: [usize; 7] = [8, 16, 32, 40, 64, 128, 256];
let params_and_bit_sizes = iproduct!(PARAMS, BIT_SIZES);
Self {
params_and_bit_sizes,
let is_multi_bit = match env::var("__TFHE_RS_BENCH_TYPE") {
Ok(val) => val.to_lowercase() == "multi_bit",
Err(_) => false,
};
if is_multi_bit {
let params = vec![PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS.into()];
let bit_sizes = vec![8, 16, 32, 40, 64];
let params_and_bit_sizes = iproduct!(params, bit_sizes);
Self {
params_and_bit_sizes,
}
} else {
// FIXME One set of parameter is tested since we want to benchmark only quickest
// operations.
let params = vec![
PARAM_MESSAGE_2_CARRY_2_KS_PBS.into(),
// PARAM_MESSAGE_3_CARRY_3_KS_PBS.into(),
// PARAM_MESSAGE_4_CARRY_4_KS_PBS.into(),
];
let bit_sizes = vec![8, 16, 32, 40, 64, 128, 256];
let params_and_bit_sizes = iproduct!(params, bit_sizes);
Self {
params_and_bit_sizes,
}
}
}
}
impl Iterator for ParamsAndNumBlocksIter {
type Item = (tfhe::shortint::Parameters, usize, usize);
type Item = (tfhe::shortint::PBSParameters, usize, usize);
fn next(&mut self) -> Option<Self::Item> {
let (param, bit_size) = self.params_and_bit_sizes.next()?;
let num_block =
(bit_size as f64 / (param.message_modulus.0 as f64).log(2.0)).ceil() as usize;
(bit_size as f64 / (param.message_modulus().0 as f64).log(2.0)).ceil() as usize;
Some((param, num_block, bit_size))
}
@@ -61,7 +79,7 @@ fn bench_server_key_binary_function_dirty_inputs<F>(
display_name: &str,
binary_op: F,
) where
F: Fn(&ServerKey, &mut RadixCiphertextBig, &mut RadixCiphertextBig),
F: Fn(&ServerKey, &mut RadixCiphertext, &mut RadixCiphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
@@ -88,7 +106,7 @@ fn bench_server_key_binary_function_dirty_inputs<F>(
let mut ct_1 = cks.encrypt_radix(clear_1, num_block);
// Raise the degree, so as to ensure worst case path in operations
let mut carry_mod = param.carry_modulus.0;
let mut carry_mod = param.carry_modulus().0;
while carry_mod > 0 {
// Raise the degree, so as to ensure worst case path in operations
let clearlow = rng.gen::<u128>();
@@ -113,12 +131,14 @@ fn bench_server_key_binary_function_dirty_inputs<F>(
)
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus().0.ilog2(); num_block],
);
}
@@ -133,7 +153,7 @@ fn bench_server_key_binary_function_clean_inputs<F>(
display_name: &str,
binary_op: F,
) where
F: Fn(&ServerKey, &mut RadixCiphertextBig, &mut RadixCiphertextBig),
F: Fn(&ServerKey, &mut RadixCiphertext, &mut RadixCiphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
@@ -171,12 +191,14 @@ fn bench_server_key_binary_function_clean_inputs<F>(
)
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus().0.ilog2(); num_block],
);
}
@@ -191,7 +213,7 @@ fn bench_server_key_unary_function_dirty_inputs<F>(
display_name: &str,
unary_fn: F,
) where
F: Fn(&ServerKey, &mut RadixCiphertextBig),
F: Fn(&ServerKey, &mut RadixCiphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
@@ -216,7 +238,7 @@ fn bench_server_key_unary_function_dirty_inputs<F>(
let mut ct_0 = cks.encrypt_radix(clear_0, num_block);
// Raise the degree, so as to ensure worst case path in operations
let mut carry_mod = param.carry_modulus.0;
let mut carry_mod = param.carry_modulus().0;
while carry_mod > 0 {
// Raise the degree, so as to ensure worst case path in operations
let clearlow = rng.gen::<u128>();
@@ -240,12 +262,14 @@ fn bench_server_key_unary_function_dirty_inputs<F>(
)
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus().0.ilog2(); num_block],
);
}
@@ -260,7 +284,7 @@ fn bench_server_key_unary_function_clean_inputs<F>(
display_name: &str,
unary_fn: F,
) where
F: Fn(&ServerKey, &mut RadixCiphertextBig),
F: Fn(&ServerKey, &mut RadixCiphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
@@ -294,12 +318,14 @@ fn bench_server_key_unary_function_clean_inputs<F>(
)
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus().0.ilog2(); num_block],
);
}
@@ -312,7 +338,7 @@ fn bench_server_key_binary_scalar_function_dirty_inputs<F>(
display_name: &str,
binary_op: F,
) where
F: Fn(&ServerKey, &mut RadixCiphertextBig, u64),
F: Fn(&ServerKey, &mut RadixCiphertext, u64),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
@@ -335,7 +361,7 @@ fn bench_server_key_binary_scalar_function_dirty_inputs<F>(
let mut ct_0 = cks.encrypt_radix(clear_0, num_block);
// Raise the degree, so as to ensure worst case path in operations
let mut carry_mod = param.carry_modulus.0;
let mut carry_mod = param.carry_modulus().0;
while carry_mod > 0 {
// Raise the degree, so as to ensure worst case path in operations
let clearlow = rng.gen::<u128>();
@@ -361,12 +387,14 @@ fn bench_server_key_binary_scalar_function_dirty_inputs<F>(
)
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus().0.ilog2(); num_block],
);
}
@@ -379,7 +407,7 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F>(
display_name: &str,
binary_op: F,
) where
F: Fn(&ServerKey, &mut RadixCiphertextBig, u64),
F: Fn(&ServerKey, &mut RadixCiphertext, u64),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
@@ -415,12 +443,14 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F>(
)
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus().0.ilog2(); num_block],
);
}
@@ -531,6 +561,7 @@ define_server_key_bench_default_fn!(method_name: mul_parallelized, display_name:
define_server_key_bench_default_fn!(method_name: bitand_parallelized, display_name: bitand);
define_server_key_bench_default_fn!(method_name: bitxor_parallelized, display_name: bitxor);
define_server_key_bench_default_fn!(method_name: bitor_parallelized, display_name: bitor);
define_server_key_bench_unary_default_fn!(method_name: bitnot_parallelized, display_name: bitnot);
define_server_key_bench_fn!(method_name: unchecked_add, display_name: add);
define_server_key_bench_fn!(method_name: unchecked_sub, display_name: sub);
@@ -573,6 +604,46 @@ define_server_key_bench_scalar_fn!(
define_server_key_bench_scalar_default_fn!(method_name: scalar_add_parallelized, display_name: add);
define_server_key_bench_scalar_default_fn!(method_name: scalar_sub_parallelized, display_name: sub);
define_server_key_bench_scalar_default_fn!(method_name: scalar_mul_parallelized, display_name: mul);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_left_shift_parallelized,
display_name: left_shift
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_right_shift_parallelized,
display_name: right_shift
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_eq_parallelized,
display_name: scalar_equal
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_ne_parallelized,
display_name: scalar_not_equal
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_le_parallelized,
display_name: scalar_less_or_equal
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_lt_parallelized,
display_name: scalar_less_than
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_ge_parallelized,
display_name: scalar_greater_or_equal
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_gt_parallelized,
display_name: scalar_greater_than
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_max_parallelized,
display_name: scalar_max
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_min_parallelized,
display_name: scalar_min
);
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_add, display_name: add);
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_sub, display_name: sub);
@@ -644,13 +715,31 @@ define_server_key_bench_fn!(
define_server_key_bench_default_fn!(method_name: max_parallelized, display_name: max);
define_server_key_bench_default_fn!(method_name: min_parallelized, display_name: min);
define_server_key_bench_default_fn!(method_name: eq_parallelized, display_name: equal);
define_server_key_bench_default_fn!(method_name: ne_parallelized, display_name: not_equal);
define_server_key_bench_default_fn!(method_name: lt_parallelized, display_name: less_than);
define_server_key_bench_default_fn!(method_name: le_parallelized, display_name: less_or_equal);
define_server_key_bench_default_fn!(method_name: gt_parallelized, display_name: greater_than);
define_server_key_bench_default_fn!(method_name: ge_parallelized, display_name: greater_or_equal);
define_server_key_bench_default_fn!(
method_name: left_shift_parallelized,
display_name: left_shift
);
define_server_key_bench_default_fn!(
method_name: right_shift_parallelized,
display_name: right_shift
);
define_server_key_bench_default_fn!(
method_name: rotate_left_parallelized,
display_name: rotate_left
);
define_server_key_bench_default_fn!(
method_name: rotate_right_parallelized,
display_name: rotate_right
);
criterion_group!(
smart_arithmetic_operation,
smart_ops,
smart_neg,
smart_add,
smart_mul,
@@ -667,7 +756,7 @@ criterion_group!(
);
criterion_group!(
smart_arithmetic_parallelized_operation,
smart_parallelized_ops,
smart_add_parallelized,
smart_sub_parallelized,
smart_mul_parallelized,
@@ -684,45 +773,62 @@ criterion_group!(
);
criterion_group!(
arithmetic_parallelized_operation,
default_parallelized_ops,
add_parallelized,
sub_parallelized,
mul_parallelized,
neg_parallelized,
bitand_parallelized,
bitnot_parallelized,
bitor_parallelized,
bitxor_parallelized,
max_parallelized,
min_parallelized,
eq_parallelized,
ne_parallelized,
lt_parallelized,
le_parallelized,
gt_parallelized,
ge_parallelized,
left_shift_parallelized,
right_shift_parallelized,
rotate_left_parallelized,
rotate_right_parallelized,
);
criterion_group!(
smart_scalar_arithmetic_operation,
smart_scalar_ops,
smart_scalar_add,
smart_scalar_sub,
smart_scalar_mul,
);
criterion_group!(
smart_scalar_arithmetic_parallel_operation,
smart_scalar_parallelized_ops,
smart_scalar_add_parallelized,
smart_scalar_sub_parallelized,
smart_scalar_mul_parallelized,
);
criterion_group!(
scalar_arithmetic_parallel_operation,
default_scalar_parallelized_ops,
scalar_add_parallelized,
scalar_sub_parallelized,
scalar_mul_parallelized,
scalar_left_shift_parallelized,
scalar_right_shift_parallelized,
scalar_eq_parallelized,
scalar_ne_parallelized,
scalar_lt_parallelized,
scalar_le_parallelized,
scalar_gt_parallelized,
scalar_ge_parallelized,
scalar_min_parallelized,
scalar_max_parallelized,
);
criterion_group!(
unchecked_arithmetic_operation,
unchecked_ops,
unchecked_add,
unchecked_sub,
unchecked_mul,
@@ -739,7 +845,7 @@ criterion_group!(
);
criterion_group!(
unchecked_scalar_arithmetic_operation,
unchecked_scalar_ops,
unchecked_scalar_add,
unchecked_scalar_sub,
unchecked_small_scalar_mul,
@@ -757,36 +863,27 @@ criterion_group!(
criterion_group!(misc, full_propagate, full_propagate_parallelized);
// User-oriented benchmark group.
// This gather all the operations that a high-level user could use.
criterion_group!(
fast_integer_benchmarks,
bitand_parallelized,
bitor_parallelized,
bitxor_parallelized,
add_parallelized,
sub_parallelized,
mul_parallelized,
neg_parallelized,
min_parallelized,
max_parallelized,
eq_parallelized,
lt_parallelized,
le_parallelized,
gt_parallelized,
ge_parallelized,
scalar_add_parallelized,
scalar_sub_parallelized,
scalar_mul_parallelized,
);
fn main() {
match env::var("__TFHE_RS_BENCH_OP_FLAVOR") {
Ok(val) => {
match val.to_lowercase().as_str() {
"default" => default_parallelized_ops(),
"default_scalar" => default_scalar_parallelized_ops(),
"smart" => smart_ops(),
"smart_scalar" => smart_scalar_ops(),
"smart_parallelized" => smart_parallelized_ops(),
"smart_scalar_parallelized" => smart_scalar_parallelized_ops(),
"unchecked" => unchecked_ops(),
"unchecked_scalar" => unchecked_scalar_ops(),
"misc" => misc(),
_ => panic!("unknown benchmark operations flavor"),
};
}
Err(_) => {
default_parallelized_ops();
default_scalar_parallelized_ops()
}
};
criterion_main!(
fast_integer_benchmarks,
// smart_arithmetic_operation,
// smart_arithmetic_parallelized_operation,
// smart_scalar_arithmetic_operation,
// smart_scalar_arithmetic_parallel_operation,
// unchecked_arithmetic_operation,
// unchecked_scalar_arithmetic_operation,
// misc,
);
Criterion::default().configure_from_args().final_summary();
}

View File

@@ -10,7 +10,7 @@ use tfhe::core_crypto::seeders::new_seeder;
use tfhe::shortint::prelude::*;
fn criterion_bench(c: &mut Criterion) {
let parameters = PARAM_MESSAGE_2_CARRY_2;
let parameters = PARAM_MESSAGE_2_CARRY_2_KS_PBS;
let mut seeder = new_seeder();
let mut deterministic_seeder =
DeterministicSeeder::<ActivatedRandomGenerator>::new(seeder.seed());

View File

@@ -1,41 +1,43 @@
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use criterion::{criterion_group, criterion_main, Criterion};
use crate::utilities::{write_to_json, OperatorType};
use std::env;
use criterion::{criterion_group, Criterion};
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::*;
use tfhe::shortint::{CiphertextBig, Parameters, ServerKey};
use tfhe::shortint::{Ciphertext, ClassicPBSParameters, ServerKey, ShortintParameterSet};
use rand::Rng;
use tfhe::shortint::keycache::KEY_CACHE;
use tfhe::shortint::keycache::KEY_CACHE_WOPBS;
use tfhe::shortint::parameters::parameters_wopbs::WOPBS_PARAM_MESSAGE_4_NORM2_6;
use tfhe::shortint::parameters::parameters_wopbs::WOPBS_PARAM_MESSAGE_4_NORM2_6_KS_PBS;
const SERVER_KEY_BENCH_PARAMS: [Parameters; 4] = [
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
const SERVER_KEY_BENCH_PARAMS: [ClassicPBSParameters; 4] = [
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,
];
const SERVER_KEY_BENCH_PARAMS_EXTENDED: [Parameters; 15] = [
PARAM_MESSAGE_1_CARRY_0,
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_0,
PARAM_MESSAGE_2_CARRY_1,
PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_0,
PARAM_MESSAGE_3_CARRY_2,
PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_0,
PARAM_MESSAGE_4_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
PARAM_MESSAGE_5_CARRY_0,
PARAM_MESSAGE_6_CARRY_0,
PARAM_MESSAGE_7_CARRY_0,
PARAM_MESSAGE_8_CARRY_0,
const SERVER_KEY_BENCH_PARAMS_EXTENDED: [ClassicPBSParameters; 15] = [
PARAM_MESSAGE_1_CARRY_0_KS_PBS,
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
PARAM_MESSAGE_2_CARRY_0_KS_PBS,
PARAM_MESSAGE_2_CARRY_1_KS_PBS,
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
PARAM_MESSAGE_3_CARRY_0_KS_PBS,
PARAM_MESSAGE_3_CARRY_2_KS_PBS,
PARAM_MESSAGE_3_CARRY_3_KS_PBS,
PARAM_MESSAGE_4_CARRY_0_KS_PBS,
PARAM_MESSAGE_4_CARRY_3_KS_PBS,
PARAM_MESSAGE_4_CARRY_4_KS_PBS,
PARAM_MESSAGE_5_CARRY_0_KS_PBS,
PARAM_MESSAGE_6_CARRY_0_KS_PBS,
PARAM_MESSAGE_7_CARRY_0_KS_PBS,
PARAM_MESSAGE_8_CARRY_0_KS_PBS,
];
fn bench_server_key_unary_function<F>(
@@ -43,19 +45,20 @@ fn bench_server_key_unary_function<F>(
bench_name: &str,
display_name: &str,
unary_op: F,
params: &[Parameters],
params: &[ClassicPBSParameters],
) where
F: Fn(&ServerKey, &mut CiphertextBig),
F: Fn(&ServerKey, &mut Ciphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
for param in params.iter() {
let keys = KEY_CACHE.get_from_param(*param);
let param: PBSParameters = (*param).into();
let keys = KEY_CACHE.get_from_param(param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let clear_text = rng.gen::<u64>() % modulus;
@@ -68,12 +71,14 @@ fn bench_server_key_unary_function<F>(
})
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
*param,
param,
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus().0.ilog2(),
vec![param.message_modulus().0.ilog2()],
);
}
@@ -85,19 +90,20 @@ fn bench_server_key_binary_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[Parameters],
params: &[ClassicPBSParameters],
) where
F: Fn(&ServerKey, &mut CiphertextBig, &mut CiphertextBig),
F: Fn(&ServerKey, &mut Ciphertext, &mut Ciphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
for param in params.iter() {
let keys = KEY_CACHE.get_from_param(*param);
let param: PBSParameters = (*param).into();
let keys = KEY_CACHE.get_from_param(param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
let clear_1 = rng.gen::<u64>() % modulus;
@@ -112,12 +118,14 @@ fn bench_server_key_binary_function<F>(
})
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
*param,
param,
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus().0.ilog2(),
vec![param.message_modulus().0.ilog2()],
);
}
@@ -129,19 +137,20 @@ fn bench_server_key_binary_scalar_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[Parameters],
params: &[ClassicPBSParameters],
) where
F: Fn(&ServerKey, &mut CiphertextBig, u8),
F: Fn(&ServerKey, &mut Ciphertext, u8),
{
let mut bench_group = c.benchmark_group(bench_name);
for param in params {
let keys = KEY_CACHE.get_from_param(*param);
let param: PBSParameters = (*param).into();
let keys = KEY_CACHE.get_from_param(param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
let clear_1 = rng.gen::<u64>() % modulus;
@@ -155,12 +164,14 @@ fn bench_server_key_binary_scalar_function<F>(
})
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
*param,
param,
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus().0.ilog2(),
vec![param.message_modulus().0.ilog2()],
);
}
@@ -172,19 +183,20 @@ fn bench_server_key_binary_scalar_division_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[Parameters],
params: &[ClassicPBSParameters],
) where
F: Fn(&ServerKey, &mut CiphertextBig, u8),
F: Fn(&ServerKey, &mut Ciphertext, u8),
{
let mut bench_group = c.benchmark_group(bench_name);
for param in params {
let keys = KEY_CACHE.get_from_param(*param);
let param: PBSParameters = (*param).into();
let keys = KEY_CACHE.get_from_param(param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
assert_ne!(modulus, 1);
let clear_0 = rng.gen::<u64>() % modulus;
@@ -202,12 +214,14 @@ fn bench_server_key_binary_scalar_division_function<F>(
})
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
*param,
param,
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus().0.ilog2(),
vec![param.message_modulus().0.ilog2()],
);
}
@@ -218,12 +232,13 @@ fn carry_extract(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("carry_extract");
for param in SERVER_KEY_BENCH_PARAMS {
let param: PBSParameters = param.into();
let keys = KEY_CACHE.get_from_param(param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
@@ -236,12 +251,14 @@ fn carry_extract(c: &mut Criterion) {
})
});
write_to_json(
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
"carry_extract",
&OperatorType::Atomic,
param.message_modulus().0.ilog2(),
vec![param.message_modulus().0.ilog2()],
);
}
@@ -252,14 +269,15 @@ fn programmable_bootstrapping(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("programmable_bootstrap");
for param in SERVER_KEY_BENCH_PARAMS {
let param: PBSParameters = param.into();
let keys = KEY_CACHE.get_from_param(param);
let (cks, sks) = (keys.client_key(), keys.server_key());
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let acc = sks.generate_accumulator(|x| x);
let acc = sks.generate_lookup_table(|x| x);
let clear_0 = rng.gen::<u64>() % modulus;
@@ -273,18 +291,29 @@ fn programmable_bootstrapping(c: &mut Criterion) {
})
});
write_to_json(&bench_id, param, param.name(), "pbs", &OperatorType::Atomic);
write_to_json::<u64, _>(
&bench_id,
param,
param.name(),
"pbs",
&OperatorType::Atomic,
param.message_modulus().0.ilog2(),
vec![param.message_modulus().0.ilog2()],
);
}
bench_group.finish();
}
// TODO: remove?
fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("programmable_bootstrap");
let param = WOPBS_PARAM_MESSAGE_4_NORM2_6;
let param = WOPBS_PARAM_MESSAGE_4_NORM2_6_KS_PBS;
let param_set: ShortintParameterSet = param.try_into().unwrap();
let pbs_params = param_set.pbs_parameters().unwrap();
let keys = KEY_CACHE_WOPBS.get_from_param((param, param));
let keys = KEY_CACHE_WOPBS.get_from_param((pbs_params, param));
let (cks, _, wopbs_key) = (keys.client_key(), keys.server_key(), keys.wopbs_key());
let mut rng = rand::thread_rng();
@@ -611,33 +640,32 @@ define_server_key_scalar_div_bench_fn!(
);
criterion_group!(
arithmetic_operation,
unchecked_neg,
unchecked_add,
unchecked_sub,
unchecked_mul_lsb,
unchecked_mul_msb,
unchecked_div,
smart_ops,
smart_bitand,
smart_bitor,
smart_bitxor,
smart_add,
smart_sub,
smart_mul_lsb,
);
criterion_group!(
unchecked_ops,
unchecked_neg,
unchecked_add,
unchecked_sub,
unchecked_mul_lsb,
unchecked_mul_msb,
unchecked_div,
unchecked_greater,
unchecked_less,
unchecked_equal,
carry_extract,
// programmable_bootstrapping,
// multivalue_programmable_bootstrapping
//bench_two_block_pbs
//wopbs_v0_norm2_2,
//bench_wopbs_param_message_8_norm2_5,
programmable_bootstrapping
);
criterion_group!(
arithmetic_scalar_operation,
unchecked_scalar_ops,
unchecked_scalar_add,
unchecked_scalar_mul,
unchecked_scalar_sub,
@@ -682,9 +710,35 @@ criterion_group!(
scalar_not_equal
);
criterion_main!(
// arithmetic_operation,
// arithmetic_scalar_operation,
default_ops,
default_scalar_ops,
mod casting;
criterion_group!(
casting,
casting::pack_cast_64,
casting::pack_cast,
casting::cast
);
fn main() {
fn default_bench() {
casting();
default_ops();
default_scalar_ops();
}
match env::var("__TFHE_RS_BENCH_OP_FLAVOR") {
Ok(val) => {
match val.to_lowercase().as_str() {
"default" => default_bench(),
"smart" => smart_ops(),
"unchecked" => {
unchecked_ops();
unchecked_scalar_ops();
}
_ => panic!("unknown benchmark operations flavor"),
};
}
Err(_) => default_bench(),
};
Criterion::default().configure_from_args().final_summary();
}

View File

@@ -0,0 +1,137 @@
use crate::utilities::{write_to_json, OperatorType};
use tfhe::shortint::prelude::*;
use rayon::prelude::*;
use criterion::Criterion;
pub fn pack_cast_64(c: &mut Criterion) {
let bench_name = "pack_cast_64";
let mut bench_group = c.benchmark_group(bench_name);
let (client_key_1, server_key_1): (ClientKey, ServerKey) =
gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let (client_key_2, server_key_2): (ClientKey, ServerKey) =
gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let ks_param = PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS;
let ks_param_name = "PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS";
let ksk = KeySwitchingKey::new(
(&client_key_1, &server_key_1),
(&client_key_2, &server_key_2),
ks_param,
);
let vec_ct = vec![client_key_1.encrypt(1); 64];
let bench_id = format!("{bench_name}_{ks_param_name}");
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
let _ = (0..32)
.into_par_iter()
.map(|i| {
let byte_idx = 7 - i / 4;
let pair_idx = i % 4;
let b0 = &vec_ct[8 * byte_idx + 2 * pair_idx];
let b1 = &vec_ct[8 * byte_idx + 2 * pair_idx + 1];
ksk.cast(
&server_key_1.unchecked_add(b0, &server_key_1.unchecked_scalar_mul(b1, 2)),
)
})
.collect::<Vec<_>>();
});
});
write_to_json::<u64, _>(
&bench_id,
ks_param,
ks_param_name,
"pack_cast_64",
&OperatorType::Atomic,
0,
vec![],
);
}
pub fn pack_cast(c: &mut Criterion) {
let bench_name = "pack_cast";
let mut bench_group = c.benchmark_group(bench_name);
let (client_key_1, server_key_1): (ClientKey, ServerKey) =
gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let (client_key_2, server_key_2): (ClientKey, ServerKey) =
gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let ks_param = PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS;
let ks_param_name = "PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS";
let ksk = KeySwitchingKey::new(
(&client_key_1, &server_key_1),
(&client_key_2, &server_key_2),
ks_param,
);
let ct_1 = client_key_1.encrypt(1);
let ct_2 = client_key_1.encrypt(1);
let bench_id = format!("{bench_name}_{ks_param_name}");
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
let _ = ksk.cast(
&server_key_1.unchecked_add(&ct_1, &server_key_1.unchecked_scalar_mul(&ct_2, 2)),
);
});
});
write_to_json::<u64, _>(
&bench_id,
ks_param,
ks_param_name,
"pack_cast",
&OperatorType::Atomic,
0,
vec![],
);
}
pub fn cast(c: &mut Criterion) {
let bench_name = "cast";
let mut bench_group = c.benchmark_group(bench_name);
let (client_key_1, server_key_1): (ClientKey, ServerKey) =
gen_keys(PARAM_MESSAGE_1_CARRY_1_KS_PBS);
let (client_key_2, server_key_2): (ClientKey, ServerKey) =
gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let ks_param = PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS;
let ks_param_name = "PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS";
let ksk = KeySwitchingKey::new(
(&client_key_1, &server_key_1),
(&client_key_2, &server_key_2),
ks_param,
);
let ct = client_key_1.encrypt(1);
let bench_id = format!("{bench_name}_{ks_param_name}");
bench_group.bench_function(&bench_id, |b| {
b.iter(|| {
let _ = ksk.cast(&ct);
});
});
write_to_json::<u64, _>(
&bench_id,
ks_param,
ks_param_name,
"cast",
&OperatorType::Atomic,
0,
vec![],
);
}

View File

@@ -5,10 +5,12 @@ use std::path::PathBuf;
use tfhe::boolean::parameters::BooleanParameters;
use tfhe::core_crypto::prelude::*;
#[cfg(feature = "shortint")]
use tfhe::shortint::Parameters;
use tfhe::shortint::parameters::ShortintKeySwitchingParameters;
#[cfg(feature = "shortint")]
use tfhe::shortint::PBSParameters;
#[derive(Clone, Copy, Default, Serialize)]
pub struct CryptoParametersRecord {
pub struct CryptoParametersRecord<Scalar: UnsignedInteger> {
pub lwe_dimension: Option<LweDimension>,
pub glwe_dimension: Option<GlweDimension>,
pub polynomial_size: Option<PolynomialSize>,
@@ -25,10 +27,11 @@ pub struct CryptoParametersRecord {
pub cbs_base_log: Option<DecompositionBaseLog>,
pub message_modulus: Option<usize>,
pub carry_modulus: Option<usize>,
pub ciphertext_modulus: Option<CiphertextModulus<Scalar>>,
}
#[cfg(feature = "boolean")]
impl From<BooleanParameters> for CryptoParametersRecord {
impl<Scalar: UnsignedInteger> From<BooleanParameters> for CryptoParametersRecord<Scalar> {
fn from(params: BooleanParameters) -> Self {
CryptoParametersRecord {
lwe_dimension: Some(params.lwe_dimension),
@@ -47,30 +50,67 @@ impl From<BooleanParameters> for CryptoParametersRecord {
cbs_base_log: None,
message_modulus: None,
carry_modulus: None,
ciphertext_modulus: Some(CiphertextModulus::<Scalar>::new_native()),
}
}
}
#[cfg(feature = "shortint")]
impl From<Parameters> for CryptoParametersRecord {
fn from(params: Parameters) -> Self {
impl<Scalar> From<PBSParameters> for CryptoParametersRecord<Scalar>
where
Scalar: UnsignedInteger + CastInto<u128>,
{
fn from(params: PBSParameters) -> Self {
CryptoParametersRecord {
lwe_dimension: Some(params.lwe_dimension),
glwe_dimension: Some(params.glwe_dimension),
polynomial_size: Some(params.polynomial_size),
lwe_modular_std_dev: Some(params.lwe_modular_std_dev),
glwe_modular_std_dev: Some(params.glwe_modular_std_dev),
pbs_base_log: Some(params.pbs_base_log),
pbs_level: Some(params.pbs_level),
lwe_dimension: Some(params.lwe_dimension()),
glwe_dimension: Some(params.glwe_dimension()),
polynomial_size: Some(params.polynomial_size()),
lwe_modular_std_dev: Some(params.lwe_modular_std_dev()),
glwe_modular_std_dev: Some(params.glwe_modular_std_dev()),
pbs_base_log: Some(params.pbs_base_log()),
pbs_level: Some(params.pbs_level()),
ks_base_log: Some(params.ks_base_log()),
ks_level: Some(params.ks_level()),
pfks_level: None,
pfks_base_log: None,
pfks_modular_std_dev: None,
cbs_level: None,
cbs_base_log: None,
message_modulus: Some(params.message_modulus().0),
carry_modulus: Some(params.carry_modulus().0),
ciphertext_modulus: Some(
params
.ciphertext_modulus()
.try_to()
.expect("failed to convert ciphertext modulus"),
),
}
}
}
#[cfg(feature = "shortint")]
impl<Scalar: UnsignedInteger> From<ShortintKeySwitchingParameters>
for CryptoParametersRecord<Scalar>
{
fn from(params: ShortintKeySwitchingParameters) -> Self {
CryptoParametersRecord {
lwe_dimension: None,
glwe_dimension: None,
polynomial_size: None,
lwe_modular_std_dev: None,
glwe_modular_std_dev: None,
pbs_base_log: None,
pbs_level: None,
ks_base_log: Some(params.ks_base_log),
ks_level: Some(params.ks_level),
pfks_level: Some(params.pfks_level),
pfks_base_log: Some(params.pfks_base_log),
pfks_modular_std_dev: Some(params.pfks_modular_std_dev),
cbs_level: Some(params.cbs_level),
cbs_base_log: Some(params.cbs_base_log),
message_modulus: Some(params.message_modulus.0),
carry_modulus: Some(params.carry_modulus.0),
pfks_level: None,
pfks_base_log: None,
pfks_modular_std_dev: None,
cbs_level: None,
cbs_base_log: None,
message_modulus: None,
carry_modulus: None,
ciphertext_modulus: None,
}
}
}
@@ -113,18 +153,19 @@ pub enum OperatorType {
}
#[derive(Serialize)]
struct BenchmarkParametersRecord {
struct BenchmarkParametersRecord<Scalar: UnsignedInteger> {
display_name: String,
crypto_parameters_alias: String,
crypto_parameters: CryptoParametersRecord,
crypto_parameters: CryptoParametersRecord<Scalar>,
message_modulus: Option<usize>,
carry_modulus: Option<usize>,
ciphertext_modulus: usize,
bit_size: u32,
polynomial_multiplication: PolynomialMultiplication,
precision: u32,
error_probability: f64,
integer_representation: IntegerRepresentation,
decomposition_basis: u32,
decomposition_basis: Vec<u32>,
pbs_algorithm: Option<String>,
execution_type: ExecutionType,
key_set_type: KeySetType,
@@ -133,12 +174,17 @@ struct BenchmarkParametersRecord {
}
/// Writes benchmarks parameters to disk in JSON format.
pub fn write_to_json<T: Into<CryptoParametersRecord>>(
pub fn write_to_json<
Scalar: UnsignedInteger + Serialize,
T: Into<CryptoParametersRecord<Scalar>>,
>(
bench_id: &str,
params: T,
params_alias: impl Into<String>,
display_name: impl Into<String>,
operator_type: &OperatorType,
bit_size: u32,
decomposition_basis: Vec<u32>,
) {
let params = params.into();
@@ -158,11 +204,12 @@ pub fn write_to_json<T: Into<CryptoParametersRecord>>(
message_modulus: params.message_modulus,
carry_modulus: params.carry_modulus,
ciphertext_modulus: 64,
bit_size,
polynomial_multiplication: PolynomialMultiplication::Fft,
precision: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
error_probability: 2f64.powf(-41.0),
integer_representation: IntegerRepresentation::Radix,
decomposition_basis: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
decomposition_basis,
pbs_algorithm: None, // To be added in future version
execution_type,
key_set_type: KeySetType::Single,

View File

@@ -7,6 +7,18 @@ fn gen_c_api() {
return;
}
fn get_build_profile_name() -> String {
// The profile name is always the 3rd last part of the path (with 1 based indexing).
// e.g. /code/core/target/cli/build/my-build-info-9f91ba6f99d7a061/out
let out_dir = std::env::var("OUT_DIR")
.expect("OUT_DIR is not set, cannot determine build profile, aborting");
out_dir
.split(std::path::MAIN_SEPARATOR)
.nth_back(3)
.expect("Cannot determine build profile, aborting")
.to_string()
}
/// Find the location of the `target/` directory. Note that this may be
/// overridden by `cmake`, so we also need to check the `CARGO_TARGET_DIR`
/// variable.
@@ -14,7 +26,8 @@ fn gen_c_api() {
if let Ok(target) = env::var("CARGO_TARGET_DIR") {
PathBuf::from(target)
} else {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("../target/release")
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join(format!("../target/{}", get_build_profile_name()))
}
}

View File

@@ -2,7 +2,10 @@ project(tfhe-c-api-tests)
cmake_minimum_required(VERSION 3.16)
set(TFHE_C_API_RELEASE "${CMAKE_CURRENT_SOURCE_DIR}/../../target/release/")
if(NOT CARGO_PROFILE)
set(CARGO_PROFILE release)
endif()
set(TFHE_C_API_RELEASE "${CMAKE_CURRENT_SOURCE_DIR}/../../target/${CARGO_PROFILE}")
include_directories(${TFHE_C_API_RELEASE})
add_library(Tfhe STATIC IMPORTED)

View File

@@ -61,13 +61,13 @@ void test_default_keygen_w_serde(void) {
assert(c_result == true);
destroy_boolean_client_key(cks);
destroy_boolean_server_key(sks);
destroy_boolean_ciphertext(ct);
destroy_boolean_ciphertext(deser_ct);
destroy_boolean_compressed_ciphertext(cct);
destroy_boolean_compressed_ciphertext(deser_cct);
destroy_boolean_ciphertext(decompressed_ct);
boolean_destroy_client_key(cks);
boolean_destroy_server_key(sks);
boolean_destroy_ciphertext(ct);
boolean_destroy_ciphertext(deser_ct);
boolean_destroy_compressed_ciphertext(cct);
boolean_destroy_compressed_ciphertext(deser_cct);
boolean_destroy_ciphertext(decompressed_ct);
destroy_buffer(&ct_ser_buffer);
}
@@ -75,50 +75,52 @@ void test_predefined_keygen_w_serde(void) {
BooleanClientKey *cks = NULL;
BooleanServerKey *sks = NULL;
int gen_keys_ok = boolean_gen_keys_with_predefined_parameters_set(
BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS, &cks, &sks);
int gen_keys_ok =
boolean_gen_keys_with_parameters(BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS, &cks, &sks);
assert(gen_keys_ok == 0);
destroy_boolean_client_key(cks);
destroy_boolean_server_key(sks);
boolean_destroy_client_key(cks);
boolean_destroy_server_key(sks);
gen_keys_ok = boolean_gen_keys_with_predefined_parameters_set(
BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS, &cks, &sks);
gen_keys_ok =
boolean_gen_keys_with_parameters(BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS, &cks, &sks);
assert(gen_keys_ok == 0);
destroy_boolean_client_key(cks);
destroy_boolean_server_key(sks);
boolean_destroy_client_key(cks);
boolean_destroy_server_key(sks);
}
void test_custom_keygen(void) {
BooleanClientKey *cks = NULL;
BooleanServerKey *sks = NULL;
BooleanParameters *params = NULL;
int params_ok = boolean_create_parameters(10, 1, 1024, 10e-100, 10e-100, 3, 1, 4, 2, &params);
assert(params_ok == 0);
BooleanParameters params = {
.lwe_dimension = 10,
.glwe_dimension = 1,
.polynomial_size = 1024,
.lwe_modular_std_dev = 10e-100,
.glwe_modular_std_dev = 10e-100,
.pbs_base_log = 3,
.pbs_level = 1,
.ks_base_log = 4,
.ks_level = 2,
};
int gen_keys_ok = boolean_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
destroy_boolean_parameters(params);
destroy_boolean_client_key(cks);
destroy_boolean_server_key(sks);
boolean_destroy_client_key(cks);
boolean_destroy_server_key(sks);
}
void test_public_keygen(void) {
BooleanClientKey *cks = NULL;
BooleanPublicKey *pks = NULL;
BooleanParameters *params = NULL;
BooleanCiphertext *ct = NULL;
int get_params_ok = boolean_get_parameters(BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS, &params);
assert(get_params_ok == 0);
int gen_keys_ok = boolean_gen_client_key(params, &cks);
int gen_keys_ok = boolean_gen_client_key(BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS, &cks);
assert(gen_keys_ok == 0);
int gen_pks = boolean_gen_public_key(cks, &pks);
@@ -135,10 +137,9 @@ void test_public_keygen(void) {
assert(result == true);
destroy_boolean_parameters(params);
destroy_boolean_client_key(cks);
destroy_boolean_public_key(pks);
destroy_boolean_ciphertext(ct);
boolean_destroy_client_key(cks);
boolean_destroy_public_key(pks);
boolean_destroy_ciphertext(ct);
}
int main(void) {

View File

@@ -51,9 +51,9 @@ void test_binary_boolean_function(BooleanClientKey *cks, BooleanServerKey *sks,
assert(decrypted_result == expected);
destroy_boolean_ciphertext(ct_left);
destroy_boolean_ciphertext(ct_right);
destroy_boolean_ciphertext(ct_result);
boolean_destroy_ciphertext(ct_left);
boolean_destroy_ciphertext(ct_right);
boolean_destroy_ciphertext(ct_result);
}
}
}
@@ -103,8 +103,8 @@ void test_binary_boolean_function_assign(
assert(decrypted_result == expected);
destroy_boolean_ciphertext(ct_left_and_result);
destroy_boolean_ciphertext(ct_right);
boolean_destroy_ciphertext(ct_left_and_result);
boolean_destroy_ciphertext(ct_right);
}
}
}
@@ -139,8 +139,8 @@ void test_binary_boolean_function_scalar(BooleanClientKey *cks, BooleanServerKey
assert(decrypted_result == expected);
destroy_boolean_ciphertext(ct_left);
destroy_boolean_ciphertext(ct_result);
boolean_destroy_ciphertext(ct_left);
boolean_destroy_ciphertext(ct_result);
}
}
}
@@ -171,7 +171,7 @@ void test_binary_boolean_function_scalar_assign(BooleanClientKey *cks, BooleanSe
assert(decrypted_result == expected);
destroy_boolean_ciphertext(ct_left_and_result);
boolean_destroy_ciphertext(ct_left_and_result);
}
}
}
@@ -205,8 +205,8 @@ void test_not(BooleanClientKey *cks, BooleanServerKey *sks) {
assert(decrypted_result == expected);
destroy_boolean_ciphertext(ct_in);
destroy_boolean_ciphertext(ct_result);
boolean_destroy_ciphertext(ct_in);
boolean_destroy_ciphertext(ct_result);
}
}
}
@@ -239,7 +239,7 @@ void test_not_assign(BooleanClientKey *cks, BooleanServerKey *sks) {
assert(decrypted_result == expected);
destroy_boolean_ciphertext(ct_in_and_result);
boolean_destroy_ciphertext(ct_in_and_result);
}
}
}
@@ -300,10 +300,10 @@ void test_mux(BooleanClientKey *cks, BooleanServerKey *sks) {
assert(decrypted_result == expected);
destroy_boolean_ciphertext(ct_cond);
destroy_boolean_ciphertext(ct_then);
destroy_boolean_ciphertext(ct_else);
destroy_boolean_ciphertext(ct_result);
boolean_destroy_ciphertext(ct_cond);
boolean_destroy_ciphertext(ct_then);
boolean_destroy_ciphertext(ct_else);
boolean_destroy_ciphertext(ct_result);
}
}
}
@@ -334,12 +334,8 @@ void test_server_key(void) {
BooleanCompressedServerKey *deser_csks = NULL;
Buffer sks_ser_buffer = {.pointer = NULL, .length = 0};
BooleanServerKey *deser_sks = NULL;
BooleanParameters *params = NULL;
int get_params_ok = boolean_get_parameters(BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS, &params);
assert(get_params_ok == 0);
int gen_cks_ok = boolean_gen_client_key(params, &cks);
int gen_cks_ok = boolean_gen_client_key(BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS, &cks);
assert(gen_cks_ok == 0);
int gen_csks_ok = boolean_gen_compressed_server_key(cks, &csks);
@@ -411,13 +407,12 @@ void test_server_key(void) {
test_binary_boolean_function_scalar_assign(deser_cks, deser_sks, c_xnor,
boolean_server_key_xnor_scalar_assign);
destroy_boolean_client_key(cks);
destroy_boolean_compressed_server_key(csks);
destroy_boolean_server_key(sks);
destroy_boolean_client_key(deser_cks);
destroy_boolean_compressed_server_key(deser_csks);
destroy_boolean_server_key(deser_sks);
destroy_boolean_parameters(params);
boolean_destroy_client_key(cks);
boolean_destroy_compressed_server_key(csks);
boolean_destroy_server_key(sks);
boolean_destroy_client_key(deser_cks);
boolean_destroy_compressed_server_key(deser_csks);
boolean_destroy_server_key(deser_sks);
destroy_buffer(&cks_ser_buffer);
destroy_buffer(&csks_ser_buffer);
destroy_buffer(&sks_ser_buffer);

View File

@@ -9,22 +9,54 @@ int uint128_client_key(const ClientKey *client_key) {
FheUint128 *lhs = NULL;
FheUint128 *rhs = NULL;
FheUint128 *result = NULL;
U128 lhs_clear = {10, 20};
U128 rhs_clear = {1, 2};
U128 result_clear = {0};
ok = fhe_uint128_try_encrypt_with_client_key_u128(10, 20, client_key, &lhs);
ok = fhe_uint128_try_encrypt_with_client_key_u128(lhs_clear, client_key, &lhs);
assert(ok == 0);
ok = fhe_uint128_try_encrypt_with_client_key_u128(1, 2, client_key, &rhs);
ok = fhe_uint128_try_encrypt_with_client_key_u128(rhs_clear, client_key, &rhs);
assert(ok == 0);
ok = fhe_uint128_sub(lhs, rhs, &result);
assert(ok == 0);
uint64_t w0, w1;
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
ok = fhe_uint128_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(w0 == 9);
assert(w1 == 18);
assert(result_clear.w0 == 9);
assert(result_clear.w1 == 18);
fhe_uint128_destroy(lhs);
fhe_uint128_destroy(rhs);
fhe_uint128_destroy(result);
return ok;
}
int uint128_encrypt_trivial(const ClientKey *client_key) {
int ok;
FheUint128 *lhs = NULL;
FheUint128 *rhs = NULL;
FheUint128 *result = NULL;
U128 lhs_clear = {10, 20};
U128 rhs_clear = {1, 2};
U128 result_clear = {0};
ok = fhe_uint128_try_encrypt_trivial_u128(lhs_clear, &lhs);
assert(ok == 0);
ok = fhe_uint128_try_encrypt_trivial_u128(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_uint128_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint128_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 9);
assert(result_clear.w1 == 18);
fhe_uint128_destroy(lhs);
fhe_uint128_destroy(rhs);
@@ -37,22 +69,24 @@ int uint128_public_key(const ClientKey *client_key, const PublicKey *public_key)
FheUint128 *lhs = NULL;
FheUint128 *rhs = NULL;
FheUint128 *result = NULL;
U128 lhs_clear = {10, 20};
U128 rhs_clear = {1, 2};
U128 result_clear = {0};
ok = fhe_uint128_try_encrypt_with_public_key_u128(1, 2, public_key, &lhs);
ok = fhe_uint128_try_encrypt_with_public_key_u128(lhs_clear, public_key, &lhs);
assert(ok == 0);
ok = fhe_uint128_try_encrypt_with_public_key_u128(10, 20, public_key, &rhs);
ok = fhe_uint128_try_encrypt_with_public_key_u128(rhs_clear, public_key, &rhs);
assert(ok == 0);
ok = fhe_uint128_add(lhs, rhs, &result);
assert(ok == 0);
uint64_t w0, w1;
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
ok = fhe_uint128_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(w0 == 11);
assert(w1 == 22);
assert(result_clear.w0 == 11);
assert(result_clear.w1 == 22);
fhe_uint128_destroy(lhs);
fhe_uint128_destroy(rhs);
@@ -66,7 +100,7 @@ int main(void) {
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_uint128_small(&builder);
config_builder_enable_default_integers_small(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
@@ -79,6 +113,7 @@ int main(void) {
set_server_key(server_key);
uint128_client_key(client_key);
uint128_encrypt_trivial(client_key);
uint128_public_key(client_key, public_key);
client_key_destroy(client_key);

View File

@@ -4,20 +4,15 @@
#include <inttypes.h>
#include <stdio.h>
int uint256_client_key(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
U256 *lhs_clear = NULL;
U256 *rhs_clear = NULL;
U256 *result_clear = NULL;
ok = u256_from_u64_words(1, 2, 3, 4, &lhs_clear);
assert(ok == 0);
ok = u256_from_u64_words(5, 6, 7, 8, &rhs_clear);
assert(ok == 0);
FheUint64 *cast_result = NULL;
U256 lhs_clear = {1, 2, 3, 4};
U256 rhs_clear = {5, 6, 7, 8};
U256 result_clear = {0};
ok = fhe_uint256_try_encrypt_with_client_key_u256(lhs_clear, client_key, &lhs);
assert(ok == 0);
@@ -31,18 +26,52 @@ int uint256_client_key(const ClientKey *client_key) {
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
uint64_t w0, w1, w2, w3;
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
assert(result_clear.w0 == 6);
assert(result_clear.w1 == 8);
assert(result_clear.w2 == 10);
assert(result_clear.w3 == 12);
// try some casting
ok = fhe_uint256_cast_into_fhe_uint64(result, &cast_result);
assert(ok == 0);
uint64_t u64_clear;
ok = fhe_uint64_decrypt(cast_result, client_key, &u64_clear);
assert(ok == 0);
assert(u64_clear == 6);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
fhe_uint64_destroy(cast_result);
return ok;
}
int uint256_encrypt_trivial(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
U256 lhs_clear = {1, 2, 3, 4};
U256 rhs_clear = {5, 6, 7, 8};
U256 result_clear = {0};
ok = fhe_uint256_try_encrypt_trivial_u256(lhs_clear, &lhs);
assert(ok == 0);
assert(w0 == 6);
assert(w1 == 8);
assert(w2 == 10);
assert(w3 == 12);
ok = fhe_uint256_try_encrypt_trivial_u256(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_uint256_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 6);
assert(result_clear.w1 == 8);
assert(result_clear.w2 == 10);
assert(result_clear.w3 == 12);
u256_destroy(lhs_clear);
u256_destroy(rhs_clear);
u256_destroy(result_clear);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
@@ -54,14 +83,9 @@ int uint256_public_key(const ClientKey *client_key, const PublicKey *public_key)
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
U256 *lhs_clear = NULL;
U256 *rhs_clear = NULL;
U256 *result_clear = NULL;
ok = u256_from_u64_words(5, 6, 7, 8, &lhs_clear);
assert(ok == 0);
ok = u256_from_u64_words(1, 2, 3, 4, &rhs_clear);
assert(ok == 0);
U256 lhs_clear = {5, 6, 7, 8};
U256 rhs_clear = {1, 2, 3, 4};
U256 result_clear = {0};
ok = fhe_uint256_try_encrypt_with_public_key_u256(lhs_clear, public_key, &lhs);
assert(ok == 0);
@@ -75,18 +99,11 @@ int uint256_public_key(const ClientKey *client_key, const PublicKey *public_key)
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
uint64_t w0, w1, w2, w3;
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
assert(ok == 0);
assert(result_clear.w0 == 4);
assert(result_clear.w1 == 4);
assert(result_clear.w2 == 4);
assert(result_clear.w3 == 4);
assert(w0 == 4);
assert(w1 == 4);
assert(w2 == 4);
assert(w3 == 4);
u256_destroy(lhs_clear);
u256_destroy(rhs_clear);
u256_destroy(result_clear);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
@@ -99,7 +116,7 @@ int main(void) {
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_uint256_small(&builder);
config_builder_enable_default_integers_small(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
@@ -112,6 +129,7 @@ int main(void) {
set_server_key(server_key);
uint256_client_key(client_key);
uint256_encrypt_trivial(client_key);
uint256_public_key(client_key, public_key);
client_key_destroy(client_key);

View File

@@ -1,75 +1,104 @@
#include <tfhe.h>
#include <stdio.h>
#include <inttypes.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
int client_key_test(const ClientKey *client_key) {
int ok;
FheBool *lhs = NULL;
FheBool *rhs = NULL;
FheBool *result = NULL;
int ok;
FheBool *lhs = NULL;
FheBool *rhs = NULL;
FheBool *result = NULL;
bool lhs_clear = 0;
bool rhs_clear = 1;
bool lhs_clear = 0;
bool rhs_clear = 1;
ok = fhe_bool_try_encrypt_with_client_key_bool(lhs_clear, client_key, &lhs);
assert(ok == 0);
ok = fhe_bool_try_encrypt_with_client_key_bool(lhs_clear, client_key, &lhs);
assert(ok == 0);
ok = fhe_bool_try_encrypt_with_client_key_bool(rhs_clear, client_key, &rhs);
assert(ok == 0);
ok = fhe_bool_try_encrypt_with_client_key_bool(rhs_clear, client_key, &rhs);
assert(ok == 0);
ok = fhe_bool_bitand(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_bool_bitand(lhs, rhs, &result);
assert(ok == 0);
bool clear;
ok = fhe_bool_decrypt(result, client_key, &clear);
assert(ok == 0);
bool clear;
ok = fhe_bool_decrypt(result, client_key, &clear);
assert(ok == 0);
assert(clear == (lhs_clear & rhs_clear));
assert(clear == (lhs_clear & rhs_clear));
fhe_bool_destroy(lhs);
fhe_bool_destroy(rhs);
fhe_bool_destroy(result);
fhe_bool_destroy(lhs);
fhe_bool_destroy(rhs);
fhe_bool_destroy(result);
return ok;
return ok;
}
int public_key_test(const ClientKey *client_key, const PublicKey *public_key) {
int ok;
FheBool *lhs = NULL;
FheBool *rhs = NULL;
FheBool *result = NULL;
int ok;
FheBool *lhs = NULL;
FheBool *rhs = NULL;
FheBool *result = NULL;
bool lhs_clear = 0;
bool rhs_clear = 1;
bool lhs_clear = 0;
bool rhs_clear = 1;
ok = fhe_bool_try_encrypt_with_public_key_bool(lhs_clear, public_key, &lhs);
assert(ok == 0);
ok = fhe_bool_try_encrypt_with_public_key_bool(lhs_clear, public_key, &lhs);
assert(ok == 0);
ok = fhe_bool_try_encrypt_with_public_key_bool(rhs_clear, public_key, &rhs);
assert(ok == 0);
ok = fhe_bool_try_encrypt_with_public_key_bool(rhs_clear, public_key, &rhs);
assert(ok == 0);
ok = fhe_bool_bitand(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_bool_bitand(lhs, rhs, &result);
assert(ok == 0);
bool clear;
ok = fhe_bool_decrypt(result, client_key, &clear);
assert(ok == 0);
bool clear;
ok = fhe_bool_decrypt(result, client_key, &clear);
assert(ok == 0);
assert(clear == (lhs_clear & rhs_clear));
assert(clear == (lhs_clear & rhs_clear));
fhe_bool_destroy(lhs);
fhe_bool_destroy(rhs);
fhe_bool_destroy(result);
fhe_bool_destroy(lhs);
fhe_bool_destroy(rhs);
fhe_bool_destroy(result);
return ok;
return ok;
}
int main(void)
{
int trivial_encrypt_test(const ClientKey *client_key) {
int ok;
FheBool *lhs = NULL;
FheBool *rhs = NULL;
FheBool *result = NULL;
bool lhs_clear = 0;
bool rhs_clear = 1;
ok = fhe_bool_try_encrypt_trivial_bool(lhs_clear, &lhs);
assert(ok == 0);
ok = fhe_bool_try_encrypt_trivial_bool(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_bool_bitand(lhs, rhs, &result);
assert(ok == 0);
bool clear;
ok = fhe_bool_decrypt(result, client_key, &clear);
assert(ok == 0);
assert(clear == (lhs_clear & rhs_clear));
fhe_bool_destroy(lhs);
fhe_bool_destroy(rhs);
fhe_bool_destroy(result);
return ok;
}
int main(void) {
ConfigBuilder *builder;
Config *config;
@@ -88,7 +117,8 @@ int main(void)
client_key_test(client_key);
public_key_test(client_key, public_key);
trivial_encrypt_test(client_key);
client_key_destroy(client_key);
public_key_destroy(public_key);
server_key_destroy(server_key);

View File

@@ -0,0 +1,217 @@
#include <tfhe.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
int uint256_client_key(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
FheUint64 *cast_result = NULL;
U256 lhs_clear = {1, 2, 3, 4};
U256 rhs_clear = {5, 6, 7, 8};
U256 result_clear = {0};
ok = fhe_uint256_try_encrypt_with_client_key_u256(lhs_clear, client_key, &lhs);
assert(ok == 0);
ok = fhe_uint256_try_encrypt_with_client_key_u256(rhs_clear, client_key, &rhs);
assert(ok == 0);
ok = fhe_uint256_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 6);
assert(result_clear.w1 == 8);
assert(result_clear.w2 == 10);
assert(result_clear.w3 == 12);
// try some casting
ok = fhe_uint256_cast_into_fhe_uint64(result, &cast_result);
assert(ok == 0);
uint64_t u64_clear;
ok = fhe_uint64_decrypt(cast_result, client_key, &u64_clear);
assert(ok == 0);
assert(u64_clear == 6);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
fhe_uint64_destroy(cast_result);
return ok;
}
int uint256_encrypt_trivial(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
U256 lhs_clear = {1, 2, 3, 4};
U256 rhs_clear = {5, 6, 7, 8};
U256 result_clear = {0};
ok = fhe_uint256_try_encrypt_trivial_u256(lhs_clear, &lhs);
assert(ok == 0);
ok = fhe_uint256_try_encrypt_trivial_u256(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_uint256_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 6);
assert(result_clear.w1 == 8);
assert(result_clear.w2 == 10);
assert(result_clear.w3 == 12);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
return ok;
}
int uint256_public_key(const ClientKey *client_key,
const CompressedCompactPublicKey *compressed_public_key) {
int ok;
CompactPublicKey *public_key = NULL;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
CompactFheUint256List *list = NULL;
U256 result_clear = {0};
U256 clears[2] = {{5, 6, 7, 8}, {1, 2, 3, 4}};
ok = compressed_compact_public_key_decompress(compressed_public_key, &public_key);
assert(ok == 0);
// Compact list example
{
ok = compact_fhe_uint256_list_try_encrypt_with_compact_public_key_u256(&clears[0], 2,
public_key, &list);
assert(ok == 0);
size_t len = 0;
ok = compact_fhe_uint256_list_len(list, &len);
assert(ok == 0);
assert(len == 2);
FheUint256 *expand_output[2] = {NULL};
ok = compact_fhe_uint256_list_expand(list, &expand_output[0], 2);
assert(ok == 0);
// transfer ownership
lhs = expand_output[0];
rhs = expand_output[1];
// We can destroy the compact list
// The expanded ciphertext are independant from it
compact_fhe_uint256_list_destroy(list);
ok = fhe_uint256_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 4);
assert(result_clear.w1 == 4);
assert(result_clear.w2 == 4);
assert(result_clear.w3 == 4);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
}
{
ok = fhe_uint256_try_encrypt_with_compact_public_key_u256(clears[0], public_key, &lhs);
assert(ok == 0);
ok = fhe_uint256_try_encrypt_with_compact_public_key_u256(clears[1], public_key, &rhs);
assert(ok == 0);
ok = fhe_uint256_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 4);
assert(result_clear.w1 == 4);
assert(result_clear.w2 == 4);
assert(result_clear.w3 == 4);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
}
compact_public_key_destroy(public_key);
return ok;
}
int main(void) {
int ok = 0;
{
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_custom_integers(&builder,
SHORTINT_PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
ServerKey *server_key = NULL;
CompressedCompactPublicKey *compressed_public_key = NULL;
generate_keys(config, &client_key, &server_key);
compressed_compact_public_key_new(client_key, &compressed_public_key);
set_server_key(server_key);
uint256_client_key(client_key);
uint256_encrypt_trivial(client_key);
uint256_public_key(client_key, compressed_public_key);
client_key_destroy(client_key);
compressed_compact_public_key_destroy(compressed_public_key);
server_key_destroy(server_key);
}
{
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_custom_integers(&builder,
SHORTINT_PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
ServerKey *server_key = NULL;
CompressedCompactPublicKey *compressed_public_key = NULL;
generate_keys(config, &client_key, &server_key);
compressed_compact_public_key_new(client_key, &compressed_public_key);
set_server_key(server_key);
uint256_client_key(client_key);
uint256_encrypt_trivial(client_key);
uint256_public_key(client_key, compressed_public_key);
client_key_destroy(client_key);
compressed_compact_public_key_destroy(compressed_public_key);
server_key_destroy(server_key);
}
return ok;
}

View File

@@ -4,7 +4,6 @@
#include <inttypes.h>
#include <stdio.h>
int uint8_client_key(const ClientKey *client_key) {
int ok;
FheUint8 *lhs = NULL;
@@ -145,7 +144,7 @@ int main(void) {
ok = config_builder_all_disabled(&builder);
assert(ok == 0);
ok = config_builder_enable_default_uint8(&builder);
ok = config_builder_enable_default_integers(&builder);
assert(ok == 0);
ok = config_builder_build(builder, &config);
assert(ok == 0);
@@ -182,7 +181,7 @@ int main(void) {
ok = config_builder_all_disabled(&builder);
assert(ok == 0);
ok = config_builder_enable_default_uint8_small(&builder);
ok = config_builder_enable_default_integers_small(&builder);
assert(ok == 0);
ok = config_builder_build(builder, &config);
assert(ok == 0);

View File

@@ -13,8 +13,8 @@ void micro_bench_and() {
// int gen_keys_ok = boolean_gen_keys_with_default_parameters(&cks, &sks);
// assert(gen_keys_ok == 0);
int gen_keys_ok = boolean_gen_keys_with_predefined_parameters_set(
BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS, &cks, &sks);
int gen_keys_ok =
boolean_gen_keys_with_parameters(BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS, &cks, &sks);
assert(gen_keys_ok == 0);
int num_loops = 10000;
@@ -32,7 +32,7 @@ void micro_bench_and() {
for (int idx_loops = 0; idx_loops < num_loops; ++idx_loops) {
BooleanCiphertext *ct_result = NULL;
boolean_server_key_and(sks, ct_left, ct_right, &ct_result);
destroy_boolean_ciphertext(ct_result);
boolean_destroy_ciphertext(ct_result);
}
clock_t stop = clock();
@@ -41,8 +41,10 @@ void micro_bench_and() {
printf("%g ms, mean %g ms\n", elapsed_ms, mean_ms);
destroy_boolean_client_key(cks);
destroy_boolean_server_key(sks);
boolean_destroy_client_key(cks);
boolean_destroy_server_key(sks);
boolean_destroy_ciphertext(ct_left);
boolean_destroy_ciphertext(ct_right);
}
int main(void) {

View File

@@ -8,16 +8,13 @@
void test_predefined_keygen_w_serde(void) {
ShortintClientKey *cks = NULL;
ShortintServerKey *sks = NULL;
ShortintParameters *params = NULL;
ShortintCiphertext *ct = NULL;
Buffer ct_ser_buffer = {.pointer = NULL, .length = 0};
ShortintCiphertext *deser_ct = NULL;
ShortintCompressedCiphertext *cct = NULL;
ShortintCompressedCiphertext *deser_cct = NULL;
ShortintCiphertext *decompressed_ct = NULL;
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2_KS_PBS;
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
@@ -65,30 +62,26 @@ void test_predefined_keygen_w_serde(void) {
assert(c_result == 3);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
destroy_shortint_parameters(params);
destroy_shortint_ciphertext(ct);
destroy_shortint_ciphertext(deser_ct);
destroy_shortint_compressed_ciphertext(cct);
destroy_shortint_compressed_ciphertext(deser_cct);
destroy_shortint_ciphertext(decompressed_ct);
shortint_destroy_client_key(cks);
shortint_destroy_server_key(sks);
shortint_destroy_ciphertext(ct);
shortint_destroy_ciphertext(deser_ct);
shortint_destroy_compressed_ciphertext(cct);
shortint_destroy_compressed_ciphertext(deser_cct);
shortint_destroy_ciphertext(decompressed_ct);
destroy_buffer(&ct_ser_buffer);
}
void test_server_key_trivial_encrypt(void) {
ShortintClientKey *cks = NULL;
ShortintServerKey *sks = NULL;
ShortintParameters *params = NULL;
ShortintCiphertext *ct = NULL;
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2_KS_PBS;
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
int encrypt_ok = shortint_server_key_create_trivial(sks, 3, ShortintCiphertextBig, &ct);
int encrypt_ok = shortint_server_key_create_trivial(sks, 3, &ct);
assert(encrypt_ok == 0);
uint64_t result = -1;
@@ -97,45 +90,49 @@ void test_server_key_trivial_encrypt(void) {
assert(result == 3);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
destroy_shortint_parameters(params);
destroy_shortint_ciphertext(ct);
shortint_destroy_client_key(cks);
shortint_destroy_server_key(sks);
shortint_destroy_ciphertext(ct);
}
void test_custom_keygen(void) {
ShortintClientKey *cks = NULL;
ShortintServerKey *sks = NULL;
ShortintParameters *params = NULL;
int params_ok = shortint_create_parameters(10, 1, 1024, 10e-100, 10e-100, 2, 3, 2, 3, 2, 3,
10e-100, 2, 3, 2, 2, SHORTINT_NATIVE_MODULUS, &params);
assert(params_ok == 0);
ShortintPBSParameters params = {
.lwe_dimension = 10,
.glwe_dimension = 1,
.polynomial_size = 1024,
.lwe_modular_std_dev = 10e-100,
.glwe_modular_std_dev = 10e-100,
.pbs_base_log = 2,
.pbs_level = 3,
.ks_base_log = 2,
.ks_level = 3,
.message_modulus = 2,
.carry_modulus = 2,
.modulus_power_of_2_exponent = 64,
.encryption_key_choice = ShortintEncryptionKeyChoiceBig,
};
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
destroy_shortint_parameters(params);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
shortint_destroy_client_key(cks);
shortint_destroy_server_key(sks);
}
void test_public_keygen(ShortintPublicKeyKind pk_kind) {
void test_public_keygen(ShortintPBSParameters params) {
ShortintClientKey *cks = NULL;
ShortintPublicKey *pks = NULL;
ShortintPublicKey *pks_deser = NULL;
ShortintParameters *params = NULL;
ShortintCiphertext *ct = NULL;
Buffer pks_ser_buff = {.pointer = NULL, .length = 0};
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
int gen_keys_ok = shortint_gen_client_key(params, &cks);
assert(gen_keys_ok == 0);
int gen_pks = shortint_gen_public_key(cks, pk_kind, &pks);
int gen_pks = shortint_gen_public_key(cks, &pks);
assert(gen_pks == 0);
int pks_ser = shortint_serialize_public_key(pks, &pks_ser_buff);
@@ -156,28 +153,23 @@ void test_public_keygen(ShortintPublicKeyKind pk_kind) {
assert(result == 2);
destroy_shortint_parameters(params);
destroy_shortint_client_key(cks);
destroy_shortint_public_key(pks);
destroy_shortint_public_key(pks_deser);
shortint_destroy_client_key(cks);
shortint_destroy_public_key(pks);
shortint_destroy_public_key(pks_deser);
destroy_buffer(&pks_ser_buff);
destroy_shortint_ciphertext(ct);
shortint_destroy_ciphertext(ct);
}
void test_compressed_public_keygen(ShortintPublicKeyKind pk_kind) {
void test_compressed_public_keygen(ShortintPBSParameters params) {
ShortintClientKey *cks = NULL;
ShortintCompressedPublicKey *cpks = NULL;
ShortintPublicKey *pks = NULL;
ShortintParameters *params = NULL;
ShortintCiphertext *ct = NULL;
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
int gen_keys_ok = shortint_gen_client_key(params, &cks);
assert(gen_keys_ok == 0);
int gen_cpks = shortint_gen_compressed_public_key(cks, pk_kind, &cpks);
int gen_cpks = shortint_gen_compressed_public_key(cks, &cpks);
assert(gen_cpks == 0);
uint64_t msg = 2;
@@ -203,20 +195,19 @@ void test_compressed_public_keygen(ShortintPublicKeyKind pk_kind) {
assert(result == 2);
destroy_shortint_parameters(params);
destroy_shortint_client_key(cks);
destroy_shortint_compressed_public_key(cpks);
destroy_shortint_public_key(pks);
destroy_shortint_ciphertext(ct);
shortint_destroy_client_key(cks);
shortint_destroy_compressed_public_key(cpks);
shortint_destroy_public_key(pks);
shortint_destroy_ciphertext(ct);
}
int main(void) {
test_predefined_keygen_w_serde();
test_custom_keygen();
test_public_keygen(ShortintPublicKeyBig);
test_public_keygen(ShortintPublicKeySmall);
test_compressed_public_keygen(ShortintPublicKeyBig);
test_compressed_public_keygen(ShortintPublicKeySmall);
test_public_keygen(SHORTINT_PARAM_MESSAGE_2_CARRY_2_KS_PBS);
test_public_keygen(SHORTINT_PARAM_MESSAGE_2_CARRY_2_PBS_KS);
test_compressed_public_keygen(SHORTINT_PARAM_MESSAGE_2_CARRY_2_KS_PBS);
test_compressed_public_keygen(SHORTINT_PARAM_MESSAGE_2_CARRY_2_PBS_KS);
test_server_key_trivial_encrypt();
return EXIT_SUCCESS;
}

View File

@@ -5,31 +5,31 @@
#include <stdlib.h>
#include <tgmath.h>
uint64_t double_accumulator_2_bits_message(uint64_t in) { return (in * 2) % 4; }
uint64_t double_lookup_table_2_bits_message(uint64_t in) { return (in * 2) % 4; }
uint64_t get_max_value_of_accumulator_generator(uint64_t (*accumulator_func)(uint64_t),
size_t message_bits) {
uint64_t get_max_value_of_lookup_table_generator(uint64_t (*lookup_table_func)(uint64_t),
size_t message_bits) {
uint64_t max_value = 0;
for (size_t idx = 0; idx < (1 << message_bits); ++idx) {
uint64_t acc_value = accumulator_func((uint64_t)idx);
uint64_t acc_value = lookup_table_func((uint64_t)idx);
max_value = acc_value > max_value ? acc_value : max_value;
}
return max_value;
}
uint64_t product_accumulator_2_bits_encrypted_mul(uint64_t left, uint64_t right) {
uint64_t product_lookup_table_2_bits_encrypted_mul(uint64_t left, uint64_t right) {
return (left * right) % 4;
}
uint64_t get_max_value_of_bivariate_accumulator_generator(uint64_t (*accumulator_func)(uint64_t,
uint64_t),
size_t message_bits_left,
size_t message_bits_right) {
uint64_t get_max_value_of_bivariate_lookup_table_generator(uint64_t (*lookup_table_func)(uint64_t,
uint64_t),
size_t message_bits_left,
size_t message_bits_right) {
uint64_t max_value = 0;
for (size_t idx_left = 0; idx_left < (1 << message_bits_left); ++idx_left) {
for (size_t idx_right = 0; idx_right < (1 << message_bits_right); ++idx_right) {
uint64_t acc_value = accumulator_func((uint64_t)idx_left, (uint64_t)idx_right);
uint64_t acc_value = lookup_table_func((uint64_t)idx_left, (uint64_t)idx_right);
max_value = acc_value > max_value ? acc_value : max_value;
}
}
@@ -38,19 +38,16 @@ uint64_t get_max_value_of_bivariate_accumulator_generator(uint64_t (*accumulator
}
void test_shortint_pbs_2_bits_message(void) {
ShortintPBSLookupTable *accumulator = NULL;
ShortintPBSLookupTable *lookup_table = NULL;
ShortintClientKey *cks = NULL;
ShortintServerKey *sks = NULL;
ShortintParameters *params = NULL;
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2_KS_PBS;
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
int gen_acc_ok = shortint_server_key_generate_pbs_accumulator(
sks, double_accumulator_2_bits_message, &accumulator);
int gen_acc_ok = shortint_server_key_generate_pbs_lookup_table(
sks, double_lookup_table_2_bits_message, &lookup_table);
assert(gen_acc_ok == 0);
for (int in_idx = 0; in_idx < 4; ++in_idx) {
@@ -68,11 +65,11 @@ void test_shortint_pbs_2_bits_message(void) {
assert(degree == 3);
int pbs_ok = shortint_server_key_programmable_bootstrap(sks, accumulator, ct, &ct_out);
int pbs_ok = shortint_server_key_programmable_bootstrap(sks, lookup_table, ct, &ct_out);
assert(pbs_ok == 0);
size_t degree_to_set =
(size_t)get_max_value_of_accumulator_generator(double_accumulator_2_bits_message, 2);
(size_t)get_max_value_of_lookup_table_generator(double_lookup_table_2_bits_message, 2);
int set_degree_ok = shortint_ciphertext_set_degree(ct_out, degree_to_set);
assert(set_degree_ok == 0);
@@ -87,13 +84,14 @@ void test_shortint_pbs_2_bits_message(void) {
int decrypt_non_assign_ok = shortint_client_key_decrypt(cks, ct_out, &result_non_assign);
assert(decrypt_non_assign_ok == 0);
assert(result_non_assign == double_accumulator_2_bits_message(in_val));
assert(result_non_assign == double_lookup_table_2_bits_message(in_val));
int pbs_assign_ok = shortint_server_key_programmable_bootstrap_assign(sks, accumulator, ct_out);
int pbs_assign_ok =
shortint_server_key_programmable_bootstrap_assign(sks, lookup_table, ct_out);
assert(pbs_assign_ok == 0);
degree_to_set =
(size_t)get_max_value_of_accumulator_generator(double_accumulator_2_bits_message, 2);
(size_t)get_max_value_of_lookup_table_generator(double_lookup_table_2_bits_message, 2);
set_degree_ok = shortint_ciphertext_set_degree(ct_out, degree_to_set);
assert(set_degree_ok == 0);
@@ -102,32 +100,28 @@ void test_shortint_pbs_2_bits_message(void) {
int decrypt_assign_ok = shortint_client_key_decrypt(cks, ct_out, &result_assign);
assert(decrypt_assign_ok == 0);
assert(result_assign == double_accumulator_2_bits_message(result_non_assign));
assert(result_assign == double_lookup_table_2_bits_message(result_non_assign));
destroy_shortint_ciphertext(ct);
destroy_shortint_ciphertext(ct_out);
shortint_destroy_ciphertext(ct);
shortint_destroy_ciphertext(ct_out);
}
destroy_shortint_pbs_accumulator(accumulator);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
destroy_shortint_parameters(params);
shortint_destroy_pbs_lookup_table(lookup_table);
shortint_destroy_client_key(cks);
shortint_destroy_server_key(sks);
}
void test_shortint_bivariate_pbs_2_bits_message(void) {
ShortintBivariatePBSLookupTable *accumulator = NULL;
ShortintBivariatePBSLookupTable *lookup_table = NULL;
ShortintClientKey *cks = NULL;
ShortintServerKey *sks = NULL;
ShortintParameters *params = NULL;
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2_KS_PBS;
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
int gen_acc_ok = shortint_server_key_generate_bivariate_pbs_accumulator(
sks, product_accumulator_2_bits_encrypted_mul, &accumulator);
int gen_acc_ok = shortint_server_key_generate_bivariate_pbs_lookup_table(
sks, product_lookup_table_2_bits_encrypted_mul, &lookup_table);
assert(gen_acc_ok == 0);
for (int left_idx = 0; left_idx < 4; ++left_idx) {
@@ -145,12 +139,12 @@ void test_shortint_bivariate_pbs_2_bits_message(void) {
int encrypt_right_ok = shortint_client_key_encrypt(cks, right_val, &ct_right);
assert(encrypt_right_ok == 0);
int pbs_ok = shortint_server_key_bivariate_programmable_bootstrap(sks, accumulator, ct_left,
int pbs_ok = shortint_server_key_bivariate_programmable_bootstrap(sks, lookup_table, ct_left,
ct_right, &ct_out);
assert(pbs_ok == 0);
size_t degree_to_set = (size_t)get_max_value_of_bivariate_accumulator_generator(
product_accumulator_2_bits_encrypted_mul, 2, 2);
size_t degree_to_set = (size_t)get_max_value_of_bivariate_lookup_table_generator(
product_lookup_table_2_bits_encrypted_mul, 2, 2);
int set_degree_ok = shortint_ciphertext_set_degree(ct_right, degree_to_set);
assert(set_degree_ok == 0);
@@ -159,14 +153,14 @@ void test_shortint_bivariate_pbs_2_bits_message(void) {
int decrypt_non_assign_ok = shortint_client_key_decrypt(cks, ct_out, &result_non_assign);
assert(decrypt_non_assign_ok == 0);
assert(result_non_assign == product_accumulator_2_bits_encrypted_mul(left_val, right_val));
assert(result_non_assign == product_lookup_table_2_bits_encrypted_mul(left_val, right_val));
int pbs_assign_ok = shortint_server_key_bivariate_programmable_bootstrap_assign(
sks, accumulator, ct_out, ct_right);
sks, lookup_table, ct_out, ct_right);
assert(pbs_assign_ok == 0);
degree_to_set =
(size_t)get_max_value_of_accumulator_generator(double_accumulator_2_bits_message, 2);
(size_t)get_max_value_of_lookup_table_generator(double_lookup_table_2_bits_message, 2);
set_degree_ok = shortint_ciphertext_set_degree(ct_out, degree_to_set);
assert(set_degree_ok == 0);
@@ -176,18 +170,17 @@ void test_shortint_bivariate_pbs_2_bits_message(void) {
assert(decrypt_assign_ok == 0);
assert(result_assign ==
product_accumulator_2_bits_encrypted_mul(result_non_assign, right_val));
product_lookup_table_2_bits_encrypted_mul(result_non_assign, right_val));
destroy_shortint_ciphertext(ct_left);
destroy_shortint_ciphertext(ct_right);
destroy_shortint_ciphertext(ct_out);
shortint_destroy_ciphertext(ct_left);
shortint_destroy_ciphertext(ct_right);
shortint_destroy_ciphertext(ct_out);
}
}
destroy_shortint_bivariate_pbs_accumulator(accumulator);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
destroy_shortint_parameters(params);
shortint_destroy_bivariate_pbs_lookup_table(lookup_table);
shortint_destroy_client_key(cks);
shortint_destroy_server_key(sks);
}
int main(void) {

View File

@@ -21,8 +21,8 @@ typedef int (*UnaryAssignCallback)(const ShortintServerKey *, ShortintCiphertext
void test_shortint_unary_op(const ShortintClientKey *cks, const ShortintServerKey *sks,
const ShortintClientKey *cks_small, const ShortintServerKey *sks_small,
const uint32_t message_bits, const uint32_t carry_bits,
uint64_t (*c_fun)(uint64_t), UnaryCallback api_fun) {
const uint32_t message_bits, uint64_t (*c_fun)(uint64_t),
UnaryCallback api_fun) {
int message_max = 1 << message_bits;
@@ -47,7 +47,7 @@ void test_shortint_unary_op(const ShortintClientKey *cks, const ShortintServerKe
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt_small(cks_in_use, in, &ct_in);
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, in, &ct_in);
assert(encrypt_left_ok == 0);
}
@@ -61,8 +61,8 @@ void test_shortint_unary_op(const ShortintClientKey *cks, const ShortintServerKe
assert(decrypted_result == expected);
destroy_shortint_ciphertext(ct_in);
destroy_shortint_ciphertext(ct_result);
shortint_destroy_ciphertext(ct_in);
shortint_destroy_ciphertext(ct_result);
}
}
}
@@ -70,8 +70,7 @@ void test_shortint_unary_op(const ShortintClientKey *cks, const ShortintServerKe
void test_shortint_unary_op_assign(const ShortintClientKey *cks, const ShortintServerKey *sks,
const ShortintClientKey *cks_small,
const ShortintServerKey *sks_small, const uint32_t message_bits,
const uint32_t carry_bits, uint64_t (*c_fun)(uint64_t),
UnaryAssignCallback api_fun) {
uint64_t (*c_fun)(uint64_t), UnaryAssignCallback api_fun) {
int message_max = 1 << message_bits;
@@ -95,7 +94,7 @@ void test_shortint_unary_op_assign(const ShortintClientKey *cks, const ShortintS
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt_small(cks_in_use, in, &ct_in_and_result);
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, in, &ct_in_and_result);
assert(encrypt_left_ok == 0);
}
@@ -109,15 +108,15 @@ void test_shortint_unary_op_assign(const ShortintClientKey *cks, const ShortintS
assert(decrypted_result == expected);
destroy_shortint_ciphertext(ct_in_and_result);
shortint_destroy_ciphertext(ct_in_and_result);
}
}
}
void test_shortint_binary_op(const ShortintClientKey *cks, const ShortintServerKey *sks,
const ShortintClientKey *cks_small, const ShortintServerKey *sks_small,
const uint32_t message_bits, const uint32_t carry_bits,
uint64_t (*c_fun)(uint64_t, uint64_t), BinaryCallback api_fun) {
const uint32_t message_bits, uint64_t (*c_fun)(uint64_t, uint64_t),
BinaryCallback api_fun) {
int message_max = 1 << message_bits;
@@ -148,10 +147,10 @@ void test_shortint_binary_op(const ShortintClientKey *cks, const ShortintServerK
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt_small(cks_in_use, left, &ct_left);
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left);
assert(encrypt_left_ok == 0);
int encrypt_right_ok = shortint_client_key_encrypt_small(cks_in_use, right, &ct_right);
int encrypt_right_ok = shortint_client_key_encrypt(cks_in_use, right, &ct_right);
assert(encrypt_right_ok == 0);
}
@@ -165,9 +164,9 @@ void test_shortint_binary_op(const ShortintClientKey *cks, const ShortintServerK
assert(decrypted_result == expected);
destroy_shortint_ciphertext(ct_left);
destroy_shortint_ciphertext(ct_right);
destroy_shortint_ciphertext(ct_result);
shortint_destroy_ciphertext(ct_left);
shortint_destroy_ciphertext(ct_right);
shortint_destroy_ciphertext(ct_result);
}
}
}
@@ -176,7 +175,7 @@ void test_shortint_binary_op(const ShortintClientKey *cks, const ShortintServerK
void test_shortint_binary_op_assign(const ShortintClientKey *cks, const ShortintServerKey *sks,
const ShortintClientKey *cks_small,
const ShortintServerKey *sks_small, const uint32_t message_bits,
const uint32_t carry_bits,
uint64_t (*c_fun)(uint64_t, uint64_t),
BinaryAssignCallback api_fun) {
@@ -208,11 +207,10 @@ void test_shortint_binary_op_assign(const ShortintClientKey *cks, const Shortint
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok =
shortint_client_key_encrypt_small(cks_in_use, left, &ct_left_and_result);
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left_and_result);
assert(encrypt_left_ok == 0);
int encrypt_right_ok = shortint_client_key_encrypt_small(cks_in_use, right, &ct_right);
int encrypt_right_ok = shortint_client_key_encrypt(cks_in_use, right, &ct_right);
assert(encrypt_right_ok == 0);
}
@@ -227,8 +225,134 @@ void test_shortint_binary_op_assign(const ShortintClientKey *cks, const Shortint
assert(decrypted_result == expected);
destroy_shortint_ciphertext(ct_left_and_result);
destroy_shortint_ciphertext(ct_right);
shortint_destroy_ciphertext(ct_left_and_result);
shortint_destroy_ciphertext(ct_right);
}
}
}
}
uint64_t homomorphic_div(uint64_t left, uint64_t right, uint64_t value_on_div_by_zero) {
if (right != 0) {
return left / right;
} else {
// Special value chosen in the shortint implementation in case of a division by 0
return value_on_div_by_zero;
}
}
void test_shortint_div(const ShortintClientKey *cks, const ShortintServerKey *sks,
const ShortintClientKey *cks_small, const ShortintServerKey *sks_small,
const uint32_t message_bits) {
int message_max = 1 << message_bits;
for (int is_big = 0; is_big < 2; ++is_big) {
for (int val_left = 0; val_left < message_max; ++val_left) {
for (int val_right = 0; val_right < message_max; ++val_right) {
ShortintCiphertext *ct_left = NULL;
ShortintCiphertext *ct_right = NULL;
ShortintCiphertext *ct_result = NULL;
const ShortintClientKey *cks_in_use = NULL;
const ShortintServerKey *sks_in_use = NULL;
uint64_t left = (uint64_t)val_left;
uint64_t right = (uint64_t)val_right;
uint64_t expected = homomorphic_div(left, right, (uint64_t)(message_max - 1)) % message_max;
if (is_big == 1) {
cks_in_use = cks;
sks_in_use = sks;
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left);
assert(encrypt_left_ok == 0);
int encrypt_right_ok = shortint_client_key_encrypt(cks_in_use, right, &ct_right);
assert(encrypt_right_ok == 0);
} else {
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left);
assert(encrypt_left_ok == 0);
int encrypt_right_ok = shortint_client_key_encrypt(cks_in_use, right, &ct_right);
assert(encrypt_right_ok == 0);
}
int api_call_ok =
shortint_server_key_unchecked_div(sks_in_use, ct_left, ct_right, &ct_result);
assert(api_call_ok == 0);
uint64_t decrypted_result = -1;
int decrypt_ok = shortint_client_key_decrypt(cks_in_use, ct_result, &decrypted_result);
assert(decrypt_ok == 0);
assert(decrypted_result == expected);
shortint_destroy_ciphertext(ct_left);
shortint_destroy_ciphertext(ct_right);
shortint_destroy_ciphertext(ct_result);
}
}
}
}
void test_shortint_div_assign(const ShortintClientKey *cks, const ShortintServerKey *sks,
const ShortintClientKey *cks_small,
const ShortintServerKey *sks_small, const uint32_t message_bits) {
int message_max = 1 << message_bits;
for (int is_big = 0; is_big < 2; ++is_big) {
for (int val_left = 0; val_left < message_max; ++val_left) {
for (int val_right = 0; val_right < message_max; ++val_right) {
ShortintCiphertext *ct_left_and_result = NULL;
ShortintCiphertext *ct_right = NULL;
const ShortintClientKey *cks_in_use = NULL;
const ShortintServerKey *sks_in_use = NULL;
uint64_t left = (uint64_t)val_left;
uint64_t right = (uint64_t)val_right;
uint64_t expected = homomorphic_div(left, right, (uint64_t)(message_max - 1)) % message_max;
if (is_big == 1) {
cks_in_use = cks;
sks_in_use = sks;
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left_and_result);
assert(encrypt_left_ok == 0);
int encrypt_right_ok = shortint_client_key_encrypt(cks_in_use, right, &ct_right);
assert(encrypt_right_ok == 0);
} else {
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left_and_result);
assert(encrypt_left_ok == 0);
int encrypt_right_ok = shortint_client_key_encrypt(cks_in_use, right, &ct_right);
assert(encrypt_right_ok == 0);
}
int api_call_ok =
shortint_server_key_unchecked_div_assign(sks_in_use, ct_left_and_result, ct_right);
assert(api_call_ok == 0);
uint64_t decrypted_result = -1;
int decrypt_ok =
shortint_client_key_decrypt(cks_in_use, ct_left_and_result, &decrypted_result);
assert(decrypt_ok == 0);
assert(decrypted_result == expected);
shortint_destroy_ciphertext(ct_left_and_result);
shortint_destroy_ciphertext(ct_right);
}
}
}
@@ -236,7 +360,7 @@ void test_shortint_binary_op_assign(const ShortintClientKey *cks, const Shortint
void test_shortint_binary_scalar_op(
const ShortintClientKey *cks, const ShortintServerKey *sks, const ShortintClientKey *cks_small,
const ShortintServerKey *sks_small, const uint32_t message_bits, const uint32_t carry_bits,
const ShortintServerKey *sks_small, const uint32_t message_bits,
uint64_t (*c_fun)(uint64_t, uint8_t),
int (*api_fun)(const ShortintServerKey *, ShortintCiphertext *, uint8_t, ShortintCiphertext **),
uint8_t forbidden_scalar_values[], size_t forbidden_scalar_values_len) {
@@ -280,7 +404,7 @@ void test_shortint_binary_scalar_op(
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt_small(cks_in_use, left, &ct_left);
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left);
assert(encrypt_left_ok == 0);
}
@@ -294,8 +418,8 @@ void test_shortint_binary_scalar_op(
assert(decrypted_result == expected);
destroy_shortint_ciphertext(ct_left);
destroy_shortint_ciphertext(ct_result);
shortint_destroy_ciphertext(ct_left);
shortint_destroy_ciphertext(ct_result);
}
}
}
@@ -303,7 +427,7 @@ void test_shortint_binary_scalar_op(
void test_shortint_binary_scalar_op_assign(
const ShortintClientKey *cks, const ShortintServerKey *sks, const ShortintClientKey *cks_small,
const ShortintServerKey *sks_small, const uint32_t message_bits, const uint32_t carry_bits,
const ShortintServerKey *sks_small, const uint32_t message_bits,
uint64_t (*c_fun)(uint64_t, uint8_t),
int (*api_fun)(const ShortintServerKey *, ShortintCiphertext *, uint8_t),
uint8_t forbidden_scalar_values[], size_t forbidden_scalar_values_len) {
@@ -346,8 +470,7 @@ void test_shortint_binary_scalar_op_assign(
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok =
shortint_client_key_encrypt_small(cks_in_use, left, &ct_left_and_result);
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left_and_result);
assert(encrypt_left_ok == 0);
}
@@ -362,7 +485,7 @@ void test_shortint_binary_scalar_op_assign(
assert(decrypted_result == expected);
destroy_shortint_ciphertext(ct_left_and_result);
shortint_destroy_ciphertext(ct_left_and_result);
}
}
}
@@ -373,15 +496,6 @@ uint64_t sub(uint64_t left, uint64_t right) { return left - right; }
uint64_t mul(uint64_t left, uint64_t right) { return left * right; }
uint64_t neg(uint64_t in) { return -in; }
uint64_t homomorphic_div(uint64_t left, uint64_t right) {
if (right != 0) {
return left / right;
} else {
// Special value chosen in the shortint implementation in case of a division by 0
return 0;
}
}
uint64_t bitand(uint64_t left, uint64_t right) { return left & right; }
uint64_t bitxor(uint64_t left, uint64_t right) { return left ^ right; }
uint64_t bitor (uint64_t left, uint64_t right) { return left | right; }
@@ -419,10 +533,10 @@ void test_server_key(void) {
ShortintCompressedServerKey *deser_csks = NULL;
Buffer sks_ser_buffer = {.pointer = NULL, .length = 0};
ShortintServerKey *deser_sks = NULL;
ShortintParameters *params = NULL;
ShortintClientKey *cks_small = NULL;
ShortintServerKey *sks_small = NULL;
ShortintParameters *params_small = NULL;
ShortintPBSParameters params = {0};
ShortintPBSParameters params_small = {0};
const uint32_t message_bits = 2;
const uint32_t carry_bits = 2;
@@ -430,7 +544,7 @@ void test_server_key(void) {
int get_params_ok = shortint_get_parameters(message_bits, carry_bits, &params);
assert(get_params_ok == 0);
int get_params_small_ok = shortint_get_parameters(message_bits, carry_bits, &params_small);
int get_params_small_ok = shortint_get_parameters_small(message_bits, carry_bits, &params_small);
assert(get_params_small_ok == 0);
int gen_cks_ok = shortint_gen_client_key(params, &cks);
@@ -475,269 +589,246 @@ void test_server_key(void) {
assert(deser_sks_ok == 0);
printf("add\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, add,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, add,
(BinaryCallback)shortint_server_key_smart_add);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, add,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, add,
(BinaryCallback)shortint_server_key_unchecked_add);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, add,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, add,
(BinaryAssignCallback)shortint_server_key_smart_add_assign);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, add,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, add,
(BinaryAssignCallback)shortint_server_key_unchecked_add_assign);
printf("sub\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, sub,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, sub,
(BinaryCallback)shortint_server_key_smart_sub);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, sub,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, sub,
(BinaryCallback)shortint_server_key_unchecked_sub);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, sub,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, sub,
(BinaryAssignCallback)shortint_server_key_smart_sub_assign);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, sub,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, sub,
(BinaryAssignCallback)shortint_server_key_unchecked_sub_assign);
printf("mul\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, mul,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, mul,
(BinaryCallback)shortint_server_key_smart_mul);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, mul,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, mul,
(BinaryCallback)shortint_server_key_unchecked_mul);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, mul,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, mul,
(BinaryAssignCallback)shortint_server_key_smart_mul_assign);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, mul,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, mul,
(BinaryAssignCallback)shortint_server_key_unchecked_mul_assign);
printf("left_shift\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, left_shift,
deser_cks, deser_sks, cks_small, sks_small, message_bits, left_shift,
(BinaryScalarCallback)shortint_server_key_smart_scalar_left_shift, NULL, 0);
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, left_shift,
deser_cks, deser_sks, cks_small, sks_small, message_bits, left_shift,
(BinaryScalarCallback)shortint_server_key_unchecked_scalar_left_shift, NULL, 0);
test_shortint_binary_scalar_op_assign(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, left_shift,
deser_cks, deser_sks, cks_small, sks_small, message_bits, left_shift,
shortint_server_key_smart_scalar_left_shift_assign, NULL, 0);
test_shortint_binary_scalar_op_assign(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, left_shift,
deser_cks, deser_sks, cks_small, sks_small, message_bits, left_shift,
shortint_server_key_unchecked_scalar_left_shift_assign, NULL, 0);
printf("right_shift\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, right_shift,
deser_cks, deser_sks, cks_small, sks_small, message_bits, right_shift,
(BinaryScalarCallback)shortint_server_key_smart_scalar_right_shift, NULL, 0);
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, right_shift,
deser_cks, deser_sks, cks_small, sks_small, message_bits, right_shift,
(BinaryScalarCallback)shortint_server_key_unchecked_scalar_right_shift, NULL, 0);
test_shortint_binary_scalar_op_assign(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, right_shift,
deser_cks, deser_sks, cks_small, sks_small, message_bits, right_shift,
shortint_server_key_smart_scalar_right_shift_assign, NULL, 0);
test_shortint_binary_scalar_op_assign(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, right_shift,
deser_cks, deser_sks, cks_small, sks_small, message_bits, right_shift,
shortint_server_key_unchecked_scalar_right_shift_assign, NULL, 0);
printf("scalar_add\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_add,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_add,
(BinaryScalarCallback)shortint_server_key_smart_scalar_add, NULL, 0);
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_add,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_add,
(BinaryScalarCallback)shortint_server_key_unchecked_scalar_add, NULL, 0);
test_shortint_binary_scalar_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, scalar_add,
shortint_server_key_smart_scalar_add_assign, NULL, 0);
scalar_add, shortint_server_key_smart_scalar_add_assign,
NULL, 0);
test_shortint_binary_scalar_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, scalar_add,
shortint_server_key_unchecked_scalar_add_assign, NULL, 0);
scalar_add, shortint_server_key_unchecked_scalar_add_assign,
NULL, 0);
printf("scalar_sub\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_sub,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_sub,
(BinaryScalarCallback)shortint_server_key_smart_scalar_sub, NULL, 0);
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_sub,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_sub,
(BinaryScalarCallback)shortint_server_key_unchecked_scalar_sub, NULL, 0);
test_shortint_binary_scalar_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, scalar_sub,
shortint_server_key_smart_scalar_sub_assign, NULL, 0);
scalar_sub, shortint_server_key_smart_scalar_sub_assign,
NULL, 0);
test_shortint_binary_scalar_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, scalar_sub,
shortint_server_key_unchecked_scalar_sub_assign, NULL, 0);
scalar_sub, shortint_server_key_unchecked_scalar_sub_assign,
NULL, 0);
printf("scalar_mul\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_mul,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_mul,
(BinaryScalarCallback)shortint_server_key_smart_scalar_mul, NULL, 0);
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_mul,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_mul,
(BinaryScalarCallback)shortint_server_key_unchecked_scalar_mul, NULL, 0);
test_shortint_binary_scalar_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, scalar_mul,
shortint_server_key_smart_scalar_mul_assign, NULL, 0);
scalar_mul, shortint_server_key_smart_scalar_mul_assign,
NULL, 0);
test_shortint_binary_scalar_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, scalar_mul,
shortint_server_key_unchecked_scalar_mul_assign, NULL, 0);
scalar_mul, shortint_server_key_unchecked_scalar_mul_assign,
NULL, 0);
printf("bitand\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, bitand, (BinaryCallback)shortint_server_key_smart_bitand);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, bitand, (BinaryCallback)shortint_server_key_unchecked_bitand);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, bitand,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitand,
(BinaryCallback)shortint_server_key_smart_bitand);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitand,
(BinaryCallback)shortint_server_key_unchecked_bitand);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitand,
(BinaryAssignCallback)shortint_server_key_smart_bitand_assign);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, bitand,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitand,
(BinaryAssignCallback)shortint_server_key_unchecked_bitand_assign);
printf("bitxor\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
bitxor, (BinaryCallback)shortint_server_key_smart_bitxor);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
bitxor, (BinaryCallback)shortint_server_key_unchecked_bitxor);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, bitxor,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitxor,
(BinaryCallback)shortint_server_key_smart_bitxor);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitxor,
(BinaryCallback)shortint_server_key_unchecked_bitxor);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitxor,
(BinaryAssignCallback)shortint_server_key_smart_bitxor_assign);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, bitxor,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitxor,
(BinaryAssignCallback)shortint_server_key_unchecked_bitxor_assign);
printf("bitor\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
bitor, (BinaryCallback)shortint_server_key_smart_bitor);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
bitor, (BinaryCallback)shortint_server_key_unchecked_bitor);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, bitor,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitor,
(BinaryCallback)shortint_server_key_smart_bitor);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitor,
(BinaryCallback)shortint_server_key_unchecked_bitor);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitor,
(BinaryAssignCallback)shortint_server_key_smart_bitor_assign);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, bitor,
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, bitor,
(BinaryAssignCallback)shortint_server_key_unchecked_bitor_assign);
printf("greater\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
greater, (BinaryCallback)shortint_server_key_smart_greater);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
greater, (BinaryCallback)shortint_server_key_unchecked_greater);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, greater,
(BinaryCallback)shortint_server_key_smart_greater);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, greater,
(BinaryCallback)shortint_server_key_unchecked_greater);
printf("greater_or_equal\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits,
greater_or_equal,
(BinaryCallback)shortint_server_key_smart_greater_or_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits,
greater_or_equal,
(BinaryCallback)shortint_server_key_unchecked_greater_or_equal);
printf("less\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
less, (BinaryCallback)shortint_server_key_smart_less);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
less, (BinaryCallback)shortint_server_key_unchecked_less);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, less,
(BinaryCallback)shortint_server_key_smart_less);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, less,
(BinaryCallback)shortint_server_key_unchecked_less);
printf("less_or_equal\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
less_or_equal, (BinaryCallback)shortint_server_key_smart_less_or_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
less_or_equal,
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, less_or_equal,
(BinaryCallback)shortint_server_key_smart_less_or_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, less_or_equal,
(BinaryCallback)shortint_server_key_unchecked_less_or_equal);
printf("equal\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
equal, (BinaryCallback)shortint_server_key_smart_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
equal, (BinaryCallback)shortint_server_key_unchecked_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, equal,
(BinaryCallback)shortint_server_key_smart_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, equal,
(BinaryCallback)shortint_server_key_unchecked_equal);
printf("not_equal\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
not_equal, (BinaryCallback)shortint_server_key_smart_not_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
not_equal, (BinaryCallback)shortint_server_key_unchecked_not_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, not_equal,
(BinaryCallback)shortint_server_key_smart_not_equal);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, not_equal,
(BinaryCallback)shortint_server_key_unchecked_not_equal);
printf("scalar_greater\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_greater,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_greater,
(BinaryScalarCallback)shortint_server_key_smart_scalar_greater, NULL, 0);
printf("scalar_greater_or_equal\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_greater_or_equal,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_greater_or_equal,
(BinaryScalarCallback)shortint_server_key_smart_scalar_greater_or_equal, NULL, 0);
printf("scalar_less\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_less,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_less,
(BinaryScalarCallback)shortint_server_key_smart_scalar_less, NULL, 0);
printf("scalar_less_or_equal\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_less_or_equal,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_less_or_equal,
(BinaryScalarCallback)shortint_server_key_smart_scalar_less_or_equal, NULL, 0);
printf("scalar_equal\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_equal,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_equal,
(BinaryScalarCallback)shortint_server_key_smart_scalar_equal, NULL, 0);
printf("scalar_not_equal\n");
test_shortint_binary_scalar_op(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_not_equal,
deser_cks, deser_sks, cks_small, sks_small, message_bits, scalar_not_equal,
(BinaryScalarCallback)shortint_server_key_smart_scalar_not_equal, NULL, 0);
printf("neg\n");
test_shortint_unary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, neg,
test_shortint_unary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, neg,
(UnaryCallback)shortint_server_key_smart_neg);
test_shortint_unary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, neg,
test_shortint_unary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, neg,
(UnaryCallback)shortint_server_key_unchecked_neg);
test_shortint_unary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, neg,
test_shortint_unary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, neg,
(UnaryAssignCallback)shortint_server_key_smart_neg_assign);
test_shortint_unary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, neg,
test_shortint_unary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits, neg,
(UnaryAssignCallback)shortint_server_key_unchecked_neg_assign);
printf("div\n");
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
homomorphic_div, (BinaryCallback)shortint_server_key_smart_div);
test_shortint_binary_op(deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits,
homomorphic_div, (BinaryCallback)shortint_server_key_unchecked_div);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, homomorphic_div,
(BinaryAssignCallback)shortint_server_key_smart_div_assign);
test_shortint_binary_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, homomorphic_div,
(BinaryAssignCallback)shortint_server_key_unchecked_div_assign);
test_shortint_div(deser_cks, deser_sks, cks_small, sks_small, message_bits);
test_shortint_div(deser_cks, deser_sks, cks_small, sks_small, message_bits);
test_shortint_div_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits);
test_shortint_div_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits);
printf("scalar_div\n");
uint8_t forbidden_scalar_div_values[1] = {0};
test_shortint_binary_scalar_op(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, scalar_div,
scalar_div,
(BinaryScalarCallback)shortint_server_key_unchecked_scalar_div,
forbidden_scalar_div_values, 1);
test_shortint_binary_scalar_op_assign(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_div,
shortint_server_key_unchecked_scalar_div_assign, forbidden_scalar_div_values, 1);
test_shortint_binary_scalar_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
scalar_div, shortint_server_key_unchecked_scalar_div_assign,
forbidden_scalar_div_values, 1);
printf("scalar_mod\n");
uint8_t forbidden_scalar_mod_values[1] = {0};
test_shortint_binary_scalar_op(deser_cks, deser_sks, cks_small, sks_small, message_bits,
carry_bits, scalar_mod,
scalar_mod,
(BinaryScalarCallback)shortint_server_key_unchecked_scalar_mod,
forbidden_scalar_mod_values, 1);
test_shortint_binary_scalar_op_assign(
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_mod,
shortint_server_key_unchecked_scalar_mod_assign, forbidden_scalar_mod_values, 1);
test_shortint_binary_scalar_op_assign(deser_cks, deser_sks, cks_small, sks_small, message_bits,
scalar_mod, shortint_server_key_unchecked_scalar_mod_assign,
forbidden_scalar_mod_values, 1);
destroy_shortint_client_key(cks);
destroy_shortint_client_key(cks_small);
destroy_shortint_compressed_server_key(csks);
destroy_shortint_server_key(sks);
destroy_shortint_server_key(sks_small);
destroy_shortint_client_key(deser_cks);
destroy_shortint_compressed_server_key(deser_csks);
destroy_shortint_server_key(deser_sks);
destroy_shortint_parameters(params);
destroy_shortint_parameters(params_small);
shortint_destroy_client_key(cks);
shortint_destroy_client_key(cks_small);
shortint_destroy_compressed_server_key(csks);
shortint_destroy_server_key(sks);
shortint_destroy_server_key(sks_small);
shortint_destroy_client_key(deser_cks);
shortint_destroy_compressed_server_key(deser_csks);
shortint_destroy_server_key(deser_sks);
destroy_buffer(&cks_ser_buffer);
destroy_buffer(&csks_ser_buffer);
destroy_buffer(&sks_ser_buffer);

View File

@@ -3,44 +3,50 @@
* [What is TFHE-rs?](README.md)
## Getting Started
* [Installation](getting_started/installation.md)
* [Quick Start](getting_started/quick_start.md)
* [Operations](getting_started/operations.md)
* [Benchmarks](getting_started/benchmarks.md)
* [Security and Cryptography](getting_started/security_and_cryptography.md)
* [Installation](getting\_started/installation.md)
* [Quick Start](getting\_started/quick\_start.md)
* [Supported Operations](getting\_started/operations.md)
* [Benchmarks](getting\_started/benchmarks.md)
* [Security and Cryptography](getting\_started/security\_and\_cryptography.md)
## Tutorials
* [Homomorphic Parity Bit](tutorials/parity_bit.md)
* [Homomorphic Case Changing on Latin String](tutorials/latin_fhe_string.md)
## High Level API
* [Tutorial](high_level_api/tutorial.md)
* [Operations](high_level_api/operations.md)
* [Serialization/Deserialization](high_level_api/serialization.md)
## How To
* [Configure Rust](how_to/rust_configuration.md)
* [Serialize/Deserialize](how_to/serialization.md)
* [Compress Ciphertexts/Keys](how_to/compress.md)
* [Use Public Key Encryption](how_to/public_key.md)
* [Use Trivial Ciphertext](how_to/trivial_ciphertext.md)
* [Use Parallelized PBS](how_to/parallelized_pbs.md)
* [Use the C API](how_to/c_api.md)
* [Use the JS on WASM API](how_to/js_on_wasm_api.md)
## Boolean
* [Tutorial](Boolean/tutorial.md)
* [Operations](Boolean/operations.md)
* [Cryptographic Parameters](Boolean/parameters.md)
* [Serialization/Deserialization](Boolean/serialization.md)
## Fine-grained APIs
* [Quick Start](fine_grained_api/quick_start.md)
* [Boolean](fine_grained_api/Boolean/tutorial.md)
* [Operations](fine_grained_api/Boolean/operations.md)
* [Cryptographic Parameters](fine_grained_api/Boolean/parameters.md)
* [Serialization/Deserialization](fine_grained_api/Boolean/serialization.md)
## Shortint
* [Tutorial](shortint/tutorial.md)
* [Operations](shortint/operations.md)
* [Cryptographic Parameters](shortint/parameters.md)
* [Serialization/Deserialization](shortint/serialization.md)
* [Shortint](fine_grained_api/shortint/tutorial.md)
* [Operations](fine_grained_api/shortint/operations.md)
* [Cryptographic Parameters](fine_grained_api/shortint/parameters.md)
* [Serialization/Deserialization](fine_grained_api/shortint/serialization.md)
## Integer
* [Tutorial](integer/tutorial.md)
* [Operations](integer/operations.md)
* [Cryptographic Parameters](integer/parameters.md)
* [Serialization/Deserialization](integer/serialization.md)
* [Integer](fine_grained_api/integer/tutorial.md)
* [Operations](fine_grained_api/integer/operations.md)
* [Cryptographic Parameters](fine_grained_api/integer/parameters.md)
* [Serialization/Deserialization](fine_grained_api/integer/serialization.md)
## C API
* [High-Level API](c_api/high-level-api.md)
* [Shortint API](c_api/shortint-api.md)
## Application Tutorials
* [SHA256 with *Boolean API*](application_tutorials/sha256_bool.md)
* [Dark Market with *Integer API*](application_tutorials/dark_market.md)
* [Homomorphic Regular Expressions *Integer API*](application_tutorials/regex.md)
## JS on WASM API
* [Tutorial](js_on_wasm_api/tutorial.md)
## Low-Level Core Cryptography
## Crypto Core API [Advanced users]
* [Quick Start](core_crypto/presentation.md)
* [Tutorial](core_crypto/tutorial.md)

View File

@@ -0,0 +1,479 @@
# Dark Market Tutorial
In this tutorial, we are going to build a dark market application using TFHE-rs. A dark market is a marketplace where
buy and sell orders are not visible to the public before they are filled. Different algorithms aim to
solve this problem, we are going to implement the algorithm defined [in this paper](https://eprint.iacr.org/2022/923.pdf) with TFHE-rs.
We will first implement the algorithm in plain Rust and then we will see how to use TFHE-rs to
implement the same algorithm with FHE.
In addition, we will also implement a modified version of the algorithm that allows for more concurrent operations which
improves the performance in hardware where there are multiple cores.
## Specifications
#### Inputs:
* A list of sell orders where each sell order is only defined in volume terms, it is assumed that the price is fetched
from a different source.
* A list of buy orders where each buy order is only defined in volume terms, it is assumed that the price is fetched
from a different source.
#### Input constraints:
* The sell and buy orders are within the range [1,100].
* The maximum number of sell and buy orders is 500, respectively.
#### Outputs:
There is no output returned at the end of the algorithm. Instead, the algorithm makes changes on the given input lists.
The number of filled orders is written over the original order count in the respective lists. If it is not possible to
fill the orders, the order count is set to zero.
#### Example input and output:
##### Example 1:
| | Sell | Buy |
|--------|--------------------|-----------|
| Input | [ 5, 12, 7, 4, 3 ] | [ 19, 2 ] |
| Output | [ 5, 12, 4, 0, 0 ] | [ 19, 2 ] |
Last three indices of the filled sell orders are zero because there is no buy orders to match them.
##### Example 2:
| | Sell | Buy |
|--------|-------------------|----------------------|
| Input | [ 3, 1, 1, 4, 2 ] | [ 5, 3, 3, 2, 4, 1 ] |
| Output | [ 3, 1, 1, 4, 2 ] | [ 5, 3, 3, 0, 0, 0 ] |
Last three indices of the filled buy orders are zero because there is no sell orders to match them.
## Plain Implementation
1. Calculate the total sell volume and the total buy volume.
```rust
let total_sell_volume: u16 = sell_orders.iter().sum();
let total_buy_volume: u16 = buy_orders.iter().sum();
```
2. Find the total volume that will be transacted. In the paper, this amount is calculated with the formula:
```
(total_sell_volume > total_buy_volume) * (total_buy_volume total_sell_volume) + total_sell_volume
```
When closely observed, we can see that this formula can be replaced with the `min` function. Therefore, we calculate this
value by taking the minimum of the total sell volume and the total buy volume.
```rust
let total_volume = std::cmp::min(total_buy_volume, total_sell_volume);
```
3. Beginning with the first item, start filling the sell orders one by one. We apply the `min` function replacement also
here.
```rust
let mut volume_left_to_transact = total_volume;
for sell_order in sell_orders.iter_mut() {
let filled_amount = std::cmp::min(volume_left_to_transact, *sell_order);
*sell_order = filled_amount;
volume_left_to_transact -= filled_amount;
}
```
The number of orders that are filled is indicated by modifying the input list. For example, if the first sell order is
1000 and the total volume is 500, then the first sell order will be modified to 500 and the second sell order will be
modified to 0.
4. Do the fill operation also for the buy orders.
```rust
let mut volume_left_to_transact = total_volume;
for buy_order in buy_orders.iter_mut() {
let filled_amount = std::cmp::min(volume_left_to_transact, *buy_order);
*buy_order = filled_amount;
volume_left_to_transact -= filled_amount;
}
```
#### The complete algorithm in plain Rust:
```rust
fn volume_match_plain(sell_orders: &mut Vec<u16>, buy_orders: &mut Vec<u16>) {
let total_sell_volume: u16 = sell_orders.iter().sum();
let total_buy_volume: u16 = buy_orders.iter().sum();
let total_volume = std::cmp::min(total_buy_volume, total_sell_volume);
let mut volume_left_to_transact = total_volume;
for sell_order in sell_orders.iter_mut() {
let filled_amount = std::cmp::min(volume_left_to_transact, *sell_order);
*sell_order = filled_amount;
volume_left_to_transact -= filled_amount;
}
let mut volume_left_to_transact = total_volume;
for buy_order in buy_orders.iter_mut() {
let filled_amount = std::cmp::min(volume_left_to_transact, *buy_order);
*buy_order = filled_amount;
volume_left_to_transact -= filled_amount;
}
}
```
## FHE Implementation
For the FHE implementation, we first start with finding the right bit size for our algorithm to work without
overflows.
The variables that are declared in the algorithm and their maximum values are described in the table below:
| Variable | Maximum Value | Bit Size |
|-------------------------|---------------|----------|
| total_sell_volume | 50000 | 16 |
| total_buy_volume | 50000 | 16 |
| total_volume | 50000 | 16 |
| volume_left_to_transact | 50000 | 16 |
| sell_order | 100 | 7 |
| buy_order | 100 | 7 |
As we can observe from the table, we need **16 bits of message space** to be able to run the algorithm without
overflows. TFHE-rs provides different presets for the different bit sizes. Since we need 16 bits of message, we are
going to use the `integer` module to implement the algorithm.
Here are the input types of our algorithm:
* `sell_orders` is of type `Vec<tfhe::integer::RadixCipherText>`
* `buy_orders` is of type `Vec<tfhe::integer::RadixCipherText>`
* `server_key` is of type `tfhe::integer::ServerKey`
Now, we can start implementing the algorithm with FHE:
1. Calculate the total sell volume and the total buy volume.
```rust
let mut total_sell_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for sell_order in sell_orders.iter_mut() {
server_key.smart_add_assign(&mut total_sell_volume, sell_order);
}
let mut total_buy_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for buy_order in buy_orders.iter_mut() {
server_key.smart_add_assign(&mut total_buy_volume, buy_order);
}
```
2. Find the total volume that will be transacted by taking the minimum of the total sell volume and the total buy
volume.
```rust
let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_volume);
```
3. Beginning with the first item, start filling the sell and buy orders one by one. We can create `fill_orders` closure to
reduce code duplication since the code for filling buy orders and sell orders are the same.
```rust
let fill_orders = |orders: &mut [RadixCiphertext]| {
let mut volume_left_to_transact = total_volume.clone();
for mut order in orders.iter_mut() {
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, &mut order);
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
};
fill_orders(sell_orders);
fill_orders(buy_orders);
```
#### The complete algorithm in TFHE-rs:
```rust
const NUMBER_OF_BLOCKS: usize = 8;
fn volume_match_fhe(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
let mut total_sell_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for sell_order in sell_orders.iter_mut() {
server_key.smart_add_assign(&mut total_sell_volume, sell_order);
}
let mut total_buy_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for buy_order in buy_orders.iter_mut() {
server_key.smart_add_assign(&mut total_buy_volume, buy_order);
}
let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_volume);
let fill_orders = |orders: &mut [RadixCiphertext]| {
let mut volume_left_to_transact = total_volume.clone();
for mut order in orders.iter_mut() {
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, &mut order);
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
};
fill_orders(sell_orders);
fill_orders(buy_orders);
}
```
### Optimizing the implementation
* TFHE-rs provides parallelized implementations of the operations. We can use these parallelized
implementations to speed up the algorithm. For example, we can use `smart_add_assign_parallelized` instead of
`smart_add_assign`.
* We can parallelize vector sum with Rayon and `reduce` operation.
```rust
let parallel_vector_sum = |vec: &mut [RadixCiphertext]| {
vec.to_vec().into_par_iter().reduce(
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
server_key.smart_add_parallelized(&mut acc, &mut ele)
},
)
};
```
* We can run vector summation on `buy_orders` and `sell_orders` in parallel since these operations do not depend on each other.
```rust
let (mut total_sell_volume, mut total_buy_volume) =
rayon::join(|| vector_sum(sell_orders), || vector_sum(buy_orders));
```
* We can match sell and buy orders in parallel since the matching does not depend on each other.
```rust
rayon::join(|| fill_orders(sell_orders), || fill_orders(buy_orders));
```
#### Optimized algorithm
```rust
fn volume_match_fhe_parallelized(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
let parallel_vector_sum = |vec: &mut [RadixCiphertext]| {
vec.to_vec().into_par_iter().reduce(
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
server_key.smart_add_parallelized(&mut acc, &mut ele)
},
)
};
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|| parallel_vector_sum(sell_orders),
|| parallel_vector_sum(buy_orders),
);
let total_volume =
server_key.smart_min_parallelized(&mut total_sell_volume, &mut total_buy_volume);
let fill_orders = |orders: &mut [RadixCiphertext]| {
let mut volume_left_to_transact = total_volume.clone();
for mut order in orders.iter_mut() {
let mut filled_amount =
server_key.smart_min_parallelized(&mut volume_left_to_transact, &mut order);
server_key
.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
*order = filled_amount;
}
};
rayon::join(|| fill_orders(sell_orders), || fill_orders(buy_orders));
}
```
## Modified Algorithm
When observed closely, there is only a small amount of concurrency introduced in the `fill_orders` part of the algorithm.
The reason is that the `volume_left_to_transact` is shared between all the orders and should be modified sequentially.
This means that the orders cannot be filled in parallel. If we can somehow remove this dependency, we can fill the orders in parallel.
In order to do so, we closely observe the function of `volume_left_to_transact` variable in the algorithm. We can see that it is being used to check whether we can fill the current order or not.
Instead of subtracting the current order value from `volume_left_to_transact` in each loop, we can add this value to the next order
index and check the availability by comparing the current order value with the total volume. If the current order value
(now representing the sum of values before this order plus this order) is smaller than the total number of matching orders,
we can safely fill all the orders and continue the loop. If not, we should partially fill the orders with what is left from
matching orders.
We will call the new list the "prefix sum" of the array.
The new version for the plain `fill_orders` is as follows:
```rust
let fill_orders = |orders: &mut [u64], prefix_sum: &[u64], total_orders: u64|{
orders.iter().for_each(|order : &mut u64| {
if (total_orders >= prefix_sum[i]) {
continue;
} else if total_orders >= prefix_sum.get(i-1).unwrap_or(0) {
*order = total_orders - prefix_sum.get(i-1).unwrap_or(0);
} else {
*order = 0;
}
});
};
```
To write this new function we need transform the conditional code into a mathematical expression since FHE does not support conditional operations.
```rust
let fill_orders = |orders: &mut [u64], prefix_sum: &[u64], total_orders: u64| {
orders.iter().for_each(|order| : &mut){
*order = *order + ((total_orders >= prefix_sum - std::cmp::min(total_orders, prefix_sum.get(i - 1).unwrap_or(&0).clone()) - *order);
}
};
```
New `fill_order` function requires a prefix sum array. We are going to calculate this prefix sum array in parallel
with the algorithm described [here](https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda).
The sample code in the paper is written in CUDA. When we try to implement the algorithm in Rust we see that the compiler does not allow us to do so.
The reason for that is while the algorithm does not access the same array element in any of the threads(the index calculations using `d` and `k` values never overlap),
Rust compiler cannot understand this and does not let us share the same array between threads.
So we modify how the algorithm is implemented, but we don't change the algorithm itself.
Here is the modified version of the algorithm in TFHE-rs:
```rust
fn volume_match_fhe_modified(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
let compute_prefix_sum = |arr: &[RadixCiphertext]| {
if arr.is_empty() {
return arr.to_vec();
}
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
.into_par_iter()
.map(|i| {
if i < arr.len() {
arr[i].clone()
} else {
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
}
})
.collect();
// Up sweep
for d in 0..(prefix_sum.len().ilog2() as u32) {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
});
}
// Down sweep
let last = prefix_sum.last().unwrap().clone();
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for d in (0..(prefix_sum.len().ilog2() as u32)).rev() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let t = chunk.last().unwrap().clone();
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left);
chunk[(length - 1) / 2] = t;
});
}
prefix_sum.push(last);
prefix_sum[1..=arr.len()].to_vec()
};
println!("Creating prefix sum arrays...");
let time = Instant::now();
let (prefix_sum_sell_orders, prefix_sum_buy_orders) = rayon::join(
|| compute_prefix_sum(sell_orders),
|| compute_prefix_sum(buy_orders),
);
println!("Created prefix sum arrays in {:?}", time.elapsed());
let fill_orders = |total_orders: &RadixCiphertext,
orders: &mut [RadixCiphertext],
prefix_sum_arr: &[RadixCiphertext]| {
orders
.into_par_iter()
.enumerate()
.for_each(move |(i, order)| {
server_key.smart_add_assign_parallelized(
order,
&mut server_key.smart_mul_parallelized(
&mut server_key
.smart_ge_parallelized(&mut order.clone(), &mut total_orders.clone()),
&mut server_key.smart_sub_parallelized(
&mut server_key.smart_sub_parallelized(
&mut total_orders.clone(),
&mut server_key.smart_min_parallelized(
&mut total_orders.clone(),
&mut prefix_sum_arr
.get(i - 1)
.unwrap_or(
&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
)
.clone(),
),
),
&mut order.clone(),
),
),
);
});
};
let total_buy_orders = &mut prefix_sum_buy_orders
.last()
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
.clone();
let total_sell_orders = &mut prefix_sum_sell_orders
.last()
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
.clone();
println!("Matching orders...");
let time = Instant::now();
rayon::join(
|| fill_orders(total_sell_orders, buy_orders, &prefix_sum_buy_orders),
|| fill_orders(total_buy_orders, sell_orders, &prefix_sum_sell_orders),
);
println!("Matched orders in {:?}", time.elapsed());
}
```
## Running the tutorial
The plain, FHE and parallel FHE implementations can be run by providing respective arguments as described below.
```bash
# Runs FHE implementation
cargo run --release --package tfhe --example dark_market --features="integer internal-keycache" -- fhe
# Runs parallelized FHE implementation
cargo run --release --package tfhe --example dark_market --features="integer internal-keycache" -- fhe-parallel
# Runs modified FHE implementation
cargo run --release --package tfhe --example dark_market --features="integer internal-keycache" -- fhe-modified
# Runs plain implementation
cargo run --release --package tfhe --example dark_market --features="integer internal-keycache" -- plain
# Multiple implementations can be run within same instance
cargo run --release --package tfhe --example dark_market --features="integer internal-keycache" -- plain fhe-parallel
```
## Conclusion
In this tutorial, we've learned how to implement the volume matching algorithm described [in this paper](https://eprint.iacr.org/2022/923.pdf) in plain Rust and in TFHE-rs.
We've identified the right bit size for our problem at hand, used operations defined in `TFHE-rs`, and introduced concurrency to the algorithm to increase its performance.

View File

@@ -0,0 +1,512 @@
# FHE Regex Pattern Matching Tutorial
This tutorial explains how to build a regex Pattern Matching Engine (PME) where ciphertext is the
content that is evaluated.
A regex PME is an essential tool for programmers. It allows you to perform complex searches on content.
A less powerful simple search on string can only find matches of the exact given sequence of
characters (e.g., your browser's default search function). Regex PMEs
are more powerful, allowing searches on certain structures of text, where a
structure may take any form in multiple possible sequences of characters. The
structure to be searched is defined with the regex, a very concise
language.
Here are some example regexes to give you an idea of what is possible:
Regex | Semantics
--- | ---
/abc/ | Searches for the sequence `abc` (equivalent to a simple text search)
/^abc/ | Searches for the sequence `abc` at the beginning of the content
/a?bc/ | Searches for sequences `abc`, `bc`
/ab\|c+d/ | Searches for sequences of `ab`, `c` repeated 1 or more times, followed by `d`
Regexes are powerful enough to be able to express structures like email address
formats. This capability is what makes regexes useful for many programming
solutions.
There are two main components identifiable in a PME:
1. The pattern that is to be matched has to be parsed, translated from a
textual representation into a recursively structured object (an Abstract
Syntax Tree, or AST).
2. This AST must then be applied to the text that it is to be matched against,
resulting in a 'yes' or 'no' to whether the pattern has matched (in the case of
our FHE implementation, this result is an encrypted 'yes' or an encrypted 'no').
Parsing is a well understood problem. There are a couple of different
approaches possible here. Regardless of the approach chosen, it starts with
figuring out what language we want to support. That is, what are
the kinds of sentences we want our regex language to include? A few
example sentences we definitely want to support are, for example: `/a/`,
`/a?bc/`, `/^ab$/`, `/ab|cd/`, however example sentences don't suffice as
a specification because they can never be exhaustive (they're endless). We need
something to specify _exactly_ the full set of sentences our language supports.
There exists a language that can help us describe our own language's structure exactly:
Grammar.
## The Grammar and datastructure
It is useful to start with defining the Grammar before starting to write
code for the parser because the code structure follows directly from the
Grammar. A Grammar consists of a generally small set of rules. For example,
a very basic Grammar could look like this:
```
Start := 'a'
```
This describes a language that only contains the sentence "a". Not a very interesting language.
We can make it more interesting though by introducing choice into the Grammar
with \| (called a 'pipe') operators. If we want the above Grammar to accept
either "a" or "b":
```
Start := 'a' | 'b'
```
So far, only Grammars with a single rule have been shown. However, a Grammar can
consist of multiple rules. Most languages require it. So let's consider a more meaningful language,
one that accepts sentences consisting of one or more digits. We could describe such a language
with the following Grammar:
```
Start := Digit+
Digit := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
```
The `+` after `Digit` is another Grammar operator. With it, we specify that
Digit must be matched one or more times. Here are all the Grammar operators that
are relevant for this tutorial:
Operator | Example | Semantics
--- | --- | ---
`\|` | a \| b | we first try matching on 'a' - if no match, we try to match on 'b'
`+` | a+ | match 'a' one or more times
`*` | a* | match 'a' any amount of times (including zero times)
`?` | a? | optionally match 'a' (match zero or one time)
`.` | . | match any character
`..` | a .. b | match on a range of alphabetically ordered characters from 'a', up to and including 'b'
` ` | a b | sequencing; match on 'a' and then on 'b'
In the case of the example PME, the Grammar is as follows (notice the unquoted ? and quoted ?, etc. The unquoted characters are Grammar operators, and the quoted are characters we are matching in the parsing).
```
Start := '/' '^'? Regex '$'? '/' Modifier?
Regex := Term '|' Term
| Term
Term := Factor*
Factor := Atom '?'
| Repeated
| Atom
Repeated := Atom '*'
| Atom '+'
| Atom '{' Digit* ','? '}'
| Atom '{' Digit+ ',' Digit* '}'
Atom := '.'
| '\' .
| Character
| '[' Range ']'
| '(' Regex ')'
Range := '^' Range
| AlphaNum '-' AlphaNum
| AlphaNum+
Digit := '0' .. '9'
Character := AlphaNum
| '&' | ';' | ':' | ',' | '`' | '~' | '-' | '_' | '!' | '@' | '#' | '%' | '\'' | '\"'
AlphaNum := 'a' .. 'z'
| 'A' .. 'Z'
| '0' .. '9'
Modifier := 'i'
```
We will refer occasionally to specific parts in the Grammar listed above by \<rule name\>.\<variant index\> (where the first rule variant has index 1).
With the Grammar defined, we can start defining a type to parse into. In Rust, we
have the `enum` kind of type that is perfect for this, as it allows you to define
multiple variants that may recurse. I prefer to start by defining variants that
do not recurse (i.e., that don't contain nested regex expressions):
```rust
enum RegExpr {
Char { c: char }, // matching against a single character (Atom.2 and Atom.3)
AnyChar, // matching _any_ character (Atom.1)
SOF, // matching only at the beginning of the content ('^' in Start.1)
EOF, // matching only at the end of the content (the '$' in Start.1)
Range { cs: Vec<char> }, // matching on a list of characters (Range.3, eg '[acd]')
Between { from: char, to: char }, // matching between 2 characters based on ascii ordering (Range.2, eg '[a-g]')
}
```
With this, we can translate the following basic regexes:
Pattern | RegExpr value
--- | ---
`/a/` | `RegExpr::Char { c: 'a' }`
`/\\^/` | `RegExpr::Char { c: '^' }`
`/./` | `RegExpr::AnyChar`
`/^/` | `RegExpr::SOF`
`/$/` | `RegExpr::EOF`
`/[acd]/` | `RegExpr::Range { vec!['a', 'c', 'd'] }`
`/[a-g]/` | `RegExpr::Between { from: 'a', to: 'g' }`
Notice we're not yet able to sequence multiple components together. Let's define
the first variant that captures recursive RegExpr for this:
```rust
enum RegExpr {
...
Seq { re_xs: Vec<RegExpr> }, // matching sequences of RegExpr components (Term.1)
}
```
With this Seq (short for sequence) variant, we allow translating patterns that
contain multiple components:
Pattern | RegExpr value
--- | ---
`/ab/` | `RegExpr::Seq { re_xs: vec![RegExpr::Char { c: 'a' }, RegExpr::Char { c: 'b' }] }`
`/^a.$/` | `RegExpr::Seq { re_xs: vec![RegExpr::SOF, RexExpr::Char { 'a' }, RegExpr::AnyChar, RegExpr::EOF] }`
`/a[f-l]/` | `RegExpr::Seq { re_xs: vec![RegExpr::Char { c: 'a' }, RegExpr::Between { from: 'f', to: 'l' }] }`
Let's finish the RegExpr datastructure by adding variants for 'Optional' matching,
'Not' logic in a range, and 'Either' left or right matching:
```rust
enum RegExpr {
...
Optional { opt_re: Box<RegExpr> }, // matching optionally (Factor.1)
Not { not_re: Box<RegExpr> }, // matching inversely on a range (Range.1)
Either { l_re: Box<RegExpr>, r_re: Box<RegExpr> }, // matching the left or right regex (Regex.1)
}
```
Some features may make the most sense being implemented during post-processing of
the parsed datastructure. For example, the case insensitivity feature (the `i`
Modifier) is implemented in the example implementation by taking the parsed
RegExpr and mutating every character mentioned inside to cover both the lower
case as well as the upper case variant (see function `case_insensitive` in
`parser.rs` for the example implementation).
The modifier `i` in our Grammar (for enabling case insensitivity) was easiest
to implement by applying a post-processing step to the parser.
We are now able to translate any complex regex into a RegExpr value. For example:
Pattern | RegExpr value
--- | ---
`/a?/` | `RegExpr::Optional { opt_re: Box::new(RegExpr::Char { c: 'a' }) }`
`/[a-d]?/` | `RegExpr::Optional { opt_re: Box::new(RegExpr::Between { from: 'a', to: 'd' }) }`
`/[^ab]/` | `RegExpr::Not { not_re: Box::new(RegExpr::Range { cs: vec!['a', 'b'] }) }`
`/av\|d?/` | `RegExpr::Either { l_re: Box::new(RegExpr::Seq { re_xs: vec![RegExpr::Char { c: 'a' }, RegExpr::Char { c: 'v' }] }), r_re: Box::new(RegExpr::Optional { opt_re: Box::new(RegExpr::Char { c: 'd' }) }) }`
`/(av\|d)?/` | `RegExpr::Optional { opt_re: Box::new(RegExpr::Either { l_re: Box::new(RegExpr::Seq { re_xs: vec![RegExpr::Char { c: 'a' }, RegExpr::Char { c: 'v' }] }), r_re: Box::new(RegExpr::Char { c: 'd' }) }) }`
With both the Grammar and the datastructure to parse into defined, we can now
start implementing the actual parsing logic. There are multiple ways this can
be done. For example, there exist tools that can automatically generate parser
code by giving it the Grammar definition (these are called parser generators).
However, you might prefer to write parsers with a parser combinator library.
This may be the better option for you because the behavior in runtime is easier to understand
for parsers constructed with a parser combinator library than of parsers that were
generated with a parser generator tool.
Rust offers a number of popular parser combinator libraries. This tutorial used
`combine`, but any other library would work just as well. Choose whichever appeals
the most to you (including any parser generator tool). The implementation of
our regex parser will differ significantly depending on the approach you choose,
so we will not cover this in detail here. You may look at the parser code in the example
implementation to get an idea of how this could be done. In general though, the Grammar and the
datastructure are the important components, while the parser code follows directly from these.
## Matching the RegExpr to encrypted content
The next challenge is to build the execution engine, where we take a RegExpr
value and recurse into it to apply the necessary actions on the encrypted
content. We first have to define how we actually encode our content into an
encrypted state. Once that is defined, we can start working on how we will
execute our RegExpr onto the encrypted content.
### Encoding and encrypting the content.
It is not possible to encrypt the entire content into a single encrypted value.
We can only encrypt numbers and perform operations on those encrypted numbers with
FHE. Therefore, we have to find a scheme where we encode the content into a
sequence of numbers that are then encrypted individually to form a sequence of
encrypted numbers.
We recommend the following two strategies:
1. to map each character of the content into the u8 ascii value, and then encrypt
each bit of these u8 values individually.
2. to, instead of encrypting each bit individually, encrypt each u8 ascii value in
its entirety.
Strategy 1 requires more high-level TFHE-rs operations to check for
a simple character match (we have to check each bit individually for
equality as opposed to checking the entire byte in one, high-level TFHE-rs
operation), though some experimentation did show that both options performed
equally well on a regex like `/a/`. This is likely because bitwise FHE
operations are relatively cheap compared to u8 FHE operations. However,
option 1 falls apart as soon as you introduce '[a-z]' regex logic.
With option 2, it is possible to complete this match with just three TFHE-rs
operations: `ge`, `le`, and `bitand`.
```rust
// note: this is pseudocode
c = <the encrypted character under inspection>;
sk = <the server key, aka the public key>
ge_from = sk.ge(c, 'a');
le_to = sk.le(c, 'z');
result = sk.bitand(ge_from, le_to);
```
If, on the other hand, we had encrypted the content with the first strategy,
there would be no way to test for `greater/equal than from` and `less/equal
than to`. We'd have to check for the potential equality of each character between
`from` and `to`, and then join the results together with a sequence of
`sk.bitor`; that would require far more cryptographic operations than in strategy 2.
Because FHE operations are computationally expensive, and strategy 1 requires
significantly more FHE operations for matching on `[a-z]` regex logic, we
should opt for strategy 2.
### Matching with the AST versus matching with a derived DFA.
There are a lot of regex PMEs. It's been built many times and it's been
researched thoroughly. There are different strategies possible here.
A straight forward strategy is to directly recurse into our RegExpr
value and apply the necessary matching operations onto the content. In a way,
this is nice because it allows us to link the RegExpr structure directly to
the matching semantics, resulting in code that is easier to
understand, maintain, etc.
Alternatively, there exists an algorithm that transforms the AST (i.e., the
RegExpr, in our case) into a Deterministic Finite Automata (DFA). Normally, this
is a favorable approach in terms of efficiency because the derived DFA can be
walked over without needing to backtrack (whereas the former strategy cannot
prevent backtracking). This means that the content can be walked over from
character to character, and depending on what the character is at this
cursor, the DFA is conjunctively traveled in a definite direction which
ultimately leads us to the `yes, there is a match` or the `no, there is no
match`. There is a small upfront cost of having to translate the AST into the
DFA, but the lack of backtracking during matching generally makes up for
this, especially if the content that it is matched against is significantly big.
In our case though, we are matching on encrypted content. We have no way to know
what the character at our cursor is, and therefore no way to find this definite
direction to go forward in the DFA. Therefore, translating the AST into the DFA does
not help us as it does in normal regex PMEs. For this reason, consider opting for the
former strategy because it allows for matching logic that is easier to understand.
### Matching.
In the previous section, we decided we'll match by traversing into the RegExpr
value. This section will explain exactly how to do that. Similarly to defining
the Grammar, it is often best to start with working out the non-recursive
RegExpr variants.
We'll start by defining the function that will recursively traverse into the RegExpr value:
```rust
type StringCiphertext = Vec<RadixCiphertext>;
type ResultCiphertext = RadixCiphertext;
fn match(
sk: &ServerKey,
content: &StringCipherText,
re: &RegExpr,
content_pos: usize,
) -> Vec<(ResultCiphertext, usize)> {
let content_char = &content[c_pos];
match re {
...
}
}
```
`sk` is the server key (aka, public key),`content` is what we'll be matching
against, `re` is the RegExpr value we built when parsing the regex, and `c_pos`
is the cursor position (the index in content we are currently matching
against).
The result is a vector of tuples, with the first value of the tuple being the computed
ciphertext result, and the second value being the content position after the
regex components were applied. It's a vector because certain RegExpr variants
require the consideration of a list of possible execution paths. For example,
RegExpr::Optional might succeed by applying _or_ and *not* applying the optional
regex (notice that in the former case, `c_pos` moves forward whereas in the
latter case it stays put).
On first call, a `match` of the entire regex pattern starts with `c_pos=0`.
Then `match` is called again for the entire regex pattern with `c_pos=1`, etc. until
`c_pos` exceeds the length of the content. Each of these alternative match results
are then joined together with `sk.bitor` operations (this works because if one of them results
in 'true' then, in general, our matching algorithm should return 'true').
The `...` within the match statement above is what we will be working out for
some of the RegExpr variants now. Starting with `RegExpr::Char`:
```rust
case RegExpr::Char { c } => {
vec![(sk.eq(content_char, c), c_pos + 1)]
},
```
Let's consider an example of the variant above. If we apply `/a/` to content
`bac`, we'll have the following list of `match` calls `re` and `c_pos` values
(for simplicity, `re` is denoted in regex pattern instead of in RegExpr value):
re | c\_pos | Ciphertext operation
--- | --- | ---
/a/ | 0 | sk.eq(content[0], a)
/a/ | 1 | sk.eq(content[1], a)
/a/ | 2 | sk.eq(content[2], a)
And we would arrive at the following sequence of ciphertext operations:
```
sk.bitor(sk.eq(content[0], a), sk.bitor(sk.eq(content[1], a), sk.eq(content[2], a)))
```
AnyChar is a no operation:
```rust
case RegExpr::AnyChar => {
// note: ct_true is just some constant representing True that is trivially encoded into ciphertext
return vec![(ct_true, c_pos + 1)];
}
```
The sequence iterates over its `re_xs`, increasing the content position
accordingly, and joins the results with `bitand` operations:
```rust
case RegExpr::Seq { re_xs } => {
re_xs.iter().fold(|prev_results, re_x| {
prev_results.iter().flat_map(|(prev_res, prev_c_pos)| {
(x_res, new_c_pos) = match(sk, content, re_x, prev_c_pos);
(sk.bitand(prev_res, x_res), new_c_pos)
})
}, (ct_true, c_pos))
},
```
Other variants are similar, as they recurse and manipulate `re` and `c_pos`
accordingly. Hopefully, the general idea is already clear.
Ultimately the entire pattern-matching logic unfolds into a sequence of
the following set of FHE operations:
1. eq (tests for an exact character match)
2. ge (tests for 'greater than' or 'equal to' a character)
3. le (tests for 'less than' or 'equal to' a character)
4. bitand (bitwise AND, used for sequencing multiple regex components)
5. bitor (bitwise OR, used for folding multiple possible execution variants'
results into a single result)
6. bitxor (bitwise XOR, used for the 'not' logic in ranges)
### Optimizations.
Generally, the included example PME follows the approach outlined above. However, there were
two additional optimizations applied. Both of these optimizations involved
reducing the number of unnecessary FHE operations. Given how computationally expensive
these operations are, it makes sense to optimize for this (and to ignore any suboptimal
memory usage of our PME, etc.).
The first optimization involved delaying the execution of FHE operations to _after_
the generation of all possible execution paths to be considered. This optimization
allows us to prune execution paths during execution path construction that are provably
going to result in an encrypted false value, without having already performed the FHE
operations up to the point of pruning. Consider the regex `/^a+b$/`, and we are applying
this to a content of size 4. If we are executing execution paths naively, we would go ahead
and check for all possible amounts of `a` repetitions: `ab`, `aab`, `aaab`.
However, while building the execution paths, we can use the fact that `a+` must
begin at the beginning of the content, and that `b` must be the final character
of the content. From this follows that we only have to check for the following
sentence: `aaab`. Delaying execution of the FHE operations until after we've
built the possible execution paths in this example reduced the number of FHE
operations applied by approximately half.
The second optimization involved preventing the same FHE conditions to be
re-evaluated. Consider the regex `/^a?ab/`. This would give us the following
possible execution paths to consider:
1. `content[0] == a && content[1] == a && content[2] == b` (we match the `a` in
`a?`)
2. `content[0] == a && content[1] == b` (we don't match the `a` in `a?`)
Notice that, for both execution paths, we are checking for `content[0] == a`.
Even though we cannot see what the encrypted result is, we do know that it's
either going to be an encrypted false for both cases or an encrypted true for
both cases. Therefore, we can skip the re-evaluation of `content[0] == a` and
simply copy the result from the first evaluation over. This optimization
involves maintaining a cache of known expression evaluation results and
reusing those where possible.
## Trying out the example implementation
The implementation that guided the writing of this tutorial can be found
under `tfhe/examples/regex_engine`.
When compiling with `--example regex_engine`, a binary is produced that serves
as a basic demo. Simply call it with the content string as a first argument and
the pattern string as a second argument. For example,
`cargo run --release --features=x86_64-unix,integer --example regex_engine -- 'this is the content' '/^pattern$/'`;
note it's advised to compile the executable with `--release` flag as the key
generation and homomorphic operations otherwise seem to experience a heavy
performance penalty.
On execution, a private and public key pair are created. Then, the content is
encrypted with the client key, and the regex pattern is applied onto the
encrypted content string - with access given only to the server key. Finally, it
decrypts the resulting encrypted result using the client key and prints the
verdict to the console.
To get more information on exact computations and performance, set the `RUST_LOG`
environment variable to `debug` or to `trace`.
### Supported regex patterns
This section specifies the supported set of regex patterns in the regex engine.
#### Components
A regex is described by a sequence of components surrounded by `/`, the
following components are supported:
Name | Notation | Examples
--- | --- | ---
Character | Simply the character itself | `/a/`, `/b/`, `/Z/`, `/5/`
Character range | `[<character>-<character]` | `/[a-d]/`, `/[C-H]`/
Any character | `.` | `/a.c/`
Escaped symbol | `\<symbol>` | `/\^/`, `/\$/`
Parenthesis | `(<regex>)` | `/(abc)*/`, `/d(ab)?/`
Optional | `<regex>?` | `/a?/`, `/(az)?/`
Zero or more | `<regex>*` | `/a*/`, `/ab*c/`
One or more | `<regex>+` | `/a+/`, `/ab+c/`
Exact repeat | `<regex{<number>}>` | `/ab{2}c/`
At least repeat | `<regex{<number>,}>` | `/ab{2,}c/`
At most repeat | `<regex{,<number>}>` | `/ab{,2}c/`
Repeat between | `<regex{<number>,<number>}>` | `/ab{2,4}c/`
Either | `<regex>\|<regex>` | `/a\|b/`, `/ab\|cd/`
Start matching | `/^<regex>` | `/^abc/`
End matching | `<regex>$/` | `/abc$/`
#### Modifiers
Modifiers are mode selectors that affect the entire regex behavior. One modifier is
currently supported:
- Case insensitive matching, by appending an `i` after the regex pattern. For example: `/abc/i`
#### General examples
These components and modifiers can be combined to form any desired regex
pattern. To give some idea of what is possible, here is a non-exhaustive list of
supported regex patterns:
Pattern | Description
--- | ---
`/^abc$/` | Matches with content that equals exactly `abc` (case sensitive)
`/^abc$/i` | Matches with content that equals `abc` (case insensitive)
`/abc/` | Matches with content that contains somewhere `abc`
`/ab?c/` | Matches with content that contains somewhere `abc` or somwhere `ab`
`/^ab*c$/` | For example, matches with: `ac`, `abc`, `abbbbc`
`/^[a-c]b\|cd$/` | Matches with: `ab`, `bb`, `cb`, `cd`
`/^[a-c]b\|cd$/i` | Matches with: `ab`, `Ab`, `aB`, ..., `cD`, `CD`
`/^d(abc)+d$/` | For example, matches with: `dabcd`, `dabcabcd`, `dabcabcabcd`
`/^a.*d$/` | Matches with any content that starts with `a` and ends with `d`

View File

@@ -0,0 +1,322 @@
# Tutorial
## Intro
In this tutorial we will go through the steps to turn a regular sha256 implementation into its homomorphic version. We explain the basics of the sha256 function first, and then how to implement it homomorphically with performance considerations.
## Sha256
The first step in this experiment is actually implementing the sha256 function. We can find the specification [here](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf), but let's summarize the three main sections of the document.
#### Padding
The sha256 function processes the input data in blocks or chunks of 512 bits. Before actually performing the hash computations we have to pad the input in the following way:
* Append a single "1" bit
* Append a number of "0" bits such that exactly 64 bits are left to make the message length a multiple of 512
* Append the last 64 bits as a binary encoding of the original input length
Or visually:
```
0 L L+1 L+1+k L+1+k+64
|-----------------------------------|---|--------------------------------|----------------------|
Original input (L bits) "1" bit "0" bits Encoding of the number L
```
Where the numbers on the top represent the length of the padded input at each position, and L+1+k+64 is a multiple of 512 (the length of the padded input).
#### Operations and functions
Let's take a look at the operations that we will use as building blocks for functions inside the sha256 computation. These are bitwise AND, XOR, NOT, addition modulo 2^32 and the Rotate Right (ROTR) and Shift Right (SHR) operations, all working with 32-bit words and producing a new word.
We combine these operations inside the sigma (with 4 variations), Ch and Maj functions. At the end of the day, when we change the sha256 to be computed homomorphically, we will mainly change the isolated code of each operation.
Here is the definition of each function:
```
Ch(x, y, z) = (x AND y) XOR ((NOT x) AND z)
Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z)
Σ0(x) = ROTR-2(x) XOR ROTR-13(x) XOR ROTR-22(x)
Σ1(x) = ROTR-6(x) XOR ROTR-11(x) XOR ROTR-25(x)
σ0(x) = ROTR-7(x) XOR ROTR-18(x) XOR SHR-3(x)
σ1(x) = ROTR-17(x) XOR ROTR-19(x) XOR SHR-10(x)
```
There are some things to note about the functions. Firstly we see that Maj can be simplified by applying the boolean distributive law (x AND y) XOR (x AND z) = x AND (y XOR z). So the new Maj function looks like this:
```
Maj(x, y, z) = (x AND (y XOR z)) XOR (y AND z)
```
Next we can also see that Ch can be simplified by using a single bitwise multiplexer. Let's take a look at the truth table of the Ch expression.
| x | y | z | Result |
| - | - | - | ------ |
| 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 |
| 0 | 1 | 0 | 0 |
| 0 | 1 | 1 | 1 |
| 1 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
| 1 | 1 | 1 | 1 |
When ```x = 0``` the result is identical to ```z```, but when ```x = 1``` the result is identical to ```y```. This is the same as saying ```if x {y} else {z}```. Hence we can replace the 4 bitwise operations of Ch by a single bitwise multiplexer.
Note that all these operations can be evaluated homomorphically. ROTR and SHR can be evaluated by changing the index of each individual bit of the word, even if each bit is encrypted, without using any homomorphic operation. Bitwise AND, XOR and multiplexer can be computed homomorphically and addition modulo 2^32 can be broken down into boolean homomorphic operations as well.
#### Sha256 computation
As we have mentioned, the sha256 function works with chunks of 512 bits. For each chunk, we will compute 64 32-bit words. 16 will come from the 512 bits and the rest will be computed using the previous functions. After computing the 64 words, and still within the same chunk iteration, a compression loop will compute a hash value (8 32-bit words), again using the previous functions and some constants to mix everything up. When we finish the last chunk iteration, the resulting hash values will be the output of the sha256 function.
Here is how this function looks like using arrays of 32 bools to represent words:
```rust
fn sha256(padded_input: Vec<bool>) -> [bool; 256] {
// Initialize hash values with constant values
let mut hash: [[bool; 32]; 8] = [
hex_to_bools(0x6a09e667), hex_to_bools(0xbb67ae85),
hex_to_bools(0x3c6ef372), hex_to_bools(0xa54ff53a),
hex_to_bools(0x510e527f), hex_to_bools(0x9b05688c),
hex_to_bools(0x1f83d9ab), hex_to_bools(0x5be0cd19),
];
let chunks = padded_input.chunks(512);
for chunk in chunks {
let mut w = [[false; 32]; 64];
// Copy first 16 words from current chunk
for i in 0..16 {
w[i].copy_from_slice(&chunk[i * 32..(i + 1) * 32]);
}
// Compute the other 48 words
for i in 16..64 {
w[i] = add(add(add(sigma1(&w[i - 2]), w[i - 7]), sigma0(&w[i - 15])), w[i - 16]);
}
let mut a = hash[0];
let mut b = hash[1];
let mut c = hash[2];
let mut d = hash[3];
let mut e = hash[4];
let mut f = hash[5];
let mut g = hash[6];
let mut h = hash[7];
// Compression loop, each iteration uses a specific constant from K
for i in 0..64 {
let temp1 = add(add(add(add(h, ch(&e, &f, &g)), w[i]), hex_to_bools(K[i])), sigma_upper_case_1(&e));
let temp2 = add(sigma_upper_case_0(&a), maj(&a, &b, &c));
h = g;
g = f;
f = e;
e = add(d, temp1);
d = c;
c = b;
b = a;
a = add(temp1, temp2);
}
hash[0] = add(hash[0], a);
hash[1] = add(hash[1], b);
hash[2] = add(hash[2], c);
hash[3] = add(hash[3], d);
hash[4] = add(hash[4], e);
hash[5] = add(hash[5], f);
hash[6] = add(hash[6], g);
hash[7] = add(hash[7], h);
}
// Concatenate the final hash values to produce a 256-bit hash
let mut output = [false; 256];
for i in 0..8 {
output[i * 32..(i + 1) * 32].copy_from_slice(&hash[i]);
}
output
}
```
## Making it homomorphic
The key idea is that we can replace each bit of ```padded_input``` with a Fully Homomorphic Encryption of the same bit value, and operate over the encrypted values using homomorphic operations. To achieve this we need to change the function signatures and deal with the borrowing rules of the Ciphertext type (which represents an encrypted bit) but the structure of the sha256 function remains the same. The part of the code that requires more consideration is the implementation of the sha256 operations, since they will use homomorphic boolean operations internally.
Homomorphic operations are really expensive, so we have to remove their unnecessary use and maximize parallelization in order to speed up the program. To simplify our code we use the Rayon crate which provides parallel iterators and efficiently manages threads. Let's now take a look at each sha256 operation!
#### Rotate Right and Shift Right
As we have highlighted, these two operations can be evaluated by changing the position of each encrypted bit in the word, thereby requiring 0 homomorphic operations. Here is our implementation:
```rust
fn rotate_right(x: &[Ciphertext; 32], n: usize) -> [Ciphertext; 32] {
let mut result = x.clone();
result.rotate_right(n);
result
}
fn shift_right(x: &[Ciphertext; 32], n: usize, sk: &ServerKey) -> [Ciphertext; 32] {
let mut result = x.clone();
result.rotate_right(n);
result[..n].fill_with(|| sk.trivial_encrypt(false));
result
}
```
#### Bitwise XOR, AND, Multiplexer
To implement these operations we will use the ```xor```, ```and``` and ```mux``` methods provided by the tfhe library to evaluate each boolean operation homomorphically. It's important to note that, since we will operate bitwise, we can parallelize the homomorphic computations. In other words, we can homomorphically XOR the bits at index 0 of two words using a thread, while XORing the bits at index 1 using another thread, and so on. This means we could compute these bitwise operations using up to 32 concurrent threads (since we work with 32-bit words).
Here is our implementation of the bitwise homomorphic XOR operation. The ```par_iter``` and ```par_iter_mut``` methods create a parallel iterator that we use to compute each individual XOR efficiently. The other two bitwise operations are implemented in the same way.
```rust
fn xor(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
let mut result = a.clone();
result.par_iter_mut()
.zip(a.par_iter().zip(b.par_iter()))
.for_each(|(dst, (lhs, rhs))| *dst = sk.xor(lhs, rhs));
result
}
```
#### Addition modulo 2^32
This is perhaps the trickiest operation to efficiently implement in a homomorphic fashion. A naive implementation could use the Ripple Carry Adder algorithm, which is straightforward but cannot be parallelized because each step depends on the previous one.
A better choice would be the Carry Lookahead Adder, which allows us to use the parallelized AND and XOR bitwise operations. With this design, our adder is around 50% faster than the Ripple Carry Adder.
```rust
pub fn add(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
let propagate = xor(a, b, sk); // Parallelized bitwise XOR
let generate = and(a, b, sk); // Parallelized bitwise AND
let carry = compute_carry(&propagate, &generate, sk);
let sum = xor(&propagate, &carry, sk); // Parallelized bitwise XOR
sum
}
fn compute_carry(propagate: &[Ciphertext; 32], generate: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
let mut carry = trivial_bools(&[false; 32], sk);
carry[31] = sk.trivial_encrypt(false);
for i in (0..31).rev() {
carry[i] = sk.or(&generate[i + 1], &sk.and(&propagate[i + 1], &carry[i + 1]));
}
carry
}
```
To even improve performance more, the function that computes the carry signals can also be parallelized using parallel prefix algorithms. These algorithms involve more boolean operations (so homomorphic operations for us) but may be faster because of their parallel nature. We have implemented the Brent-Kung and Ladner-Fischer algorithms, which entail different tradeoffs.
Brent-Kung has the least amount of boolean operations we could find (140 when using grey cells, for 32-bit numbers), which makes it suitable when we can't process many operations concurrently and fast. Our results confirm that it's indeed faster than both the sequential algorithm and Ladner-Fischer when run on regular computers.
On the other hand, Ladner-Fischer performs more boolean operations (209 using grey cells) than Brent-Kung, but they are performed in larger batches. Hence we can compute more operations in parallel and finish earlier, but we need more fast threads available or they will slow down the carry signals computation. Ladner-Fischer can be suitable when using cloud-based computing services, which offer many high-speed threads.
Our implementation uses Brent-Kung by default, but Ladner-Fischer can be enabled when needed by using the ```--ladner-fischer``` command line argument.
For more information about parallel prefix adders you can read [this paper](https://www.iosrjournals.org/iosr-jece/papers/Vol6-Issue1/A0610106.pdf) or [this other paper](https://www.ijert.org/research/design-and-implementation-of-parallel-prefix-adder-for-improving-the-performance-of-carry-lookahead-adder-IJERTV4IS120608.pdf).
Finally, with all these sha256 operations working homomorphically, our functions will be homomomorphic as well along with the whole sha256 function (after adapting the code to work with the Ciphertext type). Let's talk about other performance improvements we can make before we finish.
### More parallel processing
If we inspect the main ```sha256_fhe``` function, we will find operations that can be performed in parallel. For instance, within the compression loop, ```temp1``` and ```temp2``` can be computed concurrently. An efficient way to parallelize computations here is using the ```rayon::join()``` function, which uses parallel processing only when there are available CPUs. Recall that the two temporary values in the compression loop are the result of several additions, so we can use nested calls to ```rayon::join()``` to potentially parallelize more operations.
Another way to speed up consecutive additions would be using the Carry Save Adder, a very efficient adder that takes 3 numbers and returns a sum and carry sequence. If our inputs are A, B and C, we can construct a CSA with our previously implemented Maj function and the bitwise XOR operation as follows:
```
Carry = Maj(A, B, C)
Sum = A XOR B XOR C
```
By chaining CSAs, we can input the sum and carry from a preceding stage along with another number into a new CSA. Finally, to get the result of the additions we add the sum and carry sequences using a conventional adder. At the end we are performing the same number of additions, but some of them are now CSAs, speeding up the process. Let's see all this together in the ```temp1``` and ```temp2``` computations.
```rust
let (temp1, temp2) = rayon::join(
|| {
let ((sum, carry), s1) = rayon::join(
|| {
let ((sum, carry), ch) = rayon::join(
|| csa(&h, &w[i], &trivial_bools(&hex_to_bools(K[i]), sk), sk),
|| ch(&e, &f, &g, sk),
);
csa(&sum, &carry, &ch, sk)
},
|| sigma_upper_case_1(&e, sk)
);
let (sum, carry) = csa(&sum, &carry, &s1, sk);
add(&sum, &carry, sk)
},
|| {
add(&sigma_upper_case_0(&a, sk), &maj(&a, &b, &c, sk), sk)
},
);
```
The first closure of the outer call to join will return ```temp1``` and the second ```temp2```. Inside the first outer closure we call join recursively until we reach the addition of the value ```h```, the current word ```w[i]``` and the current constant ```K[i]``` by using the CSA, while potentially computing in parallel the ```ch``` function. Then we take the sum, carry and ch values and add them again using the CSA.
All this is done while potentially computing the ```sigma_upper_case_1``` function. Finally we input the previous sum, carry and sigma values to the CSA and perform the final addition with ```add```. Once again, this is done while potentially computing ```sigma_upper_case_0``` and ```maj``` and adding them to get ```temp2```, in the second outer closure.
With some changes of this type, we finally get a homomorphic sha256 function that doesn't leave unused computational resources.
## How to use sha256_bool
First of all, the most important thing when running the program is using the ```--release``` flag. The use of sha256_bool would look like this, given the implementation of ```encrypt_bools``` and ```decrypt_bools```:
```rust
fn main() {
let matches = Command::new("Homomorphic sha256")
.arg(Arg::new("ladner_fischer")
.long("ladner-fischer")
.help("Use the Ladner Fischer parallel prefix algorithm for additions")
.action(ArgAction::SetTrue))
.get_matches();
// If set using the command line flag "--ladner-fischer" this algorithm will be used in additions
let ladner_fischer: bool = matches.get_flag("ladner_fischer");
// INTRODUCE INPUT FROM STDIN
let mut input = String::new();
println!("Write input to hash:");
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
input = input.trim_end_matches('\n').to_string();
println!("You entered: \"{}\"", input);
// CLIENT PADS DATA AND ENCRYPTS IT
let (ck, sk) = gen_keys();
let padded_input = pad_sha256_input(&input);
let encrypted_input = encrypt_bools(&padded_input, &ck);
// SERVER COMPUTES OVER THE ENCRYPTED PADDED DATA
println!("Computing the hash");
let encrypted_output = sha256_fhe(encrypted_input, ladner_fischer, &sk);
// CLIENT DECRYPTS THE OUTPUT
let output = decrypt_bools(&encrypted_output, &ck);
let outhex = bools_to_hex(output);
println!("{}", outhex);
}
```
By using ```stdin``` we can supply the data to hash using a file instead of the command line. For example, if our file ```input.txt``` is in the same directory as the project, we can use the following shell command after building with ```cargo build --release```:
```sh
./target/release/examples/sha256_bool < input.txt
```
Our implementation also accepts hexadecimal inputs. To be considered as such, the input must start with "0x" and contain only valid hex digits (otherwise it's interpreted as text).
Finally see that padding is executed on the client side. This has the advantage of hiding the exact length of the input to the server, who already doesn't know anything about the contents of it but may extract information from the length.
Another option would be to perform padding on the server side. The padding function would receive the encrypted input and pad it with trivial bit encryptions. We could then integrate the padding function inside the ```sha256_fhe``` function computed by the server.

View File

@@ -1,172 +0,0 @@
# Shortint API
## Using the shortint C API
This library exposes a C binding to the TFHE-rs shortint API to implement _Fully Homomorphic Encryption_ (FHE) programs.
## First steps using TFHE-rs C API
### Setting up TFHE-rs C API for use in a C program.
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo +nightly build --release --features=x86_64-unix,boolean-c-api,shortint-c-api -p tfhe
```
or on a Unix aarch64 machine using the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo build +nightly --release --features=aarch64-unix,boolean-c-api,shortint-c-api -p tfhe
```
All features are opt-in, but for simplicity here, the C API is enabled for Boolean and shortint.
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO\_ROOT}/target/release/"
The build system needs to be set up so that the C or C++ program links against TFHE-rs C API binaries.
Here is a minimal CMakeLists.txt to do just that:
```cmake
project(my-project)
cmake_minimum_required(VERSION 3.16)
set(TFHE_C_API "/path/to/tfhe-rs/binaries/and/header")
include_directories(${TFHE_C_API})
add_library(tfhe STATIC IMPORTED)
set_target_properties(tfhe PROPERTIES IMPORTED_LOCATION ${TFHE_C_API}/libtfhe.a)
if(APPLE)
find_library(SECURITY_FRAMEWORK Security)
if (NOT SECURITY_FRAMEWORK)
message(FATAL_ERROR "Security framework not found")
endif()
endif()
set(EXECUTABLE_NAME my-executable)
add_executable(${EXECUTABLE_NAME} main.c)
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC tfhe m pthread dl)
if(APPLE)
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC ${SECURITY_FRAMEWORK})
endif()
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
```
### Commented code of a PBS doubling a 2-bits encrypted message using `TFHE-rs C API`.
The steps required to perform the multiplication by 2 of a 2-bits ciphertext using a PBS are detailed. This is NOT the most efficient way of doing this operation, but it can help to show the management required to run a PBS manually using the C API.
WARNING: The following example does not have proper memory management in the error case to make it easier to fit the code on this page.
To run the example below, the above CMakeLists.txt and main.c files need to be in the same directory. The commands to run are:
```shell
# /!\ Be sure to update CMakeLists.txt to give the absolute path to the compiled tfhe library
$ ls
CMakeLists.txt main.c
$ mkdir build && cd build
$ cmake .. -DCMAKE_BUILD_TYPE=RELEASE
...
$ make
...
$ ./my-executable
Result: 2
$
```
```c
#include "tfhe.h"
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
uint64_t double_accumulator_2_bits_message(uint64_t in) { return (in * 2) % 4; }
uint64_t get_max_value_of_accumulator_generator(uint64_t (*accumulator_func)(uint64_t),
size_t message_bits)
{
uint64_t max_value = 0;
for (size_t idx = 0; idx < (1 << message_bits); ++idx)
{
uint64_t acc_value = accumulator_func((uint64_t)idx);
max_value = acc_value > max_value ? acc_value : max_value;
}
return max_value;
}
int main(void)
{
ShortintPBSLookupTable *accumulator = NULL;
ShortintClientKey *cks = NULL;
ShortintServerKey *sks = NULL;
ShortintParameters *params = NULL;
// Get the parameters for 2 bits messages with 2 bits of carry
int get_params_ok = shortint_get_parameters(2, 2, &params);
assert(get_params_ok == 0);
// Generate the keys with the parameters
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
assert(gen_keys_ok == 0);
// Generate the accumulator for the PBS
int gen_acc_ok = shortint_server_key_generate_pbs_accumulator(
sks, double_accumulator_2_bits_message, &accumulator);
assert(gen_acc_ok == 0);
ShortintCiphertext *ct = NULL;
ShortintCiphertext *ct_out = NULL;
// We will compute 1 * 2 using a PBS, it's not the recommended way to perform a multiplication,
// but it shows how to manage a PBS manually in the C API
uint64_t in_val = 1;
// Encrypt the input value
int encrypt_ok = shortint_client_key_encrypt(cks, in_val, &ct);
assert(encrypt_ok == 0);
// Check the degree is set to the maximum value that can be encrypted on 2 bits, i.e. 3
// This check is not required and is just added to show, the degree information can be retrieved
// in the C APi
size_t degree = -1;
int get_degree_ok = shortint_ciphertext_get_degree(ct, &degree);
assert(get_degree_ok == 0);
assert(degree == 3);
// Apply the PBS on our encrypted input
int pbs_ok = shortint_server_key_programmable_bootstrap(sks, accumulator, ct, &ct_out);
assert(pbs_ok == 0);
// Set the degree to keep consistency for potential further computations
// Note: This is only required for the PBS
size_t degree_to_set =
(size_t)get_max_value_of_accumulator_generator(double_accumulator_2_bits_message, 2);
int set_degree_ok = shortint_ciphertext_set_degree(ct_out, degree_to_set);
assert(set_degree_ok == 0);
// Decrypt the result
uint64_t result = -1;
int decrypt_non_assign_ok = shortint_client_key_decrypt(cks, ct_out, &result);
assert(decrypt_non_assign_ok == 0);
// Check the result is what we expect i.e. 2
assert(result == double_accumulator_2_bits_message(in_val));
printf("Result: %ld\n", result);
// Destroy entities from the C API
destroy_shortint_ciphertext(ct);
destroy_shortint_ciphertext(ct_out);
destroy_shortint_pbs_accumulator(accumulator);
destroy_shortint_client_key(cks);
destroy_shortint_server_key(sks);
destroy_shortint_parameters(params);
return EXIT_SUCCESS;
}
```

View File

@@ -1,12 +1,12 @@
# Quick Start
The `core_crypto` module from TFHE-rs is dedicated to the implementation of the cryptographic tools related to TFHE. To construct an FHE application, the [shortint](../shortint/tutorial.md) and/or [Boolean](../Boolean/tutorial.md) modules (based on this one) are recommended.
The `core_crypto` module from `TFHE-rs` is dedicated to the implementation of the cryptographic tools related to TFHE. To construct an FHE application, the [shortint](../fine_grained_api/shortint/tutorial.md) and/or [Boolean](../fine_grained_api/Boolean/tutorial.md) modules (based on `core_crypto`) are recommended.
The `core_crypto` module offers an API to low-level cryptographic primitives and objects, like `lwe_encryption` or `rlwe_ciphertext`. Its goal is to propose an easy-to-use API for cryptographers.
The `core_crypto` module offers an API to low-level cryptographic primitives and objects, like `lwe_encryption` or `rlwe_ciphertext`. The goal is to propose an easy-to-use API for cryptographers.
The overall code architecture is split in two parts: one for entity definitions and another focused on algorithms. The entities contain the definition of useful types, like LWE ciphertext or bootstrapping keys. The algorithms are then naturally defined to work using these entities.
The API is convenient to easily add or modify existing algorithms or to have direct access to the raw data. Even if the LWE ciphertext object is defined along with functions giving access to the body, this is also possible to bypass these to get directly the $$i^{th}$$ element of LWE mask.
The API is convenient to add or modify existing algorithms, or to have direct access to the raw data. Even if the LWE ciphertext object is defined, along with functions giving access to the body, it is also possible to bypass these to get directly the $$i^{th}$$ element of LWE mask.
For instance, the code to encrypt and then decrypt a message looks like:

View File

@@ -2,39 +2,39 @@
## Using the `core_crypto` primitives
Welcome to this tutorial about TFHE-rs `core_crypto` module.
Welcome to this tutorial about `TFHE-rs` `core_crypto` module.
### Setting up TFHE-rs to use the `core_crypto` module
To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
To use `TFHE-rs`, it first has to be added as a dependency in the `Cargo.toml`:
```toml
tfhe = { version = "0.2.0", features = [ "x86_64-unix" ] }
tfhe = { version = "0.3.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.
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.
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically\_secure\_pseudorandom\_number\_generator) used in TFHE-rs.
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically\_secure\_pseudorandom\_number\_generator) used in `TFHE-rs`.
In short: For x86\_64-based machines running Unix-like OSes:
In short: For `x86_64`-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.2.0", features = ["x86_64-unix"] }
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
```
For Apple Silicon or aarch64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.2.0", features = ["aarch64-unix"] }
tfhe = { version = "0.3.0", features = ["aarch64-unix"] }
```
For x86\_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
For `x86_64`-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
```toml
tfhe = { version = "0.2.0", features = ["x86_64"] }
tfhe = { version = "0.3.0", features = ["x86_64"] }
```
### Commented code to double a 2-bits message in a leveled fashion and using a PBS with the `core_crypto` module.
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.
As a complete example showing the usage of some common primitives of the `core_crypto` APIs, the following Rust code homomorphically computes 2 \* 3 using two different methods. First using a cleartext multiplication and then using a PBS.
@@ -239,12 +239,12 @@ pub fn main() {
);
// Decrypt the PBS multiplication result
let pbs_multipliation_plaintext: Plaintext<u64> =
let pbs_multiplication_plaintext: Plaintext<u64> =
decrypt_lwe_ciphertext(&big_lwe_sk, &pbs_multiplication_ct);
// Round and remove our encoding
let pbs_multiplication_result: u64 =
signed_decomposer.closest_representable(pbs_multipliation_plaintext.0) / delta;
signed_decomposer.closest_representable(pbs_multiplication_plaintext.0) / delta;
println!("Checking result...");
assert_eq!(6, pbs_multiplication_result);

View File

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

View File

@@ -1,6 +1,7 @@
# Operations
# Boolean Operations
This contains the operations available in tfhe::boolean, along with code examples.
In tfhe::boolean, the available operations are mainly related to their equivalent Boolean gates (i.e., AND, OR... etc). What follows are examples of a unary gate (NOT) and a binary gate (XOR). The last one is about the ternary MUX gate, which allows homomorphic computation of conditional statements of the form `If..Then..Else`.
## The NOT unary gate

View File

@@ -8,7 +8,7 @@ Some cryptographic parameters will require tuning to ensure both the correctness
To make it simpler, **we've provided two sets of parameters**, which ensure correct computations for a certain probability with the standard security of 128 bits. There exists an error probability due to the probabilistic nature of the encryption, which requires adding randomness (noise) following a Gaussian distribution. If this noise is too large, the decryption will not give a correct result. There is a trade-off between efficiency and correctness: generally, using a less efficient parameter set (in terms of computation time) leads to a smaller risk of having an error during homomorphic evaluation.
In the two proposed sets of parameters, the only difference lies in this probability error. The default parameter set ensures a probability error of at most $$2^{-40}$$ when computing a programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error probability claimed in the original [TFHE paper](https://eprint.iacr.org/2018/421), namely $$2^{-165}$$, but it is up-to-date regarding security requirements.
In the two proposed sets of parameters, the only difference lies in this error probability. The default parameter set ensures an error probability of at most $$2^{-40}$$ when computing a programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error probability claimed in the original [TFHE paper](https://eprint.iacr.org/2018/421), namely $$2^{-165}$$, but it is up-to-date regarding security requirements.
The following array summarizes this:

View File

@@ -1,5 +1,8 @@
# Tutorial
In `tfhe::boolean`, the available operations are mainly related to their equivalent Boolean gates (i.e., AND, OR... etc). What follows are examples of a unary gate (NOT) and a binary gate (XOR). The last one is about the ternary MUX gate, which allows homomorphic computation of conditional statements of the form `If..Then..Else`.
This library is meant to be used both on the **server side** and the **client side**. The typical use case should follow the subsequent steps:
1. On the **client side**, generate the `client` and `server keys`.

View File

@@ -1,12 +1,12 @@
# Operations
The structure and operations related to the integers are described in this section.
The structure and operations related to integers are described in this section.
## How an integer is represented
In `integer`, the encrypted data is split amongst many ciphertexts encrypted with the `shortint` library. Below is a scheme representing an integer composed by k shortint ciphertexts.
![](../\_static/integer-ciphertext.png)
![](../../_static/integer-ciphertext.png)
This crate implements two ways to represent an integer:
@@ -21,37 +21,37 @@ The definition of an integer requires a basis and a number of blocks. This is do
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
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_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);
}
```
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 operations over shortints.
In this representation, the correctness of operations requires the carries to be propagated throughout the ciphertext. This operation is costly, since it relies on the computation of many programmable bootstrapping operations over shortints.
### CRT-based integers.
The second approach to represent large integers is based on the Chinese Remainder Theorem. In this case, 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 4 bits. The CRT-based integer are 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.
The second approach to represent large integers is based on the Chinese Remainder Theorem. In this case, 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 4 bits. The CRT-based integer are defined modulus $$\prod b_i$$. For an integer $$m$$, its CRT decomposition is simply defined as $$m \bmod{b_0}, m \bmod{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.
In the following example, the chosen basis is $$B = [2, 3, 5]$$. The integer is defined modulus $$2*3*5 = 30$$. There is no need to pre-size the number of blocks since it is determined from the number of values composing the basis. Here, the integer is split over three blocks.
```rust
use tfhe::integer::CrtClientKey;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
fn main() {
let basis = vec![2, 3, 5];
let cks = CrtClientKey::new(PARAM_MESSAGE_2_CARRY_2, basis);
let cks = CrtClientKey::new(PARAM_MESSAGE_2_CARRY_2_KS_PBS, basis);
}
```
This representation has many advantages: no carry propagation is required, cleaning the carry buffer of each ciphertext block is enough. This implies that operations can easily be
parallelized. It also allows the efficient computation of 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 to the computations is required, but performance might be improved.
A variant of the CRT is proposed where each block might be associated to a different key couple. Here, a keychain to the computations is required, but this may result in a performance improvement.
## List of available operations
@@ -86,10 +86,10 @@ For example, the addition has both variants:
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.
* `default`: Always compute the operation and always clear the carry. Could be **slower** than smart, but ensure that the timings are consistent from one call to another.
* `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. Some of those will require a mutable reference as input: this is because the inputs' carry might be cleaned, but this will not change the underlying encrypted value.
* `default`: always compute the operation and always clear the carry. Could be **slower** than smart, but ensure that the timings are consistent from one call to another.
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
@@ -101,11 +101,11 @@ As an example, let's do a scalar multiplication, a subtraction, and an addition.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);
let msg1 = 12u64;
let msg2 = 11u64;
@@ -113,7 +113,7 @@ fn main() {
let scalar = 3u64;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
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);
@@ -139,11 +139,11 @@ If the same circuit is done but using the `checked` flavor, a panic will occur:
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
fn main() {
let num_block = 2;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);
let msg1 = 12u64;
let msg2 = 11u64;
@@ -151,7 +151,7 @@ fn main() {
let scalar = 3u64;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
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);
@@ -177,19 +177,19 @@ Using the `smart` flavor will output the correct result all the time. However, t
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);
let msg1 = 12;
let msg2 = 11;
let msg3 = 9;
let scalar = 3;
let msg1 = 12u64;
let msg2 = 11u64;
let msg3 = 9u64;
let scalar = 3u64;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
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);
@@ -216,19 +216,19 @@ Using `default` could **slow down** computations.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);
let msg1 = 12;
let msg2 = 11;
let msg3 = 9;
let scalar = 3;
let msg1 = 12u64;
let msg2 = 11u64;
let msg3 = 9u64;
let scalar = 3u64;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
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);

View File

@@ -2,7 +2,7 @@
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. TFHE-rs uses the serde framework, so serde's Serialize and Deserialize are implemented.
The easiest way to send these data to a server is to use the serialization and deserialization features. `TFHE-rs` uses the serde framework, so serde's Serialize and Deserialize are implemented.
To be able to serialize our data, a [data format](https://serde.rs/#data-formats) needs to be picked. Here, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
@@ -20,20 +20,20 @@ bincode = "1.3.3"
use bincode;
use std::io::Cursor;
use tfhe::integer::{gen_keys_radix, ServerKey, RadixCiphertextBig};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::integer::{gen_keys_radix, ServerKey, RadixCiphertext};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
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_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, 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 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);
@@ -46,7 +46,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Simulate sending serialized data to a server and getting
// back the serialized result
let serialized_result = server_function(&serialized_data)?;
let result: RadixCiphertextBig = bincode::deserialize(&serialized_result)?;
let result: RadixCiphertext = bincode::deserialize(&serialized_result)?;
let output: u64 = client_key.decrypt(&result);
assert_eq!(output, (msg1 + msg2) % modulus);
@@ -57,8 +57,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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: RadixCiphertextBig = bincode::deserialize_from(&mut serialized_data)?;
let ct_2: RadixCiphertextBig = bincode::deserialize_from(&mut serialized_data)?;
let ct_1: RadixCiphertext = bincode::deserialize_from(&mut serialized_data)?;
let ct_2: RadixCiphertext = bincode::deserialize_from(&mut serialized_data)?;
let result = server_key.unchecked_add(&ct_1, &ct_2);

View File

@@ -1,6 +1,6 @@
# Tutorial
The steps to homomorphically evaluate an integer circuit are described here.
`tfhe::integer` is dedicated to unsigned integers smaller than 256 bits. The steps to homomorphically evaluate an integer circuit are described here.
## Key Types
@@ -14,7 +14,7 @@ The `ClientKey` is the key that encrypts and decrypts messages, thus this key is
The `ServerKey` is the key that is used to actually do the FHE computations. It contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, so 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.
To reflect this, computation/operation methods are tied to the `ServerKey` type.
The `PublicKey` is a key used to encrypt messages. It can be publicly shared to allow users to encrypt data such that only the `ClientKey` holder will be able to decrypt. Encrypting with the `PublicKey` does not alter the homomorphic capabilities associated to the `ServerKey`.
@@ -29,12 +29,12 @@ We are now going to build a pair of keys that can encrypt an **8-bit** integer b
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
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_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);
}
```
@@ -44,12 +44,12 @@ Once we have our keys, we can encrypt values:
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
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_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);
let msg1 = 128u64;
let msg2 = 13u64;
@@ -66,16 +66,16 @@ Once the client key is generated, the public key can be derived and used to encr
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::integer::PublicKeyBig;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::integer::PublicKey;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, _) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, _) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);
//We generate the public key from the secret client key:
let public_key = PublicKeyBig::new(&client_key);
let public_key = PublicKey::new(&client_key);
//encryption
let msg1 = 128u64;
@@ -93,18 +93,18 @@ With our `server_key`, and encrypted values, we can now do an addition and then
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
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_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, 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;
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);

View File

@@ -0,0 +1,161 @@
# Quick Start
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans, short integers (named shortint in the rest of this documentation), or integers up to 256 bits. It allows you to execute a circuit on an **untrusted server** because both circuit inputs and outputs are kept **private**. Data are indeed encrypted on the client side, before being sent to the server. On the server side, every computation is performed on ciphertexts.
The server, however, has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. Then the user can decrypt it with the `secret key`.
## General method to write an homomorphic circuit program
The overall process to write an homomorphic program is the same for all types. The basic steps for using the TFHE-rs library are the following:
1. Choose a data type (Boolean, shortint, integer)
2. Import the library
3. Create client and server keys
4. Encrypt data with the client key
5. Compute over encrypted data using the server key
6. Decrypt data with the client key
### API levels.
This library has different modules, with different levels of abstraction.
There is the **core\_crypto** module, which is the lowest level API with the primitive functions and types of the TFHE scheme.
Above the core\_crypto module, there are the **Boolean**, **shortint**, and **integer** modules, which contain easy to use APIs enabling evaluation of Boolean, short integer, and integer circuits.
Finally, there is the high-level module built on top of the Boolean, shortint, integer modules. This module is meant to abstract cryptographic complexities: no cryptographical knowledge is required to start developing an FHE application. Another benefit of the high-level module is the drastically simplified development process compared to lower level modules.
#### high-level API
TFHE-rs exposes a high-level API by default that includes datatypes that try to match Rust's native types by having overloaded operators (+, -, ...).
Here is an example of how the high-level API is used:
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
let result = a + b;
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
}
```
#### Boolean example
Here is an example of how the library can be used to evaluate a Boolean circuit:
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
```rust
use tfhe::boolean::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys();
// We use the client secret key to encrypt two messages:
let ct_1 = client_key.encrypt(true);
let ct_2 = client_key.encrypt(false);
// We use the server public key to execute a boolean circuit:
// if ((NOT ct_2) NAND (ct_1 AND ct_2)) then (NOT ct_2) else (ct_1 AND ct_2)
let ct_3 = server_key.not(&ct_2);
let ct_4 = server_key.and(&ct_1, &ct_2);
let ct_5 = server_key.nand(&ct_3, &ct_4);
let ct_6 = server_key.mux(&ct_5, &ct_3, &ct_4);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_6);
assert_eq!(output, true);
}
```
#### shortint example
Here is a full example using shortint:
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
```rust
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
// using parameters with 2 bits of message and 2 bits of carry
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let msg1 = 1;
let msg2 = 0;
let modulus = client_key.parameters.message_modulus().0;
// 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 as u64);
}
```
#### integer example
{% hint style="warning" %}
Use the `--release` flag to run this example (eg: `cargo run --release`)
{% endhint %}
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We create keys for radix represention to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, 8);
let clear_a = 2382u16;
let clear_b = 29374u16;
let mut a = cks.encrypt(clear_a as u64);
let mut b = cks.encrypt(clear_b as u64);
let encrypted_max = sks.smart_max_parallelized(&mut a, &mut b);
let decrypted_max: u64 = cks.decrypt(&encrypted_max);
assert_eq!(decrypted_max as u16, clear_a.max(clear_b))
}
```
The library is simple to use and can evaluate **homomorphic circuits of arbitrary length**. The description of the algorithms can be found in the [TFHE](https://doi.org/10.1007/s00145-019-09319-x) paper (also available as [ePrint 2018/421](https://ia.cr/2018/421)).

View File

@@ -1,6 +1,6 @@
# Operations
The structure and the operations related to the short integers are described in this section.
The structure and operations related to short integers are described in this section.
## How a shortint is represented
@@ -8,11 +8,11 @@ In `shortint`, the encrypted data is stored in an LWE ciphertext.
Conceptually, the message stored in an LWE ciphertext is divided into a **carry buffer** and a **message buffer**.
![](../\_static/ciphertext-representation.png)
![](../../_static/ciphertext-representation.png)
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus. The exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus. The part of the message which exceeds the message modulus is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in incorrect outputs.
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in an incorrect output.
In order to ensure the correctness of the computation, we track the maximum value encrypted in a ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to safely resume the computations. In `shortint` the carry modulus is considered useful as a means to do more computations.
@@ -23,23 +23,23 @@ The operations available via a `ServerKey` may come in different variants:
* operations that take their inputs as encrypted values
* scalar operations that take at least one non-encrypted value as input
For example, the addition has both variants:
For example, the addition has two variants:
* `ServerKey::unchecked_add`, which takes two encrypted values and adds them.
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (the so-called scalar) and adds them.
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (a so-called scalar) and adds them.
Each operation may come in different 'flavors':
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely;
* `smart`: Always does the operation. If the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible;
* `default`: Always does the operation and always clears the carry. Could be **slower** than smart, but it ensures that the timings are consistent from one call to another.
* `unchecked`: always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
* `checked`: checks are done before computing the operation, returning an error if operation cannot be done safely;
* `smart`: always does the operation. If the operation cannot be computed safely, the smart operation will clear the carry to make the operation possible. Some of those will require a mutable reference as input: this is to allow the modification of the carry, but this will not change the underlying encrypted value;
* `default`: always does the operation and always clears the carry. Could be **slower** than smart, but it ensures that the timings are consistent from one call to another.
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
## How to use operation types
Let's try to do a circuit evaluation using the different flavors of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise,`checked` and `smart` are the best options.
Let's try to do a circuit evaluation using the different flavors of operations that we have already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise,`checked` and `smart` are the best options.
Let's do a scalar multiplication, a subtraction, and a multiplication.
@@ -49,13 +49,13 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -82,13 +82,13 @@ use std::error::Error;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -125,13 +125,13 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -147,7 +147,7 @@ fn main() {
}
```
The main advantage of the default flavor is to ensure predictable timings as long as only this kind of operation is used.
The main advantage of the default flavor is to ensure predictable timings as long as this is the only kind of operation which is used.
{% hint style="warning" %}
Using `default` could **slow-down** computations.
@@ -159,13 +159,13 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -215,8 +215,8 @@ use tfhe::shortint::prelude::*;
fn main() {
// Generate the client key and the server key:
let (cks, _) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let pks = PublicKeyBig::new(&cks);
let (cks, _) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let pks = PublicKey::new(&cks);
let msg = 2;
// Encryption of one message:
@@ -236,12 +236,12 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -267,12 +267,12 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -298,12 +298,12 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -327,17 +327,16 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
//define the accumulator as the
let acc = server_key.generate_accumulator(|n| n.count_ones().into());
let acc = server_key.generate_lookup_table(|n| n.count_ones().into());
// add the two ciphertexts
let ct_res = server_key.apply_lookup_table(&ct_1, &acc);
@@ -360,19 +359,19 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys to compute over Z/2^2Z, with 2 carry bits
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let msg2 = 2;
let modulus = client_key.parameters.message_modulus.0 as u64;
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_2 = client_key.encrypt(msg2);
// Compute the accumulator for the bivariate functions
let acc = server_key.generate_accumulator_bivariate(|x,y| (x.count_ones()
// 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);

View File

@@ -1,25 +1,27 @@
# Cryptographic Parameters
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing using programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting\_started/security\_and\_cryptography.md) for more details about the encryption process).
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when using programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../../getting_started/security_and_cryptography.md) for more details about the encryption process).
## Parameters and message precision
`shortint` comes with sets of parameters that permit the use of the library functionalities securely and efficiently. Each parameter set is associated to the message and carry precisions. Thus, each key pair is entangled to precision.
`shortint` comes with sets of parameters that permit the use of the library functionalities securely and efficiently. Each parameter set is associated to the message and carry precisions. Therefore, each key pair is entangled to precision.
The user is allowed to choose which set of parameters to use when creating the pair of keys.
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
The difference between the parameter sets is the total amount of space dedicated to the plaintext, how it is split between the message buffer and the carry buffer, and the order in which the keyswitch (KS) and bootstrap (PBS) are computed. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}_{KS_PBS | PBS_KS}`. For example, the set of parameters for a message buffer of 5 bits, a carry buffer of 2 bits and where the keyswitch is computed before the bootstrap is `PARAM_MESSAGE_5_CARRY_2_KS_PBS`.
Note that the `KS_PBS` order should have better performance at the expense of ciphertext size, `PBS_KS` is the opposite.
This example contains keys that are generated to have messages encoded over 2 bits (i.e., computations are done modulus $$2^2 = 4$$) with 2 bits of carry.
The `PARAM_MESSAGE_2_CARRY_2` parameter set is the default `shortint` parameter set that you can also use through the `tfhe::shortint::prelude::DEFAULT_PARAMETERS` constant.
The `PARAM_MESSAGE_2_CARRY_2_KS_PBS` parameter set is the default `shortint` parameter set that you can also use through the `tfhe::shortint::prelude::DEFAULT_PARAMETERS` constant.
```rust
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 3;
let msg2 = 2;
@@ -32,11 +34,11 @@ fn main() {
## Impact of parameters on the operations
As shown [here](../getting\_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
As shown [here](../../getting_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
### Generic bi-variate functions.
The computations of bi-variate functions is based on a trick: _concatenating_ two ciphertexts into one. Where the carry buffer is not at least as large as the message one, this trick no longer works. Many bi-variate operations, such as comparisons, then cannot be correctly computed. The only exception concerns multiplication.
The computations of bi-variate functions is based on a trick: _concatenating_ two ciphertexts into one. Where the carry buffer is not at least as large as the message buffer, this trick no longer works. In this case, many bi-variate operations, such as comparisons, cannot be correctly computed. The only exception concerns multiplication.
### Multiplication.
@@ -44,7 +46,7 @@ In the case of multiplication, two algorithms are implemented: the first one rel
## User-defined parameter sets
It is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `Parameter` structure fields.
It is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `ClassicPBSParameters` structure fields.
For instance:
@@ -53,7 +55,7 @@ use tfhe::shortint::prelude::*;
fn main() {
let param = unsafe {
Parameters::new(
ClassicPBSParameters::new(
LweDimension(656),
GlweDimension(2),
PolynomialSize(512),
@@ -63,14 +65,10 @@ fn main() {
DecompositionLevelCount(2),
DecompositionBaseLog(3),
DecompositionLevelCount(4),
StandardDev(0.00000000037411618952047216),
DecompositionBaseLog(15),
DecompositionLevelCount(1),
DecompositionLevelCount(0),
DecompositionBaseLog(0),
MessageModulus(4),
CarryModulus(1),
CiphertextModulus::new_native(),
EncryptionKeyChoice::Big,
)
};
}

View File

@@ -2,9 +2,9 @@
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that performs the computations.
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework. Serde's Serialize and Deserialize are then implemented on tfhe::shortint's types.
The easiest way to send these data to a server is to use the serialization and deserialization features. `tfhe::shortint` uses the [serde](https://crates.io/crates/serde) framework. Serde's Serialize and Deserialize are then implemented on the `tfhe::shortint` types.
To serialize the data, we need to pick a [data format](https://serde.rs/#data-formats). For our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
To serialize the data, we need to pick a [data format](https://serde.rs/#data-formats). For our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is a binary format.
```toml
# Cargo.toml
@@ -23,7 +23,7 @@ use tfhe::shortint::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 1;
let msg2 = 0;
@@ -39,7 +39,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Simulate sending serialized data to a server and getting
// back the serialized result
let serialized_result = server_function(&serialized_data)?;
let result: CiphertextBig = bincode::deserialize(&serialized_result)?;
let result: Ciphertext = bincode::deserialize(&serialized_result)?;
let output = client_key.decrypt(&result);
assert_eq!(output, msg1 + msg2);
@@ -50,8 +50,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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: CiphertextBig = bincode::deserialize_from(&mut serialized_data)?;
let ct_2: CiphertextBig = 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);

View File

@@ -1,6 +1,6 @@
# Tutorial
The steps to homomorphically evaluate a circuit are described below.
`tfhe::shortint` is dedicated to small unsigned integers smaller than 8 bits. The steps to homomorphically evaluate a circuit are described below.
## Key generation
@@ -12,7 +12,7 @@ The steps to homomorphically evaluate a circuit are described below.
The `ClientKey` is the key that encrypts and decrypts messages (integer values up to 8 bits here). It 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. Most importantly, it contains 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`.
The `ServerKey` is the key that is used to evaluate the FHE computations. Most importantly, it contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server (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`.
Computation/operation methods are tied to the `ServerKey` type.
@@ -23,7 +23,7 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
}
```
@@ -36,7 +36,7 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 1;
let msg2 = 0;
@@ -56,8 +56,8 @@ use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, _) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let public_key = PublicKeyBig::new(&client_key);
let (client_key, _) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let public_key = PublicKey::new(&client_key);
let msg1 = 1;
let msg2 = 0;
@@ -70,19 +70,19 @@ fn main() {
## Computing and decrypting
With the `server_key`, addition is now possible over encrypted values. The resulting plaintext is recovered after the decryption with the secret client key.
Using the `server_key`, addition is possible over encrypted values. The resulting plaintext is recovered after the decryption via the secret client key.
```rust
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 1;
let msg2 = 0;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);

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