mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-11 07:38:08 -05:00
Compare commits
340 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb1a95e20d | ||
|
|
1d19fcfdb9 | ||
|
|
c93bb51714 | ||
|
|
b1788cc9df | ||
|
|
6000ef39ab | ||
|
|
c087858f65 | ||
|
|
62d6852d07 | ||
|
|
85e8988f29 | ||
|
|
ac04ed0893 | ||
|
|
60385f1489 | ||
|
|
7c8926b645 | ||
|
|
e5cf51230a | ||
|
|
cdc25d6c60 | ||
|
|
ab59514b0d | ||
|
|
7c2fa7529c | ||
|
|
945ce4617f | ||
|
|
ff84e70ca9 | ||
|
|
956c4080f5 | ||
|
|
fea1b4db92 | ||
|
|
13c1fcb6e7 | ||
|
|
69b9bd3860 | ||
|
|
747693e889 | ||
|
|
e452d5d6d2 | ||
|
|
5425ba5199 | ||
|
|
aeda381f12 | ||
|
|
9b2cccfee6 | ||
|
|
b534f6a406 | ||
|
|
7a72dd2619 | ||
|
|
343f31e070 | ||
|
|
a2384e0d1f | ||
|
|
37da2f1f1e | ||
|
|
8c775e5a27 | ||
|
|
43ba7e103d | ||
|
|
448e634748 | ||
|
|
6268752ac9 | ||
|
|
e0ed2d91c6 | ||
|
|
fef389e002 | ||
|
|
ae30f7c086 | ||
|
|
3f719a30f6 | ||
|
|
d28880ac30 | ||
|
|
ca9cdc0e73 | ||
|
|
f768e62d89 | ||
|
|
ee96a0ff18 | ||
|
|
ee944b3129 | ||
|
|
672f855770 | ||
|
|
362992a4ba | ||
|
|
2b24eb304d | ||
|
|
b484b8a851 | ||
|
|
6dea738725 | ||
|
|
3bb342879f | ||
|
|
9f024e2dac | ||
|
|
190b483d23 | ||
|
|
e799d240a7 | ||
|
|
16596137c1 | ||
|
|
03cd7ef15a | ||
|
|
4cda0a7211 | ||
|
|
9b668c1d50 | ||
|
|
dc4d9c7968 | ||
|
|
e3e7abd652 | ||
|
|
4265fbe67e | ||
|
|
337400ce3d | ||
|
|
be650d8e6b | ||
|
|
47604a6297 | ||
|
|
95d6fc5b1b | ||
|
|
19a6855b82 | ||
|
|
f894c33bfd | ||
|
|
6578aff8a4 | ||
|
|
9096c62f32 | ||
|
|
22f186af17 | ||
|
|
7820523d1f | ||
|
|
c0386c7e54 | ||
|
|
1ea73a68c4 | ||
|
|
6a02ae04e1 | ||
|
|
becd11b45f | ||
|
|
366964f1e6 | ||
|
|
32f8561af1 | ||
|
|
063ad26b9e | ||
|
|
dba18a889a | ||
|
|
0f5e1f0141 | ||
|
|
d4c7aff90b | ||
|
|
1d9f8c57da | ||
|
|
aa58748d33 | ||
|
|
412463ed27 | ||
|
|
72e7f16179 | ||
|
|
a1fcfcc55e | ||
|
|
5ede4d6b0c | ||
|
|
9430e6dcf8 | ||
|
|
74f47e3655 | ||
|
|
0d57da7608 | ||
|
|
25a43181e0 | ||
|
|
b8e64377fa | ||
|
|
c206aa89b8 | ||
|
|
f39e318019 | ||
|
|
40b9497dbf | ||
|
|
e1cfb0e3f7 | ||
|
|
a410aaaed6 | ||
|
|
d7a4e87efb | ||
|
|
3bc1536fa6 | ||
|
|
6633496e7b | ||
|
|
14f7ca7492 | ||
|
|
accd3cfb3f | ||
|
|
7f050c0fe9 | ||
|
|
c4769cbc0f | ||
|
|
1633eb573f | ||
|
|
10174cdac6 | ||
|
|
475b838943 | ||
|
|
1bdc447915 | ||
|
|
a04d68f1fb | ||
|
|
42b569bcd7 | ||
|
|
8999ea3766 | ||
|
|
b0d059eef1 | ||
|
|
64f9dc0813 | ||
|
|
52afc382a0 | ||
|
|
1e94d80044 | ||
|
|
68fa6b78a4 | ||
|
|
75f05c0f3a | ||
|
|
bf6f699e8c | ||
|
|
d3b3c5ab21 | ||
|
|
ceb26def05 | ||
|
|
638f210555 | ||
|
|
1294727b11 | ||
|
|
e954247f1b | ||
|
|
8d9ba2a1f9 | ||
|
|
34fc96319d | ||
|
|
13ad7d5468 | ||
|
|
9151eb72b3 | ||
|
|
8d8b8ab511 | ||
|
|
0c30e7525a | ||
|
|
385c907807 | ||
|
|
6266d18211 | ||
|
|
0a39f369d2 | ||
|
|
bb6663cfe5 | ||
|
|
06713fa42d | ||
|
|
b59afc7eee | ||
|
|
2ede9fb852 | ||
|
|
ccf21c1716 | ||
|
|
f3dc9e52f6 | ||
|
|
195efaf09c | ||
|
|
3c9325f939 | ||
|
|
a542b64dea | ||
|
|
e8a560b887 | ||
|
|
14da0ca001 | ||
|
|
5d8a138c69 | ||
|
|
10b0ff7f8b | ||
|
|
2279da604b | ||
|
|
f21fb9068c | ||
|
|
87b9431881 | ||
|
|
c2c43a2313 | ||
|
|
9db7a42f8b | ||
|
|
a47d8e3ee1 | ||
|
|
b2407d530e | ||
|
|
97830e934a | ||
|
|
91d04d97e9 | ||
|
|
a228f24abc | ||
|
|
8ee7b14abe | ||
|
|
85dc0f0164 | ||
|
|
c6eb6da0a0 | ||
|
|
acfe8697b7 | ||
|
|
8c4ecb805f | ||
|
|
0ad2d8cef2 | ||
|
|
1931315f73 | ||
|
|
af865f8d75 | ||
|
|
f8f6323ad4 | ||
|
|
b29008830c | ||
|
|
a43dbebd1b | ||
|
|
d224821aaa | ||
|
|
d24896ed09 | ||
|
|
106624048c | ||
|
|
5849cc9e7d | ||
|
|
02e6d3c955 | ||
|
|
3acaa2e242 | ||
|
|
e293dc2bc1 | ||
|
|
d9e0220dce | ||
|
|
2539e3e0c7 | ||
|
|
28cacfca86 | ||
|
|
e894bb0b11 | ||
|
|
313ccf3014 | ||
|
|
305baa1a6b | ||
|
|
357dee3197 | ||
|
|
6580d652bb | ||
|
|
5db0584356 | ||
|
|
b691bc9820 | ||
|
|
0653c7c896 | ||
|
|
bd9e453615 | ||
|
|
4673a6349e | ||
|
|
b63181b21a | ||
|
|
0ae2722729 | ||
|
|
5945a52eba | ||
|
|
384850f7fa | ||
|
|
4a88290a97 | ||
|
|
62843a4ef6 | ||
|
|
f5653f551d | ||
|
|
43670d7b15 | ||
|
|
97e2d96661 | ||
|
|
c90e0626f9 | ||
|
|
da9ae6a70d | ||
|
|
c5dbbaa071 | ||
|
|
a0dae1c9ae | ||
|
|
b2e3773c40 | ||
|
|
a66d377599 | ||
|
|
8b7b3d02b7 | ||
|
|
82b3d2154e | ||
|
|
702360c03f | ||
|
|
d065e98888 | ||
|
|
7dee0a9202 | ||
|
|
4a5be86cfa | ||
|
|
ccc41a89af | ||
|
|
c9258e7515 | ||
|
|
00c31f4802 | ||
|
|
d09169d6bc | ||
|
|
729d019bc1 | ||
|
|
823fb6d989 | ||
|
|
0876d7fec0 | ||
|
|
c302a4f871 | ||
|
|
8f12073bce | ||
|
|
2614d6430a | ||
|
|
87c153423e | ||
|
|
c94922d6a2 | ||
|
|
aeff001bf6 | ||
|
|
f3d1b1bc49 | ||
|
|
4e4b15a8be | ||
|
|
268371fda6 | ||
|
|
d773d3e7ff | ||
|
|
d2392e887f | ||
|
|
6cf14a5161 | ||
|
|
ae76230bd9 | ||
|
|
cbf846dea7 | ||
|
|
952f70fdf9 | ||
|
|
914007383f | ||
|
|
3fd6b0d917 | ||
|
|
fd4139dadc | ||
|
|
5c81e04c0b | ||
|
|
c6fb496ea1 | ||
|
|
d7226bcfb9 | ||
|
|
f792cc2737 | ||
|
|
3a2434b5ff | ||
|
|
712af5d2b9 | ||
|
|
2bdad26a9a | ||
|
|
bd1a5b9a87 | ||
|
|
ad59566621 | ||
|
|
62803dfb82 | ||
|
|
913f1d517a | ||
|
|
e624a74871 | ||
|
|
5d52a23c0b | ||
|
|
a6091682d1 | ||
|
|
7e3cc2d6e9 | ||
|
|
0e64b38f30 | ||
|
|
f0165e62d3 | ||
|
|
db2a7a4582 | ||
|
|
0e1f54ef54 | ||
|
|
44091cb038 | ||
|
|
3c6c90b0c5 | ||
|
|
c43d84491a | ||
|
|
4ef7a73efe | ||
|
|
1a72c4a814 | ||
|
|
d8abb9c2b2 | ||
|
|
740dee2267 | ||
|
|
387c025e90 | ||
|
|
a0dee63a2f | ||
|
|
9d9c407f7f | ||
|
|
ff062a33f9 | ||
|
|
e353af5a72 | ||
|
|
d274891948 | ||
|
|
3129d18247 | ||
|
|
59925e4273 | ||
|
|
15864202d7 | ||
|
|
68dce4eeb8 | ||
|
|
390fffac88 | ||
|
|
1cb8aa026f | ||
|
|
7db702cebf | ||
|
|
583bfaa643 | ||
|
|
1b3baf5635 | ||
|
|
0481fdadfb | ||
|
|
3ce9017784 | ||
|
|
9e5de38050 | ||
|
|
afc19a9b5b | ||
|
|
48f7457330 | ||
|
|
fe31bbf7c1 | ||
|
|
80a426f1df | ||
|
|
525225a4b2 | ||
|
|
c222459d07 | ||
|
|
aec0a17a1c | ||
|
|
dbfc0b969b | ||
|
|
499f904a61 | ||
|
|
778414da89 | ||
|
|
a31087badf | ||
|
|
2dd6c237f9 | ||
|
|
286d016003 | ||
|
|
03f63ec202 | ||
|
|
4d08b61064 | ||
|
|
91b310289d | ||
|
|
bdd4461702 | ||
|
|
c6060eb478 | ||
|
|
8ac33a9f63 | ||
|
|
ba984c2537 | ||
|
|
c933f6d900 | ||
|
|
b182d8ef05 | ||
|
|
4aef755a81 | ||
|
|
67e9b02283 | ||
|
|
00bbfd1545 | ||
|
|
a239b9e386 | ||
|
|
04415320d9 | ||
|
|
4a0fb6b42e | ||
|
|
b445e349a6 | ||
|
|
2aa84d2b3c | ||
|
|
d0d0b542ac | ||
|
|
07f496ac23 | ||
|
|
b3e456de28 | ||
|
|
4bdb507086 | ||
|
|
98d2e358bb | ||
|
|
be4e1a878d | ||
|
|
120e7b5a6b | ||
|
|
3185310610 | ||
|
|
1c40890aeb | ||
|
|
be7f26a30f | ||
|
|
6a3d579749 | ||
|
|
9e04f031e8 | ||
|
|
506fd88468 | ||
|
|
da1592f997 | ||
|
|
d38edb5096 | ||
|
|
6d6fcb9562 | ||
|
|
9a2212e305 | ||
|
|
b8d437cbde | ||
|
|
b89ca6fd87 | ||
|
|
d92bcb3ef4 | ||
|
|
34011798f5 | ||
|
|
3e192630d5 | ||
|
|
76ec565217 | ||
|
|
0c7159c040 | ||
|
|
8ea446a105 | ||
|
|
3c5ffca775 | ||
|
|
cc67dc9bb6 | ||
|
|
0891ea5551 | ||
|
|
45fb747c20 | ||
|
|
dc9c651d3b | ||
|
|
95646ca03a | ||
|
|
2d4b8e3aa3 | ||
|
|
352a2c69ab | ||
|
|
d7b7b84f5b | ||
|
|
ca16a80dfb |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
xtask = "run --manifest-path ./tasks/Cargo.toml --"
|
||||
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# 4 space indentation
|
||||
[*.rs]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
9
.github/dependabot.yaml
vendored
Normal file
9
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every sunday
|
||||
interval: "weekly"
|
||||
day: "sunday"
|
||||
77
.github/workflows/aws_tfhe_integer_tests.yml
vendored
Normal file
77
.github/workflows/aws_tfhe_integer_tests.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: AWS Integer 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
|
||||
matrix_item:
|
||||
description: 'Build matrix item'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
integer-tests:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}_${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ${{ github.event.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 }}"
|
||||
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
- 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
|
||||
|
||||
- name: Run integer tests
|
||||
run: |
|
||||
BIG_TESTS_INSTANCE=TRUE make test_integer_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: "Integer tests finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
|
||||
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
45
.github/workflows/aws_tfhe_tests.yml
vendored
45
.github/workflows/aws_tfhe_tests.yml
vendored
@@ -22,11 +22,17 @@ on:
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
matrix_item:
|
||||
description: 'Build matrix item'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
shortint-tests:
|
||||
concurrency:
|
||||
group: ${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
|
||||
group: ${{ github.workflow }}_${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
steps:
|
||||
@@ -36,22 +42,27 @@ jobs:
|
||||
echo "ID: ${{ github.event.inputs.instance_id }}"
|
||||
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
|
||||
echo "Type: ${{ github.event.inputs.instance_type }}"
|
||||
echo "Request ID: ${{ github.event.inputs.request_id }}"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
- name: Set up home
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install latest stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Run core tests
|
||||
run: |
|
||||
make test_core_crypto
|
||||
AVX512_SUPPORT=ON make test_core_crypto
|
||||
|
||||
- name: Run boolean tests
|
||||
run: |
|
||||
make test_boolean
|
||||
|
||||
- name: Run C API tests
|
||||
run: |
|
||||
@@ -61,33 +72,21 @@ jobs:
|
||||
run: |
|
||||
make test_user_doc
|
||||
|
||||
- name: Install AWS CLI
|
||||
- name: Run js on wasm API tests
|
||||
run: |
|
||||
apt update
|
||||
apt install -y awscli
|
||||
|
||||
- name: Configure AWS credentials from Test account
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_IAM_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_IAM_KEY }}
|
||||
role-to-assume: concrete-lib-ci
|
||||
aws-region: eu-west-3
|
||||
role-duration-seconds: 10800
|
||||
|
||||
- name: Download keys locally
|
||||
run: aws s3 cp --recursive --no-progress s3://concrete-libs-keycache ./keys
|
||||
make test_nodejs_wasm_api_in_docker
|
||||
|
||||
- name: Gen Keys if required
|
||||
run: |
|
||||
make gen_key_cache
|
||||
|
||||
- name: Sync keys
|
||||
run: aws s3 sync ./keys s3://concrete-libs-keycache
|
||||
|
||||
- name: Run shortint tests
|
||||
run: |
|
||||
make test_shortint_ci
|
||||
BIG_TESTS_INSTANCE=TRUE make test_shortint_ci
|
||||
|
||||
- name: Run high-level API tests
|
||||
run: |
|
||||
BIG_TESTS_INSTANCE=TRUE make test_high_level_api
|
||||
|
||||
- name: Slack Notification
|
||||
if: ${{ always() }}
|
||||
|
||||
113
.github/workflows/aws_tfhe_tests_w_gpu.yml
vendored
113
.github/workflows/aws_tfhe_tests_w_gpu.yml
vendored
@@ -1,113 +0,0 @@
|
||||
# Compile and test project on an AWS instance
|
||||
name: AWS tests on GPU
|
||||
|
||||
# This workflow is meant to be run via Zama CI bot Slab.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: "AWS instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: "AWS instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: "AWS EC2 instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: "-C target-cpu=native"
|
||||
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
jobs:
|
||||
run-tests-linux:
|
||||
concurrency:
|
||||
group: ${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
|
||||
cancel-in-progress: true
|
||||
name: Test code in EC2
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# explicit include-based build matrix, of known valid options
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
cuda: "11.8"
|
||||
old_cuda: "11.1"
|
||||
cuda_arch: "70"
|
||||
gcc: 8
|
||||
env:
|
||||
CUDA_PATH: /usr/local/cuda-${{ matrix.cuda }}
|
||||
OLD_CUDA_PATH: /usr/local/cuda-${{ matrix.old_cuda }}
|
||||
|
||||
steps:
|
||||
- name: EC2 instance configuration used
|
||||
run: |
|
||||
echo "IDs: ${{ github.event.inputs.instance_id }}"
|
||||
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
|
||||
echo "Type: ${{ github.event.inputs.instance_type }}"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up home
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
- name: Export CUDA variables
|
||||
run: |
|
||||
echo "CUDA_PATH=$CUDA_PATH" >> "${GITHUB_ENV}"
|
||||
echo "$CUDA_PATH/bin" >> "${GITHUB_PATH}"
|
||||
echo "LD_LIBRARY_PATH=$CUDA_PATH/lib:$LD_LIBRARY_PATH" >> "${GITHUB_ENV}"
|
||||
# Specify the correct host compilers
|
||||
- name: Export gcc and g++ variables
|
||||
run: |
|
||||
echo "CC=/usr/bin/gcc-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
|
||||
echo "CXX=/usr/bin/g++-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
|
||||
echo "CUDAHOSTCXX=/usr/bin/g++-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
|
||||
echo "CUDACXX=$CUDA_PATH/bin/nvcc" >> "${GITHUB_ENV}"
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
- name: Install latest stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Cuda clippy
|
||||
run: |
|
||||
make clippy_cuda
|
||||
|
||||
- name: Run core cuda tests
|
||||
run: |
|
||||
make test_core_crypto_cuda
|
||||
|
||||
- name: Test tfhe-rs/boolean with cpu
|
||||
run: |
|
||||
make test_boolean
|
||||
|
||||
- name: Test tfhe-rs/boolean with cuda backend with CUDA 11.8
|
||||
run: |
|
||||
make test_boolean_cuda
|
||||
|
||||
- name: Export variables for CUDA 11.1
|
||||
run: |
|
||||
echo "CUDA_PATH=$OLD_CUDA_PATH" >> "${GITHUB_ENV}"
|
||||
echo "LD_LIBRARY_PATH=$OLD_CUDA_PATH/lib:$LD_LIBRARY_PATH" >> "${GITHUB_ENV}"
|
||||
echo "CUDACXX=$OLD_CUDA_PATH/bin/nvcc" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Test tfhe-rs/boolean with cuda backend with CUDA 11.1
|
||||
run: |
|
||||
cargo clean
|
||||
make test_boolean_cuda
|
||||
|
||||
- name: Slack Notification
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
|
||||
env:
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
|
||||
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
|
||||
SLACK_MESSAGE: "(Slab ci-bot beta) AWS tests GPU finished with status ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
|
||||
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
116
.github/workflows/boolean_benchmark.yml
vendored
Normal file
116
.github/workflows/boolean_benchmark.yml
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
# Run boolean benchmarks on an AWS instance and return parsed results to Slab CI bot.
|
||||
name: Boolean benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: "Instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: "Instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: "Instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: "Slab request ID"
|
||||
type: string
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
|
||||
|
||||
jobs:
|
||||
run-boolean-benchmarks:
|
||||
name: Execute boolean benchmarks in EC2
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Instance configuration used
|
||||
run: |
|
||||
echo "IDs: ${{ inputs.instance_id }}"
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
|
||||
- name: Get benchmark date
|
||||
run: |
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up home
|
||||
# "Install rust" step require root user to have a HOME directory which is not set.
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
make AVX512_SUPPORT=ON bench_boolean
|
||||
|
||||
- name: Parse results
|
||||
run: |
|
||||
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
|
||||
COMMIT_HASH="$(git describe --tags --dirty)"
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--database tfhe_rs \
|
||||
--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: Measure key sizes
|
||||
run: |
|
||||
make measure_boolean_key_sizes
|
||||
|
||||
- name: Parse key sizes results
|
||||
run: |
|
||||
python3 ./ci/benchmark_parser.py tfhe/boolean_key_sizes.csv ${{ env.RESULTS_FILENAME }} \
|
||||
--key-sizes \
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
with:
|
||||
name: ${{ github.sha }}_boolean
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
|
||||
|
||||
- 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 }}')"
|
||||
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 }}
|
||||
41
.github/workflows/cargo_build.yml
vendored
41
.github/workflows/cargo_build.yml
vendored
@@ -17,53 +17,40 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
- name: Get rust toolchain to use for checks and lints
|
||||
id: toolchain
|
||||
- name: Run pcc checks
|
||||
run: |
|
||||
echo "rs-toolchain=$(make rs_toolchain)" >> "${GITHUB_OUTPUT}"
|
||||
make pcc
|
||||
|
||||
- name: Check format
|
||||
- name: Build Release core
|
||||
run: |
|
||||
make check_fmt
|
||||
|
||||
- name: Build doc
|
||||
run: |
|
||||
make doc
|
||||
|
||||
- name: Clippy boolean
|
||||
run: |
|
||||
make clippy_boolean
|
||||
make build_core AVX512_SUPPORT=ON
|
||||
make build_core_experimental AVX512_SUPPORT=ON
|
||||
|
||||
- name: Build Release boolean
|
||||
run: |
|
||||
make build_boolean
|
||||
|
||||
- name: Clippy shortint
|
||||
run: |
|
||||
make clippy_shortint
|
||||
|
||||
- name: Build Release shortint
|
||||
run: |
|
||||
make build_shortint
|
||||
|
||||
- name: Clippy shortint and boolean
|
||||
- name: Build Release integer
|
||||
run: |
|
||||
make clippy
|
||||
make build_integer
|
||||
|
||||
- name: Build Release shortint and boolean
|
||||
- name: Build Release tfhe full
|
||||
run: |
|
||||
make build_boolean_and_shortint
|
||||
|
||||
- name: C API Clippy
|
||||
run: |
|
||||
make clippy_c_api
|
||||
make build_tfhe_full
|
||||
|
||||
- name: Build Release c_api
|
||||
run: |
|
||||
make build_c_api
|
||||
|
||||
# The wasm build check is a bit annoying to set-up here and is done during the tests in
|
||||
# aws_tfhe_tests.yml
|
||||
|
||||
7
.github/workflows/check_commit.yml
vendored
7
.github/workflows/check_commit.yml
vendored
@@ -2,16 +2,13 @@
|
||||
name: Check commit and PR compliance
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
jobs:
|
||||
check-commit-pr:
|
||||
name: Check commit and PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check first line
|
||||
uses: gsactions/commit-message-checker@v1
|
||||
uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee
|
||||
with:
|
||||
pattern: '^((feat|fix|chore|refactor|style|test|docs|doc)\(\w+\)\:) .+$'
|
||||
flags: "gs"
|
||||
@@ -22,7 +19,7 @@ jobs:
|
||||
accessToken: ${{ secrets.GITHUB_TOKEN }} # github access token is only required if checkAllCommitMessages is true
|
||||
|
||||
- name: Check line length
|
||||
uses: gsactions/commit-message-checker@v1
|
||||
uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee
|
||||
with:
|
||||
pattern: '(^.{0,74}$\r?\n?){0,20}'
|
||||
flags: "gm"
|
||||
|
||||
104
.github/workflows/integer_benchmark.yml
vendored
Normal file
104
.github/workflows/integer_benchmark.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# Run integer benchmarks on an AWS instance and return parsed results to Slab CI bot.
|
||||
name: Integer benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: "Instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: "Instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: "Instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: "Slab request ID"
|
||||
type: string
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
|
||||
|
||||
jobs:
|
||||
run-integer-benchmarks:
|
||||
name: Execute integer benchmarks in EC2
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Instance configuration used
|
||||
run: |
|
||||
echo "IDs: ${{ inputs.instance_id }}"
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
|
||||
- name: Get benchmark date
|
||||
run: |
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up home
|
||||
# "Install rust" step require root user to have a HOME directory which is not set.
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
make AVX512_SUPPORT=ON bench_integer
|
||||
|
||||
- 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@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
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 }}
|
||||
60
.github/workflows/m1_tests.yml
vendored
60
.github/workflows/m1_tests.yml
vendored
@@ -20,58 +20,50 @@ jobs:
|
||||
runs-on: ["self-hosted", "m1mac"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
- name: Install latest stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Build doc
|
||||
- name: Run pcc checks
|
||||
run: |
|
||||
make doc
|
||||
make pcc
|
||||
|
||||
- name: Clippy boolean
|
||||
- name: Build Release core
|
||||
run: |
|
||||
make clippy_boolean
|
||||
make build_core
|
||||
|
||||
- name: Build Release boolean
|
||||
run: |
|
||||
make build_boolean
|
||||
|
||||
- name: Clippy shortint
|
||||
run: |
|
||||
make clippy_shortint
|
||||
|
||||
- name: Build Release shortint
|
||||
run: |
|
||||
make build_shortint
|
||||
|
||||
- name: Clippy shortint and boolean
|
||||
- name: Build Release integer
|
||||
run: |
|
||||
make clippy
|
||||
make build_integer
|
||||
|
||||
- name: Build Release shortint and boolean
|
||||
- name: Build Release tfhe full
|
||||
run: |
|
||||
make build_boolean_and_shortint
|
||||
|
||||
- name: C API Clippy
|
||||
run: |
|
||||
make clippy_c_api
|
||||
make build_tfhe_full
|
||||
|
||||
- name: Build Release c_api
|
||||
run: |
|
||||
make build_c_api
|
||||
|
||||
- name: Test tfhe-rs/boolean with cpu
|
||||
run: |
|
||||
make test_boolean
|
||||
|
||||
- name: Run core tests
|
||||
run: |
|
||||
make test_core_crypto
|
||||
|
||||
- name: Run boolean tests
|
||||
run: |
|
||||
make test_boolean
|
||||
|
||||
- name: Run C API tests
|
||||
run: |
|
||||
make test_c_api
|
||||
@@ -80,29 +72,21 @@ jobs:
|
||||
run: |
|
||||
make test_user_doc
|
||||
|
||||
- name: Configure AWS credentials from Test account
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_IAM_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_IAM_KEY }}
|
||||
role-to-assume: concrete-lib-ci
|
||||
aws-region: eu-west-3
|
||||
role-duration-seconds: 10800
|
||||
|
||||
- name: Download keys locally
|
||||
run: aws s3 cp --recursive --no-progress s3://concrete-libs-keycache ./keys
|
||||
# JS tests are more easily launched in docker, we won't test that on M1 as docker is pretty
|
||||
# slow on Apple machines due to the virtualization layer.
|
||||
|
||||
- name: Gen Keys if required
|
||||
run: |
|
||||
make gen_key_cache
|
||||
|
||||
- name: Sync keys
|
||||
run: aws s3 sync ./keys s3://concrete-libs-keycache
|
||||
|
||||
- name: Run shortint tests
|
||||
run: |
|
||||
make test_shortint_ci
|
||||
|
||||
- name: Run integer tests
|
||||
run: |
|
||||
make test_integer_ci
|
||||
|
||||
remove_label:
|
||||
name: Remove m1_test label
|
||||
runs-on: ubuntu-latest
|
||||
@@ -110,13 +94,13 @@ jobs:
|
||||
- cargo-builds
|
||||
if: ${{ always() }}
|
||||
steps:
|
||||
- uses: actions-ecosystem/action-remove-labels@v1
|
||||
- uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
|
||||
with:
|
||||
labels: m1_test
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Slack Notification
|
||||
if: ${{ always() }}
|
||||
if: ${{ needs.cargo-builds.result != 'skipped' }}
|
||||
continue-on-error: true
|
||||
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
|
||||
env:
|
||||
|
||||
52
.github/workflows/make_release.yml
vendored
Normal file
52
.github/workflows/make_release.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# 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
|
||||
|
||||
jobs:
|
||||
publish_release:
|
||||
name: Publish Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Publish crate.io package
|
||||
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
|
||||
run: |
|
||||
make build_web_js_api
|
||||
|
||||
- name: Publish web package
|
||||
uses: JS-DevTools/npm-publish@541aa6b21b4a1e9990c95a92c21adc16b35e9551
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: tfhe/pkg/package.json
|
||||
dry-run: ${{ inputs.dry_run }}
|
||||
|
||||
- name: Build 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
|
||||
uses: JS-DevTools/npm-publish@541aa6b21b4a1e9990c95a92c21adc16b35e9551
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: tfhe/pkg/package.json
|
||||
dry-run: ${{ inputs.dry_run }}
|
||||
104
.github/workflows/pbs_benchmark.yml
vendored
Normal file
104
.github/workflows/pbs_benchmark.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# Run PBS benchmarks on an AWS instance and return parsed results to Slab CI bot.
|
||||
name: PBS 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
|
||||
|
||||
jobs:
|
||||
run-pbs-benchmarks:
|
||||
name: Execute PBS 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@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up home
|
||||
# "Install rust" step require root user to have a HOME directory which is not set.
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
make AVX512_SUPPORT=ON bench_pbs
|
||||
|
||||
- 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 }}" \
|
||||
--name-suffix avx512 \
|
||||
--walk-subdirs \
|
||||
--throughput
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
with:
|
||||
name: ${{ github.sha }}_pbs
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
|
||||
|
||||
- name: Send data to Slab
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Computing HMac on downloaded artifact"
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
|
||||
echo "Sending results to Slab..."
|
||||
curl -v -k \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: store_data_v2" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @${{ env.RESULTS_FILENAME }} \
|
||||
${{ secrets.SLAB_URL }}
|
||||
114
.github/workflows/shortint_benchmark.yml
vendored
Normal file
114
.github/workflows/shortint_benchmark.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# Run shortint benchmarks on an AWS instance and return parsed results to Slab CI bot.
|
||||
name: Shortint benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: "Instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: "Instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: "Instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: "Slab request ID"
|
||||
type: string
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
|
||||
|
||||
jobs:
|
||||
run-shortint-benchmarks:
|
||||
name: Execute shortint benchmarks in EC2
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Instance configuration used
|
||||
run: |
|
||||
echo "IDs: ${{ inputs.instance_id }}"
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
|
||||
- name: Get benchmark date
|
||||
run: |
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up home
|
||||
# "Install rust" step require root user to have a HOME directory which is not set.
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
make AVX512_SUPPORT=ON bench_shortint
|
||||
|
||||
- name: Parse results
|
||||
run: |
|
||||
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
|
||||
COMMIT_HASH="$(git describe --tags --dirty)"
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--database tfhe_rs \
|
||||
--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: Measure key sizes
|
||||
run: |
|
||||
make measure_shortint_key_sizes
|
||||
|
||||
- name: Parse key sizes results
|
||||
run: |
|
||||
python3 ./ci/benchmark_parser.py tfhe/shortint_key_sizes.csv ${{ env.RESULTS_FILENAME }} \
|
||||
--key-sizes \
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
with:
|
||||
name: ${{ github.sha }}_shortint
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
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 }}
|
||||
36
.github/workflows/start_benchmarks.yml
vendored
Normal file
36
.github/workflows/start_benchmarks.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Start all benchmark jobs on Slab CI bot.
|
||||
name: Start all benchmarks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
start-benchmarks:
|
||||
strategy:
|
||||
matrix:
|
||||
command: [boolean_bench, shortint_bench, integer_bench, pbs_bench]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
|
||||
|
||||
- name: Start AWS job in Slab
|
||||
shell: bash
|
||||
run: |
|
||||
echo -n '{"command": "${{ matrix.command }}", "git_ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' > command.json
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh command.json '${{ secrets.JOB_SECRET }}')"
|
||||
curl -v -k \
|
||||
--fail-with-body \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: start_aws" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @command.json \
|
||||
${{ secrets.SLAB_URL }}
|
||||
37
.github/workflows/sync_on_push.yml
vendored
Normal file
37
.github/workflows/sync_on_push.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Sync repos
|
||||
name: Sync repos
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-repo:
|
||||
if: ${{ github.repository == 'zama-ai/tfhe-rs' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Save repo
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
with:
|
||||
name: repo-archive
|
||||
path: '.'
|
||||
- name: git-sync
|
||||
uses: wei/git-sync@55c6b63b4f21607da0e9877ca9b4d11a29fc6d83
|
||||
with:
|
||||
source_repo: "zama-ai/tfhe-rs"
|
||||
source_branch: "main"
|
||||
destination_repo: "https://${{ secrets.BOT_USERNAME }}:${{ secrets.CONCRETE_ACTIONS_TOKEN }}@github.com/${{ secrets.SYNC_DEST_REPO }}"
|
||||
destination_branch: "main"
|
||||
- name: git-sync tags
|
||||
uses: wei/git-sync@55c6b63b4f21607da0e9877ca9b4d11a29fc6d83
|
||||
with:
|
||||
source_repo: "zama-ai/tfhe-rs"
|
||||
source_branch: "refs/tags/*"
|
||||
destination_repo: "https://${{ secrets.BOT_USERNAME }}:${{ secrets.CONCRETE_ACTIONS_TOKEN }}@github.com/${{ secrets.SYNC_DEST_REPO }}"
|
||||
destination_branch: "refs/tags/*"
|
||||
18
.github/workflows/trigger_aws_tests_on_pr.yml
vendored
Normal file
18
.github/workflows/trigger_aws_tests_on_pr.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Trigger an AWS build each time commits are pushed to a pull request.
|
||||
name: PR AWS build trigger
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
|
||||
with:
|
||||
allow-repeats: true
|
||||
message: |
|
||||
@slab-ci cpu_test
|
||||
@slab-ci cpu_integer_test
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,7 +3,12 @@ target/
|
||||
.vscode/
|
||||
|
||||
# Path we use for internal-keycache during tests
|
||||
keys/
|
||||
./keys/
|
||||
# In case of symlinked keys
|
||||
./keys
|
||||
|
||||
**/Cargo.lock
|
||||
**/*.bin
|
||||
|
||||
# Some of our bench outputs
|
||||
/tfhe/benchmarks_parameters
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["tfhe"]
|
||||
members = ["tfhe", "tasks"]
|
||||
|
||||
[profile.bench]
|
||||
lto = "fat"
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
# Compiles much faster for tests and allows reasonable performance for iterating
|
||||
[profile.devo]
|
||||
inherits = "dev"
|
||||
opt-level = 3
|
||||
lto = "off"
|
||||
|
||||
9
LICENSE
9
LICENSE
@@ -1,6 +1,6 @@
|
||||
BSD 3-Clause Clear License
|
||||
|
||||
Copyright © 2022 ZAMA.
|
||||
Copyright © 2023 ZAMA.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -16,7 +16,7 @@ materials provided with the distribution.
|
||||
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE*.
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
@@ -26,8 +26,3 @@ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CA
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*In addition to the rights carried by this license, ZAMA grants to the user a non-exclusive,
|
||||
free and non-commercial license on all patents filed in its name relating to the open-source
|
||||
code (the "Patents") for the sole purpose of evaluation, development, research, prototyping
|
||||
and experimentation.
|
||||
|
||||
289
Makefile
289
Makefile
@@ -1,13 +1,25 @@
|
||||
SHELL:=$(shell /usr/bin/env which bash)
|
||||
RS_CHECK_TOOLCHAIN:=$(shell cat toolchain.txt)
|
||||
OS:=$(shell uname)
|
||||
RS_CHECK_TOOLCHAIN:=$(shell cat toolchain.txt | tr -d '\n')
|
||||
CARGO_RS_CHECK_TOOLCHAIN:=+$(RS_CHECK_TOOLCHAIN)
|
||||
TARGET_ARCH_FEATURE:=$(shell ./scripts/get_arch_feature.sh)
|
||||
RS_BUILD_TOOLCHAIN:=$(shell \
|
||||
( (echo $(TARGET_ARCH_FEATURE) | grep -q x86) && echo stable) || echo $(RS_CHECK_TOOLCHAIN))
|
||||
CARGO_RS_BUILD_TOOLCHAIN:=+$(RS_BUILD_TOOLCHAIN)
|
||||
CARGO_PROFILE?=release
|
||||
MIN_RUST_VERSION:=1.65
|
||||
AVX512_SUPPORT?=OFF
|
||||
WASM_RUSTFLAGS:=
|
||||
BIG_TESTS_INSTANCE?=FALSE
|
||||
# This is done to avoid forgetting it, we still precise the RUSTFLAGS in the commands to be able to
|
||||
# copy paste the command in the termianl and change them if required without forgetting the flags
|
||||
export RUSTFLAGS:=-C target-cpu=native
|
||||
# copy paste the command in the terminal and change them if required without forgetting the flags
|
||||
export RUSTFLAGS?=-C target-cpu=native
|
||||
|
||||
ifeq ($(AVX512_SUPPORT),ON)
|
||||
AVX512_FEATURE=nightly-avx512
|
||||
else
|
||||
AVX512_FEATURE=
|
||||
endif
|
||||
|
||||
.PHONY: rs_check_toolchain # Echo the rust toolchain used for checks
|
||||
rs_check_toolchain:
|
||||
@@ -21,21 +33,30 @@ rs_build_toolchain:
|
||||
install_rs_check_toolchain:
|
||||
@rustup toolchain list | grep -q "$(RS_CHECK_TOOLCHAIN)" || \
|
||||
rustup toolchain install --profile default "$(RS_CHECK_TOOLCHAIN)" || \
|
||||
echo "Unable to install $(RS_CHECK_TOOLCHAIN) toolchain, check your rustup installation. \
|
||||
Rustup can be downloaded at https://rustup.rs/"
|
||||
( echo "Unable to install $(RS_CHECK_TOOLCHAIN) toolchain, check your rustup installation. \
|
||||
Rustup can be downloaded at https://rustup.rs/" && exit 1 )
|
||||
|
||||
.PHONY: install_rs_build_toolchain # Install the toolchain used for builds
|
||||
install_rs_build_toolchain:
|
||||
@rustup toolchain list | grep -q "$(RS_BUILD_TOOLCHAIN)" || \
|
||||
@( rustup toolchain list | grep -q "$(RS_BUILD_TOOLCHAIN)" && \
|
||||
./scripts/check_cargo_min_ver.sh \
|
||||
--rust-toolchain "$(CARGO_RS_BUILD_TOOLCHAIN)" \
|
||||
--min-rust-version "$(MIN_RUST_VERSION)" ) || \
|
||||
rustup toolchain install --profile default "$(RS_BUILD_TOOLCHAIN)" || \
|
||||
echo "Unable to install $(RS_BUILD_TOOLCHAIN) toolchain, check your rustup installation. \
|
||||
Rustup can be downloaded at https://rustup.rs/"
|
||||
( echo "Unable to install $(RS_BUILD_TOOLCHAIN) toolchain, check your rustup installation. \
|
||||
Rustup can be downloaded at https://rustup.rs/" && exit 1 )
|
||||
|
||||
.PHONY: install_cargo_nextest # Install cargo nextest used for shortint tests
|
||||
install_cargo_nextest: install_rs_build_toolchain
|
||||
@cargo nextest --version > /dev/null 2>&1 || \
|
||||
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-nextest --locked || \
|
||||
echo "Unable to install cargo nextest, unknown error."
|
||||
( echo "Unable to install cargo nextest, unknown error." && exit 1 )
|
||||
|
||||
.PHONY: 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: fmt # Format rust code
|
||||
fmt: install_rs_check_toolchain
|
||||
@@ -45,6 +66,15 @@ fmt: install_rs_check_toolchain
|
||||
check_fmt: install_rs_check_toolchain
|
||||
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt --check
|
||||
|
||||
.PHONY: clippy_core # Run clippy lints on core_crypto with and without experimental features
|
||||
clippy_core: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
--features=$(TARGET_ARCH_FEATURE) \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_boolean # Run clippy lints enabling the boolean features
|
||||
clippy_boolean: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
@@ -57,10 +87,16 @@ clippy_shortint: install_rs_check_toolchain
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy # Run clippy lints enabling the boolean, shortint
|
||||
clippy: install_rs_check_toolchain
|
||||
.PHONY: clippy_integer # Run clippy lints enabling the integer features
|
||||
clippy_integer: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint \
|
||||
--features=$(TARGET_ARCH_FEATURE),integer \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy # Run clippy lints enabling the boolean, shortint, integer
|
||||
clippy: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_c_api # Run clippy lints enabling the boolean, shortint and the C API
|
||||
@@ -69,83 +105,236 @@ clippy_c_api: install_rs_check_toolchain
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_cuda # Run clippy lints enabling the boolean, shortint, cuda and c API features
|
||||
clippy_cuda: install_rs_check_toolchain
|
||||
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint and the js wasm API
|
||||
clippy_js_wasm_api: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
--features=$(TARGET_ARCH_FEATURE),cuda,boolean-c-api,shortint-c-api \
|
||||
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_tasks # Run clippy lints on helper tasks crate.
|
||||
clippy_tasks:
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
-p tasks -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_all_targets # Run clippy lints on all targets (benches, examples, etc.)
|
||||
clippy_all_targets:
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_all # Run all clippy targets
|
||||
clippy_all: clippy clippy_boolean clippy_shortint clippy_integer clippy_all_targets clippy_c_api \
|
||||
clippy_js_wasm_api clippy_tasks clippy_core
|
||||
|
||||
.PHONY: clippy_fast # Run main clippy targets
|
||||
clippy_fast: clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core
|
||||
|
||||
.PHONY: gen_key_cache # Run the script to generate keys and cache them for shortint tests
|
||||
gen_key_cache: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
|
||||
--example generates_test_keys \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe
|
||||
|
||||
.PHONY: build_core # Build core_crypto without experimental features
|
||||
build_core: install_rs_build_toolchain install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE) -p tfhe
|
||||
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),$(AVX512_FEATURE) -p tfhe; \
|
||||
fi
|
||||
|
||||
.PHONY: build_core_experimental # Build core_crypto with experimental features
|
||||
build_core_experimental: install_rs_build_toolchain install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental -p tfhe
|
||||
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe; \
|
||||
fi
|
||||
|
||||
.PHONY: build_boolean # Build with boolean enabled
|
||||
build_boolean: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe --all-targets
|
||||
|
||||
.PHONY: build_shortint # Build with shortint enabled
|
||||
build_shortint: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint -p tfhe
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint -p tfhe --all-targets
|
||||
|
||||
.PHONY: build_boolean_and_shortint # Build with boolean and shortint enabled
|
||||
build_boolean_and_shortint: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint -p tfhe
|
||||
.PHONY: build_integer # Build with integer enabled
|
||||
build_integer: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),integer -p tfhe --all-targets
|
||||
|
||||
.PHONY: build_tfhe_full # Build with boolean, shortint and integer enabled
|
||||
build_tfhe_full: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer -p tfhe --all-targets
|
||||
|
||||
.PHONY: build_c_api # Build the C API for boolean and shortint
|
||||
build_c_api: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api -p tfhe
|
||||
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: test_core_crypto # Run the tests of the core_crypto module
|
||||
test_core_crypto: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
--features=$(TARGET_ARCH_FEATURE) -p tfhe -- core_crypto::
|
||||
.PHONY: build_web_js_api # Build the js API targeting the web browser
|
||||
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
|
||||
|
||||
.PHONY: test_core_crypto_cuda # Run the tests of the core_crypto module with cuda enabled
|
||||
test_core_crypto_cuda: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),cuda -p tfhe -- core_crypto::backends::cuda::
|
||||
.PHONY: build_node_js_api # Build the js API targeting nodejs
|
||||
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
|
||||
|
||||
.PHONY: test_core_crypto # Run the tests of the core_crypto module including experimental ones
|
||||
test_core_crypto: install_rs_build_toolchain install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental -p tfhe -- core_crypto::
|
||||
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe -- core_crypto::; \
|
||||
fi
|
||||
|
||||
.PHONY: test_boolean # Run the tests of the boolean module
|
||||
test_boolean: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
|
||||
|
||||
.PHONY: test_boolean_cuda # Run the tests of the boolean module with cuda enabled
|
||||
test_boolean_cuda: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,cuda -p tfhe -- boolean::
|
||||
|
||||
.PHONY: test_c_api # Run the tests for the C API
|
||||
test_c_api: install_rs_build_toolchain
|
||||
./scripts/c_api_tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
|
||||
test_c_api: build_c_api
|
||||
./scripts/c_api_tests.sh
|
||||
|
||||
.PHONY: test_shortint_ci # Run the tests for shortint ci
|
||||
test_shortint_ci: install_rs_build_toolchain install_cargo_nextest
|
||||
./scripts/shortint-tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
|
||||
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
|
||||
./scripts/shortint-tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
|
||||
|
||||
.PHONY: test_shortint # Run all the tests for shortint
|
||||
test_shortint: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- shortint::
|
||||
|
||||
.PHONY: test_integer_ci # Run the tests for integer ci
|
||||
test_integer_ci: install_rs_build_toolchain install_cargo_nextest
|
||||
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
|
||||
./scripts/integer-tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
|
||||
|
||||
.PHONY: test_integer # Run all the tests for integer
|
||||
test_integer: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache -p tfhe -- integer::
|
||||
|
||||
.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::
|
||||
|
||||
.PHONY: test_user_doc # Run tests from the .md documentation
|
||||
test_user_doc: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release --doc \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,boolean,internal-keycache -p tfhe \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) --doc \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \
|
||||
-- test_user_docs::
|
||||
|
||||
.PHONY: doc # Build rust doc
|
||||
doc: install_rs_check_toolchain
|
||||
RUSTDOCFLAGS="--html-in-header katex-header.html" \
|
||||
RUSTDOCFLAGS="--html-in-header katex-header.html -Dwarnings" \
|
||||
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" doc \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint --no-deps
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer --no-deps
|
||||
|
||||
.PHONY: format_doc_latex # Format the documentation latex equations to avoid broken rendering.
|
||||
format_doc_latex:
|
||||
cargo xtask format_latex_doc
|
||||
@"$(MAKE)" --no-print-directory fmt
|
||||
@printf "\n===============================\n\n"
|
||||
@printf "Please manually inspect changes made by format_latex_doc, rustfmt can break equations \
|
||||
if the line length is exceeded\n"
|
||||
@printf "\n===============================\n"
|
||||
|
||||
.PHONY: check_compile_tests # Build tests in debug without running them
|
||||
check_compile_tests: build_c_api
|
||||
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
|
||||
|
||||
.PHONY: build_nodejs_test_docker # Build a docker image with tools to run nodejs tests for wasm API
|
||||
build_nodejs_test_docker:
|
||||
DOCKER_BUILDKIT=1 docker build --build-arg RUST_TOOLCHAIN="$(RS_BUILD_TOOLCHAIN)" \
|
||||
-f docker/Dockerfile.wasm_tests -t tfhe-wasm-tests .
|
||||
|
||||
.PHONY: test_nodejs_wasm_api_in_docker # Run tests for the nodejs on wasm API in a docker container
|
||||
test_nodejs_wasm_api_in_docker: build_nodejs_test_docker
|
||||
if [[ -t 1 ]]; then RUN_FLAGS="-it"; else RUN_FLAGS="-i"; fi && \
|
||||
docker run --rm "$${RUN_FLAGS}" \
|
||||
-v "$$(pwd)":/tfhe-wasm-tests/tfhe-rs \
|
||||
-v tfhe-rs-root-target-cache:/root/tfhe-rs-target \
|
||||
-v tfhe-rs-pkg-cache:/tfhe-wasm-tests/tfhe-rs/tfhe/pkg \
|
||||
-v tfhe-rs-root-cargo-registry-cache:/root/.cargo/registry \
|
||||
-v tfhe-rs-root-cache:/root/.cache \
|
||||
tfhe-wasm-tests /bin/bash -i -c 'make test_nodejs_wasm_api'
|
||||
|
||||
.PHONY: test_nodejs_wasm_api # Run tests for the nodejs on wasm API
|
||||
test_nodejs_wasm_api: build_node_js_api
|
||||
cd tfhe && node --test js_on_wasm_tests
|
||||
|
||||
.PHONY: no_tfhe_typo # Check we did not invert the h and f in tfhe
|
||||
no_tfhe_typo:
|
||||
@./scripts/no_tfhe_typo.sh
|
||||
|
||||
.PHONY: bench_integer # Run benchmarks for integer
|
||||
bench_integer: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" 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 \
|
||||
--bench shortint-bench \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
|
||||
|
||||
.PHONY: bench_boolean # Run benchmarks for boolean
|
||||
bench_boolean: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
|
||||
--bench boolean-bench \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache,$(AVX512_FEATURE) -p tfhe
|
||||
|
||||
.PHONY: bench_pbs # Run benchmarks for PBS
|
||||
bench_pbs: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
|
||||
--bench pbs-bench \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
|
||||
|
||||
.PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint
|
||||
measure_shortint_key_sizes: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
|
||||
--example shortint_key_sizes \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache
|
||||
|
||||
.PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean
|
||||
measure_boolean_key_sizes: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
|
||||
--example boolean_key_sizes \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache
|
||||
|
||||
.PHONY: pcc # pcc stands for pre commit checks
|
||||
pcc: no_tfhe_typo check_fmt doc clippy_all check_compile_tests
|
||||
|
||||
.PHONY: fpcc # pcc stands for pre commit checks, the f stands for fast
|
||||
fpcc: no_tfhe_typo check_fmt doc clippy_fast check_compile_tests
|
||||
|
||||
.PHONY: conformance # Automatically fix problems that can be fixed
|
||||
conformance: fmt
|
||||
|
||||
.PHONY: help # Generate list of targets with descriptions
|
||||
help:
|
||||
@grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1\t\2/' | expand -t30 | sort
|
||||
@grep '^\.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1\t\2/' | expand -t30 | sort
|
||||
|
||||
131
README.md
131
README.md
@@ -1,31 +1,25 @@
|
||||
<p align="center">
|
||||
<!-- product name logo -->
|
||||
<img width=600 src="https://user-images.githubusercontent.com/86411313/201107820-b1b861be-6b3f-46cc-bccd-ed051201781a.png">
|
||||
<img width=600 src="https://user-images.githubusercontent.com/5758427/231206749-8f146b97-3c5a-4201-8388-3ffa88580415.png">
|
||||
</p>
|
||||
<hr/>
|
||||
<p align="center">
|
||||
<a href="https://docs.zama.ai/tfhe-rs"> 📒 Read documentation</a> | <a href="https://zama.ai/community"> 💛 Community support</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<!-- Version badge using shields.io -->
|
||||
<a href="https://github.com/zama-ai/tfhe-rs/releases">
|
||||
<img src="https://img.shields.io/github/v/release/zama-ai/tfhe-rs?style=flat-square">
|
||||
</a>
|
||||
<!-- Link to docs badge using shields.io -->
|
||||
<a href="https://docs.zama.ai/tfhe-rs">
|
||||
<img src="https://img.shields.io/badge/read-documentation-yellow?style=flat-square">
|
||||
</a>
|
||||
<!-- Community forum badge using shields.io -->
|
||||
<a href="https://community.zama.ai">
|
||||
<img src="https://img.shields.io/badge/community%20forum-online-brightgreen?style=flat-square">
|
||||
</a>
|
||||
<!-- Open source badge using shields.io -->
|
||||
<a href="https://docs.zama.ai/tfhe-rs/developers/contributing">
|
||||
<img src="https://img.shields.io/badge/we're%20open%20source-contributing.md-blue?style=flat-square">
|
||||
</a>
|
||||
<!-- Follow on twitter badge using shields.io -->
|
||||
<a href="https://twitter.com/zama_fhe">
|
||||
<img src="https://img.shields.io/twitter/follow/zama_fhe?color=blue&style=flat-square">
|
||||
<!-- Zama Bounty Program -->
|
||||
<a href="https://github.com/zama-ai/bounty-program">
|
||||
<img src="https://img.shields.io/badge/Contribute-Zama%20Bounty%20Program-yellow?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
<hr/>
|
||||
|
||||
**TFHE-rs** is a pure Rust implementation of TFHE for boolean and small integer
|
||||
|
||||
**TFHE-rs** is a pure Rust implementation of TFHE for boolean and integer
|
||||
arithmetics over encrypted data. It includes:
|
||||
- a **Rust** API
|
||||
- a **C** API
|
||||
@@ -33,38 +27,61 @@ arithmetics over encrypted data. It includes:
|
||||
|
||||
**TFHE-rs** is meant for developers and researchers who want full control over
|
||||
what they can do with TFHE, while not having to worry about the low level
|
||||
implementation. The goal is to have a stable, simple, high-performance and
|
||||
implementation. The goal is to have a stable, simple, high-performance, and
|
||||
production-ready library for all the advanced features of TFHE.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
To use the latest version of `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
|
||||
+ For x86_64-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.1.0", features = [ "boolean","shortint","x86_64-unix" ] }
|
||||
tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64-unix"] }
|
||||
```
|
||||
|
||||
+ For Apple Silicon or aarch64-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "*", features = ["boolean", "shortint", "integer", "aarch64-unix"] }
|
||||
```
|
||||
Note: users with ARM devices must use `TFHE-rs` by compiling using the `nightly` toolchain.
|
||||
|
||||
|
||||
+ For x86_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND)
|
||||
running Windows:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "*", features = ["boolean", "shortint", "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,
|
||||
eg: `cargo run --release`.
|
||||
|
||||
Here is a full example evaluating a Boolean circuit:
|
||||
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let (mut client_key, mut server_key) = gen_keys();
|
||||
|
||||
// We use the client secret key to encrypt two messages:
|
||||
// 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)
|
||||
// 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:
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output = client_key.decrypt(&ct_6);
|
||||
assert_eq!(output, true);
|
||||
}
|
||||
@@ -76,24 +93,57 @@ Another example of how the library can be used with shortints:
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let (client_key, server_key) = gen_keys(Parameters::default());
|
||||
// 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 msg1 = 1;
|
||||
let msg2 = 0;
|
||||
let msg1 = 3;
|
||||
let msg2 = 2;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
// Encrypt two messages using the (private) client key:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
|
||||
// We use the server public key to execute an integer circuit:
|
||||
let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);
|
||||
// Homomorphically compute an addition
|
||||
let ct_add = server_key.unchecked_add(&ct_1, &ct_2);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output = client_key.decrypt(&ct_3);
|
||||
assert_eq!(output, (msg1 + msg2) % modulus as u64);
|
||||
// Define the Hamming weight function
|
||||
// f: x -> sum of the bits of x
|
||||
let f = |x:u64| x.count_ones() as u64;
|
||||
|
||||
// Generate the accumulator for the function
|
||||
let acc = server_key.generate_accumulator(f);
|
||||
|
||||
// Compute the function over the ciphertext using the PBS
|
||||
let ct_res = server_key.apply_lookup_table(&ct_add, &acc);
|
||||
|
||||
// Decrypt the ciphertext using the (private) client key
|
||||
let output = client_key.decrypt(&ct_res);
|
||||
assert_eq!(output, f(msg1 + msg2));
|
||||
}
|
||||
```
|
||||
|
||||
An example using integer:
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
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 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))
|
||||
}
|
||||
```
|
||||
|
||||
@@ -101,7 +151,7 @@ fn main() {
|
||||
|
||||
There are two ways to contribute to TFHE-rs:
|
||||
|
||||
- you can open issues to report bugs or typos and to suggest new ideas
|
||||
- you can open issues to report bugs or typos, or to suggest new ideas
|
||||
- you can ask to become an official contributor by emailing [hello@zama.ai](mailto:hello@zama.ai).
|
||||
(becoming an approved contributor involves signing our Contributor License Agreement (CLA))
|
||||
|
||||
@@ -112,6 +162,11 @@ Only approved contributors can send pull requests, so please make sure to get in
|
||||
This library uses several dependencies and we would like to thank the contributors of those
|
||||
libraries.
|
||||
|
||||
## Need support?
|
||||
<a target="_blank" href="https://community.zama.ai">
|
||||
<img src="https://user-images.githubusercontent.com/5758427/231115030-21195b55-2629-4c01-9809-be5059243999.png">
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
This software is distributed under the BSD-3-Clause-Clear license. If you have any questions,
|
||||
|
||||
353
ci/benchmark_parser.py
Normal file
353
ci/benchmark_parser.py
Normal file
@@ -0,0 +1,353 @@
|
||||
"""
|
||||
benchmark_parser
|
||||
----------------
|
||||
|
||||
Parse criterion benchmark or keys size results.
|
||||
"""
|
||||
import argparse
|
||||
import csv
|
||||
import pathlib
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
ONE_HOUR_IN_NANOSECONDS = 3600E9
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('results',
|
||||
help='Location of criterion benchmark results directory.'
|
||||
'If the --key-size option is used, then the value would have to point to'
|
||||
'a CSV file.')
|
||||
parser.add_argument('output_file', help='File storing parsed results')
|
||||
parser.add_argument('-d', '--database', dest='database',
|
||||
help='Name of the database used to store results')
|
||||
parser.add_argument('-w', '--hardware', dest='hardware',
|
||||
help='Hardware reference used to perform benchmark')
|
||||
parser.add_argument('-V', '--project-version', dest='project_version',
|
||||
help='Commit hash reference')
|
||||
parser.add_argument('-b', '--branch', dest='branch',
|
||||
help='Git branch name on which benchmark was performed')
|
||||
parser.add_argument('--commit-date', dest='commit_date',
|
||||
help='Timestamp of commit hash used in project_version')
|
||||
parser.add_argument('--bench-date', dest='bench_date',
|
||||
help='Timestamp when benchmark was run')
|
||||
parser.add_argument('--name-suffix', dest='name_suffix', default='',
|
||||
help='Suffix to append to each of the result test names')
|
||||
parser.add_argument('--append-results', dest='append_results', action='store_true',
|
||||
help='Append parsed results to an existing file')
|
||||
parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true',
|
||||
help='Check for results in subdirectories')
|
||||
parser.add_argument('--key-sizes', dest='key_sizes', action='store_true',
|
||||
help='Parse only the results regarding keys size measurements')
|
||||
parser.add_argument('--throughput', dest='throughput', action='store_true',
|
||||
help='Compute and append number of operations per millisecond and'
|
||||
'operations per dollar')
|
||||
parser.add_argument('--backend', dest='backend', default='cpu',
|
||||
help='Backend on which benchmarks have run')
|
||||
|
||||
|
||||
def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throughput=False,
|
||||
hardware_hourly_cost=None):
|
||||
"""
|
||||
Parse all the benchmark results in a directory. It will attempt to parse all the files having a
|
||||
.json extension at the top-level of this directory.
|
||||
|
||||
:param directory: path to directory that contains raw results as :class:`pathlib.Path`
|
||||
:param walk_subdirs: traverse results subdirectories if parameters changes for benchmark case.
|
||||
:param name_suffix: a :class:`str` suffix to apply to each test name found
|
||||
:param compute_throughput: compute number of operations per millisecond and operations per
|
||||
dollar
|
||||
:param hardware_hourly_cost: hourly cost of the hardware used in dollar
|
||||
|
||||
:return: tuple of :class:`list` as (data points, parsing failures)
|
||||
"""
|
||||
excluded_directories = ["child_generate", "fork", "parent_generate", "report"]
|
||||
result_values = []
|
||||
parsing_failures = []
|
||||
bench_class = "evaluate"
|
||||
|
||||
for dire in directory.iterdir():
|
||||
if dire.name in excluded_directories or not dire.is_dir():
|
||||
continue
|
||||
for subdir in dire.iterdir():
|
||||
if walk_subdirs:
|
||||
if subdir.name == "new":
|
||||
pass
|
||||
else:
|
||||
subdir = subdir.joinpath("new")
|
||||
if not subdir.exists():
|
||||
continue
|
||||
elif subdir.name != "new":
|
||||
continue
|
||||
|
||||
full_name, test_name = parse_benchmark_file(subdir)
|
||||
if test_name is None:
|
||||
parsing_failures.append((full_name, "'function_id' field is null in report"))
|
||||
continue
|
||||
|
||||
try:
|
||||
params, display_name, operator = get_parameters(test_name)
|
||||
except Exception as err:
|
||||
parsing_failures.append((full_name, f"failed to get parameters: {err}"))
|
||||
continue
|
||||
|
||||
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,
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"latency",
|
||||
operator,
|
||||
params,
|
||||
display_name=display_name
|
||||
)
|
||||
)
|
||||
|
||||
if stat_name == "mean" and compute_throughput:
|
||||
test_suffix = "ops-per-ms"
|
||||
test_name_parts.append(test_suffix)
|
||||
result_values.append(
|
||||
_create_point(
|
||||
compute_ops_per_millisecond(value),
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"throughput",
|
||||
operator,
|
||||
params,
|
||||
display_name="_".join([display_name, test_suffix])
|
||||
)
|
||||
)
|
||||
test_name_parts.pop()
|
||||
|
||||
if hardware_hourly_cost is not None:
|
||||
test_suffix = "ops-per-dollar"
|
||||
test_name_parts.append(test_suffix)
|
||||
result_values.append(
|
||||
_create_point(
|
||||
compute_ops_per_dollar(value, hardware_hourly_cost),
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"throughput",
|
||||
operator,
|
||||
params,
|
||||
display_name="_".join([display_name, test_suffix])
|
||||
)
|
||||
)
|
||||
|
||||
return result_values, parsing_failures
|
||||
|
||||
|
||||
def _create_point(value, test_name, bench_class, bench_type, operator, params, display_name=None):
|
||||
return {
|
||||
"value": value,
|
||||
"test": test_name,
|
||||
"name": display_name,
|
||||
"class": bench_class,
|
||||
"type": bench_type,
|
||||
"operator": operator,
|
||||
"params": params}
|
||||
|
||||
|
||||
def parse_benchmark_file(directory):
|
||||
"""
|
||||
Parse file containing details of the parameters used for a benchmark.
|
||||
|
||||
:param directory: directory where a benchmark case results are located as :class:`pathlib.Path`
|
||||
|
||||
:return: name of the test as :class:`str`
|
||||
"""
|
||||
raw_res = _parse_file_to_json(directory, "benchmark.json")
|
||||
return raw_res["full_id"], raw_res["function_id"]
|
||||
|
||||
|
||||
def parse_estimate_file(directory):
|
||||
"""
|
||||
Parse file containing timing results for a benchmark.
|
||||
|
||||
:param directory: directory where a benchmark case results are located as :class:`pathlib.Path`
|
||||
|
||||
:return: :class:`dict` of data points
|
||||
"""
|
||||
raw_res = _parse_file_to_json(directory, "estimates.json")
|
||||
return {
|
||||
stat_name: raw_res[stat_name]["point_estimate"]
|
||||
for stat_name in ("mean", "std_dev")
|
||||
}
|
||||
|
||||
|
||||
def parse_key_sizes(result_file):
|
||||
"""
|
||||
Parse file containing key sizes results. The file must be formatted as CSV.
|
||||
|
||||
:param result_file: results file as :class:`pathlib.Path`
|
||||
|
||||
:return: tuple of :class:`list` as (data points, parsing failures)
|
||||
"""
|
||||
result_values = []
|
||||
parsing_failures = []
|
||||
|
||||
with result_file.open() as csv_file:
|
||||
reader = csv.reader(csv_file)
|
||||
for (test_name, value) in reader:
|
||||
try:
|
||||
params, display_name, operator = get_parameters(test_name)
|
||||
except Exception as err:
|
||||
parsing_failures.append((test_name, f"failed to get parameters: {err}"))
|
||||
continue
|
||||
|
||||
result_values.append({
|
||||
"value": int(value),
|
||||
"test": test_name,
|
||||
"name": display_name,
|
||||
"class": "keygen",
|
||||
"type": "keysize",
|
||||
"operator": operator,
|
||||
"params": params})
|
||||
|
||||
return result_values, parsing_failures
|
||||
|
||||
|
||||
def get_parameters(bench_id):
|
||||
"""
|
||||
Get benchmarks parameters recorded for a given benchmark case.
|
||||
|
||||
:param bench_id: function name used for the benchmark case
|
||||
|
||||
:return: :class:`tuple` as ``(benchmark parameters, display name, operator type)``
|
||||
"""
|
||||
params_dir = pathlib.Path("tfhe", "benchmarks_parameters", bench_id)
|
||||
params = _parse_file_to_json(params_dir, "parameters.json")
|
||||
|
||||
display_name = params.pop("display_name")
|
||||
operator = params.pop("operator_type")
|
||||
|
||||
# Put cryptographic parameters at the same level as the others parameters
|
||||
crypto_params = params.pop("crypto_parameters")
|
||||
params.update(crypto_params)
|
||||
|
||||
return params, display_name, operator
|
||||
|
||||
|
||||
def compute_ops_per_dollar(data_point, product_hourly_cost):
|
||||
"""
|
||||
Compute numbers of operations per dollar for a given ``data_point``.
|
||||
|
||||
:param data_point: timing value measured during benchmark in nanoseconds
|
||||
:param product_hourly_cost: cost in dollar per hour of hardware used
|
||||
|
||||
:return: number of operations per dollar
|
||||
"""
|
||||
return ONE_HOUR_IN_NANOSECONDS / (product_hourly_cost * data_point)
|
||||
|
||||
|
||||
def compute_ops_per_millisecond(data_point):
|
||||
"""
|
||||
Compute numbers of operations per millisecond for a given ``data_point``.
|
||||
|
||||
:param data_point: timing value measured during benchmark in nanoseconds
|
||||
|
||||
:return: number of operations per millisecond
|
||||
"""
|
||||
return 1E6 / data_point
|
||||
|
||||
|
||||
def _parse_file_to_json(directory, filename):
|
||||
result_file = directory.joinpath(filename)
|
||||
return json.loads(result_file.read_text())
|
||||
|
||||
|
||||
def dump_results(parsed_results, filename, input_args):
|
||||
"""
|
||||
Dump parsed results formatted as JSON to file.
|
||||
|
||||
:param parsed_results: :class:`list` of data points
|
||||
:param filename: filename for dump file as :class:`pathlib.Path`
|
||||
:param input_args: CLI input arguments
|
||||
"""
|
||||
for point in parsed_results:
|
||||
point["backend"] = input_args.backend
|
||||
|
||||
if input_args.append_results:
|
||||
parsed_content = json.loads(filename.read_text())
|
||||
parsed_content["points"].extend(parsed_results)
|
||||
filename.write_text(json.dumps(parsed_content))
|
||||
else:
|
||||
filename.parent.mkdir(parents=True, exist_ok=True)
|
||||
series = {
|
||||
"database": input_args.database,
|
||||
"hardware": input_args.hardware,
|
||||
"project_version": input_args.project_version,
|
||||
"branch": input_args.branch,
|
||||
"insert_date": input_args.bench_date,
|
||||
"commit_date": input_args.commit_date,
|
||||
"points": parsed_results,
|
||||
}
|
||||
filename.write_text(json.dumps(series))
|
||||
|
||||
|
||||
def check_mandatory_args(input_args):
|
||||
"""
|
||||
Check for availability of required input arguments, the program will exit if one of them is
|
||||
not present. If `append_results` flag is set, all the required arguments will be ignored.
|
||||
|
||||
:param input_args: CLI input arguments
|
||||
"""
|
||||
if input_args.append_results:
|
||||
return
|
||||
|
||||
missing_args = []
|
||||
for arg_name in vars(input_args):
|
||||
if arg_name in ["results_dir", "output_file", "name_suffix",
|
||||
"append_results", "walk_subdirs", "key_sizes",
|
||||
"throughput"]:
|
||||
continue
|
||||
if not getattr(input_args, arg_name):
|
||||
missing_args.append(arg_name)
|
||||
|
||||
if missing_args:
|
||||
for arg_name in missing_args:
|
||||
print(f"Missing required argument: --{arg_name.replace('_', '-')}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
check_mandatory_args(args)
|
||||
|
||||
#failures = []
|
||||
raw_results = pathlib.Path(args.results)
|
||||
if not args.key_sizes:
|
||||
print("Parsing benchmark results... ")
|
||||
hardware_cost = None
|
||||
if args.throughput:
|
||||
print("Throughput computation enabled")
|
||||
ec2_costs = json.loads(
|
||||
pathlib.Path("ci/ec2_products_cost.json").read_text(encoding="utf-8"))
|
||||
try:
|
||||
hardware_cost = abs(ec2_costs[args.hardware])
|
||||
print(f"Hardware hourly cost: {hardware_cost} $/h")
|
||||
except KeyError:
|
||||
print(f"Cannot find hardware hourly cost for '{args.hardware}'")
|
||||
sys.exit(1)
|
||||
|
||||
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)
|
||||
print(f"Dump parsed results into '{output_file.resolve()}' ... ", end="")
|
||||
dump_results(results, output_file, args)
|
||||
|
||||
print("Done")
|
||||
|
||||
if failures:
|
||||
print("\nParsing failed for some results")
|
||||
print("-------------------------------")
|
||||
for name, error in failures:
|
||||
print(f"[{name}] {error}")
|
||||
sys.exit(1)
|
||||
3
ci/ec2_products_cost.json
Normal file
3
ci/ec2_products_cost.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"m6i.metal": 7.168
|
||||
}
|
||||
42
ci/slab.toml
42
ci/slab.toml
@@ -1,21 +1,39 @@
|
||||
[profile.cpu-big]
|
||||
region = "eu-west-3"
|
||||
image_id = "ami-04deffe45b5b236fd"
|
||||
instance_type = "c5a.8xlarge"
|
||||
instance_type = "m6i.32xlarge"
|
||||
|
||||
[profile.gpu]
|
||||
region = "us-east-1"
|
||||
image_id = "ami-0ae662beb44082155"
|
||||
instance_type = "p3.2xlarge"
|
||||
subnet_id = "subnet-8123c9e7"
|
||||
security_group = "sg-0466d33ced960ba35"
|
||||
[profile.bench]
|
||||
region = "eu-west-3"
|
||||
image_id = "ami-04deffe45b5b236fd"
|
||||
instance_type = "m6i.metal"
|
||||
|
||||
[command.cpu_test]
|
||||
workflow = "aws_tfhe_tests.yml"
|
||||
profile = "cpu-big"
|
||||
check_run_name = "Shortint CPU AWS Tests"
|
||||
check_run_name = "CPU AWS Tests"
|
||||
|
||||
[command.gpu_test]
|
||||
workflow = "aws_tfhe_tests_w_gpu.yml"
|
||||
profile = "gpu"
|
||||
check_run_name = "AWS tests GPU (Slab)"
|
||||
[command.cpu_integer_test]
|
||||
workflow = "aws_tfhe_integer_tests.yml"
|
||||
profile = "cpu-big"
|
||||
check_run_name = "CPU Integer AWS Tests"
|
||||
|
||||
[command.integer_bench]
|
||||
workflow = "integer_benchmark.yml"
|
||||
profile = "bench"
|
||||
check_run_name = "Integer CPU AWS Benchmarks"
|
||||
|
||||
[command.shortint_bench]
|
||||
workflow = "shortint_benchmark.yml"
|
||||
profile = "bench"
|
||||
check_run_name = "Shortint CPU AWS Benchmarks"
|
||||
|
||||
[command.boolean_bench]
|
||||
workflow = "boolean_benchmark.yml"
|
||||
profile = "bench"
|
||||
check_run_name = "Boolean CPU AWS Benchmarks"
|
||||
|
||||
[command.pbs_bench]
|
||||
workflow = "pbs_benchmark.yml"
|
||||
profile = "bench"
|
||||
check_run_name = "PBS CPU AWS Benchmarks"
|
||||
|
||||
41
docker/Dockerfile.wasm_tests
Normal file
41
docker/Dockerfile.wasm_tests
Normal file
@@ -0,0 +1,41 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV TZ=Europe/Paris
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
# Replace default archive.ubuntu.com with fr mirror
|
||||
# original archive showed performance issues and is farther away
|
||||
RUN sed -i 's|^deb http://archive.ubuntu.com/ubuntu/|deb http://mirror.ubuntu.ikoula.com/|g' /etc/apt/sources.list && \
|
||||
sed -i 's|^deb http://security.ubuntu.com/ubuntu/|deb http://mirror.ubuntu.ikoula.com/|g' /etc/apt/sources.list
|
||||
|
||||
ENV CARGO_TARGET_DIR=/root/tfhe-rs-target
|
||||
|
||||
ARG RUST_TOOLCHAIN="stable"
|
||||
|
||||
WORKDIR /tfhe-wasm-tests
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > install-rustup.sh && \
|
||||
chmod +x install-rustup.sh && \
|
||||
./install-rustup.sh -y --default-toolchain "${RUST_TOOLCHAIN}" \
|
||||
-c rust-src -t wasm32-unknown-unknown && \
|
||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf > install-wasm-pack.sh && \
|
||||
chmod +x install-wasm-pack.sh && \
|
||||
. "$HOME/.cargo/env" && \
|
||||
./install-wasm-pack.sh -y && \
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh > install-node.sh && \
|
||||
chmod +x install-node.sh && \
|
||||
./install-node.sh && \
|
||||
. "$HOME/.nvm/nvm.sh" && \
|
||||
bash -i -c 'nvm install node && nvm use node'
|
||||
|
||||
WORKDIR /tfhe-wasm-tests/tfhe-rs/
|
||||
@@ -2,8 +2,37 @@
|
||||
|
||||
set -e
|
||||
|
||||
function usage() {
|
||||
echo "$0: build and/or run the C API tests"
|
||||
echo
|
||||
echo "--help Print this message"
|
||||
echo "--build-only Pass to only build the tests without running them"
|
||||
echo
|
||||
}
|
||||
|
||||
BUILD_ONLY=0
|
||||
|
||||
while [ -n "$1" ]
|
||||
do
|
||||
case "$1" in
|
||||
"--help" | "-h" )
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
|
||||
"--build-only" )
|
||||
BUILD_ONLY=1
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown param : $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
CURR_DIR="$(dirname "$0")"
|
||||
ARCH_FEATURE="$("${CURR_DIR}/get_arch_feature.sh")"
|
||||
REPO_ROOT="${CURR_DIR}/.."
|
||||
TFHE_BUILD_DIR="${REPO_ROOT}/tfhe/build/"
|
||||
|
||||
@@ -13,8 +42,18 @@ cd "${TFHE_BUILD_DIR}"
|
||||
|
||||
cmake .. -DCMAKE_BUILD_TYPE=RELEASE
|
||||
|
||||
RUSTFLAGS="-C target-cpu=native" cargo ${1:+"${1}"} build \
|
||||
--release --features="${ARCH_FEATURE}",boolean-c-api,shortint-c-api -p tfhe
|
||||
|
||||
make -j
|
||||
make "test"
|
||||
|
||||
if [[ "${BUILD_ONLY}" == "1" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
nproc_bin=nproc
|
||||
|
||||
# macOS detects CPUs differently
|
||||
if [[ $(uname) == "Darwin" ]]; then
|
||||
nproc_bin="sysctl -n hw.logicalcpu"
|
||||
fi
|
||||
|
||||
# Let's go parallel
|
||||
ARGS="-j$("${nproc_bin}")" make test
|
||||
|
||||
60
scripts/check_cargo_min_ver.sh
Executable file
60
scripts/check_cargo_min_ver.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
function usage() {
|
||||
echo "$0: check minimum cargo version"
|
||||
echo
|
||||
echo "--help Print this message"
|
||||
echo "--rust-toolchain The toolchain to check the version for with leading"
|
||||
echo "--min-rust-version Check toolchain version is >= to this version, default is 1.65"
|
||||
echo
|
||||
}
|
||||
|
||||
RUST_TOOLCHAIN=""
|
||||
# We set the default rust version 1.65 which is the minimum version required for stable GATs
|
||||
MIN_RUST_VERSION="1.65"
|
||||
|
||||
while [ -n "$1" ]
|
||||
do
|
||||
case "$1" in
|
||||
"--help" | "-h" )
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
|
||||
"--rust-toolchain" )
|
||||
shift
|
||||
RUST_TOOLCHAIN="$1"
|
||||
;;
|
||||
|
||||
"--min-rust-version" )
|
||||
shift
|
||||
MIN_RUST_VERSION="$1"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown param : $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ "${RUST_TOOLCHAIN::1}" != "+" ]]; then
|
||||
RUST_TOOLCHAIN="+${RUST_TOOLCHAIN}"
|
||||
fi
|
||||
|
||||
ver_string="$(cargo ${RUST_TOOLCHAIN:+"${RUST_TOOLCHAIN}"} --version | \
|
||||
cut -d ' ' -f 2 | cut -d '-' -f 1)"
|
||||
ver_major="$(echo "${ver_string}" | cut -d '.' -f 1)"
|
||||
ver_minor="$(echo "${ver_string}" | cut -d '.' -f 2)"
|
||||
|
||||
min_ver_major="$(echo "${MIN_RUST_VERSION}" | cut -d '.' -f 1)"
|
||||
min_ver_minor="$(echo "${MIN_RUST_VERSION}" | cut -d '.' -f 2)"
|
||||
|
||||
if [[ "${ver_major}" -ge "${min_ver_major}" ]] && [[ "${ver_minor}" -ge "${min_ver_minor}" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 1
|
||||
@@ -4,7 +4,7 @@ set -e
|
||||
|
||||
ARCH_FEATURE=x86_64
|
||||
|
||||
IS_AARCH64="$( (uname -a | grep -c arm64) || true)"
|
||||
IS_AARCH64="$( (uname -a | grep -c "arm64\|aarch64") || true)"
|
||||
|
||||
if [[ "${IS_AARCH64}" != "0" ]]; then
|
||||
ARCH_FEATURE=aarch64
|
||||
|
||||
83
scripts/integer-tests.sh
Executable file
83
scripts/integer-tests.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
CURR_DIR="$(dirname "$0")"
|
||||
ARCH_FEATURE="$("${CURR_DIR}/get_arch_feature.sh")"
|
||||
|
||||
nproc_bin=nproc
|
||||
|
||||
# macOS detects CPUs differently
|
||||
if [[ $(uname) == "Darwin" ]]; then
|
||||
nproc_bin="sysctl -n hw.logicalcpu"
|
||||
fi
|
||||
|
||||
n_threads="$(${nproc_bin})"
|
||||
|
||||
if uname -a | grep "arm64"; then
|
||||
if [[ $(uname) == "Darwin" ]]; then
|
||||
# Keys are 4.7 gigs at max, CI M1 macs only has 8 gigs of RAM
|
||||
n_threads=1
|
||||
fi
|
||||
else
|
||||
# Keys are 4.7 gigs at max, test machine has 32 gigs of RAM
|
||||
n_threads=6
|
||||
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$/)'
|
||||
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
--tests \
|
||||
--release \
|
||||
--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::
|
||||
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$/)'
|
||||
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
--tests \
|
||||
--release \
|
||||
--package tfhe \
|
||||
--profile ci \
|
||||
--features="${ARCH_FEATURE}",integer,internal-keycache \
|
||||
--test-threads "$(${nproc_bin})" \
|
||||
-E "$filter_expression"
|
||||
|
||||
cargo ${1:+"${1}"} test \
|
||||
--release \
|
||||
--package tfhe \
|
||||
--features="${ARCH_FEATURE}",integer,internal-keycache \
|
||||
--doc \
|
||||
integer:: -- --test-threads="$(${nproc_bin})"
|
||||
fi
|
||||
|
||||
echo "Test ran in $SECONDS seconds"
|
||||
20
scripts/no_tfhe_typo.sh
Executable file
20
scripts/no_tfhe_typo.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
THIS_SCRIPT_NAME="$(basename "$0")"
|
||||
|
||||
TMP_FILE="$(mktemp)"
|
||||
|
||||
COUNT="$(git grep -rniI "thfe\|tfhr\|thfr" . | 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 "tfhe typo detected, see output log above"
|
||||
exit 1
|
||||
fi
|
||||
@@ -12,49 +12,109 @@ if [[ $(uname) == "Darwin" ]]; then
|
||||
nproc_bin="sysctl -n hw.logicalcpu"
|
||||
fi
|
||||
|
||||
n_threads="$(${nproc_bin})"
|
||||
n_threads_small="$(${nproc_bin})"
|
||||
n_threads_big="${n_threads_small}"
|
||||
|
||||
# TODO: automate thread selection by measuring host machine ram and loading the key sizes from the
|
||||
# 'keys' cache directory keeping a safety margin for test execution
|
||||
|
||||
if uname -a | grep "arm64"; then
|
||||
if [[ $(uname) == "Darwin" ]]; then
|
||||
# Keys are 2 gigs at max, CI M1 macs only has 8 gigs of RAM so a bit conservative here
|
||||
n_threads_small=3
|
||||
# Keys are 4.7 gigs at max, CI M1 macs only has 8 gigs of RAM
|
||||
n_threads=1
|
||||
n_threads_big=1
|
||||
fi
|
||||
else
|
||||
# Keys are 4.7 gigs at max, test machine has 32 gigs of RAM
|
||||
n_threads=6
|
||||
# Keys are 4.7 gigs at max, test machine has 64 gigs of RAM
|
||||
n_threads_big=13
|
||||
fi
|
||||
|
||||
filter_expression=''\
|
||||
if [[ "${BIG_TESTS_INSTANCE}" != TRUE ]]; then
|
||||
filter_expression_small_params=''\
|
||||
'('\
|
||||
' test(/^shortint::server_key::.*_param_message_1_carry_1$/)'\
|
||||
'or test(/^shortint::server_key::.*_param_message_1_carry_2$/)'\
|
||||
'or test(/^shortint::server_key::.*_param_message_1_carry_3$/)'\
|
||||
'or test(/^shortint::server_key::.*_param_message_1_carry_4$/)'\
|
||||
'or test(/^shortint::server_key::.*_param_message_1_carry_5$/)'\
|
||||
'or test(/^shortint::server_key::.*_param_message_1_carry_6$/)'\
|
||||
'or test(/^shortint::server_key::.*_param_message_2_carry_2$/)'\
|
||||
'or test(/^shortint::server_key::.*_param_message_3_carry_3$/)'\
|
||||
'or test(/^shortint::server_key::.*_param_message_4_carry_4$/)'\
|
||||
' test(/^shortint::.*_param_message_1_carry_1$/)'\
|
||||
'or test(/^shortint::.*_param_message_1_carry_2$/)'\
|
||||
'or test(/^shortint::.*_param_message_1_carry_3$/)'\
|
||||
'or test(/^shortint::.*_param_message_1_carry_4$/)'\
|
||||
'or test(/^shortint::.*_param_message_1_carry_5$/)'\
|
||||
'or test(/^shortint::.*_param_message_1_carry_6$/)'\
|
||||
'or test(/^shortint::.*_param_message_2_carry_1$/)'\
|
||||
'or test(/^shortint::.*_param_message_2_carry_2$/)'\
|
||||
'or test(/^shortint::.*_param_message_2_carry_3$/)'\
|
||||
'or test(/^shortint::.*_param_message_3_carry_1$/)'\
|
||||
'or test(/^shortint::.*_param_message_3_carry_2$/)'\
|
||||
'or test(/^shortint::.*_param_message_3_carry_3$/)'\
|
||||
')'\
|
||||
'and not test(~smart_add_and_mul)' # This test is too slow
|
||||
|
||||
export RUSTFLAGS="-C target-cpu=native"
|
||||
# Run tests only no examples or benches with small params and more threads
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
--tests \
|
||||
--release \
|
||||
--package tfhe \
|
||||
--profile ci \
|
||||
--features="${ARCH_FEATURE}",shortint,internal-keycache \
|
||||
--test-threads "${n_threads_small}" \
|
||||
-E "${filter_expression_small_params}"
|
||||
|
||||
# Run tests only no examples or benches
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
--tests \
|
||||
--release \
|
||||
--package tfhe \
|
||||
--profile ci \
|
||||
--features="${ARCH_FEATURE}",shortint,internal-keycache \
|
||||
--test-threads "${n_threads}" \
|
||||
-E "${filter_expression}"
|
||||
filter_expression_big_params=''\
|
||||
'('\
|
||||
' test(/^shortint::.*_param_message_4_carry_4$/)'\
|
||||
')'\
|
||||
'and not test(~smart_add_and_mul)'
|
||||
|
||||
cargo ${1:+"${1}"} test \
|
||||
--release \
|
||||
--package tfhe \
|
||||
--features="${ARCH_FEATURE}",shortint,internal-keycache \
|
||||
--doc \
|
||||
shortint::
|
||||
# Run tests only no examples or benches with big params and less threads
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
--tests \
|
||||
--release \
|
||||
--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::
|
||||
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
|
||||
|
||||
# Run tests only no examples or benches with small params and more threads
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
--tests \
|
||||
--release \
|
||||
--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})"
|
||||
fi
|
||||
|
||||
echo "Test ran in $SECONDS seconds"
|
||||
|
||||
12
tasks/Cargo.toml
Normal file
12
tasks/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "tasks"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = "3.1"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
simplelog = "0.12"
|
||||
453
tasks/src/format_latex_doc.rs
Normal file
453
tasks/src/format_latex_doc.rs
Normal file
@@ -0,0 +1,453 @@
|
||||
use crate::utils::project_root;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::{fmt, fs};
|
||||
|
||||
fn recurse_find_rs_files(
|
||||
root_dir: std::path::PathBuf,
|
||||
rs_files: &mut Vec<std::path::PathBuf>,
|
||||
at_root: bool,
|
||||
) {
|
||||
for curr_entry in root_dir.read_dir().unwrap() {
|
||||
let curr_path = curr_entry.unwrap().path().canonicalize().unwrap();
|
||||
if curr_path.is_file() {
|
||||
if let Some(extension) = curr_path.extension() {
|
||||
if extension == "rs" {
|
||||
rs_files.push(curr_path);
|
||||
}
|
||||
}
|
||||
} else if curr_path.is_dir() {
|
||||
if at_root {
|
||||
// Hardcoded ignores for root .git and target
|
||||
match curr_path.file_name().unwrap().to_str().unwrap() {
|
||||
".git" => continue,
|
||||
"target" => continue,
|
||||
_ => recurse_find_rs_files(curr_path.to_path_buf(), rs_files, false),
|
||||
};
|
||||
} else {
|
||||
recurse_find_rs_files(curr_path.to_path_buf(), rs_files, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LatexEscapeToolError {
|
||||
details: String,
|
||||
}
|
||||
|
||||
impl LatexEscapeToolError {
|
||||
fn new(msg: &str) -> LatexEscapeToolError {
|
||||
LatexEscapeToolError {
|
||||
details: msg.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LatexEscapeToolError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.details)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LatexEscapeToolError {}
|
||||
|
||||
const DOC_TEST_START: &str = "///";
|
||||
const DOC_COMMENT_START: &str = "//!";
|
||||
const BACKSLASH_UTF8_LEN: usize = '\\'.len_utf8();
|
||||
|
||||
enum LineType {
|
||||
DocTest { code_block_limit: bool },
|
||||
DocComment { code_block_limit: bool },
|
||||
EmptyLine,
|
||||
Other,
|
||||
}
|
||||
|
||||
fn get_line_type_and_trimmed_line(line: &str) -> (LineType, &str) {
|
||||
let mut trimmed_line = line.trim_start();
|
||||
let line_type = if trimmed_line.starts_with(DOC_COMMENT_START) {
|
||||
trimmed_line = trimmed_line
|
||||
.strip_prefix(DOC_COMMENT_START)
|
||||
.unwrap()
|
||||
.trim_start();
|
||||
let has_code_block_limit = trimmed_line.starts_with("```");
|
||||
LineType::DocComment {
|
||||
code_block_limit: has_code_block_limit,
|
||||
}
|
||||
} else if trimmed_line.starts_with(DOC_TEST_START) {
|
||||
trimmed_line = trimmed_line
|
||||
.strip_prefix(DOC_TEST_START)
|
||||
.unwrap()
|
||||
.trim_start();
|
||||
let has_code_block_limit = trimmed_line.starts_with("```");
|
||||
LineType::DocTest {
|
||||
code_block_limit: has_code_block_limit,
|
||||
}
|
||||
} else if trimmed_line.is_empty() {
|
||||
LineType::EmptyLine
|
||||
} else {
|
||||
LineType::Other
|
||||
};
|
||||
(line_type, trimmed_line)
|
||||
}
|
||||
|
||||
struct CommentContent<'a> {
|
||||
is_in_code_block: bool,
|
||||
line_start: &'a str,
|
||||
line_content: &'a str,
|
||||
}
|
||||
|
||||
fn find_contiguous_doc_comment<'a>(
|
||||
lines: &[&'a str],
|
||||
start_line_idx: usize,
|
||||
) -> (Vec<CommentContent<'a>>, usize) {
|
||||
let mut doc_comment_end_line_idx = start_line_idx + 1;
|
||||
|
||||
let mut is_in_code_block = false;
|
||||
let mut contiguous_doc_comment = Vec::<CommentContent>::new();
|
||||
|
||||
for (line_idx, line) in lines.iter().enumerate().skip(start_line_idx) {
|
||||
let (line_type, line_content) = get_line_type_and_trimmed_line(line);
|
||||
|
||||
let line_start = &line[..line.len() - line_content.len()];
|
||||
// If there is an empty line we are still in the DocComment
|
||||
let line_type = if let LineType::EmptyLine = line_type {
|
||||
LineType::DocComment {
|
||||
code_block_limit: false,
|
||||
}
|
||||
} else {
|
||||
line_type
|
||||
};
|
||||
|
||||
match line_type {
|
||||
LineType::DocComment { code_block_limit } => {
|
||||
if code_block_limit {
|
||||
// We have found a code block limit, either starting or ending, toggle the
|
||||
// flag
|
||||
is_in_code_block = !is_in_code_block;
|
||||
};
|
||||
contiguous_doc_comment.push(CommentContent {
|
||||
is_in_code_block,
|
||||
line_start,
|
||||
line_content,
|
||||
});
|
||||
// For now the only thing we know is that the next line is potentially the end of
|
||||
// the comment block, required if a file is a giant comment block to have the proper
|
||||
// bound
|
||||
doc_comment_end_line_idx = line_idx + 1;
|
||||
}
|
||||
_ => {
|
||||
// We are sure that the current line is the end of the comment block
|
||||
doc_comment_end_line_idx = line_idx;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
(contiguous_doc_comment, doc_comment_end_line_idx)
|
||||
}
|
||||
|
||||
fn find_contiguous_doc_test<'a>(
|
||||
lines: &[&'a str],
|
||||
start_line_idx: usize,
|
||||
) -> (Vec<CommentContent<'a>>, usize) {
|
||||
let mut doc_test_end_line_idx = start_line_idx + 1;
|
||||
|
||||
let mut is_in_code_block = false;
|
||||
let mut contiguous_doc_test = Vec::<CommentContent>::new();
|
||||
|
||||
for (line_idx, line) in lines.iter().enumerate().skip(start_line_idx) {
|
||||
let (line_type, line_content) = get_line_type_and_trimmed_line(line);
|
||||
|
||||
let line_start = &line[..line.len() - line_content.len()];
|
||||
// If there is an empty line we are still in the DocTest
|
||||
let line_type = if let LineType::EmptyLine = line_type {
|
||||
LineType::DocTest {
|
||||
code_block_limit: false,
|
||||
}
|
||||
} else {
|
||||
line_type
|
||||
};
|
||||
|
||||
match line_type {
|
||||
LineType::DocTest { code_block_limit } => {
|
||||
if code_block_limit {
|
||||
// We have found a code block limit, either starting or ending, toggle the
|
||||
// flag
|
||||
is_in_code_block = !is_in_code_block;
|
||||
};
|
||||
contiguous_doc_test.push(CommentContent {
|
||||
is_in_code_block,
|
||||
line_start,
|
||||
line_content,
|
||||
});
|
||||
// For now the only thing we know is that the next line is potentially the end of
|
||||
// the comment block, required if a file is a giant comment block to have the proper
|
||||
// bound
|
||||
doc_test_end_line_idx = line_idx + 1;
|
||||
}
|
||||
_ => {
|
||||
// We are sure that the current line is the end of the comment block
|
||||
doc_test_end_line_idx = line_idx;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
(contiguous_doc_test, doc_test_end_line_idx)
|
||||
}
|
||||
|
||||
fn find_contiguous_part_in_doc_test_or_comment(
|
||||
part_is_code_block: bool,
|
||||
full_doc_comment_content: &Vec<CommentContent>,
|
||||
part_start_idx: usize,
|
||||
) -> (usize, usize) {
|
||||
let mut next_line_idx = part_start_idx + 1;
|
||||
loop {
|
||||
// We have exhausted the doc comment content, break
|
||||
if next_line_idx == full_doc_comment_content.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let CommentContent {
|
||||
is_in_code_block: next_line_is_in_code_block,
|
||||
line_start: _,
|
||||
line_content: _,
|
||||
} = full_doc_comment_content[next_line_idx];
|
||||
|
||||
// We check if the next line is in a different part, if so we break
|
||||
if next_line_is_in_code_block != part_is_code_block {
|
||||
break;
|
||||
}
|
||||
next_line_idx += 1;
|
||||
}
|
||||
// next_line_idx points to the end of the part and is therefore returned as the part_stop_idx
|
||||
(part_start_idx, next_line_idx)
|
||||
}
|
||||
|
||||
enum LatexEquationKind {
|
||||
Inline,
|
||||
Multiline,
|
||||
NotAnEquation,
|
||||
}
|
||||
|
||||
fn escape_underscores_rewrite_equations(
|
||||
comment_to_rewrite: &[CommentContent],
|
||||
rewritten_content: &mut String,
|
||||
) -> Result<(), LatexEscapeToolError> {
|
||||
let mut latex_equation_kind = LatexEquationKind::NotAnEquation;
|
||||
for CommentContent {
|
||||
is_in_code_block: _,
|
||||
line_start,
|
||||
line_content,
|
||||
} in comment_to_rewrite.iter()
|
||||
{
|
||||
rewritten_content.push_str(line_start);
|
||||
let mut previous_char = '\0';
|
||||
let mut chars = line_content.chars().peekable();
|
||||
while let Some(current_char) = chars.next() {
|
||||
match (previous_char, current_char) {
|
||||
('$', '$') => {
|
||||
match latex_equation_kind {
|
||||
LatexEquationKind::Inline => {
|
||||
// Problem we find an opening $$ after an opening $, return an error
|
||||
return Err(LatexEscapeToolError::new(
|
||||
"Found an opening '$' without a corresponding closing '$'",
|
||||
));
|
||||
}
|
||||
LatexEquationKind::Multiline => {
|
||||
// Closing $$, no more in a latex equation
|
||||
latex_equation_kind = LatexEquationKind::NotAnEquation
|
||||
}
|
||||
LatexEquationKind::NotAnEquation => {
|
||||
// Opening $$, in a multiline latex equation
|
||||
latex_equation_kind = LatexEquationKind::Multiline
|
||||
}
|
||||
};
|
||||
}
|
||||
(_, '$') => {
|
||||
let is_inline_marker = chars.peek() != Some(&'$');
|
||||
if is_inline_marker {
|
||||
match latex_equation_kind {
|
||||
LatexEquationKind::Multiline => {
|
||||
// Problem we find an opening $ after an opening $$, return an error
|
||||
return Err(LatexEscapeToolError::new(
|
||||
"Found an opening '$$' without a corresponding closing '$$'",
|
||||
));
|
||||
}
|
||||
LatexEquationKind::Inline => {
|
||||
// Closing $, no more in a latex equation
|
||||
latex_equation_kind = LatexEquationKind::NotAnEquation
|
||||
}
|
||||
LatexEquationKind::NotAnEquation => {
|
||||
// Opening $, in an inline latex equation
|
||||
latex_equation_kind = LatexEquationKind::Inline
|
||||
}
|
||||
};
|
||||
}
|
||||
// If the marker is not an inline marker but a multiline marker let the other
|
||||
// case manage it at the next iteration
|
||||
}
|
||||
// If the _ is not escaped and we are in an equation we need to escape it
|
||||
(prev, '_') if prev != '\\' => match latex_equation_kind {
|
||||
LatexEquationKind::NotAnEquation => (),
|
||||
_ => rewritten_content.push('\\'),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
rewritten_content.push(current_char);
|
||||
previous_char = current_char;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_doc_lines_until_impossible<'a>(
|
||||
lines: &[&'a str],
|
||||
rewritten_content: &'a mut String,
|
||||
comment_search_fn: fn(&[&'a str], usize) -> (Vec<CommentContent<'a>>, usize),
|
||||
start_line_idx: usize,
|
||||
) -> Result<usize, LatexEscapeToolError> {
|
||||
let (full_doc_content, doc_end_line_idx) = comment_search_fn(lines, start_line_idx);
|
||||
|
||||
// Now we find code blocks parts OR pure comments parts
|
||||
let mut current_line_in_doc_idx = 0;
|
||||
while current_line_in_doc_idx < full_doc_content.len() {
|
||||
let CommentContent {
|
||||
is_in_code_block,
|
||||
line_start: _,
|
||||
line_content: _,
|
||||
} = full_doc_content[current_line_in_doc_idx];
|
||||
|
||||
let (current_part_start_idx, current_part_stop_idx) =
|
||||
find_contiguous_part_in_doc_test_or_comment(
|
||||
is_in_code_block,
|
||||
&full_doc_content,
|
||||
current_line_in_doc_idx,
|
||||
);
|
||||
|
||||
let current_part_content = &full_doc_content[current_part_start_idx..current_part_stop_idx];
|
||||
|
||||
// The current part is a code block
|
||||
if is_in_code_block {
|
||||
for CommentContent {
|
||||
is_in_code_block: _,
|
||||
line_start,
|
||||
line_content,
|
||||
} in current_part_content.iter()
|
||||
{
|
||||
// We can just push the content unmodified
|
||||
rewritten_content.push_str(line_start);
|
||||
rewritten_content.push_str(line_content);
|
||||
}
|
||||
} else {
|
||||
// The part is a pure comment, we need to rewrite equations
|
||||
escape_underscores_rewrite_equations(current_part_content, rewritten_content)?;
|
||||
}
|
||||
current_line_in_doc_idx += current_part_content.len();
|
||||
}
|
||||
|
||||
Ok(doc_end_line_idx)
|
||||
}
|
||||
|
||||
fn process_non_doc_lines_until_impossible(
|
||||
lines: &Vec<&str>,
|
||||
rewritten_content: &mut String,
|
||||
mut line_idx: usize,
|
||||
) -> usize {
|
||||
while line_idx < lines.len() {
|
||||
let line = lines[line_idx];
|
||||
match get_line_type_and_trimmed_line(line) {
|
||||
(LineType::Other, _) => {
|
||||
rewritten_content.push_str(line);
|
||||
line_idx += 1;
|
||||
}
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
line_idx
|
||||
}
|
||||
|
||||
fn escape_underscore_in_latex_doc_in_file(
|
||||
file_path: &std::path::Path,
|
||||
) -> Result<(), LatexEscapeToolError> {
|
||||
let file_name = file_path.to_str().unwrap();
|
||||
let content = std::fs::read_to_string(file_name).unwrap();
|
||||
|
||||
let number_of_underscores = content.matches('_').count();
|
||||
let potential_additional_capacity_required = number_of_underscores * BACKSLASH_UTF8_LEN;
|
||||
|
||||
// Enough for the length of the original string + the length if we had to escape *all* `_`
|
||||
// which won't happen but avoids reallocations
|
||||
let mut rewritten_content =
|
||||
String::with_capacity(content.len() + potential_additional_capacity_required);
|
||||
|
||||
let content_by_lines: Vec<&str> = content.split_inclusive('\n').collect();
|
||||
let mut line_idx = 0_usize;
|
||||
|
||||
while line_idx < content_by_lines.len() {
|
||||
let line = content_by_lines[line_idx];
|
||||
let (line_type, _) = get_line_type_and_trimmed_line(line);
|
||||
line_idx = match line_type {
|
||||
LineType::DocComment {
|
||||
code_block_limit: _,
|
||||
} => process_doc_lines_until_impossible(
|
||||
&content_by_lines,
|
||||
&mut rewritten_content,
|
||||
find_contiguous_doc_comment,
|
||||
line_idx,
|
||||
)?,
|
||||
LineType::DocTest {
|
||||
code_block_limit: _,
|
||||
} => process_doc_lines_until_impossible(
|
||||
&content_by_lines,
|
||||
&mut rewritten_content,
|
||||
find_contiguous_doc_test,
|
||||
line_idx,
|
||||
)?,
|
||||
LineType::Other => process_non_doc_lines_until_impossible(
|
||||
&content_by_lines,
|
||||
&mut rewritten_content,
|
||||
line_idx,
|
||||
),
|
||||
LineType::EmptyLine => {
|
||||
rewritten_content.push_str(line);
|
||||
line_idx + 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fs::write(file_name, rewritten_content).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn escape_underscore_in_latex_doc() -> Result<(), Error> {
|
||||
let project_root = project_root();
|
||||
let mut src_files: Vec<std::path::PathBuf> = Vec::new();
|
||||
recurse_find_rs_files(project_root, &mut src_files, true);
|
||||
|
||||
println!("Found {} files to process.", src_files.len());
|
||||
|
||||
let mut files_with_problems: Vec<(std::path::PathBuf, LatexEscapeToolError)> = Vec::new();
|
||||
|
||||
println!("Processing...");
|
||||
for file in src_files.into_iter() {
|
||||
if let Err(err) = escape_underscore_in_latex_doc_in_file(&file) {
|
||||
files_with_problems.push((file, err));
|
||||
}
|
||||
}
|
||||
println!("Done!");
|
||||
|
||||
if !files_with_problems.is_empty() {
|
||||
for (file_with_problem, error) in files_with_problems.iter() {
|
||||
println!(
|
||||
"File: {}, has error: {}",
|
||||
file_with_problem.display(),
|
||||
error
|
||||
);
|
||||
}
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Issues while processing files, check log.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
88
tasks/src/main.rs
Normal file
88
tasks/src/main.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
use clap::{Arg, Command};
|
||||
use log::LevelFilter;
|
||||
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
mod format_latex_doc;
|
||||
mod utils;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// CONSTANTS
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
lazy_static! {
|
||||
static ref DRY_RUN: AtomicBool = AtomicBool::new(false);
|
||||
static ref ROOT_DIR: PathBuf = utils::project_root();
|
||||
static ref ENV_TARGET_NATIVE: utils::Environment = {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("RUSTFLAGS", "-Ctarget-cpu=native");
|
||||
env
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// MACROS
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cmd {
|
||||
(<$env: ident> $cmd: expr) => {
|
||||
$crate::utils::execute($cmd, Some(&*$env), Some(&*$crate::ROOT_DIR))
|
||||
};
|
||||
($cmd: expr) => {
|
||||
$crate::utils::execute($cmd, None, Some(&*$crate::ROOT_DIR))
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// MAIN
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
// We parse the input args
|
||||
let matches = Command::new("tasks")
|
||||
.about("Rust scripts runner")
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.help("Prints debug messages"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("dry-run")
|
||||
.long("dry-run")
|
||||
.help("Do not execute the commands"),
|
||||
)
|
||||
.subcommand(Command::new("format_latex_doc").about("Escape underscores in latex equations"))
|
||||
.arg_required_else_help(true)
|
||||
.get_matches();
|
||||
|
||||
// We initialize the logger with proper verbosity
|
||||
let verb = if matches.contains_id("verbose") {
|
||||
LevelFilter::Debug
|
||||
} else {
|
||||
LevelFilter::Info
|
||||
};
|
||||
CombinedLogger::init(vec![TermLogger::new(
|
||||
verb,
|
||||
Config::default(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
// We set the dry-run mode if present
|
||||
if matches.contains_id("dry-run") {
|
||||
DRY_RUN.store(true, Relaxed);
|
||||
}
|
||||
|
||||
if matches.subcommand_matches("format_latex_doc").is_some() {
|
||||
format_latex_doc::escape_underscore_in_latex_doc()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
50
tasks/src/utils.rs
Normal file
50
tasks/src/utils.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use log::{debug, info};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
pub type Environment = HashMap<&'static str, &'static str>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn execute(cmd: &str, env: Option<&Environment>, cwd: Option<&PathBuf>) -> Result<(), Error> {
|
||||
info!("Executing {}", cmd);
|
||||
debug!("Env {:?}", env);
|
||||
debug!("Cwd {:?}", cwd);
|
||||
if crate::DRY_RUN.load(Relaxed) {
|
||||
info!("Skipping execution because of --dry-run mode");
|
||||
return Ok(());
|
||||
}
|
||||
let mut command = Command::new("sh");
|
||||
command
|
||||
.arg("-c")
|
||||
.arg(cmd)
|
||||
.stderr(Stdio::inherit())
|
||||
.stdout(Stdio::inherit());
|
||||
if let Some(env) = env {
|
||||
for (key, val) in env.iter() {
|
||||
command.env(key, val);
|
||||
}
|
||||
}
|
||||
if let Some(cwd) = cwd {
|
||||
command.current_dir(cwd);
|
||||
}
|
||||
let output = command.output()?;
|
||||
if !output.status.success() {
|
||||
Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
"Command exited with nonzero status.",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_root() -> PathBuf {
|
||||
Path::new(&env!("CARGO_MANIFEST_DIR"))
|
||||
.ancestors()
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.to_path_buf()
|
||||
}
|
||||
160
tfhe/Cargo.toml
160
tfhe/Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tfhe"
|
||||
version = "0.1.0"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
|
||||
@@ -8,39 +8,49 @@ homepage = "https://zama.ai/"
|
||||
documentation = "https://docs.zama.ai/tfhe-rs"
|
||||
repository = "https://github.com/zama-ai/tfhe-rs"
|
||||
license = "BSD-3-Clause-Clear"
|
||||
description = "Concrete is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
|
||||
description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
|
||||
build = "build.rs"
|
||||
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt"]
|
||||
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt", "/js_on_wasm_tests/"]
|
||||
rust-version = "1.65"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7"
|
||||
rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
kolmogorov_smirnov = "1.1.0"
|
||||
paste = "1.0.7"
|
||||
lazy_static = { version = "1.4.0" }
|
||||
criterion = "0.3.5"
|
||||
criterion = "0.4.0"
|
||||
doc-comment = "0.3.3"
|
||||
serde_json = "1.0.94"
|
||||
# Used in user documentation
|
||||
bincode = "1.3.3"
|
||||
fs2 = { version = "0.4.3"}
|
||||
fs2 = { version = "0.4.3" }
|
||||
itertools = "0.10.5"
|
||||
num_cpus = "1.15"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = { version = "0.24.3", optional = true }
|
||||
|
||||
[dependencies]
|
||||
concrete-csprng = { version = "0.2.1" }
|
||||
concrete-cuda = { version = "0.1.1", optional = true }
|
||||
concrete-csprng = { version = "0.3.0", features = [
|
||||
"generator_fallback",
|
||||
"parallel",
|
||||
] }
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
rayon = { version = "1.5.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
rayon = { version = "1.5.0" }
|
||||
bincode = { version = "1.3.3", optional = true }
|
||||
concrete-fft = { version = "0.1", optional = true }
|
||||
aligned-vec = "0.5"
|
||||
dyn-stack = { version = "0.8", optional = true }
|
||||
concrete-fft = { version = "0.2.1", features = ["serde", "fft128"] }
|
||||
pulp = "0.11"
|
||||
aligned-vec = { version = "0.5", features = ["serde"] }
|
||||
dyn-stack = { version = "0.9" }
|
||||
once_cell = "1.13"
|
||||
paste = "1.0.7"
|
||||
fs2 = { version = "0.4.3", optional = true }
|
||||
# While we wait for repeat_n in rust standard library
|
||||
itertools = "0.10.5"
|
||||
|
||||
# wasm deps
|
||||
wasm-bindgen = { version = "0.2.63", features = [
|
||||
@@ -50,15 +60,23 @@ 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 }
|
||||
getrandom = { version = "0.2.8", optional = true }
|
||||
bytemuck = "1.13.1"
|
||||
|
||||
[features]
|
||||
boolean = ["minimal_core_crypto_features"]
|
||||
shortint = ["minimal_core_crypto_features"]
|
||||
internal-keycache = ["lazy_static", "fs2"]
|
||||
boolean = []
|
||||
shortint = []
|
||||
integer = ["shortint"]
|
||||
internal-keycache = ["lazy_static", "fs2", "bincode"]
|
||||
|
||||
__c_api = ["cbindgen", "minimal_core_crypto_features"]
|
||||
# Experimental section
|
||||
experimental = []
|
||||
experimental-force_fft_algo_dif4 = []
|
||||
# End experimental section
|
||||
|
||||
__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"]
|
||||
|
||||
__wasm_api = [
|
||||
"wasm-bindgen",
|
||||
@@ -67,87 +85,40 @@ __wasm_api = [
|
||||
"serde-wasm-bindgen",
|
||||
"getrandom",
|
||||
"getrandom/js",
|
||||
"bincode",
|
||||
]
|
||||
boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
|
||||
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
|
||||
|
||||
cuda = ["backend_cuda"]
|
||||
nightly-avx512 = ["backend_fft_nightly_avx512"]
|
||||
|
||||
# A pure-rust CPU backend.
|
||||
backend_default = ["concrete-csprng/generator_soft"]
|
||||
|
||||
# An accelerated backend, using the `concrete-fft` library.
|
||||
backend_fft = ["concrete-fft", "dyn-stack"]
|
||||
backend_fft_serialization = [
|
||||
"bincode",
|
||||
"concrete-fft/serde",
|
||||
"aligned-vec/serde",
|
||||
"__commons_serialization",
|
||||
]
|
||||
backend_fft_nightly_avx512 = ["concrete-fft/nightly"]
|
||||
|
||||
# Enables the parallel engine in default backend.
|
||||
backend_default_parallel = ["__commons_parallel"]
|
||||
nightly-avx512 = ["concrete-fft/nightly", "pulp/nightly"]
|
||||
|
||||
# Enable the x86_64 specific accelerated implementation of the random generator for the default
|
||||
# backend
|
||||
backend_default_generator_x86_64_aesni = [
|
||||
"concrete-csprng/generator_x86_64_aesni",
|
||||
]
|
||||
generator_x86_64_aesni = ["concrete-csprng/generator_x86_64_aesni"]
|
||||
|
||||
# Enable the aarch64 specific accelerated implementation of the random generator for the default
|
||||
# backend
|
||||
backend_default_generator_aarch64_aes = [
|
||||
"concrete-csprng/generator_aarch64_aes",
|
||||
]
|
||||
|
||||
# Enable the serialization engine in the default backend.
|
||||
backend_default_serialization = ["bincode", "__commons_serialization"]
|
||||
|
||||
# A GPU backend, relying on Cuda acceleration
|
||||
backend_cuda = ["concrete-cuda"]
|
||||
generator_aarch64_aes = ["concrete-csprng/generator_aarch64_aes"]
|
||||
|
||||
# Private features
|
||||
__profiling = []
|
||||
__private_docs = []
|
||||
__commons_parallel = ["rayon", "concrete-csprng/parallel"]
|
||||
__commons_serialization = ["serde", "serde/derive"]
|
||||
|
||||
seeder_unix = ["concrete-csprng/seeder_unix"]
|
||||
seeder_x86_64_rdseed = ["concrete-csprng/seeder_x86_64_rdseed"]
|
||||
|
||||
minimal_core_crypto_features = [
|
||||
"backend_default",
|
||||
"backend_default_parallel",
|
||||
"backend_default_serialization",
|
||||
"backend_fft",
|
||||
"backend_fft_serialization",
|
||||
]
|
||||
|
||||
# These target_arch features enable a set of public features for concrete-core if users want a known
|
||||
# good/working configuration for concrete-core.
|
||||
# These target_arch features enable a set of public features for tfhe if users want a known
|
||||
# good/working configuration for tfhe.
|
||||
# For a target_arch that does not yet have such a feature, one can still enable features manually or
|
||||
# create a feature for said target_arch to make its use simpler.
|
||||
x86_64 = [
|
||||
"minimal_core_crypto_features",
|
||||
"backend_default_generator_x86_64_aesni",
|
||||
"seeder_x86_64_rdseed",
|
||||
]
|
||||
x86_64 = ["generator_x86_64_aesni", "seeder_x86_64_rdseed"]
|
||||
x86_64-unix = ["x86_64", "seeder_unix"]
|
||||
|
||||
# CUDA builds are Unix only at the moment
|
||||
x86_64-unix-cuda = ["x86_64-unix", "cuda"]
|
||||
|
||||
aarch64 = [
|
||||
"minimal_core_crypto_features",
|
||||
"backend_default_generator_aarch64_aes",
|
||||
]
|
||||
aarch64 = ["generator_aarch64_aes"]
|
||||
aarch64-unix = ["aarch64", "seeder_unix"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
# TODO: manage builds for docs.rs based on their documentation https://docs.rs/about
|
||||
features = ["x86_64-unix", "boolean", "shortint"]
|
||||
features = ["x86_64-unix", "boolean", "shortint", "integer"]
|
||||
rustdoc-args = ["--html-in-header", "katex-header.html"]
|
||||
|
||||
###########
|
||||
@@ -156,6 +127,23 @@ rustdoc-args = ["--html-in-header", "katex-header.html"]
|
||||
# #
|
||||
###########
|
||||
|
||||
[[bench]]
|
||||
name = "pbs-bench"
|
||||
path = "benches/core_crypto/pbs_bench.rs"
|
||||
harness = false
|
||||
required-features = ["boolean", "shortint", "internal-keycache"]
|
||||
|
||||
[[bench]]
|
||||
name = "dev-bench"
|
||||
path = "benches/core_crypto/dev_bench.rs"
|
||||
harness = false
|
||||
required-features = ["experimental", "internal-keycache"]
|
||||
|
||||
[[bench]]
|
||||
name = "pbs128-bench"
|
||||
path = "benches/core_crypto/pbs128_bench.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "boolean-bench"
|
||||
path = "benches/boolean/bench.rs"
|
||||
@@ -168,10 +156,36 @@ path = "benches/shortint/bench.rs"
|
||||
harness = false
|
||||
required-features = ["shortint", "internal-keycache"]
|
||||
|
||||
[[bench]]
|
||||
name = "integer-bench"
|
||||
path = "benches/integer/bench.rs"
|
||||
harness = false
|
||||
required-features = ["integer", "internal-keycache"]
|
||||
|
||||
[[bench]]
|
||||
name = "keygen"
|
||||
path = "benches/keygen/bench.rs"
|
||||
harness = false
|
||||
required-features = ["shortint", "internal-keycache"]
|
||||
|
||||
[[bench]]
|
||||
name = "utilities"
|
||||
path = "benches/utilities.rs"
|
||||
harness = false
|
||||
required-features = ["boolean", "shortint", "integer", "internal-keycache"]
|
||||
|
||||
[[example]]
|
||||
name = "generates_test_keys"
|
||||
required-features = ["shortint", "internal-keycache"]
|
||||
|
||||
[[example]]
|
||||
name = "boolean_key_sizes"
|
||||
required-features = ["boolean", "internal-keycache"]
|
||||
|
||||
[[example]]
|
||||
name = "shortint_key_sizes"
|
||||
required-features = ["shortint", "internal-keycache"]
|
||||
|
||||
[[example]]
|
||||
name = "micro_bench_and"
|
||||
required-features = ["boolean"]
|
||||
|
||||
60
tfhe/LICENSE
60
tfhe/LICENSE
@@ -1,32 +1,28 @@
|
||||
BSD 3-Clause Clear License
|
||||
|
||||
Copyright © 2022 ZAMA.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE*.
|
||||
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
ZAMA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*In addition to the rights carried by this license, ZAMA grants to the user a non-exclusive,
|
||||
free and non-commercial license on all patents filed in its name relating to the open-source
|
||||
code (the "Patents") for the sole purpose of evaluation, development, research, prototyping
|
||||
and experimentation.
|
||||
BSD 3-Clause Clear License
|
||||
|
||||
Copyright © 2023 ZAMA.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
ZAMA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use tfhe::boolean::client_key::ClientKey;
|
||||
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
|
||||
@@ -14,7 +18,9 @@ criterion_main!(gates_benches);
|
||||
|
||||
// Put all `bench_function` in one place
|
||||
// so the keygen is only run once per parameters saving time.
|
||||
fn bench_gates(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
let mut bench_group = c.benchmark_group("gates_benches");
|
||||
|
||||
let cks = ClientKey::new(¶ms);
|
||||
let sks = ServerKey::new(&cks);
|
||||
|
||||
@@ -22,39 +28,41 @@ fn bench_gates(c: &mut Criterion, params: BooleanParameters, parameter_name: &st
|
||||
let ct2 = cks.encrypt(false);
|
||||
let ct3 = cks.encrypt(true);
|
||||
|
||||
let id = format!("AND gate {}", parameter_name);
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.and(&ct1, &ct2))));
|
||||
let operator = OperatorType::Atomic;
|
||||
|
||||
let id = format!("NAND gate {}", parameter_name);
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.nand(&ct1, &ct2))));
|
||||
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);
|
||||
|
||||
let id = format!("OR gate {}", parameter_name);
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.or(&ct1, &ct2))));
|
||||
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);
|
||||
|
||||
let id = format!("XOR gate {}", parameter_name);
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.xor(&ct1, &ct2))));
|
||||
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);
|
||||
|
||||
let id = format!("XNOR gate {}", parameter_name);
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.xnor(&ct1, &ct2))));
|
||||
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);
|
||||
|
||||
let id = format!("NOT gate {}", parameter_name);
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.not(&ct1))));
|
||||
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);
|
||||
|
||||
let id = format!("MUX gate {}", parameter_name);
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.mux(&ct1, &ct2, &ct3))));
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
fn bench_default_parameters(c: &mut Criterion) {
|
||||
bench_gates(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn bench_default_parameters(_: &mut Criterion) {
|
||||
let _ = DEFAULT_PARAMETERS; // to avoid unused import warnings
|
||||
println!("DEFAULT_PARAMETERS not benched as they are not compatible with the cuda feature.");
|
||||
benchs(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
|
||||
}
|
||||
|
||||
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
|
||||
bench_gates(c, TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS");
|
||||
benchs(c, TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS");
|
||||
}
|
||||
|
||||
332
tfhe/benches/core_crypto/dev_bench.rs
Normal file
332
tfhe/benches/core_crypto/dev_bench.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
|
||||
criterion_group!(
|
||||
boolean_like_pbs_group,
|
||||
multi_bit_pbs::<u32>,
|
||||
pbs::<u32>,
|
||||
mem_optimized_pbs::<u32>
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
shortint_like_pbs_group,
|
||||
multi_bit_pbs::<u64>,
|
||||
pbs::<u64>,
|
||||
mem_optimized_pbs::<u64>
|
||||
);
|
||||
|
||||
criterion_main!(boolean_like_pbs_group, shortint_like_pbs_group);
|
||||
|
||||
fn get_bench_params<Scalar: Numeric>() -> (
|
||||
LweDimension,
|
||||
StandardDev,
|
||||
DecompositionBaseLog,
|
||||
DecompositionLevelCount,
|
||||
GlweDimension,
|
||||
PolynomialSize,
|
||||
LweBskGroupingFactor,
|
||||
ThreadCount,
|
||||
) {
|
||||
if Scalar::BITS == 64 {
|
||||
(
|
||||
LweDimension(742),
|
||||
StandardDev(0.000007069849454709433),
|
||||
DecompositionBaseLog(3),
|
||||
DecompositionLevelCount(5),
|
||||
GlweDimension(1),
|
||||
PolynomialSize(1024),
|
||||
LweBskGroupingFactor(2),
|
||||
ThreadCount(5),
|
||||
)
|
||||
} else if Scalar::BITS == 32 {
|
||||
(
|
||||
LweDimension(778),
|
||||
StandardDev(0.000003725679281679651),
|
||||
DecompositionBaseLog(18),
|
||||
DecompositionLevelCount(1),
|
||||
GlweDimension(3),
|
||||
PolynomialSize(512),
|
||||
LweBskGroupingFactor(2),
|
||||
ThreadCount(5),
|
||||
)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Sync>(
|
||||
c: &mut Criterion,
|
||||
) {
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
// computations
|
||||
// Define parameters for LweBootstrapKey creation
|
||||
|
||||
let (
|
||||
mut input_lwe_dimension,
|
||||
lwe_modular_std_dev,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
grouping_factor,
|
||||
thread_count,
|
||||
) = get_bench_params::<Scalar>();
|
||||
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
while input_lwe_dimension.0 % grouping_factor.0 != 0 {
|
||||
input_lwe_dimension = LweDimension(input_lwe_dimension.0 + 1);
|
||||
}
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
// Create the LweSecretKey
|
||||
let input_lwe_secret_key =
|
||||
allocate_and_generate_new_binary_lwe_secret_key(input_lwe_dimension, &mut secret_generator);
|
||||
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
|
||||
allocate_and_generate_new_binary_glwe_secret_key(
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
|
||||
|
||||
let multi_bit_bsk = FourierLweMultiBitBootstrapKey::new(
|
||||
input_lwe_dimension,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
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),
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
// 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(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let id = format!("Multi Bit PBS {}", Scalar::BITS);
|
||||
#[allow(clippy::unit_arg)]
|
||||
{
|
||||
c.bench_function(&id, |b| {
|
||||
b.iter(|| {
|
||||
multi_bit_programmable_bootstrap_lwe_ciphertext(
|
||||
&lwe_ciphertext_in,
|
||||
&mut out_pbs_ct,
|
||||
&accumulator.as_view(),
|
||||
&multi_bit_bsk,
|
||||
thread_count,
|
||||
);
|
||||
black_box(&mut out_pbs_ct);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
// computations
|
||||
// Define parameters for LweBootstrapKey creation
|
||||
|
||||
let (
|
||||
input_lwe_dimension,
|
||||
lwe_modular_std_dev,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
_,
|
||||
_,
|
||||
) = get_bench_params::<Scalar>();
|
||||
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
// Create the LweSecretKey
|
||||
let input_lwe_secret_key =
|
||||
allocate_and_generate_new_binary_lwe_secret_key(input_lwe_dimension, &mut secret_generator);
|
||||
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
|
||||
allocate_and_generate_new_binary_glwe_secret_key(
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
|
||||
|
||||
// Create the empty bootstrapping key in the Fourier domain
|
||||
let fourier_bsk = FourierLweBootstrapKey::new(
|
||||
input_lwe_dimension,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
);
|
||||
|
||||
// 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),
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
// 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(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let id = format!("PBS {}", Scalar::BITS);
|
||||
{
|
||||
c.bench_function(&id, |b| {
|
||||
b.iter(|| {
|
||||
programmable_bootstrap_lwe_ciphertext(
|
||||
&lwe_ciphertext_in,
|
||||
&mut out_pbs_ct,
|
||||
&accumulator.as_view(),
|
||||
&fourier_bsk,
|
||||
);
|
||||
black_box(&mut out_pbs_ct);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
// computations
|
||||
// Define parameters for LweBootstrapKey creation
|
||||
|
||||
let (
|
||||
input_lwe_dimension,
|
||||
lwe_modular_std_dev,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
_,
|
||||
_,
|
||||
) = get_bench_params::<Scalar>();
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
// Create the LweSecretKey
|
||||
let input_lwe_secret_key =
|
||||
allocate_and_generate_new_binary_lwe_secret_key(input_lwe_dimension, &mut secret_generator);
|
||||
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
|
||||
allocate_and_generate_new_binary_glwe_secret_key(
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
|
||||
|
||||
// Create the empty bootstrapping key in the Fourier domain
|
||||
let fourier_bsk = FourierLweBootstrapKey::new(
|
||||
input_lwe_dimension,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
);
|
||||
|
||||
// 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),
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
// 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(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
buffers.resize(
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<Scalar>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
|
||||
let id = format!("PBS mem-optimized {}", Scalar::BITS);
|
||||
{
|
||||
c.bench_function(&id, |b| {
|
||||
b.iter(|| {
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
&lwe_ciphertext_in,
|
||||
&mut out_pbs_ct,
|
||||
&accumulator.as_view(),
|
||||
&fourier_bsk,
|
||||
fft,
|
||||
buffers.stack(),
|
||||
);
|
||||
black_box(&mut out_pbs_ct);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
108
tfhe/benches/core_crypto/pbs128_bench.rs
Normal file
108
tfhe/benches/core_crypto/pbs128_bench.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use dyn_stack::PodStack;
|
||||
|
||||
fn sqr(x: f64) -> f64 {
|
||||
x * x
|
||||
}
|
||||
|
||||
fn criterion_bench(c: &mut Criterion) {
|
||||
{
|
||||
use tfhe::core_crypto::fft_impl::fft128::crypto::bootstrap::bootstrap_scratch;
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
type Scalar = u128;
|
||||
|
||||
let small_lwe_dimension = LweDimension(742);
|
||||
let glwe_dimension = GlweDimension(1);
|
||||
let polynomial_size = PolynomialSize(2048);
|
||||
let lwe_modular_std_dev = StandardDev(sqr(0.000007069849454709433));
|
||||
let pbs_base_log = DecompositionBaseLog(23);
|
||||
let pbs_level = DecompositionLevelCount(1);
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
let mut boxed_seeder = new_seeder();
|
||||
let seeder = boxed_seeder.as_mut();
|
||||
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
|
||||
let small_lwe_sk =
|
||||
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
|
||||
|
||||
let glwe_sk = GlweSecretKey::<Vec<Scalar>>::generate_new_binary(
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
|
||||
let big_lwe_sk = glwe_sk.into_lwe_secret_key();
|
||||
|
||||
let fourier_bsk = Fourier128LweBootstrapKey::new(
|
||||
small_lwe_dimension,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
pbs_base_log,
|
||||
pbs_level,
|
||||
);
|
||||
|
||||
let fft = Fft128::new(polynomial_size);
|
||||
let fft = fft.as_view();
|
||||
|
||||
let message_modulus: Scalar = 1 << 4;
|
||||
|
||||
let input_message: Scalar = 3;
|
||||
|
||||
let delta: Scalar = (1 << (Scalar::BITS - 1)) / message_modulus;
|
||||
|
||||
let plaintext = Plaintext(input_message * delta);
|
||||
|
||||
let lwe_ciphertext_in: LweCiphertextOwned<Scalar> = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
&small_lwe_sk,
|
||||
plaintext,
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator: GlweCiphertextOwned<Scalar> = GlweCiphertextOwned::new(
|
||||
Scalar::ONE,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let mut pbs_out: LweCiphertext<Vec<Scalar>> = LweCiphertext::new(
|
||||
0,
|
||||
big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let mut buf = vec![
|
||||
0u8;
|
||||
bootstrap_scratch::<Scalar>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required()
|
||||
];
|
||||
|
||||
c.bench_function("pbs128", |b| {
|
||||
b.iter(|| {
|
||||
fourier_bsk.bootstrap(
|
||||
&mut pbs_out,
|
||||
&lwe_ciphertext_in,
|
||||
&accumulator,
|
||||
fft,
|
||||
PodStack::new(&mut buf),
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_bench);
|
||||
criterion_main!(benches);
|
||||
283
tfhe/benches/core_crypto/pbs_bench.rs
Normal file
283
tfhe/benches/core_crypto/pbs_bench.rs
Normal file
@@ -0,0 +1,283 @@
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
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;
|
||||
|
||||
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 BOOLEAN_BENCH_PARAMS: [(&str, BooleanParameters); 2] = [
|
||||
("BOOLEAN_DEFAULT_PARAMS", DEFAULT_PARAMETERS),
|
||||
("BOOLEAN_TFHE_LIB_PARAMS", TFHE_LIB_PARAMETERS),
|
||||
];
|
||||
|
||||
criterion_group!(
|
||||
name = pbs_group;
|
||||
config = Criterion::default().sample_size(2000);
|
||||
targets = mem_optimized_pbs::<u64>, mem_optimized_pbs::<u32>
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
name = multi_bit_pbs_group;
|
||||
config = Criterion::default().sample_size(2000);
|
||||
targets = multi_bit_pbs::<u64>, multi_bit_pbs::<u32>
|
||||
);
|
||||
|
||||
criterion_main!(pbs_group, multi_bit_pbs_group);
|
||||
|
||||
fn benchmark_parameters<Scalar: Numeric>() -> Vec<(String, CryptoParametersRecord)> {
|
||||
if Scalar::BITS == 64 {
|
||||
SHORTINT_BENCH_PARAMS
|
||||
.iter()
|
||||
.map(|params| (params.name(), params.to_owned().into()))
|
||||
.collect()
|
||||
} else if Scalar::BITS == 32 {
|
||||
BOOLEAN_BENCH_PARAMS
|
||||
.iter()
|
||||
.map(|(name, params)| (name.to_string(), params.to_owned().into()))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_bit_benchmark_parameters<Scalar: Numeric>(
|
||||
) -> Vec<(String, (CryptoParametersRecord, LweBskGroupingFactor))> {
|
||||
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),
|
||||
),
|
||||
),
|
||||
]
|
||||
} 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) {
|
||||
let bench_name = "PBS_mem-optimized";
|
||||
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 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();
|
||||
|
||||
// Create the empty bootstrapping key in the Fourier domain
|
||||
let fourier_bsk = 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(),
|
||||
);
|
||||
|
||||
// Allocate a new LweCiphertext and encrypt our plaintext
|
||||
let lwe_ciphertext_in: LweCiphertextOwned<Scalar> = 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 mut buffers = ComputationBuffers::new();
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
buffers.resize(
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<Scalar>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
|
||||
let id = format!("{bench_name}_{name}");
|
||||
{
|
||||
bench_group.bench_function(&id, |b| {
|
||||
b.iter(|| {
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
&lwe_ciphertext_in,
|
||||
&mut out_pbs_ct,
|
||||
&accumulator.as_view(),
|
||||
&fourier_bsk,
|
||||
fft,
|
||||
buffers.stack(),
|
||||
);
|
||||
black_box(&mut out_pbs_ct);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
write_to_json(&id, *params, name, "pbs", &OperatorType::Atomic);
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Sync>(
|
||||
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);
|
||||
|
||||
// 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_programmable_bootstrap_lwe_ciphertext(
|
||||
&lwe_ciphertext_in,
|
||||
&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)),
|
||||
);
|
||||
black_box(&mut out_pbs_ct);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(&id, *params, name, "pbs", &OperatorType::Atomic);
|
||||
}
|
||||
}
|
||||
792
tfhe/benches/integer/bench.rs
Normal file
792
tfhe/benches/integer/bench.rs
Normal file
@@ -0,0 +1,792 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use itertools::iproduct;
|
||||
use rand::Rng;
|
||||
use std::array::IntoIter;
|
||||
use tfhe::integer::keycache::KEY_CACHE;
|
||||
use tfhe::integer::{RadixCiphertextBig, 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,
|
||||
};
|
||||
|
||||
/// An iterator that yields a succession of combinations
|
||||
/// of parameters and a num_block to achieve a certain bit_size ciphertext
|
||||
/// in radix decomposition
|
||||
struct ParamsAndNumBlocksIter {
|
||||
params_and_bit_sizes:
|
||||
itertools::Product<IntoIter<tfhe::shortint::Parameters, 1>, IntoIter<usize, 7>>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Iterator for ParamsAndNumBlocksIter {
|
||||
type Item = (tfhe::shortint::Parameters, 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;
|
||||
|
||||
Some((param, num_block, bit_size))
|
||||
}
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a binary operation, input ciphertexts will
|
||||
/// contain non zero carries
|
||||
fn bench_server_key_binary_function_dirty_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, &mut RadixCiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_two_values = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let mut ct_0 = cks.encrypt_radix(clear_0, num_block);
|
||||
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_1 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
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;
|
||||
while carry_mod > 0 {
|
||||
// Raise the degree, so as to ensure worst case path in operations
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_2 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_2 = cks.encrypt_radix(clear_2, num_block);
|
||||
sks.unchecked_add_assign(&mut ct_0, &ct_2);
|
||||
sks.unchecked_add_assign(&mut ct_1, &ct_2);
|
||||
|
||||
carry_mod -= 1;
|
||||
}
|
||||
|
||||
(ct_0, ct_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_two_values,
|
||||
|(mut ct_0, mut ct_1)| {
|
||||
binary_op(&sks, &mut ct_0, &mut ct_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a binary operation, input ciphertext will
|
||||
/// contain only zero carries
|
||||
fn bench_server_key_binary_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, &mut RadixCiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_two_values = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_0 = cks.encrypt_radix(clear_0, num_block);
|
||||
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_1 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_1 = cks.encrypt_radix(clear_1, num_block);
|
||||
|
||||
(ct_0, ct_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_two_values,
|
||||
|(mut ct_0, mut ct_1)| {
|
||||
binary_op(&sks, &mut ct_0, &mut ct_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a unary operation, input ciphertexts will
|
||||
/// contain non zero carries
|
||||
fn bench_server_key_unary_function_dirty_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_fn: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_one_value = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
|
||||
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;
|
||||
while carry_mod > 0 {
|
||||
// Raise the degree, so as to ensure worst case path in operations
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_2 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_2 = cks.encrypt_radix(clear_2, num_block);
|
||||
sks.unchecked_add_assign(&mut ct_0, &ct_2);
|
||||
|
||||
carry_mod -= 1;
|
||||
}
|
||||
|
||||
ct_0
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_one_value,
|
||||
|mut ct_0| {
|
||||
unary_fn(&sks, &mut ct_0);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a unary operation, input ciphertext will
|
||||
/// contain only zero carries
|
||||
fn bench_server_key_unary_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_fn: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_one_value = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
|
||||
cks.encrypt_radix(clear_0, num_block)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_one_value,
|
||||
|mut ct_0| {
|
||||
unary_fn(&sks, &mut ct_0);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn bench_server_key_binary_scalar_function_dirty_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, u64),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_one_value = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
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;
|
||||
while carry_mod > 0 {
|
||||
// Raise the degree, so as to ensure worst case path in operations
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_2 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_2 = cks.encrypt_radix(clear_2, num_block);
|
||||
sks.unchecked_add_assign(&mut ct_0, &ct_2);
|
||||
|
||||
carry_mod -= 1;
|
||||
}
|
||||
|
||||
let clear_1 = rng.gen::<u64>();
|
||||
|
||||
(ct_0, clear_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_one_value,
|
||||
|(mut ct_0, clear_1)| {
|
||||
binary_op(&sks, &mut ct_0, clear_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn bench_server_key_binary_scalar_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, u64),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_one_value = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_0 = cks.encrypt_radix(clear_0, num_block);
|
||||
|
||||
let clear_1 = rng.gen::<u64>();
|
||||
|
||||
(ct_0, clear_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_one_value,
|
||||
|(mut ct_0, clear_1)| {
|
||||
binary_op(&sks, &mut ct_0, clear_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
macro_rules! define_server_key_bench_unary_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_unary_function_dirty_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs| {
|
||||
server_key.$server_key_method(lhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_unary_default_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_unary_function_clean_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs| {
|
||||
server_key.$server_key_method(lhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_function_dirty_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_default_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_function_clean_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_scalar_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_function_dirty_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_scalar_default_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_function_clean_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(method_name: smart_add, display_name: add);
|
||||
define_server_key_bench_fn!(method_name: smart_sub, display_name: sub);
|
||||
define_server_key_bench_fn!(method_name: smart_mul, display_name: mul);
|
||||
define_server_key_bench_fn!(method_name: smart_bitand, display_name: bitand);
|
||||
define_server_key_bench_fn!(method_name: smart_bitor, display_name: bitor);
|
||||
define_server_key_bench_fn!(method_name: smart_bitxor, display_name: bitxor);
|
||||
|
||||
define_server_key_bench_fn!(method_name: smart_add_parallelized, display_name: add);
|
||||
define_server_key_bench_fn!(method_name: smart_sub_parallelized, display_name: sub);
|
||||
define_server_key_bench_fn!(method_name: smart_mul_parallelized, display_name: mul);
|
||||
define_server_key_bench_fn!(method_name: smart_bitand_parallelized, display_name: bitand);
|
||||
define_server_key_bench_fn!(method_name: smart_bitxor_parallelized, display_name: bitxor);
|
||||
define_server_key_bench_fn!(method_name: smart_bitor_parallelized, display_name: bitor);
|
||||
|
||||
define_server_key_bench_default_fn!(method_name: add_parallelized, display_name: add);
|
||||
define_server_key_bench_default_fn!(method_name: sub_parallelized, display_name: sub);
|
||||
define_server_key_bench_default_fn!(method_name: mul_parallelized, display_name: mul);
|
||||
define_server_key_bench_default_fn!(method_name: 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_fn!(method_name: unchecked_add, display_name: add);
|
||||
define_server_key_bench_fn!(method_name: unchecked_sub, display_name: sub);
|
||||
define_server_key_bench_fn!(method_name: unchecked_mul, display_name: mul);
|
||||
define_server_key_bench_fn!(method_name: unchecked_bitand, display_name: bitand);
|
||||
define_server_key_bench_fn!(method_name: unchecked_bitor, display_name: bitor);
|
||||
define_server_key_bench_fn!(method_name: unchecked_bitxor, display_name: bitxor);
|
||||
|
||||
define_server_key_bench_fn!(method_name: unchecked_mul_parallelized, display_name: mul);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_bitand_parallelized,
|
||||
display_name: bitand
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_bitor_parallelized,
|
||||
display_name: bitor
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_bitxor_parallelized,
|
||||
display_name: bitxor
|
||||
);
|
||||
|
||||
define_server_key_bench_scalar_fn!(method_name: smart_scalar_add, display_name: add);
|
||||
define_server_key_bench_scalar_fn!(method_name: smart_scalar_sub, display_name: sub);
|
||||
define_server_key_bench_scalar_fn!(method_name: smart_scalar_mul, display_name: mul);
|
||||
|
||||
define_server_key_bench_scalar_fn!(
|
||||
method_name: smart_scalar_add_parallelized,
|
||||
display_name: add
|
||||
);
|
||||
define_server_key_bench_scalar_fn!(
|
||||
method_name: smart_scalar_sub_parallelized,
|
||||
display_name: sub
|
||||
);
|
||||
define_server_key_bench_scalar_fn!(
|
||||
method_name: smart_scalar_mul_parallelized,
|
||||
display_name: mul
|
||||
);
|
||||
|
||||
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_fn!(method_name: unchecked_scalar_add, display_name: add);
|
||||
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_sub, display_name: sub);
|
||||
define_server_key_bench_scalar_fn!(method_name: unchecked_small_scalar_mul, display_name: mul);
|
||||
|
||||
define_server_key_bench_unary_fn!(method_name: smart_neg, display_name: negation);
|
||||
define_server_key_bench_unary_fn!(method_name: smart_neg_parallelized, display_name: negation);
|
||||
define_server_key_bench_unary_default_fn!(method_name: neg_parallelized, display_name: negation);
|
||||
|
||||
define_server_key_bench_unary_fn!(method_name: full_propagate, display_name: carry_propagation);
|
||||
define_server_key_bench_unary_fn!(
|
||||
method_name: full_propagate_parallelized,
|
||||
display_name: carry_propagation
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(method_name: unchecked_max, display_name: max);
|
||||
define_server_key_bench_fn!(method_name: unchecked_min, display_name: min);
|
||||
define_server_key_bench_fn!(method_name: unchecked_eq, display_name: equal);
|
||||
define_server_key_bench_fn!(method_name: unchecked_lt, display_name: less_than);
|
||||
define_server_key_bench_fn!(method_name: unchecked_le, display_name: less_or_equal);
|
||||
define_server_key_bench_fn!(method_name: unchecked_gt, display_name: greater_than);
|
||||
define_server_key_bench_fn!(method_name: unchecked_ge, display_name: greater_or_equal);
|
||||
|
||||
define_server_key_bench_fn!(method_name: unchecked_max_parallelized, display_name: max);
|
||||
define_server_key_bench_fn!(method_name: unchecked_min_parallelized, display_name: min);
|
||||
define_server_key_bench_fn!(method_name: unchecked_eq_parallelized, display_name: equal);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_lt_parallelized,
|
||||
display_name: less_than
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_le_parallelized,
|
||||
display_name: less_or_equal
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_gt_parallelized,
|
||||
display_name: greater_than
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_ge_parallelized,
|
||||
display_name: greater_or_equal
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(method_name: smart_max, display_name: max);
|
||||
define_server_key_bench_fn!(method_name: smart_min, display_name: min);
|
||||
define_server_key_bench_fn!(method_name: smart_eq, display_name: equal);
|
||||
define_server_key_bench_fn!(method_name: smart_lt, display_name: less_than);
|
||||
define_server_key_bench_fn!(method_name: smart_le, display_name: less_or_equal);
|
||||
define_server_key_bench_fn!(method_name: smart_gt, display_name: greater_than);
|
||||
define_server_key_bench_fn!(method_name: smart_ge, display_name: greater_or_equal);
|
||||
|
||||
define_server_key_bench_fn!(method_name: smart_max_parallelized, display_name: max);
|
||||
define_server_key_bench_fn!(method_name: smart_min_parallelized, display_name: min);
|
||||
define_server_key_bench_fn!(method_name: smart_eq_parallelized, display_name: equal);
|
||||
define_server_key_bench_fn!(method_name: smart_lt_parallelized, display_name: less_than);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_le_parallelized,
|
||||
display_name: less_or_equal
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_gt_parallelized,
|
||||
display_name: greater_than
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_ge_parallelized,
|
||||
display_name: greater_or_equal
|
||||
);
|
||||
|
||||
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: 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);
|
||||
|
||||
criterion_group!(
|
||||
smart_arithmetic_operation,
|
||||
smart_neg,
|
||||
smart_add,
|
||||
smart_mul,
|
||||
smart_bitand,
|
||||
smart_bitor,
|
||||
smart_bitxor,
|
||||
smart_max,
|
||||
smart_min,
|
||||
smart_eq,
|
||||
smart_lt,
|
||||
smart_le,
|
||||
smart_gt,
|
||||
smart_ge,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
smart_arithmetic_parallelized_operation,
|
||||
smart_add_parallelized,
|
||||
smart_sub_parallelized,
|
||||
smart_mul_parallelized,
|
||||
smart_bitand_parallelized,
|
||||
smart_bitor_parallelized,
|
||||
smart_bitxor_parallelized,
|
||||
smart_max_parallelized,
|
||||
smart_min_parallelized,
|
||||
smart_eq_parallelized,
|
||||
smart_lt_parallelized,
|
||||
smart_le_parallelized,
|
||||
smart_gt_parallelized,
|
||||
smart_ge_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
arithmetic_parallelized_operation,
|
||||
add_parallelized,
|
||||
sub_parallelized,
|
||||
mul_parallelized,
|
||||
bitand_parallelized,
|
||||
bitor_parallelized,
|
||||
bitxor_parallelized,
|
||||
max_parallelized,
|
||||
min_parallelized,
|
||||
eq_parallelized,
|
||||
lt_parallelized,
|
||||
le_parallelized,
|
||||
gt_parallelized,
|
||||
ge_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
smart_scalar_arithmetic_operation,
|
||||
smart_scalar_add,
|
||||
smart_scalar_sub,
|
||||
smart_scalar_mul,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
smart_scalar_arithmetic_parallel_operation,
|
||||
smart_scalar_add_parallelized,
|
||||
smart_scalar_sub_parallelized,
|
||||
smart_scalar_mul_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
scalar_arithmetic_parallel_operation,
|
||||
scalar_add_parallelized,
|
||||
scalar_sub_parallelized,
|
||||
scalar_mul_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
unchecked_arithmetic_operation,
|
||||
unchecked_add,
|
||||
unchecked_sub,
|
||||
unchecked_mul,
|
||||
unchecked_bitand,
|
||||
unchecked_bitor,
|
||||
unchecked_bitxor,
|
||||
unchecked_max,
|
||||
unchecked_min,
|
||||
unchecked_eq,
|
||||
unchecked_lt,
|
||||
unchecked_le,
|
||||
unchecked_gt,
|
||||
unchecked_ge,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
unchecked_scalar_arithmetic_operation,
|
||||
unchecked_scalar_add,
|
||||
unchecked_scalar_sub,
|
||||
unchecked_small_scalar_mul,
|
||||
unchecked_max_parallelized,
|
||||
unchecked_min_parallelized,
|
||||
unchecked_eq_parallelized,
|
||||
unchecked_lt_parallelized,
|
||||
unchecked_le_parallelized,
|
||||
unchecked_gt_parallelized,
|
||||
unchecked_ge_parallelized,
|
||||
unchecked_bitand_parallelized,
|
||||
unchecked_bitor_parallelized,
|
||||
unchecked_bitxor_parallelized,
|
||||
);
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
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,
|
||||
);
|
||||
45
tfhe/benches/keygen/bench.rs
Normal file
45
tfhe/benches/keygen/bench.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use concrete_csprng::seeders::Seeder;
|
||||
use criterion::*;
|
||||
use tfhe::core_crypto::commons::generators::DeterministicSeeder;
|
||||
use tfhe::core_crypto::prelude::{
|
||||
allocate_and_generate_new_binary_glwe_secret_key,
|
||||
par_allocate_and_generate_new_lwe_bootstrap_key, ActivatedRandomGenerator, CiphertextModulus,
|
||||
EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
};
|
||||
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 mut seeder = new_seeder();
|
||||
let mut deterministic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(deterministic_seeder.seed());
|
||||
let mut encryption_generator = EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
|
||||
deterministic_seeder.seed(),
|
||||
&mut deterministic_seeder,
|
||||
);
|
||||
let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key::<u64, _>(
|
||||
parameters.glwe_dimension,
|
||||
parameters.polynomial_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
let lwe_secret_key_after_ks = glwe_secret_key.clone().into_lwe_secret_key();
|
||||
c.bench_function("keygen", |b| {
|
||||
b.iter(|| {
|
||||
let _ = par_allocate_and_generate_new_lwe_bootstrap_key(
|
||||
&lwe_secret_key_after_ks,
|
||||
&glwe_secret_key,
|
||||
parameters.pbs_base_log,
|
||||
parameters.pbs_level,
|
||||
parameters.glwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut encryption_generator,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_bench);
|
||||
criterion_main!(benches);
|
||||
@@ -1,6 +1,11 @@
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use tfhe::shortint::keycache::NamedParam;
|
||||
use tfhe::shortint::parameters::*;
|
||||
use tfhe::shortint::{Ciphertext, Parameters, ServerKey};
|
||||
use tfhe::shortint::{CiphertextBig, Parameters, ServerKey};
|
||||
|
||||
use rand::Rng;
|
||||
use tfhe::shortint::keycache::KEY_CACHE;
|
||||
@@ -8,32 +13,91 @@ 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;
|
||||
|
||||
macro_rules! named_param {
|
||||
($param:ident) => {
|
||||
(stringify!($param), $param)
|
||||
};
|
||||
}
|
||||
|
||||
const SERVER_KEY_BENCH_PARAMS: [(&str, Parameters); 4] = [
|
||||
named_param!(PARAM_MESSAGE_1_CARRY_1),
|
||||
named_param!(PARAM_MESSAGE_2_CARRY_2),
|
||||
named_param!(PARAM_MESSAGE_3_CARRY_3),
|
||||
named_param!(PARAM_MESSAGE_4_CARRY_4),
|
||||
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,
|
||||
];
|
||||
|
||||
fn bench_server_key_binary_function<F>(c: &mut Criterion, bench_name: &str, binary_op: F)
|
||||
where
|
||||
F: Fn(&ServerKey, &mut Ciphertext, &mut Ciphertext),
|
||||
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,
|
||||
];
|
||||
|
||||
fn bench_server_key_unary_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_op: F,
|
||||
params: &[Parameters],
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut CiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
for param in params.iter() {
|
||||
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 = 1_u64 << cks.parameters.message_modulus.0;
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
|
||||
let clear_text = rng.gen::<u64>() % modulus;
|
||||
|
||||
let mut ct = cks.encrypt(clear_text);
|
||||
|
||||
let bench_id = format!("{bench_name}::{}", param.name());
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
b.iter(|| {
|
||||
unary_op(sks, &mut ct);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn bench_server_key_binary_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut CiphertextBig, &mut CiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
for param in params.iter() {
|
||||
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 clear_0 = rng.gen::<u64>() % modulus;
|
||||
let clear_1 = rng.gen::<u64>() % modulus;
|
||||
@@ -41,42 +105,110 @@ where
|
||||
let mut ct_0 = cks.encrypt(clear_0);
|
||||
let mut ct_1 = cks.encrypt(clear_1);
|
||||
|
||||
let bench_id = format!("{}::{}", bench_name, param_name);
|
||||
let bench_id = format!("{bench_name}::{}", param.name());
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
b.iter(|| {
|
||||
binary_op(sks, &mut ct_0, &mut ct_1);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn bench_server_key_binary_scalar_function<F>(c: &mut Criterion, bench_name: &str, binary_op: F)
|
||||
where
|
||||
F: Fn(&ServerKey, &mut Ciphertext, u8),
|
||||
fn bench_server_key_binary_scalar_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut CiphertextBig, u8),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
for param in params {
|
||||
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 = 1_u64 << cks.parameters.message_modulus.0;
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
let clear_1 = rng.gen::<u64>() % modulus;
|
||||
|
||||
let mut ct_0 = cks.encrypt(clear_0);
|
||||
|
||||
let bench_id = format!("{}::{}", bench_name, param_name);
|
||||
let bench_id = format!("{bench_name}::{}", param.name());
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
b.iter(|| {
|
||||
binary_op(sks, &mut ct_0, clear_1 as u8);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn bench_server_key_binary_scalar_division_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut CiphertextBig, u8),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
for param in params {
|
||||
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;
|
||||
assert_ne!(modulus, 1);
|
||||
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
let mut clear_1 = rng.gen::<u64>() % modulus;
|
||||
while clear_1 == 0 {
|
||||
clear_1 = rng.gen::<u64>() % modulus;
|
||||
}
|
||||
|
||||
let mut ct_0 = cks.encrypt(clear_0);
|
||||
|
||||
let bench_id = format!("{bench_name}::{}", param.name());
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
b.iter(|| {
|
||||
binary_op(sks, &mut ct_0, clear_1 as u8);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
@@ -85,24 +217,32 @@ where
|
||||
fn carry_extract(c: &mut Criterion) {
|
||||
let mut bench_group = c.benchmark_group("carry_extract");
|
||||
|
||||
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
|
||||
for param in SERVER_KEY_BENCH_PARAMS {
|
||||
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 = 1_u64 << cks.parameters.message_modulus.0;
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
|
||||
let ct_0 = cks.encrypt(clear_0);
|
||||
|
||||
let bench_id = format!("ServerKey::carry_extract::{}", param_name);
|
||||
let bench_id = format!("ServerKey::carry_extract::{}", param.name());
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
b.iter(|| {
|
||||
sks.carry_extract(&ct_0);
|
||||
let _ = sks.carry_extract(&ct_0);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
"carry_extract",
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
@@ -111,7 +251,7 @@ fn carry_extract(c: &mut Criterion) {
|
||||
fn programmable_bootstrapping(c: &mut Criterion) {
|
||||
let mut bench_group = c.benchmark_group("programmable_bootstrap");
|
||||
|
||||
for (param_name, param) in SERVER_KEY_BENCH_PARAMS {
|
||||
for param in SERVER_KEY_BENCH_PARAMS {
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
let (cks, sks) = (keys.client_key(), keys.server_key());
|
||||
|
||||
@@ -125,19 +265,21 @@ fn programmable_bootstrapping(c: &mut Criterion) {
|
||||
|
||||
let ctxt = cks.encrypt(clear_0);
|
||||
|
||||
let id = format!("ServerKey::programmable_bootstrap::{}", param_name);
|
||||
let bench_id = format!("ServerKey::programmable_bootstrap::{}", param.name());
|
||||
|
||||
bench_group.bench_function(&id, |b| {
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
b.iter(|| {
|
||||
sks.keyswitch_programmable_bootstrap(&ctxt, &acc);
|
||||
let _ = sks.apply_lookup_table(&ctxt, &acc);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(&bench_id, param, param.name(), "pbs", &OperatorType::Atomic);
|
||||
}
|
||||
|
||||
bench_group.finish();
|
||||
}
|
||||
|
||||
fn bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
|
||||
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;
|
||||
@@ -151,75 +293,346 @@ fn bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
|
||||
let mut ct = cks.encrypt_without_padding(clear as u64);
|
||||
let vec_lut = wopbs_key.generate_lut_native_crt(&ct, |x| x);
|
||||
|
||||
let id = format!("Shortint WOPBS: {:?}", param);
|
||||
let id = format!("Shortint WOPBS: {param:?}");
|
||||
|
||||
bench_group.bench_function(&id, |b| {
|
||||
b.iter(|| {
|
||||
wopbs_key.programmable_bootstrapping_native_crt(&mut ct, &vec_lut);
|
||||
let _ = wopbs_key.programmable_bootstrapping_native_crt(&mut ct, &vec_lut);
|
||||
})
|
||||
});
|
||||
|
||||
bench_group.finish();
|
||||
}
|
||||
|
||||
macro_rules! define_server_key_unary_bench_fn (
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_unary_function(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs| {
|
||||
let _ = server_key.$server_key_method(lhs);},
|
||||
$params)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_fn (
|
||||
($server_key_method:ident) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_function(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_scalar_bench_fn (
|
||||
($server_key_method:ident) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_function(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(unchecked_add);
|
||||
define_server_key_bench_fn!(unchecked_sub);
|
||||
define_server_key_bench_fn!(unchecked_mul_lsb);
|
||||
define_server_key_bench_fn!(unchecked_mul_msb);
|
||||
define_server_key_bench_fn!(smart_bitand);
|
||||
define_server_key_bench_fn!(smart_bitor);
|
||||
define_server_key_bench_fn!(smart_bitxor);
|
||||
define_server_key_bench_fn!(smart_add);
|
||||
define_server_key_bench_fn!(smart_sub);
|
||||
define_server_key_bench_fn!(smart_mul_lsb);
|
||||
macro_rules! define_server_key_scalar_div_bench_fn (
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_division_function(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
define_server_key_scalar_bench_fn!(unchecked_scalar_add);
|
||||
define_server_key_scalar_bench_fn!(unchecked_scalar_mul);
|
||||
define_server_key_unary_bench_fn!(
|
||||
method_name: unchecked_neg,
|
||||
display_name: negation,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_mul_lsb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_mul_msb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitand,
|
||||
display_name: bitand,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitor,
|
||||
display_name: bitor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitxor,
|
||||
display_name: bitxor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_mul_lsb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitand,
|
||||
display_name: bitand,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitor,
|
||||
display_name: bitor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitxor,
|
||||
display_name: bitxor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: greater,
|
||||
display_name: greater,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: greater_or_equal,
|
||||
display_name: greater_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: less,
|
||||
display_name: less,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: less_or_equal,
|
||||
display_name: less_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: not_equal,
|
||||
display_name: not_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_unary_bench_fn!(
|
||||
method_name: neg,
|
||||
display_name: negation,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_greater,
|
||||
display_name: greater_than,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_less,
|
||||
display_name: less_than,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_left_shift,
|
||||
display_name: left_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_right_shift,
|
||||
display_name: right_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: unchecked_scalar_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: unchecked_scalar_mod,
|
||||
display_name: modulo,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_left_shift,
|
||||
display_name: left_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_right_shift,
|
||||
display_name: right_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_mod,
|
||||
display_name: modulo,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_greater,
|
||||
display_name: greater,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_greater_or_equal,
|
||||
display_name: greater_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_less,
|
||||
display_name: less,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_less_or_equal,
|
||||
display_name: less_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_not_equal,
|
||||
display_name: not_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
arithmetic_operation,
|
||||
unchecked_neg,
|
||||
unchecked_add,
|
||||
unchecked_sub,
|
||||
unchecked_mul_lsb,
|
||||
unchecked_mul_msb,
|
||||
unchecked_div,
|
||||
smart_bitand,
|
||||
smart_bitor,
|
||||
smart_bitxor,
|
||||
smart_add,
|
||||
smart_sub,
|
||||
smart_mul_lsb,
|
||||
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,
|
||||
//bench_wopbs_param_message_8_norm2_5,
|
||||
programmable_bootstrapping
|
||||
);
|
||||
|
||||
@@ -227,6 +640,51 @@ criterion_group!(
|
||||
arithmetic_scalar_operation,
|
||||
unchecked_scalar_add,
|
||||
unchecked_scalar_mul,
|
||||
unchecked_scalar_sub,
|
||||
unchecked_scalar_div,
|
||||
unchecked_scalar_mod,
|
||||
unchecked_scalar_left_shift,
|
||||
unchecked_scalar_right_shift,
|
||||
);
|
||||
|
||||
criterion_main!(arithmetic_operation,); // arithmetic_scalar_operation,);
|
||||
criterion_group!(
|
||||
default_ops,
|
||||
neg,
|
||||
bitand,
|
||||
bitor,
|
||||
bitxor,
|
||||
add,
|
||||
sub,
|
||||
div,
|
||||
mul,
|
||||
greater,
|
||||
greater_or_equal,
|
||||
less,
|
||||
less_or_equal,
|
||||
equal,
|
||||
not_equal
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
default_scalar_ops,
|
||||
scalar_add,
|
||||
scalar_sub,
|
||||
scalar_div,
|
||||
scalar_mul,
|
||||
scalar_mod,
|
||||
scalar_left_shift,
|
||||
scalar_right_shift,
|
||||
scalar_greater,
|
||||
scalar_greater_or_equal,
|
||||
scalar_less,
|
||||
scalar_less_or_equal,
|
||||
scalar_equal,
|
||||
scalar_not_equal
|
||||
);
|
||||
|
||||
criterion_main!(
|
||||
// arithmetic_operation,
|
||||
// arithmetic_scalar_operation,
|
||||
default_ops,
|
||||
default_scalar_ops,
|
||||
);
|
||||
|
||||
184
tfhe/benches/utilities.rs
Normal file
184
tfhe/benches/utilities.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use serde::Serialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(feature = "boolean")]
|
||||
use tfhe::boolean::parameters::BooleanParameters;
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
#[cfg(feature = "shortint")]
|
||||
use tfhe::shortint::Parameters;
|
||||
|
||||
#[derive(Clone, Copy, Default, Serialize)]
|
||||
pub struct CryptoParametersRecord {
|
||||
pub lwe_dimension: Option<LweDimension>,
|
||||
pub glwe_dimension: Option<GlweDimension>,
|
||||
pub polynomial_size: Option<PolynomialSize>,
|
||||
pub lwe_modular_std_dev: Option<StandardDev>,
|
||||
pub glwe_modular_std_dev: Option<StandardDev>,
|
||||
pub pbs_base_log: Option<DecompositionBaseLog>,
|
||||
pub pbs_level: Option<DecompositionLevelCount>,
|
||||
pub ks_base_log: Option<DecompositionBaseLog>,
|
||||
pub ks_level: Option<DecompositionLevelCount>,
|
||||
pub pfks_level: Option<DecompositionLevelCount>,
|
||||
pub pfks_base_log: Option<DecompositionBaseLog>,
|
||||
pub pfks_modular_std_dev: Option<StandardDev>,
|
||||
pub cbs_level: Option<DecompositionLevelCount>,
|
||||
pub cbs_base_log: Option<DecompositionBaseLog>,
|
||||
pub message_modulus: Option<usize>,
|
||||
pub carry_modulus: Option<usize>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "boolean")]
|
||||
impl From<BooleanParameters> for CryptoParametersRecord {
|
||||
fn from(params: BooleanParameters) -> 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),
|
||||
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: None,
|
||||
carry_modulus: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "shortint")]
|
||||
impl From<Parameters> for CryptoParametersRecord {
|
||||
fn from(params: Parameters) -> 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),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum PolynomialMultiplication {
|
||||
Fft,
|
||||
// Ntt,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum IntegerRepresentation {
|
||||
Radix,
|
||||
// Crt,
|
||||
// Hybrid,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum ExecutionType {
|
||||
Sequential,
|
||||
Parallel,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum KeySetType {
|
||||
Single,
|
||||
// Multi,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum OperandType {
|
||||
CipherText,
|
||||
PlainText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum OperatorType {
|
||||
Atomic,
|
||||
// AtomicPattern,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BenchmarkParametersRecord {
|
||||
display_name: String,
|
||||
crypto_parameters_alias: String,
|
||||
crypto_parameters: CryptoParametersRecord,
|
||||
message_modulus: Option<usize>,
|
||||
carry_modulus: Option<usize>,
|
||||
ciphertext_modulus: usize,
|
||||
polynomial_multiplication: PolynomialMultiplication,
|
||||
precision: u32,
|
||||
error_probability: f64,
|
||||
integer_representation: IntegerRepresentation,
|
||||
decomposition_basis: u32,
|
||||
pbs_algorithm: Option<String>,
|
||||
execution_type: ExecutionType,
|
||||
key_set_type: KeySetType,
|
||||
operand_type: OperandType,
|
||||
operator_type: OperatorType,
|
||||
}
|
||||
|
||||
/// Writes benchmarks parameters to disk in JSON format.
|
||||
pub fn write_to_json<T: Into<CryptoParametersRecord>>(
|
||||
bench_id: &str,
|
||||
params: T,
|
||||
params_alias: impl Into<String>,
|
||||
display_name: impl Into<String>,
|
||||
operator_type: &OperatorType,
|
||||
) {
|
||||
let params = params.into();
|
||||
|
||||
let execution_type = match bench_id.contains("parallelized") {
|
||||
true => ExecutionType::Parallel,
|
||||
false => ExecutionType::Sequential,
|
||||
};
|
||||
let operand_type = match bench_id.contains("scalar") {
|
||||
true => OperandType::PlainText,
|
||||
false => OperandType::CipherText,
|
||||
};
|
||||
|
||||
let record = BenchmarkParametersRecord {
|
||||
display_name: display_name.into(),
|
||||
crypto_parameters_alias: params_alias.into(),
|
||||
crypto_parameters: params.to_owned(),
|
||||
message_modulus: params.message_modulus,
|
||||
carry_modulus: params.carry_modulus,
|
||||
ciphertext_modulus: 64,
|
||||
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(),
|
||||
pbs_algorithm: None, // To be added in future version
|
||||
execution_type,
|
||||
key_set_type: KeySetType::Single,
|
||||
operand_type,
|
||||
operator_type: operator_type.to_owned(),
|
||||
};
|
||||
|
||||
let mut params_directory = ["benchmarks_parameters", bench_id]
|
||||
.iter()
|
||||
.collect::<PathBuf>();
|
||||
fs::create_dir_all(¶ms_directory).unwrap();
|
||||
params_directory.push("parameters.json");
|
||||
|
||||
fs::write(params_directory, serde_json::to_string(&record).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
// Empty main to please clippy.
|
||||
#[allow(dead_code)]
|
||||
pub fn main() {}
|
||||
@@ -1,10 +1,12 @@
|
||||
// tfhe/build.rs
|
||||
|
||||
#[cfg(feature = "__c_api")]
|
||||
fn gen_c_api() {
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
if std::env::var("_CBINDGEN_IS_RUNNING").is_ok() {
|
||||
return;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -24,7 +26,35 @@ fn gen_c_api() {
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
cbindgen::generate(crate_dir)
|
||||
let parse_expand_features_vec = vec![
|
||||
#[cfg(feature = "__c_api")]
|
||||
"__c_api",
|
||||
#[cfg(feature = "boolean-c-api")]
|
||||
"boolean-c-api",
|
||||
#[cfg(feature = "shortint-c-api")]
|
||||
"shortint-c-api",
|
||||
#[cfg(feature = "high-level-c-api")]
|
||||
"high-level-c-api",
|
||||
#[cfg(feature = "boolean")]
|
||||
"boolean",
|
||||
#[cfg(feature = "shortint")]
|
||||
"shortint",
|
||||
#[cfg(feature = "integer")]
|
||||
"integer",
|
||||
];
|
||||
|
||||
let parse_expand_vec = if parse_expand_features_vec.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![package_name.as_str()]
|
||||
};
|
||||
|
||||
cbindgen::Builder::new()
|
||||
.with_crate(crate_dir.clone())
|
||||
.with_config(cbindgen::Config::from_root_or_default(crate_dir))
|
||||
.with_parse_expand(&parse_expand_vec)
|
||||
.with_parse_expand_features(&parse_expand_features_vec)
|
||||
.generate()
|
||||
.unwrap()
|
||||
.write_to_file(output_file);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ void test_default_keygen_w_serde(void) {
|
||||
BooleanCiphertext *ct = NULL;
|
||||
Buffer ct_ser_buffer = {.pointer = NULL, .length = 0};
|
||||
BooleanCiphertext *deser_ct = NULL;
|
||||
BooleanCompressedCiphertext *cct = NULL;
|
||||
BooleanCompressedCiphertext *deser_cct = NULL;
|
||||
BooleanCiphertext *decompressed_ct = NULL;
|
||||
|
||||
int gen_keys_ok = boolean_gen_keys_with_default_parameters(&cks, &sks);
|
||||
assert(gen_keys_ok == 0);
|
||||
@@ -37,10 +40,34 @@ void test_default_keygen_w_serde(void) {
|
||||
|
||||
assert(result == true);
|
||||
|
||||
int c_encrypt_ok = boolean_client_key_encrypt_compressed(cks, true, &cct);
|
||||
assert(c_encrypt_ok == 0);
|
||||
|
||||
int c_ser_ok = boolean_serialize_compressed_ciphertext(cct, &ct_ser_buffer);
|
||||
assert(c_ser_ok == 0);
|
||||
|
||||
deser_view.pointer = ct_ser_buffer.pointer;
|
||||
deser_view.length = ct_ser_buffer.length;
|
||||
|
||||
int c_deser_ok = boolean_deserialize_compressed_ciphertext(deser_view, &deser_cct);
|
||||
assert(c_deser_ok == 0);
|
||||
|
||||
int decomp_ok = boolean_decompress_ciphertext(cct, &decompressed_ct);
|
||||
assert(decomp_ok == 0);
|
||||
|
||||
bool c_result = false;
|
||||
int c_decrypt_ok = boolean_client_key_decrypt(cks, decompressed_ct, &c_result);
|
||||
assert(c_decrypt_ok == 0);
|
||||
|
||||
assert(c_result == true);
|
||||
|
||||
destroy_boolean_client_key(cks);
|
||||
destroy_boolean_server_key(sks);
|
||||
destroy_boolean_ciphertext(ct);
|
||||
destroy_boolean_ciphertext(deser_ct);
|
||||
destroy_boolean_compressed_ciphertext(cct);
|
||||
destroy_boolean_compressed_ciphertext(deser_cct);
|
||||
destroy_boolean_ciphertext(decompressed_ct);
|
||||
destroy_buffer(&ct_ser_buffer);
|
||||
}
|
||||
|
||||
@@ -57,7 +84,7 @@ void test_predefined_keygen_w_serde(void) {
|
||||
destroy_boolean_server_key(sks);
|
||||
|
||||
gen_keys_ok = boolean_gen_keys_with_predefined_parameters_set(
|
||||
BOOLEAN_PARAMETERS_SET_THFE_LIB_PARAMETERS, &cks, &sks);
|
||||
BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS, &cks, &sks);
|
||||
|
||||
assert(gen_keys_ok == 0);
|
||||
|
||||
|
||||
@@ -326,19 +326,41 @@ bool c_xnor(bool left, bool right) { return !c_xor(left, right); }
|
||||
|
||||
void test_server_key(void) {
|
||||
BooleanClientKey *cks = NULL;
|
||||
BooleanCompressedServerKey *csks = NULL;
|
||||
BooleanServerKey *sks = NULL;
|
||||
Buffer cks_ser_buffer = {.pointer = NULL, .length = 0};
|
||||
BooleanClientKey *deser_cks = NULL;
|
||||
Buffer csks_ser_buffer = {.pointer = NULL, .length = 0};
|
||||
BooleanCompressedServerKey *deser_csks = NULL;
|
||||
Buffer sks_ser_buffer = {.pointer = NULL, .length = 0};
|
||||
BooleanServerKey *deser_sks = NULL;
|
||||
BooleanParameters *params = NULL;
|
||||
|
||||
int gen_keys_ok = boolean_gen_keys_with_default_parameters(&cks, &sks);
|
||||
assert(gen_keys_ok == 0);
|
||||
int get_params_ok = boolean_get_parameters(BOOLEAN_PARAMETERS_SET_DEFAULT_PARAMETERS, ¶ms);
|
||||
assert(get_params_ok == 0);
|
||||
|
||||
int gen_cks_ok = boolean_gen_client_key(params, &cks);
|
||||
assert(gen_cks_ok == 0);
|
||||
|
||||
int gen_csks_ok = boolean_gen_compressed_server_key(cks, &csks);
|
||||
assert(gen_csks_ok == 0);
|
||||
|
||||
int ser_csks_ok = boolean_serialize_compressed_server_key(csks, &csks_ser_buffer);
|
||||
assert(ser_csks_ok == 0);
|
||||
|
||||
BufferView deser_view = {.pointer = csks_ser_buffer.pointer, .length = csks_ser_buffer.length};
|
||||
|
||||
int deser_csks_ok = boolean_deserialize_compressed_server_key(deser_view, &deser_csks);
|
||||
assert(deser_csks_ok == 0);
|
||||
|
||||
int decompress_csks_ok = boolean_decompress_server_key(deser_csks, &sks);
|
||||
assert(decompress_csks_ok == 0);
|
||||
|
||||
int ser_cks_ok = boolean_serialize_client_key(cks, &cks_ser_buffer);
|
||||
assert(ser_cks_ok == 0);
|
||||
|
||||
BufferView deser_view = {.pointer = cks_ser_buffer.pointer, .length = cks_ser_buffer.length};
|
||||
deser_view.pointer = cks_ser_buffer.pointer;
|
||||
deser_view.length = cks_ser_buffer.length;
|
||||
|
||||
int deser_cks_ok = boolean_deserialize_client_key(deser_view, &deser_cks);
|
||||
assert(deser_cks_ok == 0);
|
||||
@@ -390,10 +412,14 @@ void test_server_key(void) {
|
||||
boolean_server_key_xnor_scalar_assign);
|
||||
|
||||
destroy_boolean_client_key(cks);
|
||||
destroy_boolean_compressed_server_key(csks);
|
||||
destroy_boolean_server_key(sks);
|
||||
destroy_boolean_client_key(deser_cks);
|
||||
destroy_boolean_compressed_server_key(deser_csks);
|
||||
destroy_boolean_server_key(deser_sks);
|
||||
destroy_boolean_parameters(params);
|
||||
destroy_buffer(&cks_ser_buffer);
|
||||
destroy_buffer(&csks_ser_buffer);
|
||||
destroy_buffer(&sks_ser_buffer);
|
||||
}
|
||||
|
||||
|
||||
117
tfhe/c_api_tests/test_high_level_128_bits.c
Normal file
117
tfhe/c_api_tests/test_high_level_128_bits.c
Normal file
@@ -0,0 +1,117 @@
|
||||
#include <tfhe.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int uint128_client_key(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint128 *lhs = NULL;
|
||||
FheUint128 *rhs = NULL;
|
||||
FheUint128 *result = NULL;
|
||||
|
||||
ok = fhe_uint128_try_encrypt_with_client_key_u128(10, 20, client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_try_encrypt_with_client_key_u128(1, 2, 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);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 9);
|
||||
assert(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;
|
||||
|
||||
ok = fhe_uint128_try_encrypt_trivial_u128(10, 20, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_try_encrypt_trivial_u128(1, 2, &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);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 9);
|
||||
assert(w1 == 18);
|
||||
|
||||
fhe_uint128_destroy(lhs);
|
||||
fhe_uint128_destroy(rhs);
|
||||
fhe_uint128_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint128_public_key(const ClientKey *client_key, const PublicKey *public_key) {
|
||||
int ok;
|
||||
FheUint128 *lhs = NULL;
|
||||
FheUint128 *rhs = NULL;
|
||||
FheUint128 *result = NULL;
|
||||
|
||||
ok = fhe_uint128_try_encrypt_with_public_key_u128(1, 2, public_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_try_encrypt_with_public_key_u128(10, 20, 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);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 11);
|
||||
assert(w1 == 22);
|
||||
|
||||
fhe_uint128_destroy(lhs);
|
||||
fhe_uint128_destroy(rhs);
|
||||
fhe_uint128_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int ok = 0;
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
config_builder_all_disabled(&builder);
|
||||
config_builder_enable_default_uint128_small(&builder);
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
generate_keys(config, &client_key, &server_key);
|
||||
public_key_new(client_key, &public_key);
|
||||
|
||||
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);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
return ok;
|
||||
}
|
||||
165
tfhe/c_api_tests/test_high_level_256_bits.c
Normal file
165
tfhe/c_api_tests/test_high_level_256_bits.c
Normal file
@@ -0,0 +1,165 @@
|
||||
#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;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
uint64_t w0, w1, w2, w3;
|
||||
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 6);
|
||||
assert(w1 == 8);
|
||||
assert(w2 == 10);
|
||||
assert(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);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint256_encrypt_trivial(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);
|
||||
|
||||
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);
|
||||
|
||||
uint64_t w0, w1, w2, w3;
|
||||
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 6);
|
||||
assert(w1 == 8);
|
||||
assert(w2 == 10);
|
||||
assert(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);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint256_public_key(const ClientKey *client_key, const PublicKey *public_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(5, 6, 7, 8, &lhs_clear);
|
||||
assert(ok == 0);
|
||||
ok = u256_from_u64_words(1, 2, 3, 4, &rhs_clear);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_try_encrypt_with_public_key_u256(lhs_clear, public_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_try_encrypt_with_public_key_u256(rhs_clear, 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);
|
||||
|
||||
uint64_t w0, w1, w2, w3;
|
||||
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
|
||||
assert(ok == 0);
|
||||
|
||||
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);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int ok = 0;
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
config_builder_all_disabled(&builder);
|
||||
config_builder_enable_default_uint256_small(&builder);
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
generate_keys(config, &client_key, &server_key);
|
||||
public_key_new(client_key, &public_key);
|
||||
|
||||
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);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
return ok;
|
||||
}
|
||||
129
tfhe/c_api_tests/test_high_level_boolean.c
Normal file
129
tfhe/c_api_tests/test_high_level_boolean.c
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <tfhe.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
int client_key_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_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_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 public_key_test(const ClientKey *client_key, const PublicKey *public_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_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_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 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;
|
||||
|
||||
config_builder_all_disabled(&builder);
|
||||
config_builder_enable_default_bool(&builder);
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
generate_keys(config, &client_key, &server_key);
|
||||
public_key_new(client_key, &public_key);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
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);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
213
tfhe/c_api_tests/test_high_level_integers.c
Normal file
213
tfhe/c_api_tests/test_high_level_integers.c
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <tfhe.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
int uint8_client_key(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint8 *lhs = NULL;
|
||||
FheUint8 *rhs = NULL;
|
||||
FheUint8 *result = NULL;
|
||||
|
||||
uint8_t lhs_clear = 123;
|
||||
uint8_t rhs_clear = 14;
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_client_key_u8(rhs_clear, client_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_add(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint8_t clear;
|
||||
ok = fhe_uint8_decrypt(result, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == (lhs_clear + rhs_clear));
|
||||
|
||||
fhe_uint8_destroy(lhs);
|
||||
fhe_uint8_destroy(rhs);
|
||||
fhe_uint8_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint8_public_key(const ClientKey *client_key, const PublicKey *public_key) {
|
||||
int ok;
|
||||
FheUint8 *lhs = NULL;
|
||||
FheUint8 *rhs = NULL;
|
||||
FheUint8 *result = NULL;
|
||||
|
||||
uint8_t lhs_clear = 123;
|
||||
uint8_t rhs_clear = 14;
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_public_key_u8(lhs_clear, public_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_public_key_u8(rhs_clear, public_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_sub(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint8_t clear;
|
||||
ok = fhe_uint8_decrypt(result, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == (lhs_clear - rhs_clear));
|
||||
|
||||
fhe_uint8_destroy(lhs);
|
||||
fhe_uint8_destroy(rhs);
|
||||
fhe_uint8_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint8_serialization(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint8 *lhs = NULL;
|
||||
FheUint8 *deserialized_lhs = NULL;
|
||||
FheUint8 *result = NULL;
|
||||
Buffer value_buffer = {.pointer = NULL, .length = 0};
|
||||
Buffer cks_buffer = {.pointer = NULL, .length = 0};
|
||||
BufferView deser_view = {.pointer = NULL, .length = 0};
|
||||
ClientKey *deserialized_client_key = NULL;
|
||||
|
||||
uint8_t lhs_clear = 123;
|
||||
|
||||
ok = client_key_serialize(client_key, &cks_buffer);
|
||||
assert(ok == 0);
|
||||
|
||||
deser_view.pointer = cks_buffer.pointer;
|
||||
deser_view.length = cks_buffer.length;
|
||||
ok = client_key_deserialize(deser_view, &deserialized_client_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, deserialized_client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_serialize(lhs, &value_buffer);
|
||||
assert(ok == 0);
|
||||
|
||||
deser_view.pointer = value_buffer.pointer;
|
||||
deser_view.length = value_buffer.length;
|
||||
ok = fhe_uint8_deserialize(deser_view, &deserialized_lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
uint8_t clear;
|
||||
ok = fhe_uint8_decrypt(deserialized_lhs, deserialized_client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == lhs_clear);
|
||||
|
||||
if (value_buffer.pointer != NULL) {
|
||||
destroy_buffer(&value_buffer);
|
||||
}
|
||||
fhe_uint8_destroy(lhs);
|
||||
fhe_uint8_destroy(deserialized_lhs);
|
||||
fhe_uint8_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint8_compressed(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint8 *lhs = NULL;
|
||||
FheUint8 *result = NULL;
|
||||
CompressedFheUint8 *clhs = NULL;
|
||||
|
||||
uint8_t lhs_clear = 123;
|
||||
|
||||
ok = compressed_fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, client_key, &clhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = compressed_fhe_uint8_decompress(clhs, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
uint8_t clear;
|
||||
ok = fhe_uint8_decrypt(lhs, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == lhs_clear);
|
||||
|
||||
fhe_uint8_destroy(lhs);
|
||||
compressed_fhe_uint8_destroy(clhs);
|
||||
fhe_uint8_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int ok = 0;
|
||||
{
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
ok = config_builder_all_disabled(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_enable_default_uint8(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_build(builder, &config);
|
||||
assert(ok == 0);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
ok = generate_keys(config, &client_key, &server_key);
|
||||
assert(ok == 0);
|
||||
ok = public_key_new(client_key, &public_key);
|
||||
assert(ok == 0);
|
||||
ok = uint8_serialization(client_key);
|
||||
assert(ok == 0);
|
||||
ok = uint8_compressed(client_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = set_server_key(server_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = uint8_client_key(client_key);
|
||||
assert(ok == 0);
|
||||
ok = uint8_public_key(client_key, public_key);
|
||||
assert(ok == 0);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
}
|
||||
|
||||
{
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
ok = config_builder_all_disabled(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_enable_default_uint8_small(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_build(builder, &config);
|
||||
assert(ok == 0);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
ok = generate_keys(config, &client_key, &server_key);
|
||||
assert(ok == 0);
|
||||
ok = public_key_new(client_key, &public_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = set_server_key(server_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = uint8_client_key(client_key);
|
||||
assert(ok == 0);
|
||||
ok = uint8_public_key(client_key, public_key);
|
||||
assert(ok == 0);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ void micro_bench_and() {
|
||||
// assert(gen_keys_ok == 0);
|
||||
|
||||
int gen_keys_ok = boolean_gen_keys_with_predefined_parameters_set(
|
||||
BOOLEAN_PARAMETERS_SET_THFE_LIB_PARAMETERS, &cks, &sks);
|
||||
BOOLEAN_PARAMETERS_SET_TFHE_LIB_PARAMETERS, &cks, &sks);
|
||||
assert(gen_keys_ok == 0);
|
||||
|
||||
int num_loops = 10000;
|
||||
|
||||
@@ -12,6 +12,9 @@ void test_predefined_keygen_w_serde(void) {
|
||||
ShortintCiphertext *ct = NULL;
|
||||
Buffer ct_ser_buffer = {.pointer = NULL, .length = 0};
|
||||
ShortintCiphertext *deser_ct = NULL;
|
||||
ShortintCompressedCiphertext *cct = NULL;
|
||||
ShortintCompressedCiphertext *deser_cct = NULL;
|
||||
ShortintCiphertext *decompressed_ct = NULL;
|
||||
|
||||
int get_params_ok = shortint_get_parameters(2, 2, ¶ms);
|
||||
assert(get_params_ok == 0);
|
||||
@@ -41,21 +44,72 @@ void test_predefined_keygen_w_serde(void) {
|
||||
|
||||
assert(result == 3);
|
||||
|
||||
int c_encrypt_ok = shortint_client_key_encrypt_compressed(cks, 3, &cct);
|
||||
assert(c_encrypt_ok == 0);
|
||||
|
||||
int c_ser_ok = shortint_serialize_compressed_ciphertext(cct, &ct_ser_buffer);
|
||||
assert(c_ser_ok == 0);
|
||||
|
||||
deser_view.pointer = ct_ser_buffer.pointer;
|
||||
deser_view.length = ct_ser_buffer.length;
|
||||
|
||||
int c_deser_ok = shortint_deserialize_compressed_ciphertext(deser_view, &deser_cct);
|
||||
assert(c_deser_ok == 0);
|
||||
|
||||
int decomp_ok = shortint_decompress_ciphertext(cct, &decompressed_ct);
|
||||
assert(decomp_ok == 0);
|
||||
|
||||
uint64_t c_result = -1;
|
||||
int c_decrypt_ok = shortint_client_key_decrypt(cks, decompressed_ct, &c_result);
|
||||
assert(c_decrypt_ok == 0);
|
||||
|
||||
assert(c_result == 3);
|
||||
|
||||
destroy_shortint_client_key(cks);
|
||||
destroy_shortint_server_key(sks);
|
||||
destroy_shortint_parameters(params);
|
||||
destroy_shortint_ciphertext(ct);
|
||||
destroy_shortint_ciphertext(deser_ct);
|
||||
destroy_shortint_compressed_ciphertext(cct);
|
||||
destroy_shortint_compressed_ciphertext(deser_cct);
|
||||
destroy_shortint_ciphertext(decompressed_ct);
|
||||
destroy_buffer(&ct_ser_buffer);
|
||||
}
|
||||
|
||||
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, ¶ms);
|
||||
assert(get_params_ok == 0);
|
||||
|
||||
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);
|
||||
assert(encrypt_ok == 0);
|
||||
|
||||
uint64_t result = -1;
|
||||
int decrypt_ok = shortint_client_key_decrypt(cks, ct, &result);
|
||||
assert(decrypt_ok == 0);
|
||||
|
||||
assert(result == 3);
|
||||
|
||||
destroy_shortint_client_key(cks);
|
||||
destroy_shortint_server_key(sks);
|
||||
destroy_shortint_parameters(params);
|
||||
destroy_shortint_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, ¶ms);
|
||||
10e-100, 2, 3, 2, 2, SHORTINT_NATIVE_MODULUS, ¶ms);
|
||||
assert(params_ok == 0);
|
||||
|
||||
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
|
||||
@@ -67,12 +121,13 @@ void test_custom_keygen(void) {
|
||||
destroy_shortint_server_key(sks);
|
||||
}
|
||||
|
||||
void test_public_keygen(void) {
|
||||
void test_public_keygen(ShortintPublicKeyKind pk_kind) {
|
||||
ShortintClientKey *cks = NULL;
|
||||
ShortintServerKey *sks = NULL;
|
||||
ShortintPublicKey *pks = NULL;
|
||||
ShortintPublicKey *pks_deser = NULL;
|
||||
ShortintParameters *params = NULL;
|
||||
ShortintCiphertext *ct = NULL;
|
||||
Buffer pks_ser_buff = {.pointer = NULL, .length = 0};
|
||||
|
||||
int get_params_ok = shortint_get_parameters(2, 2, ¶ms);
|
||||
assert(get_params_ok == 0);
|
||||
@@ -80,15 +135,19 @@ void test_public_keygen(void) {
|
||||
int gen_keys_ok = shortint_gen_client_key(params, &cks);
|
||||
assert(gen_keys_ok == 0);
|
||||
|
||||
int gen_pks = shortint_gen_public_key(cks, &pks);
|
||||
int gen_pks = shortint_gen_public_key(cks, pk_kind, &pks);
|
||||
assert(gen_pks == 0);
|
||||
|
||||
int gen_sks = shortint_gen_server_key(cks, &sks);
|
||||
assert(gen_sks == 0);
|
||||
int pks_ser = shortint_serialize_public_key(pks, &pks_ser_buff);
|
||||
assert(pks_ser == 0);
|
||||
|
||||
BufferView pks_ser_buff_view = {.pointer = pks_ser_buff.pointer, .length = pks_ser_buff.length};
|
||||
int pks_deser_ok = shortint_deserialize_public_key(pks_ser_buff_view, &pks_deser);
|
||||
assert(pks_deser_ok == 0);
|
||||
|
||||
uint64_t msg = 2;
|
||||
|
||||
int encrypt_ok = shortint_public_key_encrypt(pks, sks, msg, &ct);
|
||||
int encrypt_ok = shortint_public_key_encrypt(pks_deser, msg, &ct);
|
||||
assert(encrypt_ok == 0);
|
||||
|
||||
uint64_t result = -1;
|
||||
@@ -99,7 +158,54 @@ void test_public_keygen(void) {
|
||||
|
||||
destroy_shortint_parameters(params);
|
||||
destroy_shortint_client_key(cks);
|
||||
destroy_shortint_server_key(sks);
|
||||
destroy_shortint_public_key(pks);
|
||||
destroy_shortint_public_key(pks_deser);
|
||||
destroy_buffer(&pks_ser_buff);
|
||||
destroy_shortint_ciphertext(ct);
|
||||
}
|
||||
|
||||
void test_compressed_public_keygen(ShortintPublicKeyKind pk_kind) {
|
||||
ShortintClientKey *cks = NULL;
|
||||
ShortintCompressedPublicKey *cpks = NULL;
|
||||
ShortintPublicKey *pks = NULL;
|
||||
ShortintParameters *params = NULL;
|
||||
ShortintCiphertext *ct = NULL;
|
||||
|
||||
int get_params_ok = shortint_get_parameters(2, 2, ¶ms);
|
||||
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);
|
||||
assert(gen_cpks == 0);
|
||||
|
||||
uint64_t msg = 2;
|
||||
|
||||
int encrypt_compressed_ok = shortint_compressed_public_key_encrypt(cpks, msg, &ct);
|
||||
assert(encrypt_compressed_ok == 0);
|
||||
|
||||
uint64_t result_compressed = -1;
|
||||
int decrypt_compressed_ok = shortint_client_key_decrypt(cks, ct, &result_compressed);
|
||||
assert(decrypt_compressed_ok == 0);
|
||||
|
||||
assert(result_compressed == 2);
|
||||
|
||||
int decompress_ok = shortint_decompress_public_key(cpks, &pks);
|
||||
assert(decompress_ok == 0);
|
||||
|
||||
int encrypt_ok = shortint_public_key_encrypt(pks, msg, &ct);
|
||||
assert(encrypt_ok == 0);
|
||||
|
||||
uint64_t result = -1;
|
||||
int decrypt_ok = shortint_client_key_decrypt(cks, ct, &result);
|
||||
assert(decrypt_ok == 0);
|
||||
|
||||
assert(result == 2);
|
||||
|
||||
destroy_shortint_parameters(params);
|
||||
destroy_shortint_client_key(cks);
|
||||
destroy_shortint_compressed_public_key(cpks);
|
||||
destroy_shortint_public_key(pks);
|
||||
destroy_shortint_ciphertext(ct);
|
||||
}
|
||||
@@ -107,6 +213,10 @@ void test_public_keygen(void) {
|
||||
int main(void) {
|
||||
test_predefined_keygen_w_serde();
|
||||
test_custom_keygen();
|
||||
test_public_keygen();
|
||||
test_public_keygen(ShortintPublicKeyBig);
|
||||
test_public_keygen(ShortintPublicKeySmall);
|
||||
test_compressed_public_keygen(ShortintPublicKeyBig);
|
||||
test_compressed_public_keygen(ShortintPublicKeySmall);
|
||||
test_server_key_trivial_encrypt();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ uint64_t get_max_value_of_bivariate_accumulator_generator(uint64_t (*accumulator
|
||||
}
|
||||
|
||||
void test_shortint_pbs_2_bits_message(void) {
|
||||
ShortintPBSAccumulator *accumulator = NULL;
|
||||
ShortintPBSLookupTable *accumulator = NULL;
|
||||
ShortintClientKey *cks = NULL;
|
||||
ShortintServerKey *sks = NULL;
|
||||
ShortintParameters *params = NULL;
|
||||
@@ -115,7 +115,7 @@ void test_shortint_pbs_2_bits_message(void) {
|
||||
}
|
||||
|
||||
void test_shortint_bivariate_pbs_2_bits_message(void) {
|
||||
ShortintBivariatePBSAccumulator *accumulator = NULL;
|
||||
ShortintBivariatePBSLookupTable *accumulator = NULL;
|
||||
ShortintClientKey *cks = NULL;
|
||||
ShortintServerKey *sks = NULL;
|
||||
ShortintParameters *params = NULL;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ language = "C"
|
||||
|
||||
############## Options for Wrapping the Contents of the Header #################
|
||||
|
||||
header = "// Copyright © 2022 ZAMA.\n// All rights reserved."
|
||||
header = "// Copyright © 2023 ZAMA.\n// All rights reserved."
|
||||
# trailer = "/* Text to put at the end of the generated file */"
|
||||
include_guard = "TFHE_RS_C_API_H"
|
||||
# pragma_once = true
|
||||
@@ -107,7 +107,6 @@ allow_static_const = true
|
||||
allow_constexpr = false
|
||||
sort_by = "Name"
|
||||
|
||||
|
||||
[macro_expansion]
|
||||
bitflags = false
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# Operations and Examples
|
||||
# Operations
|
||||
|
||||
In thfe::boolean, the available operations are mainly related to their equivalent Boolean gates,
|
||||
i.e., AND, OR,... In what follows, an example of a unary gate (NOT) and one about a binary gate
|
||||
(XOR). The last one is about the ternary MUX gate are detailed, which gives the possibility to
|
||||
homomorphically compute conditional statements of the form ``If..Then..Else``.
|
||||
In tfhe::boolean, the available operations are mainly related to their equivalent Boolean gates (i.e., AND, OR... etc). What follows 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
|
||||
|
||||
@@ -12,11 +9,11 @@ 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 a message:
|
||||
let ct_1 = client_key.encrypt(true);
|
||||
|
||||
|
||||
// We use the server public key to execute the NOT gate:
|
||||
let ct_not = server_key.not(&ct_1);
|
||||
|
||||
@@ -26,7 +23,6 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Binary gates
|
||||
|
||||
```rust
|
||||
@@ -34,12 +30,12 @@ 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 a message:
|
||||
let ct_1 = client_key.encrypt(true);
|
||||
let ct_2 = client_key.encrypt(false);
|
||||
|
||||
|
||||
// We use the server public key to execute the XOR gate:
|
||||
let ct_xor = server_key.xor(&ct_1, &ct_2);
|
||||
|
||||
@@ -49,31 +45,31 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## The MUX ternary gate
|
||||
Let ``ct_1, ct_2, ct_3`` be three Boolean
|
||||
ciphertexts. Then, the MUX gate (abbreviation of MUtipleXer) is equivalent to the operation:
|
||||
|
||||
Let `ct_1, ct_2, ct_3` be three Boolean ciphertexts. Then, the MUX gate (abbreviation of MUltipleXer) is equivalent to the operation:
|
||||
|
||||
```r
|
||||
if ct_1 {
|
||||
if ct_1 {
|
||||
return ct_2
|
||||
} else {
|
||||
return ct_3
|
||||
}
|
||||
```
|
||||
|
||||
This example show how to use the MUX ternary gate.
|
||||
This example shows how to use the MUX ternary gate:
|
||||
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
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();
|
||||
|
||||
let bool1 = true;
|
||||
let bool2 = false;
|
||||
let bool3 = true;
|
||||
|
||||
|
||||
// We use the client secret key to encrypt a message:
|
||||
let ct_1 = client_key.encrypt(true);
|
||||
let ct_2 = client_key.encrypt(false);
|
||||
44
tfhe/docs/Boolean/parameters.md
Normal file
44
tfhe/docs/Boolean/parameters.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Cryptographic Parameters
|
||||
|
||||
## Default parameters
|
||||
|
||||
The TFHE cryptographic scheme relies on a variant of [Regev cryptosystem](https://cims.nyu.edu/\~regev/papers/lwesurvey.pdf) and is based on a problem so difficult that it is even post-quantum resistant.
|
||||
|
||||
Some cryptographic parameters will require tuning to ensure both the correctness of the result and the security of the computation.
|
||||
|
||||
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.
|
||||
|
||||
The following array summarizes this:
|
||||
|
||||
| Parameter set | Error probability |
|
||||
| :-------------------: | :---------------: |
|
||||
| DEFAULT\_PARAMETERS | $$2^{-40}$$ |
|
||||
| TFHE\_LIB\_PARAMETERS | $$2^{-165}$$ |
|
||||
|
||||
## User-defined parameters
|
||||
|
||||
You can also create your own set of parameters. This is an `unsafe` operation as failing to properly fix the parameters will result in an incorrect and/or insecure computation:
|
||||
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// WARNING: might be insecure and/or incorrect
|
||||
// You can create your own set of parameters
|
||||
let parameters = unsafe {
|
||||
BooleanParameters::new(
|
||||
LweDimension(586),
|
||||
GlweDimension(2),
|
||||
PolynomialSize(512),
|
||||
StandardDev(0.00008976167396834998),
|
||||
StandardDev(0.00000002989040792967434),
|
||||
DecompositionBaseLog(8),
|
||||
DecompositionLevelCount(2),
|
||||
DecompositionBaseLog(2),
|
||||
DecompositionLevelCount(5),
|
||||
)
|
||||
};
|
||||
}
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
# Save and Load Keys From Files
|
||||
|
||||
Since the `ServerKey` and `ClientKey` types both implement the `Serialize` and
|
||||
`Deserialize` traits, you are free to use any serializer that suits you to save and load the
|
||||
`Deserialize` traits, you are free to use any serializer that suits you to save and load the
|
||||
keys to disk.
|
||||
|
||||
Here is an example using the `bincode` serialization library, which serializes to a
|
||||
@@ -48,9 +48,9 @@ fn main() {
|
||||
let loaded_client_key: ClientKey = bincode::deserialize(&encoded_client_key[..])
|
||||
.expect("failed to deserialize");
|
||||
|
||||
|
||||
|
||||
let ct_1 = client_key.encrypt(false);
|
||||
|
||||
|
||||
// We check for equality:
|
||||
assert_eq!(false, loaded_client_key.decrypt(&ct_1));
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
# Tutorial: a first boolean circuit
|
||||
# Tutorial
|
||||
|
||||
This library is meant to be used both on the **server side** and on the **client side**.
|
||||
The usual use case would follow those steps:
|
||||
This library is meant to be used both on the **server side** and the **client side**. The typical use case should follow the subsequent steps:
|
||||
|
||||
1. On the **client side**, generate the `client` and `server keys`.
|
||||
2. Send the `server key` to the **server**.
|
||||
3. Then any number of times:
|
||||
+ On the **client side**, *encryption* of the input data with the `client key`.
|
||||
+ Transmit the encrypted input to the **server**.
|
||||
+ On the **server side**, *homomorphic computation* with the `server key`.
|
||||
+ Transmit the encrypted output to the **client**.
|
||||
+ On the **client side**, *decryption* of the output data with `client key`.
|
||||
* On the **client side**, _encrypt_ the input data with the `client key`.
|
||||
* Transmit the encrypted input to the **server**.
|
||||
* On the **server side**, perform _homomorphic computation_ with the `server key`.
|
||||
* Transmit the encrypted output to the **client**.
|
||||
* On the **client side**, _decrypt_ the output data with the `client key`.
|
||||
|
||||
## 1. Setup
|
||||
## Setup
|
||||
|
||||
In the first step, the client creates two keys, the `client key` and the `server key`, with the `concrete_boolean::gen_keys` function:
|
||||
|
||||
In the first step, the client creates two keys: the `client key` and the `server key`,
|
||||
with the
|
||||
`concrete_boolean::gen_keys` function:
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
@@ -28,17 +26,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
In more details:
|
||||
* The `client_key` is of type `ClientKey`. It is **secret** and must **never** be transmitted. This key will only be used to encrypt and decrypt data.
|
||||
* The `server_key` is of type `ServerKey`. It is a **public key** and can be shared with any party. This key has to be sent to the server because it is required for homomorphic computation.
|
||||
|
||||
+ The `client_key` is of type `ClientKey`. It is **secret**, and must **never** be transmitted.
|
||||
This key will only be used to encrypt and decrypt data.
|
||||
+ The `server_key` is of type `ServerKey`. It is a **public key**, and can be shared with any
|
||||
party.
|
||||
This key has to be sent to the server because it is required for the homomorphic computation.
|
||||
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits. This way you can use any compatible serializer to store/send the data. To store the `server_key` in a binary file, you can use the `bincode` library:
|
||||
|
||||
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits.
|
||||
This way you can use any compatible serializer to store/send the data. For instance, to store
|
||||
the `server_key` in a binary file, you can use the `bincode` library:
|
||||
```rust
|
||||
use std::fs::File;
|
||||
use std::io::{Write, Read};
|
||||
@@ -80,21 +72,16 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Encrypting Inputs
|
||||
## Encrypting inputs
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be employed:
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some
|
||||
homomorphic computations.
|
||||
The client simply needs to encrypt some data and send it to the server.
|
||||
Again, the `Ciphertext` type implements the `Serialize` and
|
||||
the `Deserialize` traits, so that any serializer and communication tool suiting your use case
|
||||
can be
|
||||
used:
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// Don't consider the following line; you should follow the procedure above.
|
||||
let (mut client_key, _) = gen_keys();
|
||||
let (client_key, _) = gen_keys();
|
||||
|
||||
//---------------------------- SERVER SIDE
|
||||
|
||||
@@ -112,15 +99,10 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## 2bis. Encrypting Inputs using public key
|
||||
## Encrypting inputs using a public key
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client simply needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be utilized:
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some
|
||||
homomorphic computations.
|
||||
The client simply needs to encrypt some data and send it to the server.
|
||||
Again, the `Ciphertext` type implements the `Serialize` and
|
||||
the `Deserialize` traits, so that any serializer and communication tool suiting your use case
|
||||
can be
|
||||
used:
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
@@ -145,11 +127,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Executing a Boolean circuit
|
||||
|
||||
## Executing a Boolean Circuit
|
||||
|
||||
Once the encrypted inputs are on the **server side**, the `server_key` can be used to
|
||||
homomorphically execute the desired boolean circuit:
|
||||
Once the encrypted inputs are on the **server side**, the `server_key` can be used to homomorphically execute the desired Boolean circuit:
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
@@ -158,7 +138,7 @@ use tfhe::boolean::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// Don't consider the following lines; you should follow the procedure above.
|
||||
let (mut client_key, mut server_key) = gen_keys();
|
||||
let (client_key, server_key) = gen_keys();
|
||||
let ct_1 = client_key.encrypt(true);
|
||||
let ct_2 = client_key.encrypt(false);
|
||||
let encoded_1: Vec<u8> = bincode::serialize(&ct_1).unwrap();
|
||||
@@ -191,8 +171,7 @@ fn main() {
|
||||
|
||||
## Decrypting the output
|
||||
|
||||
Once the encrypted output is on the client side, the `client_key` can be used to
|
||||
decrypt it:
|
||||
Once the encrypted output is on the client side, the `client_key` can be used to decrypt it:
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
@@ -201,7 +180,7 @@ use tfhe::boolean::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// Don't consider the following lines; you should follow the procedure above.
|
||||
let (mut client_key, mut server_key) = gen_keys();
|
||||
let (client_key, server_key) = gen_keys();
|
||||
let ct_6 = client_key.encrypt(true);
|
||||
let encoded_output: Vec<u8> = bincode::serialize(&ct_6).unwrap();
|
||||
|
||||
@@ -218,29 +197,3 @@ fn main() {
|
||||
assert_eq!(output, true);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Cryptographic parameters
|
||||
|
||||
## Default parameters
|
||||
|
||||
The TFHE cryptographic scheme relies on a variant of [Regev cryptosystem](https://cims.nyu.edu/~regev/papers/lwesurvey.pdf), and is based on a problem so hard to solve, that is even post-quantum resistant.
|
||||
|
||||
In practice, you need to tune some cryptographic parameters, in order to ensure the correctness of the result, and the security of the computation.
|
||||
|
||||
To make it simpler, **we provide two sets of parameters**, which ensure correct computations for a certain probability with the standard security of 128 bits. There exists an error probability due the probabilistic nature of the encryption, which requires adding randomness (called noise) following a Gaussian distribution. If this noise is too large, the decryption will not give a correct result. There is a trade-off between efficiency and correctness: generally, using a less efficient parameter set (in terms of computation time) leads to a smaller risk of having an error during homomorphic evaluation.
|
||||
|
||||
In the two proposed sets of parameters, the only difference lies into this probability error.
|
||||
The default parameter set ensures a probability error of at most $$2^{-40}$$ when computing a
|
||||
programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error
|
||||
probability claimed into the original [TFHE paper](https://eprint.iacr.org/2018/421),
|
||||
namely $$2^{-165}$$, but up to date regarding security requirements.
|
||||
|
||||
The following array summarizes this:
|
||||
|
||||
| Parameter set | Error probability |
|
||||
|:-------------------:|:-----------------:|
|
||||
| DEFAULT_PARAMETERS | $$ 2^{-40} $$ |
|
||||
| TFHE_LIB_PARAMETERS | $$ 2^{-165} $$ |
|
||||
|
||||
|
||||
## User-defined parameters
|
||||
|
||||
|
||||
Note that if you desire, you can also create your own set of parameters.
|
||||
This is an `unsafe` operation as failing to properly fix the parameters will potentially result with an incorrect and/or insecure computation:
|
||||
|
||||
```rust
|
||||
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// WARNING: might be insecure and/or incorrect
|
||||
// You can create your own set of parameters
|
||||
let parameters = unsafe {
|
||||
BooleanParameters::new(
|
||||
LweDimension(586),
|
||||
GlweDimension(2),
|
||||
PolynomialSize(512),
|
||||
StandardDev(0.00008976167396834998),
|
||||
StandardDev(0.00000002989040792967434),
|
||||
DecompositionBaseLog(8),
|
||||
DecompositionLevelCount(2),
|
||||
DecompositionBaseLog(2),
|
||||
DecompositionLevelCount(5),
|
||||
)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
# What is TFHE-rs?
|
||||
|
||||
<mark style="background-color:yellow;">⭐️</mark> [<mark style="background-color:yellow;">Star the repo on Github</mark>](https://github.com/zama-ai/tfhe-rs) <mark style="background-color:yellow;">| 🗣</mark> [<mark style="background-color:yellow;">Community support forum</mark> ](https://community.zama.ai)<mark style="background-color:yellow;">| 📁</mark> [<mark style="background-color:yellow;">Contribute to the project</mark>](https://docs.zama.ai/tfhe-rs/developers/contributing)<mark style="background-color:yellow;"></mark>
|
||||
📁 [Github](https://github.com/zama-ai/tfhe-rs) | 💛 [Community support](https://zama.ai/community) | 🟨 [Zama Bounty Program](https://github.com/zama-ai/bounty-program)
|
||||
|
||||

|
||||

|
||||
|
||||
TFHE-rs is a pure Rust implementation of TFHE for boolean and small integer arithmetics over encrypted data. It includes a Rust and C API, as well as a client-side WASM API.
|
||||
TFHE-rs is a pure Rust implementation of TFHE for Boolean and integer arithmetics over encrypted data. It includes a Rust and C API, as well as a client-side WASM API.
|
||||
|
||||
TFHE-rs is meant for developers and researchers who want full control over what they can do with TFHE, while not having to worry about the low level implementation.
|
||||
TFHE-rs is meant for developers and researchers who want full control over what they can do with TFHE, while not worrying about the low level implementation.
|
||||
|
||||
The goal is to have a stable, simple, high-performance, and production-ready library for all the advanced features of TFHE.
|
||||
|
||||
### Key Cryptographic concepts
|
||||
## Key cryptographic concepts
|
||||
|
||||
TFHE-rs library implements Zama’s variant of Fully Homomorphic Encryption over the Torus (TFHE). TFHE is based on Learning With Errors (LWE), a well studied cryptographic primitive believed to be secure even against quantum computers.
|
||||
The TFHE-rs library implements Zama’s variant of Fully Homomorphic Encryption over the Torus (TFHE). TFHE is based on Learning With Errors (LWE), a well-studied cryptographic primitive believed to be secure even against quantum computers.
|
||||
|
||||
In cryptography, a raw value is called a message (also sometimes called a cleartext), an encoded message is called a plaintext and an encrypted plaintext is called a ciphertext.
|
||||
In cryptography, a raw value is called a message (also sometimes called a cleartext), while an encoded message is called a plaintext and an encrypted plaintext is called a ciphertext.
|
||||
|
||||
The idea of homomorphic encryption is that you can compute on ciphertexts while not knowing messages encrypted in them. A scheme is said to be _fully homomorphic_, meaning any program can be evaluated with it, if at least two of the following operations are supported \($$x$$is a plaintext and $$E[x]$$ is the
|
||||
corresponding ciphertext\):
|
||||
The idea of homomorphic encryption is that you can compute on ciphertexts while not knowing messages encrypted within them. A scheme is said to be _fully homomorphic_, meaning any program can be evaluated with it, if at least two of the following operations are supported ($$x$$is a plaintext and $$E[x]$$ is the corresponding ciphertext):
|
||||
|
||||
* homomorphic univariate function evaluation: $$f(E[x]) = E[f(x)]$$
|
||||
* homomorphic addition: $$E[x] + E[y] = E[x + y]$$
|
||||
@@ -28,9 +27,8 @@ Zama's variant of TFHE is fully homomorphic and deals with fixed-precision numbe
|
||||
Using FHE in a Rust program with TFHE-rs consists in:
|
||||
|
||||
* generating a client key and a server key using secure parameters:
|
||||
* client key encrypts/decrypts data and must be kept secret
|
||||
* server key is used to perform operations on encrypted data and could be
|
||||
public (also called evaluation key)
|
||||
* a client key encrypts/decrypts data and must be kept secret
|
||||
* a server key is used to perform operations on encrypted data and could be public (also called an evaluation key)
|
||||
* encrypting plaintexts using the client key to produce ciphertexts
|
||||
* operating homomorphically on ciphertexts with the server key
|
||||
* decrypting the resulting ciphertexts into plaintexts using the client key
|
||||
|
||||
@@ -10,11 +10,16 @@
|
||||
* [Benchmarks](getting\_started/benchmarks.md)
|
||||
* [Security and Cryptography](getting\_started/security\_and\_cryptography.md)
|
||||
|
||||
## Booleans
|
||||
* [Tutorial](Booleans/tutorial.md)
|
||||
* [Operations](Booleans/operations.md)
|
||||
* [Cryptographic Parameters](Booleans/parameters.md)
|
||||
* [Serialization/Deserialization](Booleans/serialization.md)
|
||||
## High Level API
|
||||
* [Tutorial](high_level_api/tutorial.md)
|
||||
* [Operations](high_level_api/operations.md)
|
||||
* [Serialization/Deserialization](high_level_api/serialization.md)
|
||||
|
||||
## Boolean
|
||||
* [Tutorial](Boolean/tutorial.md)
|
||||
* [Operations](Boolean/operations.md)
|
||||
* [Cryptographic Parameters](Boolean/parameters.md)
|
||||
* [Serialization/Deserialization](Boolean/serialization.md)
|
||||
|
||||
## Shortint
|
||||
* [Tutorial](shortint/tutorial.md)
|
||||
@@ -22,8 +27,22 @@
|
||||
* [Cryptographic Parameters](shortint/parameters.md)
|
||||
* [Serialization/Deserialization](shortint/serialization.md)
|
||||
|
||||
## Integer
|
||||
* [Tutorial](integer/tutorial.md)
|
||||
* [Operations](integer/operations.md)
|
||||
* [Cryptographic Parameters](integer/parameters.md)
|
||||
* [Serialization/Deserialization](integer/serialization.md)
|
||||
|
||||
## C API
|
||||
* [Tutorial](c_api/tutorial.md)
|
||||
* [High-Level API](c_api/high-level-api.md)
|
||||
* [Shortint API](c_api/shortint-api.md)
|
||||
|
||||
## JS on WASM API
|
||||
* [Tutorial](js_on_wasm_api/tutorial.md)
|
||||
|
||||
## Low-Level Core Cryptography
|
||||
* [Quick Start](core_crypto/presentation.md)
|
||||
* [Tutorial](core_crypto/tutorial.md)
|
||||
|
||||
## Developers
|
||||
* [Contributing](dev/contributing.md)
|
||||
|
||||
BIN
tfhe/docs/_static/ciphertext-representation.png
vendored
Normal file
BIN
tfhe/docs/_static/ciphertext-representation.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
16
tfhe/docs/_static/ciphertext-representation.svg
vendored
16
tfhe/docs/_static/ciphertext-representation.svg
vendored
@@ -1,16 +0,0 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 424 173" width="424" height="173">
|
||||
<!-- svg-source:excalidraw -->
|
||||
|
||||
<defs>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Virgil";
|
||||
src: url("https://excalidraw.com/Virgil.woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Cascadia";
|
||||
src: url("https://excalidraw.com/Cascadia.woff2");
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g stroke-linecap="round" transform="translate(26 44) rotate(0 194 38.60598503740641)"><path d="M-0.72 -1.78 C148.22 -1.79, 298.89 -0.4, 389.35 -0.66 M0.64 -0.29 C96.57 -1.4, 195.57 -0.99, 387.19 -1.07 M388.14 2.03 C392.43 23.61, 391.39 49, 385.98 75.21 M389.42 -0.67 C389.81 14.73, 390.33 33.43, 388.78 76.49 M387.8 79.39 C277.29 77.61, 166.8 75.17, -1.18 76.59 M387.01 76.05 C246.87 81.15, 103.96 81.92, 0.98 78.37 M-3.84 75.76 C-2.18 57.07, 4.75 34.74, -0.59 -2.81 M1 77.36 C-0.79 48.54, -1.41 22.74, 1.4 1.83" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(43.27283572239912 63.29950568870447) rotate(0 35.31670822942641 19.35162094763092)"><path d="M0.11 -2.85 C28.93 -4.68, 48.62 2.97, 73.79 0.71 M0.92 1.49 C21.03 0.46, 41.01 -1.23, 70.4 0.64 M70.88 3.15 C68.43 11.93, 69.14 26.71, 68.06 39.17 M69.56 -1.68 C71.59 8.67, 68.48 17.57, 70.35 40.23 M68.74 39.32 C48.28 40.85, 30.28 35.73, -0.92 40.56 M69.59 37.89 C48.04 38.14, 23.44 38.86, -0.31 39.89 M2.24 36.95 C-1.92 26.09, -1.01 13.79, -3.25 -2.11 M0.55 39.03 C1.24 29.93, -1.56 22.42, 0.25 1.64" stroke="#0b7285" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(244.47880299251847 58.546134663341206) rotate(0 77.40648379052368 25.15710723192018)"><path d="M0.5 3.27 C41.25 1.83, 85.12 4.03, 158.28 2.64 M1.74 -0.75 C60.33 -2.64, 122.13 -1.19, 153.85 -0.7 M155.49 -2.28 C158.86 11.5, 154.4 20.06, 153.49 51.17 M153.03 -0.55 C152.94 16.04, 156.21 35.59, 154.41 49.32 M157.56 51.63 C96.68 46.86, 29.51 52.23, -2.5 49.76 M155.17 52.19 C121.59 51.66, 87.99 54.57, -0.92 50.35 M-1.24 51.37 C-0.7 37.8, 0.93 29.69, -2.96 2.02 M-1.48 52.03 C0.44 36.29, -0.71 20.9, 1.27 0.34" stroke="#a61e4d" stroke-width="1" fill="none"></path></g><g transform="translate(249.47880299251847 71.70324189526139) rotate(0 72.40648379052368 12)"><text x="72.40648379052374" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.30839567747301px" fill="#a61e4d" text-anchor="middle" style="white-space: pre;" direction="ltr">noise</text></g><g stroke-linecap="round" transform="translate(35.610972568578745 52.67581047381532) rotate(0 89.501246882793 30.962593516209466)"><path d="M3.48 2.12 C42.75 -4.16, 86.77 -1.56, 177.5 3.84 M-1.92 1.02 C66.55 2.87, 130.16 1.88, 179.45 -1.63 M179.35 -3.41 C181.95 11.53, 178.24 25.72, 176.46 63.54 M180.52 0.51 C178.89 22.76, 177.57 49.34, 177.3 60.97 M175.68 59.6 C140.66 62.47, 97.79 56.72, 3.94 58.21 M177.14 61.73 C134.25 62.89, 91.18 61.47, 1.89 60.4 M-0.84 60.36 C-0.67 48.08, 3.19 30.78, -2.25 -3.82 M0.14 61.15 C-2.52 41.13, 0.16 19.4, -0.06 -0.75" stroke="#087f5b" stroke-width="1" fill="none"></path></g><g transform="translate(48.27283572239912 70.65112663633539) rotate(0 30.31670822942641 12)"><text x="30.316708229426418" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.248703637731044px" fill="#087f5b" text-anchor="middle" style="white-space: pre;" direction="ltr">carry</text></g><g stroke-linecap="round" transform="translate(123.17705735660911 63.319201995012975) rotate(0 43.5411471321695 21.286783042394035)"><path d="M3.78 -1.95 C21.85 2.27, 33.36 -1.31, 85.7 0.15 M0.95 -1.99 C30.47 0.73, 63.7 1.15, 87.61 0.81 M83.5 1.56 C89.47 15.22, 83.3 24.69, 84.55 46.54 M85.31 -0.87 C84.54 12.96, 85.46 26.61, 85.69 43.87 M85.54 45.92 C71.62 40.51, 50.01 40.74, -2.16 41.74 M85.45 44.45 C55.35 43.91, 24.36 42.43, 0.99 42.77 M-3.26 44.49 C1.26 31.11, -1.14 19.41, -1.89 2.33 M-1.27 43.89 C-1.22 30.47, 1.35 21.91, -0.8 -1.4" stroke="#0b7285" stroke-width="1" fill="none"></path></g><g transform="translate(128.1770573566091 72.60598503740698) rotate(0 38.5411471321695 12)"><text x="38.54114713216951" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.27057356608477px" fill="#087f5b" text-anchor="middle" style="white-space: pre;" direction="ltr">message</text></g><g transform="translate(371 138) rotate(0 20 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">LSB</text></g><g transform="translate(10 135) rotate(0 21.5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">MSB</text></g><g transform="translate(162 10) rotate(0 51 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">Ciphertext</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB |
BIN
tfhe/docs/_static/docs_home.jpg
vendored
BIN
tfhe/docs/_static/docs_home.jpg
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB |
BIN
tfhe/docs/_static/integer-ciphertext.png
vendored
Normal file
BIN
tfhe/docs/_static/integer-ciphertext.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
tfhe/docs/_static/tfhe-rs-doc-home.png
vendored
Normal file
BIN
tfhe/docs/_static/tfhe-rs-doc-home.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
143
tfhe/docs/c_api/high-level-api.md
Normal file
143
tfhe/docs/c_api/high-level-api.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# High-Level API
|
||||
|
||||
\#Using the High-level C API
|
||||
|
||||
This library exposes a C binding to the high-level TFHE-rs primitives 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,high-level-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,high-level-c-api -p tfhe
|
||||
```
|
||||
|
||||
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 uint128 subtraction using `TFHE-rs C API`.
|
||||
|
||||
{% hint style="warning" %}
|
||||
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.
|
||||
{% endhint %}
|
||||
|
||||
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>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int ok = 0;
|
||||
// Prepare the config builder for the high level API and choose which types to enable
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
// Put the builder in a default state without any types enabled
|
||||
config_builder_all_disabled(&builder);
|
||||
// Enable the uint128 type using the small LWE key for encryption
|
||||
config_builder_enable_default_uint128_small(&builder);
|
||||
// Populate the config
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
|
||||
// Generate the keys using the config
|
||||
generate_keys(config, &client_key, &server_key);
|
||||
// Set the server key for the current thread
|
||||
set_server_key(server_key);
|
||||
|
||||
FheUint128 *lhs = NULL;
|
||||
FheUint128 *rhs = NULL;
|
||||
FheUint128 *result = NULL;
|
||||
|
||||
// Encrypt a u128 using 64 bits words, we encrypt 20 << 64 | 10
|
||||
ok = fhe_uint128_try_encrypt_with_client_key_u128(10, 20, client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
// Encrypt a u128 using words, we encrypt 2 << 64 | 1
|
||||
ok = fhe_uint128_try_encrypt_with_client_key_u128(1, 2, client_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
// Compute the subtraction
|
||||
ok = fhe_uint128_sub(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint64_t w0, w1;
|
||||
// Decrypt
|
||||
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
|
||||
assert(ok == 0);
|
||||
|
||||
// Here the subtraction allows us to compare each word
|
||||
assert(w0 == 9);
|
||||
assert(w1 == 18);
|
||||
|
||||
// Destroy the ciphertexts
|
||||
fhe_uint128_destroy(lhs);
|
||||
fhe_uint128_destroy(rhs);
|
||||
fhe_uint128_destroy(result);
|
||||
|
||||
// Destroy the keys
|
||||
client_key_destroy(client_key);
|
||||
server_key_destroy(server_key);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
```
|
||||
@@ -1,27 +1,32 @@
|
||||
# Tutorial: using the C API
|
||||
# Shortint API
|
||||
|
||||
Welcome to this `TFHE-rs` C API tutorial!
|
||||
## Using the shortint C API
|
||||
|
||||
This library exposes a C binding to the `TFHE-rs` primitives to implement _Fully Homomorphic Encryption_ (FHE) programs.
|
||||
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
|
||||
## First steps using TFHE-rs C API
|
||||
|
||||
## Setting-up `TFHE-rs` C API for use in a C program.
|
||||
### 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:
|
||||
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
|
||||
|
||||
```shell
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features=x86_64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
RUSTFLAGS="-C target-cpu=native" cargo +nightly build --release --features=x86_64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
```
|
||||
|
||||
All features are opt-in, but for simplicity here, the C API is enabled for booleans and shortints.
|
||||
or on a Unix aarch64 machine using the following command:
|
||||
|
||||
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO_ROOT}/target/release/"
|
||||
```shell
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build +nightly --release --features=aarch64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
```
|
||||
|
||||
The build system needs to be set-up so that the C or C++ program links against `TFHE-rs` C API
|
||||
binaries.
|
||||
All features are opt-in, but for simplicity here, the C API is enabled for Boolean and shortint.
|
||||
|
||||
Here is a minimal CMakeLists.txt allowing to do just that:
|
||||
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)
|
||||
@@ -51,17 +56,14 @@ endif()
|
||||
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
|
||||
```
|
||||
|
||||
## Commented code of a PBS doubling a 2 bits encrypted message using `TFHE-rs C API`
|
||||
### Commented code of a PBS doubling a 2-bits encrypted message using `TFHE-rs C API`.
|
||||
|
||||
The steps required to perform the mutiplication by 2 of a 2 bits ciphertext
|
||||
using a PBS are detailed.
|
||||
This is NOT the most efficient way of doing this operation,
|
||||
but it allows to show the management required to run a PBS manually using the C API.
|
||||
The steps required to perform the 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:
|
||||
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
|
||||
@@ -99,7 +101,7 @@ uint64_t get_max_value_of_accumulator_generator(uint64_t (*accumulator_func)(uin
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ShortintPBSAccumulator *accumulator = NULL;
|
||||
ShortintPBSLookupTable *accumulator = NULL;
|
||||
ShortintClientKey *cks = NULL;
|
||||
ShortintServerKey *sks = NULL;
|
||||
ShortintParameters *params = NULL;
|
||||
@@ -168,7 +170,3 @@ int main(void)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
# Audience
|
||||
|
||||
Programmers wishing to use `TFHE-rs` but unable to use Rust (for various reasons) can use these bindings in their language of choice as long as it can interface with C code to bring `TFHE-rs` functionalities to said language.
|
||||
62
tfhe/docs/core_crypto/presentation.md
Normal file
62
tfhe/docs/core_crypto/presentation.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 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 offers an API to low-level cryptographic primitives and objects, like `lwe_encryption` or `rlwe_ciphertext`. Its goal is to propose an easy-to-use API for cryptographers.
|
||||
|
||||
The overall code architecture is split in two parts: one for 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.
|
||||
|
||||
For instance, the code to encrypt and then decrypt a message looks like:
|
||||
|
||||
```rust
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
// computations
|
||||
// Define parameters for LweCiphertext creation
|
||||
let lwe_dimension = LweDimension(742);
|
||||
let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
// Create the LweSecretKey
|
||||
let lwe_secret_key =
|
||||
allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
|
||||
// Create the plaintext
|
||||
let msg = 3u64;
|
||||
let plaintext = Plaintext(msg << 60);
|
||||
|
||||
// Create a new LweCiphertext
|
||||
let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), ciphertext_modulus);
|
||||
|
||||
encrypt_lwe_ciphertext(
|
||||
&lwe_secret_key,
|
||||
&mut lwe,
|
||||
plaintext,
|
||||
lwe_modular_std_dev,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe);
|
||||
|
||||
// Round and remove encoding
|
||||
// First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
let rounded = decomposer.closest_representable(decrypted_plaintext.0);
|
||||
|
||||
// Remove the encoding
|
||||
let cleartext = rounded >> 60;
|
||||
|
||||
// Check we recovered the original message
|
||||
assert_eq!(cleartext, msg);
|
||||
```
|
||||
255
tfhe/docs/core_crypto/tutorial.md
Normal file
255
tfhe/docs/core_crypto/tutorial.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Tutorial
|
||||
|
||||
## Using the `core_crypto` primitives
|
||||
|
||||
Welcome to this tutorial about TFHE-rs `core_crypto` module.
|
||||
|
||||
### Setting up TFHE-rs to use the `core_crypto` module
|
||||
|
||||
To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.4", 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.
|
||||
|
||||
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:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.4", features = ["x86_64-unix"] }
|
||||
```
|
||||
|
||||
For Apple Silicon or aarch64-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.4", features = ["aarch64-unix"] }
|
||||
```
|
||||
|
||||
For x86\_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.4", features = ["x86_64"] }
|
||||
```
|
||||
|
||||
### Commented code to double a 2-bits message in a leveled fashion and using a PBS with the `core_crypto` module.
|
||||
|
||||
As a complete example showing the usage of some common primitives of the `core_crypto` APIs, the following Rust code homomorphically computes 2 \* 3 using two different methods. First using a cleartext multiplication and then using a PBS.
|
||||
|
||||
```rust
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
|
||||
pub fn main() {
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
// computations
|
||||
// Define the parameters for a 4 bits message able to hold the doubled 2 bits message
|
||||
let small_lwe_dimension = LweDimension(742);
|
||||
let glwe_dimension = GlweDimension(1);
|
||||
let polynomial_size = PolynomialSize(2048);
|
||||
let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
let pbs_base_log = DecompositionBaseLog(23);
|
||||
let pbs_level = DecompositionLevelCount(1);
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
// Request the best seeder possible, starting with hardware entropy sources and falling back to
|
||||
// /dev/random on Unix systems if enabled via cargo features
|
||||
let mut boxed_seeder = new_seeder();
|
||||
// Get a mutable reference to the seeder as a trait object from the Box returned by new_seeder
|
||||
let seeder = boxed_seeder.as_mut();
|
||||
|
||||
// Create a generator which uses a CSPRNG to generate secret keys
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
// Create a generator which uses two CSPRNGs to generate public masks and secret encryption
|
||||
// noise
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
|
||||
println!("Generating keys...");
|
||||
|
||||
// Generate an LweSecretKey with binary coefficients
|
||||
let small_lwe_sk =
|
||||
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
|
||||
|
||||
// Generate a GlweSecretKey with binary coefficients
|
||||
let glwe_sk =
|
||||
GlweSecretKey::generate_new_binary(glwe_dimension, polynomial_size, &mut secret_generator);
|
||||
|
||||
// Create a copy of the GlweSecretKey re-interpreted as an LweSecretKey
|
||||
let big_lwe_sk = glwe_sk.clone().into_lwe_secret_key();
|
||||
|
||||
// Generate the bootstrapping key, we use the parallel variant for performance reason
|
||||
let std_bootstrapping_key = par_allocate_and_generate_new_lwe_bootstrap_key(
|
||||
&small_lwe_sk,
|
||||
&glwe_sk,
|
||||
pbs_base_log,
|
||||
pbs_level,
|
||||
glwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
// Create the empty bootstrapping key in the Fourier domain
|
||||
let mut fourier_bsk = FourierLweBootstrapKey::new(
|
||||
std_bootstrapping_key.input_lwe_dimension(),
|
||||
std_bootstrapping_key.glwe_size(),
|
||||
std_bootstrapping_key.polynomial_size(),
|
||||
std_bootstrapping_key.decomposition_base_log(),
|
||||
std_bootstrapping_key.decomposition_level_count(),
|
||||
);
|
||||
|
||||
// Use the conversion function (a memory optimized version also exists but is more complicated
|
||||
// to use) to convert the standard bootstrapping key to the Fourier domain
|
||||
convert_standard_lwe_bootstrap_key_to_fourier(&std_bootstrapping_key, &mut fourier_bsk);
|
||||
// We don't need the standard bootstrapping key anymore
|
||||
drop(std_bootstrapping_key);
|
||||
|
||||
// Our 4 bits message space
|
||||
let message_modulus = 1u64 << 4;
|
||||
|
||||
// Our input message
|
||||
let input_message = 3u64;
|
||||
|
||||
// Delta used to encode 4 bits of message + a bit of padding on u64
|
||||
let delta = (1_u64 << 63) / message_modulus;
|
||||
|
||||
// Apply our encoding
|
||||
let plaintext = Plaintext(input_message * delta);
|
||||
|
||||
// Allocate a new LweCiphertext and encrypt our plaintext
|
||||
let lwe_ciphertext_in: LweCiphertextOwned<u64> = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
&small_lwe_sk,
|
||||
plaintext,
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
// Compute a cleartext multiplication by 2
|
||||
let mut cleartext_multiplication_ct = lwe_ciphertext_in.clone();
|
||||
println!("Performing cleartext multiplication...");
|
||||
lwe_ciphertext_cleartext_mul(
|
||||
&mut cleartext_multiplication_ct,
|
||||
&lwe_ciphertext_in,
|
||||
Cleartext(2),
|
||||
);
|
||||
|
||||
// Decrypt the cleartext multiplication result
|
||||
let cleartext_multiplication_plaintext: Plaintext<u64> =
|
||||
decrypt_lwe_ciphertext(&small_lwe_sk, &cleartext_multiplication_ct);
|
||||
|
||||
// Create a SignedDecomposer to perform the rounding of the decrypted plaintext
|
||||
// We pass a DecompositionBaseLog of 5 and a DecompositionLevelCount of 1 indicating we want to
|
||||
// round the 5 MSB, 1 bit of padding plus our 4 bits of message
|
||||
let signed_decomposer =
|
||||
SignedDecomposer::new(DecompositionBaseLog(5), DecompositionLevelCount(1));
|
||||
|
||||
// Round and remove our encoding
|
||||
let cleartext_multiplication_result: u64 =
|
||||
signed_decomposer.closest_representable(cleartext_multiplication_plaintext.0) / delta;
|
||||
|
||||
println!("Checking result...");
|
||||
assert_eq!(6, cleartext_multiplication_result);
|
||||
println!(
|
||||
"Cleartext multiplication result is correct! \
|
||||
Expected 6, got {cleartext_multiplication_result}"
|
||||
);
|
||||
|
||||
// Now we will use a PBS to compute the same multiplication, it is NOT the recommended way of
|
||||
// doing this operation in terms of performance as it's much more costly than a multiplication
|
||||
// with a cleartext, however it resets the noise in a ciphertext to a nominal level and allows
|
||||
// to evaluate arbitrary functions so depending on your use case it can be a better fit.
|
||||
|
||||
// Here we will define a helper function to generate an accumulator for a PBS
|
||||
fn generate_accumulator<F>(
|
||||
polynomial_size: PolynomialSize,
|
||||
glwe_size: GlweSize,
|
||||
message_modulus: usize,
|
||||
ciphertext_modulus: CiphertextModulus<u64>,
|
||||
delta: u64,
|
||||
f: F,
|
||||
) -> GlweCiphertextOwned<u64>
|
||||
where
|
||||
F: Fn(u64) -> u64,
|
||||
{
|
||||
// N/(p/2) = size of each block, to correct noise from the input we introduce the notion of
|
||||
// box, which manages redundancy to yield a denoised value for several noisy values around
|
||||
// a true input value.
|
||||
let box_size = polynomial_size.0 / message_modulus;
|
||||
|
||||
// Create the accumulator
|
||||
let mut accumulator_u64 = vec![0_u64; polynomial_size.0];
|
||||
|
||||
// Fill each box with the encoded denoised value
|
||||
for i in 0..message_modulus {
|
||||
let index = i * box_size;
|
||||
accumulator_u64[index..index + box_size]
|
||||
.iter_mut()
|
||||
.for_each(|a| *a = f(i as u64) * delta);
|
||||
}
|
||||
|
||||
let half_box_size = box_size / 2;
|
||||
|
||||
// Negate the first half_box_size coefficients to manage negacyclicity and rotate
|
||||
for a_i in accumulator_u64[0..half_box_size].iter_mut() {
|
||||
*a_i = (*a_i).wrapping_neg();
|
||||
}
|
||||
|
||||
// Rotate the accumulator
|
||||
accumulator_u64.rotate_left(half_box_size);
|
||||
|
||||
let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
|
||||
|
||||
let accumulator =
|
||||
allocate_and_trivially_encrypt_new_glwe_ciphertext(
|
||||
glwe_size,
|
||||
&accumulator_plaintext,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
accumulator
|
||||
}
|
||||
|
||||
// Generate the accumulator for our multiplication by 2 using a simple closure
|
||||
let accumulator: GlweCiphertextOwned<u64> = generate_accumulator(
|
||||
polynomial_size,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
message_modulus as usize,
|
||||
ciphertext_modulus,
|
||||
delta,
|
||||
|x: u64| 2 * x,
|
||||
);
|
||||
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut pbs_multiplication_ct = LweCiphertext::new(
|
||||
0u64,
|
||||
big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
println!("Computing PBS...");
|
||||
programmable_bootstrap_lwe_ciphertext(
|
||||
&lwe_ciphertext_in,
|
||||
&mut pbs_multiplication_ct,
|
||||
&accumulator,
|
||||
&fourier_bsk,
|
||||
);
|
||||
|
||||
// Decrypt the PBS multiplication result
|
||||
let pbs_multipliation_plaintext: Plaintext<u64> =
|
||||
decrypt_lwe_ciphertext(&big_lwe_sk, &pbs_multiplication_ct);
|
||||
|
||||
// Round and remove our encoding
|
||||
let pbs_multiplication_result: u64 =
|
||||
signed_decomposer.closest_representable(pbs_multipliation_plaintext.0) / delta;
|
||||
|
||||
println!("Checking result...");
|
||||
assert_eq!(6, pbs_multiplication_result);
|
||||
println!(
|
||||
"Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contribute
|
||||
# Contributing
|
||||
|
||||
There are two ways to contribute to **TFHE-rs**:
|
||||
There are two ways to contribute to **TFHE-rs**. You can:
|
||||
|
||||
* you can open issues to report bugs and typos and to suggest ideas
|
||||
* you can ask to become an official contributor by emailing hello@zama.ai. Only approved contributors can end pull requests, so please make sure to get in touch before you do!
|
||||
* 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.
|
||||
|
||||
@@ -1,44 +1,68 @@
|
||||
# Benchmarks
|
||||
|
||||
Due to their nature, homomorphic operations are obviously slower than their clear equivalent. In what follows, some timings are exposed for the basic operations. For completeness, some benchmarks of other libraries are also given.
|
||||
Due to their nature, homomorphic operations are naturally slower than their clear equivalent. Some timings are exposed for basic operations. For completeness, benchmarks for other libraries are also given.
|
||||
|
||||
All the benchmarks had been launched on an AWS m6i.metal with the following specifications:
|
||||
Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
|
||||
All benchmarks were launched on an AWS m6i.metal with the following specifications: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
|
||||
|
||||
## Booleans
|
||||
## Boolean
|
||||
|
||||
This measures the execution time of a single binary boolean gate.
|
||||
This measures the execution time of a single binary Boolean gate.
|
||||
|
||||
### thfe.rs::booleans
|
||||
### tfhe-rs::boolean.
|
||||
|
||||
| Parameter set | concrete-fft | concrete-fft + avx512 |
|
||||
| --- | --- | --- |
|
||||
| DEFAULT_PARAMETERS | 8.8ms | 6.8ms |
|
||||
| TFHE_LIB_PARAMETERS | 13.6ms | 10.9ms |
|
||||
| Parameter set | Concrete FFT | Concrete FFT + avx512 |
|
||||
| --------------------- | ------------ | --------------------- |
|
||||
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
|
||||
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
|
||||
|
||||
### tfhe-lib
|
||||
### tfhe-lib.
|
||||
|
||||
| Parameter set | fftw | spqlios-fma|
|
||||
| --- | --- | --- |
|
||||
| default_128bit_gate_bootstrapping_parameters | 28.9ms | 15.7ms |
|
||||
| Parameter set | fftw | spqlios-fma |
|
||||
| ------------------------------------------------ | ------ | ----------- |
|
||||
| default\_128bit\_gate\_bootstrapping\_parameters | 28.9ms | 15.7ms |
|
||||
|
||||
### OpenFHE
|
||||
### OpenFHE.
|
||||
|
||||
| Parameter set | GINX | GINX (Intel HEXL) |
|
||||
| --- | --- | --- |
|
||||
| STD_128 | 172ms | 78ms |
|
||||
| MEDIUM | 113ms | 50.2ms |
|
||||
|
||||
## Shortints
|
||||
This measures the execution time for some operations and some parameter sets of shortints.
|
||||
|
||||
### thfe.rs::shortint
|
||||
This uses the concrete-fft + avx512 configuration.
|
||||
| Parameter set | GINX | GINX (Intel HEXL) |
|
||||
| ------------- | ----- | ----------------- |
|
||||
| STD\_128 | 172ms | 78ms |
|
||||
| MEDIUM | 113ms | 50.2ms |
|
||||
|
||||
|
||||
| Parameter set | unchecked_add | unchecked_mul_lsb | keyswitch_programmable_bootstrap |
|
||||
| --- | --- | --- | --- |
|
||||
| PARAM_MESSAGE_1_CARRY_1 | 337 ns | 10.1 ms | 9.91 ms |
|
||||
| PARAM_MESSAGE_2_CARRY_2 | 407 ns | 21.7 ms | 21.4 ms |
|
||||
| PARAM_MESSAGE_3_CARRY_3 | 3.06 µs | 161 ms | 159 ms |
|
||||
| PARAM_MESSAGE_4_CARRY_4 | 11.7 µs | 1.03 s | 956 ms |
|
||||
## Shortint
|
||||
This measures the execution time for some operations and some parameter sets of tfhe-rs::shortint.
|
||||
|
||||
This uses the Concrete FFT + avx512 configuration.
|
||||
|
||||
| Parameter set | unchecked\_add | unchecked\_mul\_lsb | keyswitch\_programmable\_bootstrap |
|
||||
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
|
||||
| PARAM\_MESSAGE\_1\_CARRY\_1 | 338 ns | 8.3 ms | 8.1 ms |
|
||||
| PARAM\_MESSAGE\_2\_CARRY\_2 | 406 ns | 18.4 ms | 18.4 ms |
|
||||
| PARAM\_MESSAGE\_3\_CARRY\_3 | 3.06 µs | 134 ms | 129.4 ms |
|
||||
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 828.1 ms |
|
||||
|
||||
Next, the timings for the operation flavor `default` are given. This flavor ensures predictable timings of an operation all along the circuit by clearing the carry space after each operation.
|
||||
|
||||
| Parameter set | add | mul\_lsb | keyswitch\_programmable\_bootstrap |
|
||||
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
|
||||
| PARAM\_MESSAGE\_1\_CARRY\_1 | 7.90 ms | 8.00 ms | 8.10 ms |
|
||||
| PARAM\_MESSAGE\_2\_CARRY\_2 | 18.4 ms | 18.1 ms | 18.4 ms |
|
||||
| PARAM\_MESSAGE\_3\_CARRY\_3 | 131.5 ms | 129.5 ms | 129.4 ms |
|
||||
| PARAM\_MESSAGE\_4\_CARRY\_4 | 852.5 ms | 839.7 ms | 828.1 ms |
|
||||
|
||||
|
||||
## Integer
|
||||
This measures the execution time for some operation sets of tfhe-rs::integer.
|
||||
|
||||
All timings are related to parallelized Radix-based integer operations, where each block is encrypted using PARAM\_MESSAGE\_2\_CARRY\_2.
|
||||
To ensure predictable timings, the operation flavor is the `default` one: a carry propagation is computed after each operation. Operation cost could be reduced by using `unchecked`, `checked`, or `smart`.
|
||||
|
||||
| Plaintext size | add | mul | greater\_than (gt) | min |
|
||||
| -------------------| -------------- | ------------------- | --------- | ------- |
|
||||
| 8 bits | 129.0 ms | 227.2 ms | 111.9 ms | 186.8 ms |
|
||||
| 16 bits | 256.3 ms | 756.0 ms | 145.3 ms | 233.1 ms |
|
||||
| 32 bits | 469.4 ms | 2.10 s | 192.0 ms | 282.9 ms |
|
||||
| 40 bits | 608.0 ms | 3.37 s | 228.4 ms | 318.6 ms |
|
||||
| 64 bits | 959.9 ms | 5.53 s | 249.0 ms | 336.5 ms |
|
||||
| 128 bits | 1.88 s | 14.1 s | 294.7 ms | 398.6 ms |
|
||||
| 256 bits | 3.66 s | 29.2 s | 361.8 ms | 509.1 ms |
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
|
||||
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.1.0", features = [ "boolean", "shortint", "x86_64-unix" ] }
|
||||
tfhe = { version = "0.2.4", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
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, eg: `cargo run --release`.
|
||||
{% endhint %}
|
||||
|
||||
## Choosing your features
|
||||
|
||||
`TFHE-rs` exposes different `cargo features` to customize the types and features used.
|
||||
@@ -17,38 +20,35 @@ tfhe = { version = "0.1.0", features = [ "boolean", "shortint", "x86_64-unix" ]
|
||||
|
||||
This crate exposes two kinds of data types. Each kind is enabled by activating its corresponding feature in the TOML line. Each kind may have multiple types:
|
||||
|
||||
| Kind | Features | Type(s) |
|
||||
| --------- | ------------- |------------------------------------------|
|
||||
| Booleans | `boolean` | Booleans |
|
||||
| ShortInts | `shortint` | Short unsigned integers |
|
||||
|
||||
| Kind | Features | Type(s) |
|
||||
| --------- | ---------- | --------------------------------- |
|
||||
| Booleans | `boolean` | Booleans |
|
||||
| ShortInts | `shortint` | Short unsigned integers |
|
||||
| Integers | `integer` | Arbitrary-sized unsigned integers |
|
||||
|
||||
### Serialization.
|
||||
|
||||
The different data types and keys exposed by the crate can be serialized / deserialized.
|
||||
|
||||
More information can be found [here](../Booleans/serialization.md) for Booleans and [here](../shortint/serialization.md) for shortint.
|
||||
More information can be found [here](../Boolean/serialization.md) for Boolean and [here](../shortint/serialization.md) for shortint.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (x86 with `RDSEED`
|
||||
instruction).
|
||||
TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (x86 with `RDSEED` instruction).
|
||||
|
||||
| OS | x86 | aarch64 |
|
||||
| --------- | ------------- |------------------|
|
||||
| Linux | `x86_64-unix` | `aarch64-unix`* |
|
||||
| macOS | `x86_64-unix` | `aarch64-unix`* |
|
||||
| Windows | `x86_64` | Unsupported |
|
||||
| OS | x86 | aarch64 |
|
||||
| ------- | ------------- | ---------------- |
|
||||
| Linux | `x86_64-unix` | `aarch64-unix`\* |
|
||||
| macOS | `x86_64-unix` | `aarch64-unix`\* |
|
||||
| Windows | `x86_64` | Unsupported |
|
||||
|
||||
{% hint style="info" %}
|
||||
Users who have ARM devices can use `TFHE-rs` by compiling using the
|
||||
`nightly` toolchain.
|
||||
Users who have ARM devices can use `TFHE-rs` by compiling using the `nightly` toolchain.
|
||||
{% endhint %}
|
||||
|
||||
### Using TFHE-rs with nightly toolchain.
|
||||
|
||||
### Using TFHE-rs with nightly toolchain
|
||||
|
||||
First, install the needed Rust toolchain:
|
||||
Install the needed Rust toolchain:
|
||||
|
||||
```shell
|
||||
rustup toolchain install nightly
|
||||
@@ -58,8 +58,6 @@ Then, you can either:
|
||||
|
||||
* Manually specify the toolchain to use in each of the cargo commands:
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
cargo +nightly build
|
||||
cargo +nightly test
|
||||
|
||||
@@ -2,52 +2,63 @@
|
||||
|
||||
## Boolean
|
||||
|
||||
The list of supported operations by the homomorphic booleans is:
|
||||
The list of supported operations by the homomorphic Booleans is:
|
||||
|
||||
|Operation Name | type |
|
||||
| ------ | ------ |
|
||||
| `not` | Unary |
|
||||
| `and` | Binary |
|
||||
| `or` | Binary |
|
||||
| `xor` | Binary |
|
||||
| `nor` | Binary |
|
||||
| `xnor` | Binary |
|
||||
| `cmux` | Ternary |
|
||||
| Operation Name | type |
|
||||
| -------------- | ------- |
|
||||
| `not` | Unary |
|
||||
| `and` | Binary |
|
||||
| `or` | Binary |
|
||||
| `xor` | Binary |
|
||||
| `nor` | Binary |
|
||||
| `xnor` | Binary |
|
||||
| `cmux` | Ternary |
|
||||
|
||||
A walk-through using homomorphic Booleans can be found [here](../Boolean/tutorial.md).
|
||||
|
||||
A walk-through using homomorphic Booleans can be found [here](../Booleans/tutorial.md).
|
||||
|
||||
|
||||
## ShortInt
|
||||
|
||||
In TFHE-rs, the shortints represent short unsigned integers encoded over 8 bits maximum. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
|
||||
## Shortint
|
||||
|
||||
In TFHE-rs, shortint represents short unsigned integers encoded over a maximum of 8 bits. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| Operation name | Type |
|
||||
|--------------- | ------ |
|
||||
| Negation | Unary |
|
||||
| Addition | Binary |
|
||||
| Subtraction | Binary |
|
||||
| Multiplication | Binary |
|
||||
| Division* | Binary |
|
||||
| Modular reduction | Binary |
|
||||
| Comparisons | Binary |
|
||||
| Left/Right Shift | Binary |
|
||||
| And | Binary |
|
||||
| Or | Binary |
|
||||
| Xor | Binary |
|
||||
| Exact Function Evaluation | Unary/Binary |
|
||||
| Operation name | Type |
|
||||
| ------------------------- | ------------ |
|
||||
| Negation | Unary |
|
||||
| Addition | Binary |
|
||||
| Subtraction | Binary |
|
||||
| Multiplication | Binary |
|
||||
| Division\* | Binary |
|
||||
| Modular reduction | Binary |
|
||||
| Comparisons | Binary |
|
||||
| Left/Right Shift | Binary |
|
||||
| And | Binary |
|
||||
| Or | Binary |
|
||||
| Xor | Binary |
|
||||
| Exact Function Evaluation | Unary/Binary |
|
||||
|
||||
{% hint style="info" %}
|
||||
\* The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns 0.
|
||||
The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. The division is tweaked so that dividing by 0 returns 0.
|
||||
{% endhint %}
|
||||
|
||||
A walk-through example can be found [here](../shortint/tutorial.md) and more examples and
|
||||
explanations can be found [here](../shortint/operations.md)
|
||||
A walk-through example can be found [here](../shortint/tutorial.md), and more examples and explanations can be found [here](../shortint/operations.md).
|
||||
|
||||
## Integer
|
||||
|
||||
In TFHE-rs, integers represent unsigned integers up to 256 bits. They are encoded using Radix representations by default (more details [here](../integer/operations.md)).
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| Operation name | Type |
|
||||
| ------------------------------ | ------ |
|
||||
| Negation | Unary |
|
||||
| Addition | Binary |
|
||||
| Subtraction | Binary |
|
||||
| Multiplication | Binary |
|
||||
| Bitwise OR, AND, XOR | Binary |
|
||||
| Equality | Binary |
|
||||
| Left/Right Shift | Binary |
|
||||
| Comparisons `<`,`<=`,`>`, `>=` | Binary |
|
||||
| Min, Max | Binary |
|
||||
|
||||
A walk-through example can be found [here](../integer/tutorial.md).
|
||||
|
||||
@@ -1,62 +1,116 @@
|
||||
# Quick start
|
||||
# Quick Start
|
||||
|
||||
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans or short integers (named shortints in the rest of this documentation).
|
||||
It allows one to execute a circuit on an **untrusted server** because both circuit inputs and outputs are kept **private**.
|
||||
Data are indeed encrypted on the client side, before being sent to the server. On the server side every computation is performed on ciphertexts.
|
||||
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans, 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. She can then decrypt it with her `secret key`.
|
||||
The server, however, has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. Then the user can decrypt it with the `secret key`.
|
||||
|
||||
## General method to write an homomorphic circuit program
|
||||
|
||||
## General method to write homomorphic circuit program
|
||||
The overall process to write an homomorphic program is the same for all types. The basic steps for using the TFHE-rs library are the following:
|
||||
|
||||
The overall process to write an homomorphic program is the same for both Boolean and short integers types.
|
||||
In a nutshell, the basic steps for using the TFHE-rs library are the following:
|
||||
- Choose a data type (Boolean or shortint)
|
||||
- Import the library
|
||||
- Create client and server keys
|
||||
- Encrypt data with the client key
|
||||
- Compute over encrypted data using the server key
|
||||
- Decrypt data with the client key
|
||||
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.
|
||||
|
||||
### Boolean example
|
||||
This library has different modules, with different levels of abstraction.
|
||||
|
||||
Here is an example to illustrate how the library can be used to evaluate a Boolean circuit:
|
||||
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 B**oolean**, **shortint**, and **integer** modules, which simply allow 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_uint8()
|
||||
.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 (mut client_key, mut server_key) = gen_keys();
|
||||
// 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:
|
||||
// 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)
|
||||
// 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:
|
||||
// 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
|
||||
#### shortint example
|
||||
|
||||
and here is a full example using shortints:
|
||||
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 the default parameters:
|
||||
let (client_key, server_key) = gen_keys(Parameters::default());
|
||||
// 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;
|
||||
@@ -76,5 +130,32 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### integer example
|
||||
|
||||
The library is pretty simple to use, and can evaluate **homomorphic circuits of arbitrary length**. The description of the algorithms can be found in the [TFHE](https://doi.org/10.1007/s00145-019-09319-x) paper (also available as [ePrint 2018/421](https://ia.cr/2018/421)).
|
||||
{% 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)).
|
||||
|
||||
@@ -1,74 +1,49 @@
|
||||
# Cryptography & Security
|
||||
# Security and Cryptography
|
||||
|
||||
# TFHE
|
||||
## TFHE
|
||||
|
||||
TFHE-rs is a cryptographic library dedicated to Fully Homomorphic Encryption. As its name
|
||||
suggests, it is based on the TFHE scheme.
|
||||
TFHE-rs is a cryptographic library dedicated to Fully Homomorphic Encryption. As its name suggests, it is based on the TFHE scheme.
|
||||
|
||||
It is interesting to understand some basics about TFHE,
|
||||
in order to apprehend where the limitations are coming from both
|
||||
in terms of precision (number of bits used to represent the plaintext values)
|
||||
and execution time (why TFHE operations are slower than native operations).
|
||||
It is necessary to understand some basics about TFHE to comprehend where the limitations are coming from, both in terms of precision (number of bits used to represent plaintext values) and execution time (why TFHE operations are slower than native operations).
|
||||
|
||||
# LWE Ciphertexts
|
||||
## LWE ciphertexts
|
||||
|
||||
Although there are many kinds of ciphertexts in TFHE,
|
||||
all the encrypted values in TFHE-rs are mainly stored as LWE ciphertexts.
|
||||
Although there are many kinds of ciphertexts in TFHE, all the encrypted values in TFHE-rs are mainly stored as LWE ciphertexts.
|
||||
|
||||
The security of TFHE relies on the LWE problem which stands for Learning With Errors.
|
||||
The problem is believed to be secure against quantum attacks.
|
||||
The security of TFHE relies on the LWE problem, which stands for Learning With Errors. The problem is believed to be secure against quantum attacks.
|
||||
|
||||
An LWE Ciphertext is a collection of 32-bits or 64-bits unsigned integers.
|
||||
Before encrypting a message in an LWE ciphertext, one needs to first encode it as a plaintext.
|
||||
This is done by shifting the message to the most significant bits of the unsigned integer type used.
|
||||
An LWE Ciphertext is a collection of 32-bit or 64-bit unsigned integers. Before encrypting a message in an LWE ciphertext, one must first encode it as a plaintext. This is done by shifting the message to the most significant bits of the unsigned integer type used.
|
||||
|
||||
Then, a little random value called noise is added to the least significant bits.
|
||||
This noise (also called error for Learning With Errors) is crucial to the security of the ciphertext.
|
||||
Then, a little random value called noise is added to the least significant bits. This noise (also called error for Learning With Errors) is crucial to the security of the ciphertext.
|
||||
|
||||
$$ plaintext = (\Delta * m) + e $$
|
||||
$$plaintext = (\Delta * m) + e$$
|
||||
|
||||

|
||||

|
||||
|
||||
To go from a **plaintext** to a **ciphertext** one needs to encrypt the plaintext using a secret key.
|
||||
To go from a **plaintext** to a **ciphertext,** one must encrypt the plaintext using a secret key.
|
||||
|
||||
An LWE secret key is a list of `n` random integers: $$S = (s_0, ..., s_n)$$.
|
||||
$$n$$ is called the $$LweDimension$$
|
||||
An LWE secret key is a list of `n` random integers: $$S = (s_0, ..., s_n)$$. $$n$$ is called the $$LweDimension$$
|
||||
|
||||
A LWE ciphertext, is composed of two parts:
|
||||
- The mask $$(a_0, ..., a_{n-1})$$
|
||||
- The body $$b$$
|
||||
A LWE ciphertext is composed of two parts:
|
||||
|
||||
The mask of a _fresh_ ciphertext (one that is the result of an encryption
|
||||
and not an operation such as ciphertext addition) is a list of `n` uniformly random values.
|
||||
* The mask $$(a_0, ..., a_{n-1})$$
|
||||
* The body $$b$$
|
||||
|
||||
The mask of a _fresh_ ciphertext (one that is the result of an encryption and not an operation, such as ciphertext addition) is a list of `n` uniformly random values.
|
||||
|
||||
The body is computed as follows:
|
||||
|
||||
$$ b = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext $$
|
||||
$$b = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext$$
|
||||
|
||||
Now that the encryption scheme is defined, to illustrate why it is slower to compute over encrypted data,
|
||||
let us show the example of the addition between ciphertexts.
|
||||
Now that the encryption scheme is defined, let's review the example of the addition between ciphertexts to illustrate why it is slower to compute over encrypted data.
|
||||
|
||||
To add two ciphertexts, we must add their $mask$ and $body$ as done below.
|
||||
To add two ciphertexts, we must add their $mask$ and $body$:
|
||||
|
||||
$$
|
||||
ct_0 = (a_{0}, ..., a_{n}, b) \\
|
||||
ct_1 = (a_{1}^{'}, ..., a_{n}^{'}, b^{'}) \\
|
||||
|
||||
ct_{2} = ct_0 + ct_1 \\
|
||||
ct_{2} = (a_{0} + a_{0}^{'}, ..., a_{n} + a_{n}^{'}, b + b^{'})\\
|
||||
|
||||
b + b^{'} = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext + (\sum_{i = 0}^{n-1}{a_i^{'} * s_i}) + plaintext^{'}\\
|
||||
|
||||
b + b^{'} = (\sum_{i = 0}^{n-1}{(a_i + a_i^{'})* s_i}) + \Delta m + \Delta m^{'} + e + e^{'}\\
|
||||
ct_0 = (a_{0}, ..., a_{n}, b) \\ ct_1 = (a_{1}^{'}, ..., a_{n}^{'}, b^{'}) \\ ct_{2} = ct_0 + ct_1 \\ ct_{2} = (a_{0} + a_{0}^{'}, ..., a_{n} + a_{n}^{'}, b + b^{'})\\ b + b^{'} = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext + (\sum_{i = 0}^{n-1}{a_i^{'} * s_i}) + plaintext^{'}\\ b + b^{'} = (\sum_{i = 0}^{n-1}{(a_i + a_i^{'})* s_i}) + \Delta m + \Delta m^{'} + e + e^{'}\\
|
||||
$$
|
||||
|
||||
To add ciphertexts, it is sufficient to add their masks and bodies.
|
||||
Instead of just adding 2 integers, one needs to add $$n + 1$$ elements.
|
||||
The addition is an intuitive example to show the slowdown of FHE computation compared to plaintext
|
||||
computation but other operations are far more expensive
|
||||
(e.g., the computation of a lookup table using the Programmable Bootstrapping)
|
||||
|
||||
# Ciphertexts Operations
|
||||
To add ciphertexts, it is sufficient to add their masks and bodies. Instead of just adding two integers, one needs to add $$n + 1$$ elements. The addition is an intuitive example to show the slowdown of FHE computation compared to plaintext computation, but other operations are far more expensive (e.g., the computation of a lookup table using Programmable Bootstrapping).
|
||||
|
||||
## Understanding noise and padding
|
||||
|
||||
@@ -77,45 +52,42 @@ In FHE, there are two types of operations that can be applied to ciphertexts:
|
||||
* **leveled operations**, which increase the noise in the ciphertext
|
||||
* **bootstrapped operations**, which reduce the noise in the ciphertext
|
||||
|
||||
In FHE, the noise must be tracked and managed in order to guarantee the correctness of the computation.
|
||||
In FHE, noise must be tracked and managed to guarantee the correctness of the computation.
|
||||
|
||||
Bootstrapping operations are used across the computation to decrease the noise in the ciphertexts, preventing it from tampering the message. The rest of the operations are called leveled because they do not need bootstrapping operations and thus are most of the time really fast.
|
||||
Bootstrapping operations are used across the computation to decrease noise within the ciphertexts, preventing it from tampering the message. The rest of the operations are called leveled because they do not need bootstrapping operations and are usually really fast as a result.
|
||||
|
||||
The following sections explain the concept of noise and padding in ciphertexts.
|
||||
|
||||
### Noise
|
||||
### Noise.
|
||||
|
||||
For it to be secure, LWE requires random noise to be added to the message at encryption time.
|
||||
|
||||
In TFHE, this random noise is drawn from a Centered Normal Distribution parameterized by a standard deviation. This standard deviation is a security parameter.
|
||||
With all other security parameters set, the larger the standard deviation is, the more secure the encryption is.
|
||||
In TFHE, this random noise is drawn from a Centered Normal Distribution, parameterized by a standard deviation. This standard deviation is a security parameter. With all other security parameters set, the more secure the encryption, the larger the standard deviation.
|
||||
|
||||
In `TFHE-rs`, the noise is encoded in the least significant bits of the plaintexts. Each leveled computation will increase the noise, and thus if too many computations are done, the noise will eventually overflow onto the significant data bits of the message and lead to an incorrect result.
|
||||
In `TFHE-rs`, noise is encoded in the least significant bits of the plaintexts. Each leveled computation increases the noise. If too many computations are performed, the noise will eventually overflow onto the significant data bits of the message and lead to an incorrect result.
|
||||
|
||||
The figure below illustrates this problem in case of an addition, where an extra bit of noise is incurred as a result.
|
||||
|
||||

|
||||

|
||||
|
||||
TFHE-rs offers the possibility to automatically manage the noise, by performing bootstrapping operations to reset the noise when needed.
|
||||
TFHE-rs offers the ability to automatically manage noise by performing bootstrapping operations to reset the noise.
|
||||
|
||||
### Padding.
|
||||
|
||||
### Padding
|
||||
Since encoded values have a fixed precision, operating on them can produce results that are outside the original interval. To avoid losing precision or wrapping around the interval, TFHE-rs uses additional bits by defining bits of **padding** on the most significant bits.
|
||||
|
||||
Since encoded values have a fixed precision, operating on them can sometime produce results that are outside the original interval. To avoid losing precision or wrapping around the interval, TFHE-rs uses additional bits by defining bits of **padding** on the most significant bits.
|
||||
As an example, consider adding two ciphertexts. Adding two values could end up outside the range of either ciphertext, and thus necessitate a carry, which would then be carried onto the first padding bit. In the figure below, each plaintext over 32 bits has one bit of padding on its left (i.e., the most significant bit). After the addition, the padding bit is no longer available, as it has been used in order for the carry. This is referred to as **consuming** bits of padding. Since no padding is left, there is no guarantee that further additions would yield correct results.
|
||||
|
||||
As an example, consider adding two ciphertexts. Adding two values could en up outside the range of either ciphertexts, and thus necessitate a carry, which would then be carried onto the first padding bit. In the figure below, each plaintext over 32 bits has one bit of padding on its left \(i.e., the most significant bit\). After the addition, the padding bit is no longer available, as it has been used in order for the carry. This is referred to as **consuming** bits of padding. Since no padding is left, there is no guarantee that additional additions would yield correct results.
|
||||
|
||||

|
||||

|
||||
|
||||
If you would like to know more about TFHE, you can find more information in our [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
|
||||
|
||||
## Security
|
||||
### Security.
|
||||
|
||||
By default, the cryptographic parameters provided by `TFHE-rs` ensure at least 128 bits of security.
|
||||
The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
|
||||
By default, the cryptographic parameters provided by `TFHE-rs` ensure at least 128 bits of security. The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
|
||||
|
||||
For all sets of parameters, the error probability when computing a univariate function over one ciphertext is $$2^{-40}$$. Note that univariate functions might be performed when arithmetic functions are computed (for instance, the multiplication of two ciphertexts).
|
||||
For all sets of parameters, the error probability when computing a univariate function over one ciphertext is $$2^{-40}$$. Note that univariate functions might be performed when arithmetic functions are computed (i.e., the multiplication of two ciphertexts).
|
||||
|
||||
## Public key encryption
|
||||
### Public key encryption.
|
||||
|
||||
In public key encryption, the public key consists in providing a given number of message encrypting the value 0. By setting the number of encryptions of 0 in the public key at $$m = \lceil (n+1) \log(q) \rceil + \lambda$$, where $$n$$ is the LWE dimension, $$q$$ is the ciphertext modulus and $$\lambda$$ is the number of security bits. In a nutshell, this construction is secure due to the left-over-hash lemma, which is essentially related to the impossibility of breaking the underlying multiple subset sum problem. By using this formula, this guarantees both a high density subset sum and an exponentially large number of possible associated random vectors per LWE sample (a,b)
|
||||
In public key encryption, the public key contains a given number of ciphertexts all encrypting the value 0. By setting the number of encryptions to 0 in the public key at $$m = \lceil (n+1) \log(q) \rceil + \lambda$$, where $$n$$ is the LWE dimension, $$q$$ is the ciphertext modulus, and $$\lambda$$ is the number of security bits. This construction is secure due to the leftover hash lemma, which relates to the impossibility of breaking the underlying multiple subset sum problem. This guarantees both a high-density subset sum and an exponentially large number of possible associated random vectors per LWE sample (a,b).
|
||||
|
||||
405
tfhe/docs/high_level_api/operations.md
Normal file
405
tfhe/docs/high_level_api/operations.md
Normal file
@@ -0,0 +1,405 @@
|
||||
# Operations
|
||||
|
||||
The structure and operations related to all types (ì.e., Booleans, shortint and integer) are described in this section.
|
||||
|
||||
## Booleans
|
||||
|
||||
Native homomorphic Booleans support common Boolean operations.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
| ------------------------------------------------------------- | ------ | ------ |
|
||||
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
|
||||
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
|
||||
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
|
||||
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
|
||||
|
||||
## ShortInt
|
||||
|
||||
Native small homomorphic integer types (e.g., FheUint3 or FheUint4) easily compute various operations. In general, computing over encrypted data is as easy as computing over clear data, since the same operation symbol is used. The addition between two ciphertexts is done using the symbol `+` between two FheUint. Many operations can be computed between a clear value (i.e. a scalar) and a ciphertext.
|
||||
|
||||
In Rust native types, any operation is modular. In Rust, `u8`, computations are done modulus 2^8. The similar idea is applied for FheUintX, where operations are done modulus 2^X. In the type FheUint3, operations are done modulo 8.
|
||||
|
||||
### Arithmetic operations.
|
||||
|
||||
Small homomorphic integer types support all common arithmetic operations, meaning `+`, `-`, `x`, `/`, `mod`.
|
||||
|
||||
The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns 0.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
| ------------------------------------------------------- | ------ | ------ |
|
||||
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
|
||||
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
|
||||
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
|
||||
| [Div](https://doc.rust-lang.org/std/ops/trait.Div.html) | `/` | Binary |
|
||||
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html) | `%` | Binary |
|
||||
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 7;
|
||||
let clear_b = 3;
|
||||
let clear_c = 2;
|
||||
|
||||
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
|
||||
let mut c = FheUint3::try_encrypt(clear_c, &keys)?;
|
||||
|
||||
|
||||
a = a * &b; // Clear equivalent computations: 7 * 3 mod 8 = 5
|
||||
b = &b + &c; // Clear equivalent computations: 3 + 2 mod 8 = 5
|
||||
b = b - 5; // Clear equivalent computations: 5 - 5 mod 8 = 0
|
||||
|
||||
let dec_a = a.decrypt(&keys);
|
||||
let dec_b = b.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_a, (clear_a * clear_b) % 8);
|
||||
assert_eq!(dec_b, ((clear_b + clear_c) - 5) % 8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Bitwise operations.
|
||||
|
||||
Small homomorphic integer types support some bitwise operations.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
| ------------------------------------------------------------- | ------ | ------ |
|
||||
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
|
||||
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
|
||||
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
|
||||
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
|
||||
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 7;
|
||||
let clear_b = 3;
|
||||
|
||||
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
a = a ^ &b;
|
||||
b = b ^ &a;
|
||||
a = a ^ &b;
|
||||
|
||||
let dec_a = a.decrypt(&keys);
|
||||
let dec_b = b.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_a, clear_b);
|
||||
assert_eq!(dec_b, clear_a);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Comparisons.
|
||||
|
||||
Small homomorphic integer types support comparison operations.
|
||||
|
||||
Due to some Rust limitations, it is not possible to overload the comparison symbols because of the inner definition of the operations. Rust expects to have a Boolean as an output, whereas a ciphertext encrypted result is returned when using homomorphic types.
|
||||
|
||||
You will need to use the different methods instead of using symbols for the comparisons. These methods follow the same naming conventions as the two standard Rust traits:
|
||||
|
||||
* [PartialOrd](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html)
|
||||
* [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 7;
|
||||
let clear_b = 3;
|
||||
|
||||
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
assert_eq!(a.gt(&b).decrypt(&keys) != 0, true);
|
||||
assert_eq!(b.le(&a).decrypt(&keys) != 0, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Univariate function evaluations.
|
||||
|
||||
The shortint type also supports the computation of univariate functions, which deep down uses TFHE's _programmable bootstrapping_.
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint4};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint4().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let pow_5 = |value: u64| {
|
||||
value.pow(5) % FheUint4::MODULUS as u64
|
||||
};
|
||||
|
||||
let clear_a = 12;
|
||||
let a = FheUint4::try_encrypt(12, &keys)?;
|
||||
|
||||
let c = a.map(pow_5);
|
||||
let decrypted = c.decrypt(&keys);
|
||||
assert_eq!(decrypted, pow_5(clear_a) as u8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Bivariate function evaluations.
|
||||
|
||||
Using the shortint type allows you to evaluate bivariate functions (i.e., functions that takes two ciphertexts as input).
|
||||
|
||||
A simple code example:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint2};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint2().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 1;
|
||||
let clear_b = 3;
|
||||
let a = FheUint2::try_encrypt(clear_a, &keys)?;
|
||||
let b = FheUint2::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
|
||||
let c = a.bivariate_function(&b, std::cmp::max);
|
||||
let decrypted = c.decrypt(&keys);
|
||||
assert_eq!(decrypted, std::cmp::max(clear_a, clear_b) as u8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Integer
|
||||
|
||||
In TFHE-rs, integers are used to encrypt any messages larger than 4 bits. All supported operations are listed below.
|
||||
|
||||
### Arithmetic operations.
|
||||
|
||||
Homomorphic integer types support arithmetic operations.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
| ------------------------------------------------------- | ------ | ------ |
|
||||
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
|
||||
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
|
||||
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
|
||||
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 15_u64;
|
||||
let clear_b = 27_u64;
|
||||
let clear_c = 43_u64;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
let mut c = FheUint8::try_encrypt(clear_c, &keys)?;
|
||||
|
||||
|
||||
a = a * &b; // Clear equivalent computations: 15 * 27 mod 256 = 149
|
||||
b = &b + &c; // Clear equivalent computations: 27 + 43 mod 256 = 70
|
||||
b = b - 76u8; // Clear equivalent computations: 70 - 76 mod 256 = 250
|
||||
|
||||
let dec_a: u8 = a.decrypt(&keys);
|
||||
let dec_b: u8 = b.decrypt(&keys);
|
||||
|
||||
assert_eq!(dec_a, ((clear_a * clear_b) % 256_u64) as u8);
|
||||
assert_eq!(dec_b, (((clear_b + clear_c).wrapping_sub(76_u64)) % 256_u64) as u8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Bitwise operations.
|
||||
|
||||
Homomorphic integer types support some bitwise operations.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
| ------------------------------------------------------------- | ------ | ------ |
|
||||
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
|
||||
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
|
||||
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
|
||||
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
|
||||
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 164;
|
||||
let clear_b = 212;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
a = a ^ &b;
|
||||
b = b ^ &a;
|
||||
a = a ^ &b;
|
||||
|
||||
let dec_a: u8 = a.decrypt(&keys);
|
||||
let dec_b: u8 = b.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_a, clear_b);
|
||||
assert_eq!(dec_b, clear_a);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Comparisons.
|
||||
|
||||
Homomorphic integers support comparison operations. Since Rust does not allow the overloading of these operations, a simple function has been associated to each one.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
| --------------------- | ------ | ------ |
|
||||
| Greater than | `gt` | Binary |
|
||||
| Greater or equal than | `ge` | Binary |
|
||||
| Lower than | `lt` | Binary |
|
||||
| Lower or equal than | `le` | Binary |
|
||||
| Equal | `eq` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a:u8 = 164;
|
||||
let clear_b:u8 = 212;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
let greater = a.gt(&b);
|
||||
let greater_or_equal = a.ge(&b);
|
||||
let lower = a.lt(&b);
|
||||
let lower_or_equal = a.le(&b);
|
||||
let equal = a.eq(&b);
|
||||
|
||||
let dec_gt : u8 = greater.decrypt(&keys);
|
||||
let dec_ge : u8 = greater_or_equal.decrypt(&keys);
|
||||
let dec_lt : u8 = lower.decrypt(&keys);
|
||||
let dec_le : u8 = lower_or_equal.decrypt(&keys);
|
||||
let dec_eq : u8 = equal.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_gt, (clear_a > clear_b ) as u8);
|
||||
assert_eq!(dec_ge, (clear_a >= clear_b) as u8);
|
||||
assert_eq!(dec_lt, (clear_a < clear_b ) as u8);
|
||||
assert_eq!(dec_le, (clear_a <= clear_b) as u8);
|
||||
assert_eq!(dec_eq, (clear_a == clear_b) as u8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Min/Max.
|
||||
|
||||
Homomorphic integers support the min/max operations.
|
||||
|
||||
| name | symbol | type |
|
||||
| ---- | ------ | ------ |
|
||||
| Min | `min` | Binary |
|
||||
| Max | `max` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a:u8 = 164;
|
||||
let clear_b:u8 = 212;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
let min = a.min(&b);
|
||||
let max = a.max(&b);
|
||||
|
||||
let dec_min : u8 = min.decrypt(&keys);
|
||||
let dec_max : u8 = max.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_min, u8::min(clear_a, clear_b));
|
||||
assert_eq!(dec_max, u8::max(clear_a, clear_b));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
73
tfhe/docs/high_level_api/serialization.md
Normal file
73
tfhe/docs/high_level_api/serialization.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Serialization/Deserialization
|
||||
|
||||
As explained in the Introduction, most types 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` uses the [serde](https://crates.io/crates/serde) framework. Serde's `Serialize` and `Deserialize` functions are implemented on TFHE's types.
|
||||
|
||||
To serialize our data, a [data format](https://serde.rs/#data-formats) should be picked. Here, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is a binary format.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.2.4", features = ["integer","x86_64-unix"]}
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
use bincode;
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
use tfhe::{ConfigBuilder, ServerKey, generate_keys, set_server_key, FheUint8};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>>{
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
let ( client_key, server_key) = generate_keys(config);
|
||||
|
||||
let msg1 = 1;
|
||||
let msg2 = 0;
|
||||
|
||||
let value_1 = FheUint8::encrypt(msg1, &client_key);
|
||||
let value_2 = FheUint8::encrypt(msg2, &client_key);
|
||||
|
||||
// Prepare to send data to the server
|
||||
// The ClientKey is _not_ sent
|
||||
let mut serialized_data = Vec::new();
|
||||
bincode::serialize_into(&mut serialized_data, &server_key)?;
|
||||
bincode::serialize_into(&mut serialized_data, &value_1)?;
|
||||
bincode::serialize_into(&mut serialized_data, &value_2)?;
|
||||
|
||||
// Simulate sending serialized data to a server and getting
|
||||
// back the serialized result
|
||||
let serialized_result = server_function(&serialized_data)?;
|
||||
let result: FheUint8 = bincode::deserialize(&serialized_result)?;
|
||||
|
||||
let output: u8 = result.decrypt(&client_key);
|
||||
assert_eq!(output, msg1 + msg2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let mut serialized_data = Cursor::new(serialized_data);
|
||||
let server_key: ServerKey = bincode::deserialize_from(&mut serialized_data)?;
|
||||
let ct_1: FheUint8 = bincode::deserialize_from(&mut serialized_data)?;
|
||||
let ct_2: FheUint8 = bincode::deserialize_from(&mut serialized_data)?;
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let result = ct_1 + ct_2;
|
||||
|
||||
let serialized_result = bincode::serialize(&result)?;
|
||||
|
||||
Ok(serialized_result)
|
||||
}
|
||||
```
|
||||
679
tfhe/docs/high_level_api/tutorial.md
Normal file
679
tfhe/docs/high_level_api/tutorial.md
Normal file
@@ -0,0 +1,679 @@
|
||||
# Tutorial
|
||||
|
||||
## Quick Start
|
||||
|
||||
The basic steps for using the high-level API of TFHE-rs are:
|
||||
|
||||
1. Importing TFHE-rs prelude;
|
||||
2. Client-side: Configuring and creating keys;
|
||||
3. Client-side: Encrypting data;
|
||||
4. Server-side: Setting the server key;
|
||||
5. Server-side: Computing over encrypted data;
|
||||
6. Client-side: Decrypting data.
|
||||
|
||||
Here is the full example (mixing client and server parts):
|
||||
|
||||
```rust
|
||||
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
// Client-side
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
let clear_a = 27u8;
|
||||
let clear_b = 128u8;
|
||||
|
||||
let a = FheUint8::encrypt(clear_a, &client_key);
|
||||
let b = FheUint8::encrypt(clear_b, &client_key);
|
||||
|
||||
//Server-side
|
||||
set_server_key(server_key);
|
||||
let result = a + b;
|
||||
|
||||
//Client-side
|
||||
let decrypted_result: u8 = result.decrypt(&client_key);
|
||||
|
||||
let clear_result = clear_a + clear_b;
|
||||
|
||||
assert_eq!(decrypted_result, clear_result);
|
||||
}
|
||||
```
|
||||
|
||||
Default configuration for x86 Unix machines:
|
||||
```toml
|
||||
tfhe = { version = "0.2.4", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
### Imports.
|
||||
|
||||
`tfhe` uses `traits` to have a consistent API for creating FHE types and enable users to write generic functions. To be able to use associated functions and methods of a trait, the trait has to be in scope.
|
||||
|
||||
To make it easier, the `prelude` 'pattern' is used. All `tfhe` important traits are in a `prelude` module that you **glob import**. With this, there is no need to remember or know the traits to import.
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
```
|
||||
|
||||
### 1. Configuring and creating keys.
|
||||
|
||||
The first step is the creation of the configuration. The configuration is used to declare which type you will use or not use, as well as enabling you to use custom crypto-parameters for these types for more advanced usage / testing.
|
||||
|
||||
Creating a configuration is done using the ConfigBuilder type.
|
||||
|
||||
In this example, 8-bit unsigned integers with default parameters are used. The `integers`
|
||||
feature must also be enabled, as per the table on the [Getting Started page](../getting_started/installation.md).
|
||||
|
||||
The config is done by first creating a builder with all types deactivated. Then, the `uint8` type with default parameters is activated.
|
||||
|
||||
```rust
|
||||
use tfhe::{ConfigBuilder, generate_keys};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
}
|
||||
```
|
||||
|
||||
The `generate_keys` command returns a client key and a server key.
|
||||
|
||||
The `client_key` is meant to stay private and not leave the client whereas the `server_key` can be made public and sent to a server for it to enable FHE computations.
|
||||
|
||||
### 2. Setting the server key.
|
||||
|
||||
The next step is to call `set_server_key`
|
||||
|
||||
This function will **move** the server key to an internal state of the crate and manage the details to give a simpler interface.
|
||||
|
||||
```rust
|
||||
use tfhe::{ConfigBuilder, generate_keys, set_server_key};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Encrypting data.
|
||||
|
||||
Encrypting data is done via the `encrypt` associated function of the \[FheEncrypt] trait.
|
||||
|
||||
Types exposed by this crate implement at least one of \[FheEncrypt] or \[FheTryEncrypt] to allow enryption.
|
||||
|
||||
```Rust
|
||||
let clear_a = 27u8;
|
||||
let clear_b = 128u8;
|
||||
|
||||
let a = FheUint8::encrypt(clear_a, &client_key);
|
||||
let b = FheUint8::encrypt(clear_b, &client_key);
|
||||
```
|
||||
|
||||
### 4. Computation and decryption.
|
||||
|
||||
Computations should be as easy as normal Rust to write, thanks to operator overloading.
|
||||
|
||||
```Rust
|
||||
let result = a + b;
|
||||
```
|
||||
|
||||
The decryption is done by using the `decrypt` method, which comes from the \[FheDecrypt] trait.
|
||||
|
||||
```Rust
|
||||
let decrypted_result: u8 = result.decrypt(&client_key);
|
||||
|
||||
let clear_result = clear_a + clear_b;
|
||||
|
||||
assert_eq!(decrypted_result, clear_result);
|
||||
```
|
||||
|
||||
## A first complete example: FheLatinString (Integer)
|
||||
|
||||
The goal of this tutorial is to build a data type that represents a Latin string in FHE while implementing the `to_lower` and `to_upper` functions.
|
||||
|
||||
The allowed characters in a Latin string are:
|
||||
|
||||
* Uppercase letters: `A B C D E F G H I J K L M N O P Q R S T U V W X Y Z`
|
||||
* Lowercase letters: `a b c d e f g h i j k l m n o p q r s t u v w x y z`
|
||||
|
||||
For the code point of the letters,`ascii` codes are used:
|
||||
|
||||
* The uppercase letters are in the range \[65, 90]
|
||||
* The lowercase letters are in the range \[97, 122]
|
||||
|
||||
`lower_case` = `upper_case` + 32 <=> `upper_case` = `lower_case` - 32
|
||||
|
||||
For this type, the `FheUint8` type is used.
|
||||
|
||||
### Types and methods.
|
||||
|
||||
This type will hold the encrypted characters as a `Vec<FheUint8>`, as well as the encrypted constant `32` to implement the functions that change the case.
|
||||
|
||||
In the `FheLatinString::encrypt` function, some data validation is done:
|
||||
|
||||
* The input string can only contain ascii letters (no digit, no special characters).
|
||||
* The input string cannot mix lower and upper case letters.
|
||||
|
||||
These two points are to work around a limitation of FHE. It is not possible to create branches, meaning the function cannot use conditional statements. Checking if the 'char' is an uppercase letter to modify it to a lowercase one cannot be done, like in the example below.
|
||||
|
||||
```rust
|
||||
fn to_lower(string: &String) -> String {
|
||||
let mut result = String::with_capacity(string.len());
|
||||
for char in string.chars() {
|
||||
if char.is_uppercase() {
|
||||
result.extend(char.to_lowercase().to_string().chars())
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
```
|
||||
|
||||
With these preconditions checked, implementing `to_lower` and `to_upper` is rather simple.
|
||||
|
||||
To use the `FheUint8` type, the `integer` feature must be activated:
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.2.4", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
|
||||
```rust
|
||||
use tfhe::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
struct FheLatinString{
|
||||
bytes: Vec<FheUint8>,
|
||||
// Constant used to switch lower case <=> upper case
|
||||
cst: FheUint8,
|
||||
}
|
||||
|
||||
impl FheLatinString {
|
||||
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
|
||||
assert!(
|
||||
string.chars().all(|char| char.is_ascii_alphabetic()),
|
||||
"The input string must only contain ascii letters"
|
||||
);
|
||||
|
||||
let has_mixed_case = string.as_bytes().windows(2).any(|window| {
|
||||
let first = char::from(*window.first().unwrap());
|
||||
let second = char::from(*window.last().unwrap());
|
||||
|
||||
(first.is_ascii_lowercase() && second.is_ascii_uppercase())
|
||||
|| (first.is_ascii_uppercase() && second.is_ascii_lowercase())
|
||||
});
|
||||
|
||||
assert!(
|
||||
!has_mixed_case,
|
||||
"The input string cannot mix lower case and upper case letters"
|
||||
);
|
||||
|
||||
let fhe_bytes = string
|
||||
.bytes()
|
||||
.map(|b| FheUint8::encrypt(b, client_key))
|
||||
.collect::<Vec<FheUint8>>();
|
||||
let cst = FheUint8::encrypt(32, client_key);
|
||||
|
||||
Self {
|
||||
bytes: fhe_bytes,
|
||||
cst,
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt(&self, client_key: &ClientKey) -> String {
|
||||
let ascii_bytes = self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|fhe_b| fhe_b.decrypt(client_key))
|
||||
.collect::<Vec<u8>>();
|
||||
String::from_utf8(ascii_bytes).unwrap()
|
||||
}
|
||||
|
||||
fn to_upper(&self) -> Self {
|
||||
Self {
|
||||
bytes: self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|b| b - &self.cst)
|
||||
.collect::<Vec<FheUint8>>(),
|
||||
cst: self.cst.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lower(&self) -> Self {
|
||||
Self {
|
||||
bytes: self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|b| b + &self.cst)
|
||||
.collect::<Vec<FheUint8>>(),
|
||||
cst: self.cst.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let my_string = FheLatinString::encrypt("zama", &client_key);
|
||||
let verif_string = my_string.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
|
||||
let my_string_upper = my_string.to_upper();
|
||||
let verif_string = my_string_upper.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
assert_eq!(verif_string, "ZAMA");
|
||||
|
||||
let my_string_lower = my_string_upper.to_lower();
|
||||
let verif_string = my_string_lower.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
assert_eq!(verif_string, "zama");
|
||||
}
|
||||
```
|
||||
|
||||
## A more complex example: Parity Bit (Boolean)
|
||||
|
||||
This example is dedicated to the building of a small function that homomorphically computes a parity bit.
|
||||
|
||||
First, a non-generic function is written. Then, generics are used to handle the case where the function inputs are both `FheBool`s and clear `bool`s.
|
||||
|
||||
The parity bit function takes as input two parameters:
|
||||
|
||||
* A slice of Boolean
|
||||
* A mode (`Odd` or `Even`)
|
||||
|
||||
This function returns a Boolean that will be either `true` or `false` so that the sum of Booleans (in the input and the returned one) is either an `Odd` or `Even` number, depending on the requested mode.
|
||||
|
||||
***
|
||||
|
||||
### Non-generic version.
|
||||
|
||||
To use Booleans, the `booleans` feature in our Cargo.toml must be enabled:
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.2.4", features = ["boolean", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
|
||||
#### function definition
|
||||
|
||||
First, the verification function is defined.
|
||||
|
||||
The way to find the parity bit is to initialize it to `false, then` `XOR` it with all the bits, one after the other, adding negation depending on the requested mode.
|
||||
|
||||
A validation function is also defined to sum together the number of the bit set within the input with the computed parity bit and check that the sum is an even or odd number, depending on the mode.
|
||||
|
||||
```rust
|
||||
use tfhe::FheBool;
|
||||
use tfhe::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ParityMode {
|
||||
// The sum bits of message + parity bit must an odd number
|
||||
Odd,
|
||||
// The sum bits of message + parity bit must an even number
|
||||
Even,
|
||||
}
|
||||
|
||||
fn compute_parity_bit(fhe_bits: &[FheBool], mode: ParityMode) -> FheBool {
|
||||
let mut parity_bit = fhe_bits[0].clone();
|
||||
for fhe_bit in &fhe_bits[1..] {
|
||||
parity_bit = fhe_bit ^ parity_bit
|
||||
}
|
||||
|
||||
match mode {
|
||||
ParityMode::Odd => !parity_bit,
|
||||
ParityMode::Even => parity_bit,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_even(n: u8) -> bool {
|
||||
(n & 1) == 0
|
||||
}
|
||||
|
||||
fn is_odd(n: u8) -> bool {
|
||||
!is_even(n)
|
||||
}
|
||||
|
||||
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
|
||||
let num_bit_set = bits
|
||||
.iter()
|
||||
.map(|bit| *bit as u8)
|
||||
.fold(parity_bit as u8, |acc, bit| acc + bit);
|
||||
|
||||
match mode {
|
||||
ParityMode::Even => is_even(num_bit_set),
|
||||
ParityMode::Odd => is_odd(num_bit_set),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### final code
|
||||
|
||||
After the mandatory configuration steps, the function is called:
|
||||
|
||||
```rust
|
||||
use tfhe::{FheBool, ConfigBuilder, generate_keys, set_server_key};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ParityMode {
|
||||
// The sum bits of message + parity bit must an odd number
|
||||
Odd,
|
||||
// The sum bits of message + parity bit must an even number
|
||||
Even,
|
||||
}
|
||||
|
||||
fn compute_parity_bit(fhe_bits: &[FheBool], mode: ParityMode) -> FheBool {
|
||||
let mut parity_bit = fhe_bits[0].clone();
|
||||
for fhe_bit in &fhe_bits[1..] {
|
||||
parity_bit = fhe_bit ^ parity_bit
|
||||
}
|
||||
|
||||
match mode {
|
||||
ParityMode::Odd => !parity_bit,
|
||||
ParityMode::Even => parity_bit,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_even(n: u8) -> bool {
|
||||
(n & 1) == 0
|
||||
}
|
||||
|
||||
fn is_odd(n: u8) -> bool {
|
||||
!is_even(n)
|
||||
}
|
||||
|
||||
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
|
||||
let num_bit_set = bits
|
||||
.iter()
|
||||
.map(|bit| *bit as u8)
|
||||
.fold(parity_bit as u8, |acc, bit| acc + bit);
|
||||
|
||||
match mode {
|
||||
ParityMode::Even => is_even(num_bit_set),
|
||||
ParityMode::Odd => is_odd(num_bit_set),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let clear_bits = [0, 1, 0, 0, 0, 1, 1].map(|b| (b != 0) as bool);
|
||||
|
||||
let fhe_bits = clear_bits
|
||||
.iter()
|
||||
.map(|bit| FheBool::encrypt(*bit, &client_key))
|
||||
.collect::<Vec<FheBool>>();
|
||||
|
||||
let mode = ParityMode::Odd;
|
||||
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
|
||||
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
|
||||
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
|
||||
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
|
||||
assert!(is_parity_bit_valid);
|
||||
|
||||
let mode = ParityMode::Even;
|
||||
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
|
||||
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
|
||||
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
|
||||
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
|
||||
assert!(is_parity_bit_valid);
|
||||
}
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
### Generic version.
|
||||
|
||||
To make the `compute_parity_bit` function compatible with both `FheBool` and `bool`, generics have to be used.
|
||||
|
||||
Writing a generic function that accepts `FHE` types as well as clear types can help test the function to see if it is correct. If the function is generic, it can run with clear data, allowing the use of print-debugging or a debugger to spot errors.
|
||||
|
||||
Writing generic functions that use operator overloading for our FHE types can be trickier than normal, since `FHE` types are not copy. So using the reference `&` is mandatory, even though this is not the case when using native types, which are all `Copy`.
|
||||
|
||||
This will make the generic bounds trickier at first.
|
||||
|
||||
#### writing the correct trait bounds
|
||||
|
||||
The function has the following signature:
|
||||
|
||||
```Rust
|
||||
fn check_parity_bit_validity(
|
||||
fhe_bits: &[FheBool],
|
||||
mode: ParityMode,
|
||||
) -> bool
|
||||
```
|
||||
|
||||
To make it generic, the first step is:
|
||||
|
||||
```Rust
|
||||
fn compute_parity_bit<BoolType>(
|
||||
fhe_bits: &[BoolType],
|
||||
mode: ParityMode,
|
||||
) -> BoolType
|
||||
```
|
||||
|
||||
Next, the generic bounds have to be defined with the `where` clause.
|
||||
|
||||
In the function, the following operators are used:
|
||||
|
||||
* `!` (trait: `Not`)
|
||||
* `^` (trait: `BitXor`)
|
||||
|
||||
By adding them to `where`, this gives:
|
||||
|
||||
```Rust
|
||||
where
|
||||
BoolType: Clone + Not<Output = BoolType>,
|
||||
BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
```
|
||||
|
||||
However, the compiler will complain:
|
||||
|
||||
```text
|
||||
---- src/user_doc_tests.rs - user_doc_tests (line 199) stdout ----
|
||||
error[E0369]: no implementation for `&BoolType ^ BoolType`
|
||||
--> src/user_doc_tests.rs:218:30
|
||||
|
|
||||
21 | parity_bit = fhe_bit ^ parity_bit
|
||||
| ------- ^ ---------- BoolType
|
||||
| |
|
||||
| &BoolType
|
||||
|
|
||||
help: consider extending the `where` bound, but there might be an alternative better way to express this requirement
|
||||
|
|
||||
17 | BoolType: BitXor<BoolType, Output=BoolType>, &BoolType: BitXor<BoolType>
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
error: aborting due to previous error
|
||||
```
|
||||
|
||||
`fhe_bit` is a reference to a `BoolType` (`&BoolType`) since it is borrowed from the `fhe_bits` slice when iterating over its elements. The first try is to change the `BitXor` bounds to what the Compiler suggests by requiring `&BoolType` to implement `BitXor` and not `BoolType`.
|
||||
|
||||
```Rust
|
||||
where
|
||||
BoolType: Clone + Not<Output = BoolType>,
|
||||
&BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
```
|
||||
|
||||
The Compiler is still not happy:
|
||||
|
||||
```text
|
||||
---- src/user_doc_tests.rs - user_doc_tests (line 236) stdout ----
|
||||
error[E0637]: `&` without an explicit lifetime name cannot be used here
|
||||
--> src/user_doc_tests.rs:251:5
|
||||
|
|
||||
17 | &BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
| ^ explicit lifetime name needed here
|
||||
|
||||
error[E0310]: the parameter type `BoolType` may not live long enough
|
||||
--> src/user_doc_tests.rs:251:16
|
||||
|
|
||||
17 | &BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the reference type `&'static BoolType` does not outlive the data it points at
|
||||
|
|
||||
help: consider adding an explicit lifetime bound...
|
||||
|
|
||||
15 | BoolType: Clone + Not<Output = BoolType> + 'static,
|
||||
|
|
||||
```
|
||||
|
||||
The way to fix this is to use `Higher-Rank Trait Bounds`:
|
||||
|
||||
```Rust
|
||||
where
|
||||
BoolType: Clone + Not<Output = BoolType>,
|
||||
for<'a> &'a BoolType: BitXor<BoolType, Output = BoolType>,
|
||||
```
|
||||
|
||||
The final code will look like this:
|
||||
|
||||
```rust
|
||||
use std::ops::{Not, BitXor};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ParityMode {
|
||||
// The sum bits of message + parity bit must an odd number
|
||||
Odd,
|
||||
// The sum bits of message + parity bit must an even number
|
||||
Even,
|
||||
}
|
||||
|
||||
fn compute_parity_bit<BoolType>(fhe_bits: &[BoolType], mode: ParityMode) -> BoolType
|
||||
where
|
||||
BoolType: Clone + Not<Output = BoolType>,
|
||||
for<'a> &'a BoolType: BitXor<BoolType, Output = BoolType>,
|
||||
{
|
||||
let mut parity_bit = fhe_bits[0].clone();
|
||||
for fhe_bit in &fhe_bits[1..] {
|
||||
parity_bit = fhe_bit ^ parity_bit
|
||||
}
|
||||
|
||||
match mode {
|
||||
ParityMode::Odd => !parity_bit,
|
||||
ParityMode::Even => parity_bit,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### final code
|
||||
|
||||
Here is a complete example that uses this function for both clear and FHE values:
|
||||
|
||||
```rust
|
||||
use tfhe::{FheBool, ConfigBuilder, generate_keys, set_server_key};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
use std::ops::{Not, BitXor};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ParityMode {
|
||||
// The sum bits of message + parity bit must an odd number
|
||||
Odd,
|
||||
// The sum bits of message + parity bit must an even number
|
||||
Even,
|
||||
}
|
||||
|
||||
fn compute_parity_bit<BoolType>(fhe_bits: &[BoolType], mode: ParityMode) -> BoolType
|
||||
where
|
||||
BoolType: Clone + Not<Output=BoolType>,
|
||||
for<'a> &'a BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
{
|
||||
let mut parity_bit = fhe_bits[0].clone();
|
||||
for fhe_bit in &fhe_bits[1..] {
|
||||
parity_bit = fhe_bit ^ parity_bit
|
||||
}
|
||||
|
||||
match mode {
|
||||
ParityMode::Odd => !parity_bit,
|
||||
ParityMode::Even => parity_bit,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_even(n: u8) -> bool {
|
||||
(n & 1) == 0
|
||||
}
|
||||
|
||||
fn is_odd(n: u8) -> bool {
|
||||
!is_even(n)
|
||||
}
|
||||
|
||||
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
|
||||
let num_bit_set = bits
|
||||
.iter()
|
||||
.map(|bit| *bit as u8)
|
||||
.fold(parity_bit as u8, |acc, bit| acc + bit);
|
||||
|
||||
match mode {
|
||||
ParityMode::Even => is_even(num_bit_set),
|
||||
ParityMode::Odd => is_odd(num_bit_set),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
|
||||
|
||||
let ( client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let clear_bits = [0, 1, 0, 0, 0, 1, 1].map(|b| (b != 0) as bool);
|
||||
|
||||
let fhe_bits = clear_bits
|
||||
.iter()
|
||||
.map(|bit| FheBool::encrypt(*bit, &client_key))
|
||||
.collect::<Vec<FheBool>>();
|
||||
|
||||
let mode = ParityMode::Odd;
|
||||
let clear_parity_bit = compute_parity_bit(&clear_bits, mode);
|
||||
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
|
||||
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
|
||||
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
|
||||
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
|
||||
assert!(is_parity_bit_valid);
|
||||
assert_eq!(decrypted_parity_bit, clear_parity_bit);
|
||||
|
||||
let mode = ParityMode::Even;
|
||||
let clear_parity_bit = compute_parity_bit(&clear_bits, mode);
|
||||
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
|
||||
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
|
||||
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
|
||||
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
|
||||
assert!(is_parity_bit_valid);
|
||||
assert_eq!(decrypted_parity_bit, clear_parity_bit);
|
||||
}
|
||||
```
|
||||
248
tfhe/docs/integer/operations.md
Normal file
248
tfhe/docs/integer/operations.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Operations
|
||||
|
||||
The structure and operations related to the 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.
|
||||
|
||||

|
||||
|
||||
This crate implements two ways to represent an integer:
|
||||
|
||||
* the Radix representation
|
||||
* the CRT (Chinese Reminder Theorem) representation
|
||||
|
||||
### Radix-based integers.
|
||||
|
||||
The first possibility to represent a large integer is to use a Radix-based decomposition on the plaintexts. Let $$B \in \mathbb{N}$$ be a basis such that the size of $$B$$ is smaller than (or equal to) 4 bits. Then, an integer $$m \in \mathbb{N}$$ can be written as $$m = m_0 + m_1*B + m_2*B^2 + ...$$, where each $$m_i$$ is strictly smaller than $$B$$. Each $$m_i$$ is then independently encrypted. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
|
||||
|
||||
The definition of an integer requires a basis and a number of blocks. This is done at key generation. Below, the keys are dedicated to unsigned integers encrypting messages over 8 bits, using a basis over 2 bits (i.e., $$B=2^2$$) and 4 blocks.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, 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.
|
||||
|
||||
### 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.
|
||||
|
||||
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;
|
||||
|
||||
fn main() {
|
||||
let basis = vec![2, 3, 5];
|
||||
let cks = CrtClientKey::new(PARAM_MESSAGE_2_CARRY_2, 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.
|
||||
|
||||
## List of available operations
|
||||
|
||||
The list of operations available in `integer` depends on the type of representation:
|
||||
|
||||
| Operation name | Radix-based | CRT-based |
|
||||
| ------------------------------ | -------------------- | -------------------------- |
|
||||
| Negation | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Addition | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Scalar Addition | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Subtraction | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Scalar Subtraction | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Multiplication | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Scalar Multiplication | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Bitwise OR, AND, XOR | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Equality | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Left/Right Shift | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
|
||||
| Comparisons `<`,`<=`,`>`, `>=` | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
|
||||
| Min, Max | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
|
||||
|
||||
## Types of operations
|
||||
|
||||
Much like `shortint`, the operations available via a `ServerKey` may come in different variants:
|
||||
|
||||
* operations that take their inputs as encrypted values.
|
||||
* scalar operations take at least one non-encrypted value as input.
|
||||
|
||||
For example, the addition has both variants:
|
||||
|
||||
* `ServerKey::unchecked_add`, which takes two encrypted values and adds them.
|
||||
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (the so-called scalar) and adds them.
|
||||
|
||||
Each operation may come in different 'flavors':
|
||||
|
||||
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space.
|
||||
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely.
|
||||
* `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation will propagate the carry buffer to make the operation possible.
|
||||
* `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.
|
||||
|
||||
## How to use each operation type
|
||||
|
||||
Let's try to do a circuit evaluation using the different flavors of already introduced operations. For a very small circuit, the `unchecked` flavor may be enough to do the computation correctly. Otherwise, `checked` and `smart` are the best options.
|
||||
|
||||
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;
|
||||
|
||||
fn main() {
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
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;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
let ct_3 = client_key.encrypt(msg2);
|
||||
|
||||
server_key.unchecked_small_scalar_mul_assign(&mut ct_1, scalar);
|
||||
|
||||
server_key.unchecked_sub_assign(&mut ct_1, &ct_2);
|
||||
|
||||
server_key.unchecked_add_assign(&mut ct_1, &ct_3);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
// The carry buffer has been overflowed, the result is not correct
|
||||
assert_ne!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
During this computation the carry buffer has been overflowed, and the output may be incorrect as all the operations were `unchecked`.
|
||||
|
||||
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;
|
||||
|
||||
fn main() {
|
||||
let num_block = 2;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
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;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
let ct_3 = client_key.encrypt(msg3);
|
||||
|
||||
let result = server_key.checked_small_scalar_mul_assign(&mut ct_1, scalar);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
|
||||
assert!(result.is_err());
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
// Only the scalar multiplication could be done
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, (msg1 * scalar) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
The `checked` flavor permits the manual management of the overflow of the carry buffer by raising an error if correctness is not guaranteed.
|
||||
|
||||
Using the `smart` flavor will output the correct result all the time. However, the computation may be slower as the carry buffer may be propagated during the computations.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12;
|
||||
let msg2 = 11;
|
||||
let msg3 = 9;
|
||||
let scalar = 3;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let mut ct_2 = client_key.encrypt(msg2);
|
||||
let mut ct_3 = client_key.encrypt(msg3);
|
||||
|
||||
server_key.smart_scalar_mul_assign(&mut ct_1, scalar);
|
||||
|
||||
server_key.smart_sub_assign(&mut ct_1, &mut ct_2);
|
||||
|
||||
server_key.smart_add_assign(&mut ct_1, &mut ct_3);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
The main advantage of the default flavor is to ensure predictable timings, as long as only this kind of operation is used. Only the parallelized version of the operations is provided.
|
||||
|
||||
{% hint style="warning" %}
|
||||
Using `default` could **slow down** computations.
|
||||
{% endhint %}
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12;
|
||||
let msg2 = 11;
|
||||
let msg3 = 9;
|
||||
let scalar = 3;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let mut ct_2 = client_key.encrypt(msg2);
|
||||
let mut ct_3 = client_key.encrypt(msg3);
|
||||
|
||||
server_key.scalar_mul_assign_parallelized(&mut ct_1, scalar);
|
||||
|
||||
server_key.sub_assign_parallelized(&mut ct_1, &mut ct_2);
|
||||
|
||||
server_key.add_assign_parallelized(&mut ct_1, &mut ct_3);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
|
||||
}
|
||||
```
|
||||
3
tfhe/docs/integer/parameters.md
Normal file
3
tfhe/docs/integer/parameters.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Cryptographic Parameters
|
||||
|
||||
`integer` does not come with its own set of parameters. Instead, it relies on parameters from `shortint`. Currently, parameter sets having the same space dedicated to the message and the carry (i.e. `PARAM_MESSAGE_{X}_CARRY_{X}` with `X` in \[1,4]) are recommended. See [here](../shortint/parameters.md) for more details about cryptographic parameters, and [here](operations.md) to see how to properly instantiate integers depending on the chosen representation.
|
||||
69
tfhe/docs/integer/serialization.md
Normal file
69
tfhe/docs/integer/serialization.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Serialization/Deserialization
|
||||
|
||||
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that does the computations.
|
||||
|
||||
The easiest way to send these data to a server is to use the serialization and deserialization features. 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.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
use bincode;
|
||||
|
||||
use std::io::Cursor;
|
||||
use tfhe::integer::{gen_keys_radix, ServerKey, RadixCiphertextBig};
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 201;
|
||||
let msg2 = 12;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
|
||||
let mut serialized_data = Vec::new();
|
||||
bincode::serialize_into(&mut serialized_data, &server_key)?;
|
||||
bincode::serialize_into(&mut serialized_data, &ct_1)?;
|
||||
bincode::serialize_into(&mut serialized_data, &ct_2)?;
|
||||
|
||||
// Simulate sending serialized data to a server and getting
|
||||
// back the serialized result
|
||||
let serialized_result = server_function(&serialized_data)?;
|
||||
let result: RadixCiphertextBig = bincode::deserialize(&serialized_result)?;
|
||||
|
||||
let output: u64 = client_key.decrypt(&result);
|
||||
assert_eq!(output, (msg1 + msg2) % modulus);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let mut serialized_data = Cursor::new(serialized_data);
|
||||
let server_key: ServerKey = bincode::deserialize_from(&mut serialized_data)?;
|
||||
let ct_1: RadixCiphertextBig = bincode::deserialize_from(&mut serialized_data)?;
|
||||
let ct_2: RadixCiphertextBig = bincode::deserialize_from(&mut serialized_data)?;
|
||||
|
||||
let result = server_key.unchecked_add(&ct_1, &ct_2);
|
||||
|
||||
let serialized_result = bincode::serialize(&result)?;
|
||||
|
||||
Ok(serialized_result)
|
||||
}
|
||||
```
|
||||
121
tfhe/docs/integer/tutorial.md
Normal file
121
tfhe/docs/integer/tutorial.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Tutorial
|
||||
|
||||
The steps to homomorphically evaluate an integer circuit are described here.
|
||||
|
||||
## Key Types
|
||||
|
||||
`integer` provides 3 basic key types:
|
||||
|
||||
* `ClientKey`
|
||||
* `ServerKey`
|
||||
* `PublicKey`
|
||||
|
||||
The `ClientKey` is the key that encrypts and decrypts messages, thus this key is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
|
||||
|
||||
The `ServerKey` is the key that is used to actually do the FHE computations. It contains 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.
|
||||
|
||||
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`.
|
||||
|
||||
## 1. Key Generation
|
||||
|
||||
To generate the keys, a user needs two parameters:
|
||||
|
||||
* A set of `shortint` cryptographic parameters.
|
||||
* The number of ciphertexts used to encrypt an integer (we call them "shortint blocks").
|
||||
|
||||
We are now going to build a pair of keys that can encrypt an **8-bit** integer by using **4** shortint blocks that store **2** bits of message each.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Encrypting values
|
||||
|
||||
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;
|
||||
|
||||
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 msg1 = 128u64;
|
||||
let msg2 = 13u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Encrypting values with the public key
|
||||
|
||||
Once the client key is generated, the public key can be derived and used to encrypt data.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::integer::PublicKeyBig;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, _) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
//We generate the public key from the secret client key:
|
||||
let public_key = PublicKeyBig::new(&client_key);
|
||||
|
||||
//encryption
|
||||
let msg1 = 128u64;
|
||||
let msg2 = 13u64;
|
||||
|
||||
// We use the public key to encrypt two messages:
|
||||
let ct_1 = public_key.encrypt_radix(msg1, num_block);
|
||||
let ct_2 = public_key.encrypt_radix(msg2, num_block);
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Computing and decrypting
|
||||
|
||||
With our `server_key`, and encrypted values, we can now do an addition and then decrypt the result.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 128;
|
||||
let msg2 = 13;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
|
||||
// We use the server public key to execute an integer circuit:
|
||||
let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_3);
|
||||
|
||||
assert_eq!(output, (msg1 + msg2) % modulus);
|
||||
}
|
||||
```
|
||||
110
tfhe/docs/js_on_wasm_api/tutorial.md
Normal file
110
tfhe/docs/js_on_wasm_api/tutorial.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Tutorial
|
||||
|
||||
## Using the JS on WASM API
|
||||
|
||||
Welcome to this TFHE-rs JS on WASM API tutorial.
|
||||
|
||||
TFHE-rs uses WASM to expose a JS binding to the client-side primitives, like key generation and encryption, of the Boolean and shortint modules.
|
||||
|
||||
There are several limitations at this time. Due to a lack of threading support in WASM, key generation can be too slow to be practical for bigger parameter sets.
|
||||
|
||||
Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory space of WASM. This means that some parameter sets are virtually unusable.
|
||||
|
||||
## First steps using TFHE-rs JS on WASM API
|
||||
|
||||
### Setting-up TFHE-rs JS on WASM API for use in nodejs programs.
|
||||
|
||||
To build the JS on WASM bindings for TFHE-rs, you need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.65) [`rust toolchain`](https://rustup.rs/).
|
||||
|
||||
In a shell, then run the following to clone the TFHE-rs repo (one may want to checkout a specific tag, here the default branch is used for the build):
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/zama-ai/tfhe-rs.git
|
||||
Cloning into 'tfhe-rs'...
|
||||
...
|
||||
Resolving deltas: 100% (3866/3866), done.
|
||||
$ cd tfhe-rs
|
||||
$ cd tfhe
|
||||
$ rustup run wasm-pack build --release --target=nodejs --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
|
||||
[INFO]: Compiling to Wasm...
|
||||
...
|
||||
[INFO]: :-) Your wasm pkg is ready to publish at ...
|
||||
```
|
||||
|
||||
The command above targets nodejs. A binding for a web browser can be generated as well using `--target=web`. This use case will not be discussed in this tutorial.
|
||||
|
||||
Both Boolean and shortint features are enabled here, but it's possible to use one without the other.
|
||||
|
||||
After the build, a new directory _**pkg**_ is present in the `tfhe` directory.
|
||||
|
||||
```shell
|
||||
$ ls pkg
|
||||
LICENSE index.html package.json tfhe.d.ts tfhe.js tfhe_bg.txt tfhe_bg.wasm tfhe_bg.wasm.d.ts
|
||||
$
|
||||
```
|
||||
|
||||
### Commented code to generate keys for shortint and encrypt a ciphertext
|
||||
|
||||
{% hint style="info" %}
|
||||
Be sure to update the path of the required clause in the example below for the TFHE package that was just built.
|
||||
{% endhint %}
|
||||
|
||||
```javascript
|
||||
// Here import assert to check the decryption went well and panic otherwise
|
||||
const assert = require('node:assert').strict;
|
||||
// Import the Shortint module from the TFHE-rs package generated earlier
|
||||
const { Shortint } = require("/path/to/built/tfhe/pkg");
|
||||
|
||||
function shortint_example() {
|
||||
// Get pre-defined parameters from the shortint module to manage messages with 4 bits of useful
|
||||
// information in total (2 bits of "message" and 2 bits of "carry")
|
||||
let params = Shortint.get_parameters(2, 2);
|
||||
// Create a new secret ClientKey, this must not be shared
|
||||
console.log("Generating client keys...")
|
||||
let cks = Shortint.new_client_key(params);
|
||||
// Encrypt 3 in a ciphertext
|
||||
console.log("Encrypting 3...")
|
||||
let ct = Shortint.encrypt(cks, BigInt(3));
|
||||
|
||||
// Demonstrate ClientKey serialization (for example saving it on disk on the user device)
|
||||
let serialized_cks = Shortint.serialize_client_key(cks);
|
||||
// Deserialization
|
||||
let deserialized_cks = Shortint.deserialize_client_key(serialized_cks);
|
||||
|
||||
// Demonstrate ciphertext serialization to send over the network
|
||||
let serialized_ct = Shortint.serialize_ciphertext(ct);
|
||||
// Deserialize a ciphertext received over the network for example
|
||||
let deserialized_ct = Shortint.deserialize_ciphertext(serialized_ct);
|
||||
|
||||
// Decrypt with the deserialized objects
|
||||
console.log("Decrypting ciphertext...")
|
||||
let decrypted = Shortint.decrypt(deserialized_cks, deserialized_ct);
|
||||
// Check decryption works as expected
|
||||
assert.deepStrictEqual(decrypted, BigInt(3));
|
||||
console.log("Decryption successful!")
|
||||
|
||||
// Generate public evaluation keys, also called ServerKey
|
||||
console.log("Generating compressed ServerKey...")
|
||||
let sks = Shortint.new_compressed_server_key(cks);
|
||||
|
||||
// Can be serialized to send over the network to the machine doing the evaluation
|
||||
let serialized_sks = Shortint.serialize_compressed_server_key(sks);
|
||||
let deserialized_sks = Shortint.deserialize_compressed_server_key(serialized_sks);
|
||||
console.log("All done!")
|
||||
}
|
||||
|
||||
shortint_example();
|
||||
```
|
||||
|
||||
The `example.js` script can then be run using [`node`](https://nodejs.org/), like so:
|
||||
|
||||
```shell
|
||||
$ node example.js
|
||||
Generating client keys...
|
||||
Encrypting 3...
|
||||
Decrypting ciphertext...
|
||||
Decryption successful!
|
||||
Generating compressed ServerKey...
|
||||
All done!
|
||||
$
|
||||
```
|
||||
@@ -1,54 +1,47 @@
|
||||
# How Shortint are represented
|
||||
# Operations
|
||||
|
||||
The structure and the operations related to the short integers are described in this section.
|
||||
|
||||
## How a shortint is represented
|
||||
|
||||
In `shortint`, the encrypted data is stored in an LWE ciphertext.
|
||||
|
||||
Conceptually, the message stored in an LWE ciphertext, is divided into
|
||||
a **carry buffer** and a **message buffer**.
|
||||
Conceptually, the message stored in an LWE ciphertext is divided into a **carry buffer** and a **message buffer**.
|
||||
|
||||

|
||||

|
||||
|
||||
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 exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
|
||||
|
||||
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in incorrect outputs.
|
||||
|
||||
In order to ensure the computation correctness, we keep track of the maximum value encrypted in a
|
||||
ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to resume safely the computations. Therefore, in `shortint` the carry modulus is mainly considered as a means to do more computations.
|
||||
In order to ensure the correctness of the computation, we 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.
|
||||
|
||||
# Types of operations
|
||||
## Types of operations
|
||||
|
||||
The operations available via a `ServerKey` may come in different variants:
|
||||
|
||||
- operations that take their inputs as encrypted values.
|
||||
- scalar operations take at least one non-encrypted value as input.
|
||||
* operations that take their inputs as encrypted values
|
||||
* scalar operations that take at least one non-encrypted value as input
|
||||
|
||||
For example, the addition has both variants:
|
||||
|
||||
- `ServerKey::unchecked_add` which takes two encrypted values and adds them.
|
||||
- `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the
|
||||
so-called scalar) and adds them.
|
||||
* `ServerKey::unchecked_add`, which takes two encrypted values and adds them.
|
||||
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (the so-called scalar) and adds them.
|
||||
|
||||
Each operation may come in different 'flavors':
|
||||
|
||||
- `unchecked`: Always does the operation, without checking if the result may exceed the capacity of
|
||||
the plaintext space. Using this operations might have an impact on the correctness of the
|
||||
following operations;
|
||||
- `checked`: Checks are done before computing the operation, returning an error if operation
|
||||
cannot be done safely;
|
||||
- `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation
|
||||
will clear the carry modulus to make the operation possible.
|
||||
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
|
||||
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely;
|
||||
* `smart`: Always does the operation. If the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible;
|
||||
* `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 3 flavors, as some of them are implemented in a way
|
||||
that the operation is always possible without ever exceeding the plaintext space capacity.
|
||||
Not all operations have these 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
|
||||
|
||||
# How to use operation types
|
||||
|
||||
Let's try to do a circuit evaluation using the different flavours of operations we already introduced.
|
||||
For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly.
|
||||
Otherwise, the `checked` and `smart` are the best options.
|
||||
|
||||
As an example, let's do a scalar multiplication, a subtraction and a multiplication.
|
||||
Let's try to do a circuit evaluation using the different 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 do a scalar multiplication, a subtraction, and a multiplication.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -78,10 +71,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
During this computation the carry buffer has been overflowed and as all the operations were `unchecked` the output
|
||||
may be incorrect.
|
||||
During this computation, the carry buffer has been overflowed and, as all the operations were `unchecked`, the output may be incorrect.
|
||||
|
||||
If we redo this same circuit but using the `checked` flavour, a panic will occur.
|
||||
If we redo this same circuit with the `checked` flavor, a panic will occur:
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -123,12 +115,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Therefore, the `checked` flavour permits to manually manage the overflow of the carry buffer
|
||||
by raising an error if the correctness is not guaranteed.
|
||||
The `checked` flavor permits manual management of the overflow of the carry buffer by raising an error if correctness is not guaranteed.
|
||||
|
||||
Lastly, using the `smart` flavour will output the correct result all the time. However, the
|
||||
computation may be slower
|
||||
as the carry buffer may be cleaned during the computations.
|
||||
Using the `smart` flavor will output the correct result all the time. However, the computation may be slower as the carry buffer may be cleaned during the computations.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -157,61 +146,90 @@ fn main() {
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64);
|
||||
}
|
||||
```
|
||||
#List of available operations
|
||||
|
||||
The main advantage of the default flavor is to ensure predictable timings as long as only this kind of operation is used.
|
||||
|
||||
{% hint style="warning" %}
|
||||
|
||||
Currently, certain operations can only be used if the parameter set chosen is compatible with the
|
||||
bivariate programmable bootstrapping, meaning the carry buffer is larger or equal than the
|
||||
message buffer. These operations are marked with a star (*).
|
||||
|
||||
Using `default` could **slow-down** computations.
|
||||
{% endhint %}
|
||||
|
||||
|
||||
The list of implemented operations for shortints is:
|
||||
- addition between two ciphertexts
|
||||
- addition between a ciphertext and an unencrypted scalar
|
||||
- comparisons `<`, `<=`, `>`, `>=`, `==` between a ciphertext and an unencrypted scalar
|
||||
- division of a ciphertext by an unencrypted scalar
|
||||
- LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
|
||||
- multiplication of a ciphertext by an unencrypted scalar
|
||||
- bitwise shift `<<`, `>>`
|
||||
- subtraction of a ciphertext by another ciphertext
|
||||
- subtraction of a ciphertext by an unencrypted scalar
|
||||
- negation of a ciphertext
|
||||
- bitwise and, or and xor (*)
|
||||
- comparisons `<`, `<=`, `>`, `>=`, `==` between two ciphertexts (*)
|
||||
- division between two ciphertexts (*)
|
||||
- MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (*)
|
||||
|
||||
In what follows, some simple code examples are given.
|
||||
|
||||
## Public key encryption
|
||||
TFHE-rs supports both private and public key encryption methods. Note that the only difference
|
||||
between both lies into the encryption step: in this case, the encryption method is called using
|
||||
`public_key` instead of `client_key`.
|
||||
|
||||
Here a small example on how to use public encryption:
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
|
||||
fn main() {
|
||||
// Generate the client key and the server key:
|
||||
let (cks, mut sks) = gen_keys();
|
||||
let pks = PublicKey::new(&cks);
|
||||
// Encryption of one message:
|
||||
let ct = pks.encrypt(true);
|
||||
// Decryption:
|
||||
let dec = cks.decrypt(&ct);
|
||||
assert_eq!(true, dec);
|
||||
// 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 msg1 = 3;
|
||||
let msg2 = 3;
|
||||
let scalar = 4;
|
||||
|
||||
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);
|
||||
let mut ct_2 = client_key.encrypt(msg2);
|
||||
|
||||
server_key.scalar_mul_assign(&mut ct_1, scalar);
|
||||
server_key.sub_assign(&mut ct_1, &mut ct_2);
|
||||
server_key.mul_lsb_assign(&mut ct_1, &mut ct_2);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
\#List of available operations
|
||||
|
||||
{% hint style="warning" %}
|
||||
Certain operations can only be used if the parameter set chosen is compatible with the bivariate programmable bootstrapping, meaning the carry buffer is larger than or equal to the message buffer. These operations are marked with a star (\*).
|
||||
{% endhint %}
|
||||
|
||||
In what follows, all examples are related to private key encryption.
|
||||
The list of implemented operations for shortint is:
|
||||
|
||||
## Arithmetic operations
|
||||
Classical arithmetic operations are supported by shortints:
|
||||
* addition between two ciphertexts
|
||||
* addition between a ciphertext and an unencrypted scalar
|
||||
* comparisons `<`, `<=`, `>`, `>=`, `==`, `!=` between a ciphertext and an unencrypted scalar
|
||||
* division of a ciphertext by an unencrypted scalar
|
||||
* LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
|
||||
* multiplication of a ciphertext by an unencrypted scalar
|
||||
* bitwise shift `<<`, `>>`
|
||||
* subtraction of a ciphertext by another ciphertext
|
||||
* subtraction of a ciphertext by an unencrypted scalar
|
||||
* negation of a ciphertext
|
||||
* bitwise and, or and xor (\*)
|
||||
* comparisons `<`, `<=`, `>`, `>=`, `==`, `!=` between two ciphertexts (\*)
|
||||
* division between two ciphertexts (\*)
|
||||
* MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (\*)
|
||||
|
||||
### Public key encryption.
|
||||
|
||||
TFHE-rs supports both private and public key encryption methods. The only difference between both lies in the encryption step: in this case, the encryption method is called using `public_key` instead of `client_key`.
|
||||
|
||||
Here is a small example on how to use public encryption:
|
||||
|
||||
```rust
|
||||
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 msg = 2;
|
||||
// Encryption of one message:
|
||||
let ct = pks.encrypt(msg);
|
||||
// Decryption:
|
||||
let dec = cks.decrypt(&ct);
|
||||
assert_eq!(dec, msg);
|
||||
}
|
||||
```
|
||||
|
||||
### Arithmetic operations.
|
||||
|
||||
Classical arithmetic operations are supported by shortint:
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -238,15 +256,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Bitwise operations
|
||||
#### bitwise operations
|
||||
|
||||
Short homomorphic integer types support some bitwise operations.
|
||||
|
||||
A simple example on how to use these operations:
|
||||
```rust
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -271,14 +287,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Comparisons
|
||||
#### comparisons
|
||||
|
||||
Short homomorphic integer types support comparison operations.
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -303,14 +318,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Univariate function evaluations
|
||||
#### univariate function evaluations
|
||||
|
||||
A simple example on how to use this operation to homomorphically compute
|
||||
the hamming weight (i.e., the number of bit equals to one) of an encrypted
|
||||
number.
|
||||
A simple example on how to use this operation to homomorphically compute the hamming weight (i.e., the number of bits equal to one) of an encrypted number.
|
||||
|
||||
```rust
|
||||
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -328,7 +340,7 @@ fn main() {
|
||||
let acc = server_key.generate_accumulator(|n| n.count_ones().into());
|
||||
|
||||
// add the two ciphertexts
|
||||
let ct_res = server_key.keyswitch_programmable_bootstrap(&ct_1, &acc);
|
||||
let ct_res = server_key.apply_lookup_table(&ct_1, &acc);
|
||||
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
@@ -337,17 +349,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Bi-variate function evaluations
|
||||
#### bi-variate function evaluations
|
||||
|
||||
Using the shortint types offers the possibility to evaluate bi-variate functions, i.e.,
|
||||
functions that takes two ciphertexts as input. This requires to choose a parameter set
|
||||
such that the carry buffer size is at least as large as the message one i.e.,
|
||||
PARAM_MESSAGE_X_CARRY_Y with X <= Y.
|
||||
Using the shortint types offers the possibility to evaluate bi-variate functions, or functions that take two ciphertexts as input. This requires choosing a parameter set such that the carry buffer size is at least as large as the message (i.e., PARAM\_MESSAGE\_X\_CARRY\_Y with X <= Y).
|
||||
|
||||
In what follows, a simple code example:
|
||||
Here is a simple code example:
|
||||
|
||||
```rust
|
||||
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -361,18 +369,16 @@ fn main() {
|
||||
|
||||
// We use the private client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
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()
|
||||
+ y.count_ones()) as u64 % modulus );
|
||||
|
||||
let ct_res = server_key.keyswitch_programmable_bootstrap_bivariate(&ct_1, &ct_2, &acc);
|
||||
let ct_res = server_key.smart_apply_lookup_table_bivariate(&ct_1, &mut ct_2, &acc);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output = client_key.decrypt(&ct_res);
|
||||
assert_eq!(output, (msg1.count_ones() as u64 + msg2.count_ones() as u64) % modulus);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
# Cryptographic parameters
|
||||
|
||||
All parameter sets provides at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equals to $$2^{-40}$$ when computing a programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting_started/security_and_cryptography.md) for more details about the encryption process).
|
||||
# Cryptographic Parameters
|
||||
|
||||
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing 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 to use the functionalities of the library securely and efficiently. Each parameter sets is associated to the message and carry precisions. Thus, each key pair is entangled to precision.
|
||||
`shortint` comes with sets of parameters that permit the use of the library functionalities securely and efficiently. Each parameter set is associated to the message and carry precisions. Thus, each key pair is entangled to precision.
|
||||
|
||||
The user is allowed to choose which set of parameters to use when creating the pair of keys.
|
||||
|
||||
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is:
|
||||
`PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
|
||||
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
|
||||
|
||||
In what follows, there is an example where keys are generated to have messages encoded over 3 bits i.e., computations are done modulus $$2^3 = 8$$), with 3 bits of carry.
|
||||
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.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -23,7 +22,7 @@ fn main() {
|
||||
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
|
||||
|
||||
let msg1 = 3;
|
||||
let msg2 = 7;
|
||||
let msg2 = 2;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
@@ -33,21 +32,19 @@ fn main() {
|
||||
|
||||
## Impact of parameters on the operations
|
||||
|
||||
As shown [here](../getting_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
|
||||
As shown [here](../getting\_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
|
||||
|
||||
### Generic bi-variate functions
|
||||
### Generic bi-variate functions.
|
||||
|
||||
The computations of bi-variate functions is based on a trick *concatenating* two ciphertexts into one. In the case where the carry buffer is not at least as large as the message one, this trick is not working anymore. Then, many bi-variate operations, such as comparisons cannot be correctly computed anymore. The only exception concerns the multiplication.
|
||||
The computations of bi-variate functions is based on a trick: _concatenating_ two ciphertexts into one. 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.
|
||||
|
||||
### Multiplication
|
||||
### Multiplication.
|
||||
|
||||
In the case of the multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication_algorithm#Quarter_square_multiplication). In order to correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM_MESSAGE_X_CARRY_Y with Y>=1). This method is in general slower than using the other one. Note that using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
|
||||
In the case of multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication\_algorithm#Quarter\_square\_multiplication). To correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM\_MESSAGE\_X\_CARRY\_Y with Y>=1). This method is slower than using the other one. Using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
|
||||
|
||||
## User-defined parameter sets
|
||||
|
||||
Beyond the predefined parameter sets, this is possible to define new parameter sets.
|
||||
To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fulfill the
|
||||
`Parameter` structure fields.
|
||||
It is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `Parameter` structure fields.
|
||||
|
||||
For instance:
|
||||
|
||||
@@ -73,16 +70,8 @@ fn main() {
|
||||
DecompositionBaseLog(0),
|
||||
MessageModulus(4),
|
||||
CarryModulus(1),
|
||||
CiphertextModulus::new_native(),
|
||||
)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
# Serialization / Deserialization
|
||||
# Serialization/Deserialization
|
||||
|
||||
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that does the computations.
|
||||
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that performs the computations.
|
||||
|
||||
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework, serde's Serialize and Deserialize are implemented on tfhe::shortint's types.
|
||||
|
||||
To be able to serialize our data, we need to pick a [data format](https://serde.rs/#data-formats), for our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
|
||||
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework. Serde's Serialize and Deserialize are then implemented on tfhe::shortint's types.
|
||||
|
||||
To 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.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
@@ -15,7 +14,6 @@ To be able to serialize our data, we need to pick a [data format](https://serde.
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
@@ -25,7 +23,7 @@ use tfhe::shortint::prelude::*;
|
||||
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (client_key, server_key) = gen_keys(Parameters::default());
|
||||
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
|
||||
|
||||
let msg1 = 1;
|
||||
let msg2 = 0;
|
||||
@@ -41,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: Ciphertext = bincode::deserialize(&serialized_result)?;
|
||||
let result: CiphertextBig = bincode::deserialize(&serialized_result)?;
|
||||
|
||||
let output = client_key.decrypt(&result);
|
||||
assert_eq!(output, msg1 + msg2);
|
||||
@@ -52,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: Ciphertext = bincode::deserialize_from(&mut serialized_data)?;
|
||||
let ct_2: Ciphertext = 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 result = server_key.unchecked_add(&ct_1, &ct_2);
|
||||
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
# Tutorial: Writing an homomorphic circuit using shortints
|
||||
# Tutorial
|
||||
|
||||
# 1. Key Generation
|
||||
The steps to homomorphically evaluate a circuit are described below.
|
||||
|
||||
`tfhe::shortint` provides 2 key types:
|
||||
- `ClientKey`
|
||||
- `ServerKey`
|
||||
## Key generation
|
||||
|
||||
The `ClientKey` is the key that encrypts and decrypts messages (integer values up to 8 bits here), thus this key is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
|
||||
`tfhe::shortint` provides 3 key types:
|
||||
|
||||
The `ServerKey` is the key that is used to actually do the FHE computations. It contains (among other things) a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, therefore it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
|
||||
* `ClientKey`
|
||||
* `ServerKey`
|
||||
* `PublicKey`
|
||||
|
||||
To reflect that, computation/operation methods are tied to the `ServerKey` type.
|
||||
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`.
|
||||
|
||||
Computation/operation methods are tied to the `ServerKey` type.
|
||||
|
||||
The `PublicKey` is the 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`.
|
||||
|
||||
```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(Parameters::default());
|
||||
// We generate a set of client/server keys
|
||||
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# 2. Encrypting values
|
||||
## Encrypting values
|
||||
|
||||
Once the keys have been generated, the client key is used to encrypt data:
|
||||
|
||||
@@ -31,8 +35,8 @@ Once the keys have been generated, the client key is used to encrypt data:
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let (client_key, server_key) = gen_keys(Parameters::default());
|
||||
// We generate a set of client/server keys
|
||||
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
|
||||
|
||||
let msg1 = 1;
|
||||
let msg2 = 0;
|
||||
@@ -43,7 +47,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
# 2 bis. Encrypting values using a public key
|
||||
## Encrypting values using a public key
|
||||
|
||||
Once the keys have been generated, the client key is used to encrypt data:
|
||||
|
||||
@@ -51,31 +55,29 @@ Once the keys have been generated, the client key is used to encrypt data:
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let (client_key, server_key) = gen_keys(Parameters::default());
|
||||
let public_key = PublicKey::new(&client_key);
|
||||
// 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 msg1 = 1;
|
||||
let msg2 = 0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let ct_1 = public_key.encrypt(&server_key, msg1);
|
||||
let ct_2 = public_key.encrypt(&server_key, msg2);
|
||||
let ct_1 = public_key.encrypt(msg1);
|
||||
let ct_2 = public_key.encrypt(msg2);
|
||||
}
|
||||
```
|
||||
|
||||
## Computing and decrypting
|
||||
|
||||
# 3. Computing and decrypting
|
||||
|
||||
With our `server_key`, and encrypted values, we can now do an addition
|
||||
and then decrypt the result.
|
||||
With the `server_key`, addition is now possible over encrypted values. The resulting plaintext is recovered after the decryption with the secret client key.
|
||||
|
||||
```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(Parameters::default());
|
||||
// We generate a set of client/server keys
|
||||
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
|
||||
|
||||
let msg1 = 1;
|
||||
let msg2 = 0;
|
||||
|
||||
77
tfhe/examples/boolean_key_sizes.rs
Normal file
77
tfhe/examples/boolean_key_sizes.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
#[path = "../benches/utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use tfhe::boolean::parameters::{DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
|
||||
use tfhe::boolean::{client_key, server_key};
|
||||
|
||||
fn write_result(file: &mut File, name: &str, value: usize) {
|
||||
let line = format!("{name},{value}\n");
|
||||
let error_message = format!("cannot write {name} result into file");
|
||||
file.write_all(line.as_bytes()).expect(&error_message);
|
||||
}
|
||||
|
||||
fn client_server_key_sizes(results_file: &Path) {
|
||||
let boolean_params_vec = vec![
|
||||
(DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS"),
|
||||
(TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS"),
|
||||
];
|
||||
File::create(results_file).expect("create results file failed");
|
||||
let mut file = OpenOptions::new()
|
||||
.append(true)
|
||||
.open(results_file)
|
||||
.expect("cannot open results file");
|
||||
|
||||
let operator = OperatorType::Atomic;
|
||||
|
||||
println!("Generating boolean (ClientKey, ServerKey)");
|
||||
for (i, (params, params_name)) in boolean_params_vec.iter().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
boolean_params_vec.len(),
|
||||
params_name.to_lowercase()
|
||||
);
|
||||
|
||||
let cks = client_key::ClientKey::new(params);
|
||||
let sks = server_key::ServerKey::new(&cks);
|
||||
let ksk_size = sks.key_switching_key_size_bytes();
|
||||
let test_name = format!("boolean_key_sizes_{params_name}_ksk");
|
||||
|
||||
write_result(&mut file, &test_name, ksk_size);
|
||||
write_to_json(&test_name, *params, *params_name, "KSK", &operator);
|
||||
|
||||
println!(
|
||||
"Element in KSK: {}, size in bytes: {}",
|
||||
sks.key_switching_key_size_elements(),
|
||||
ksk_size,
|
||||
);
|
||||
|
||||
let bsk_size = sks.bootstrapping_key_size_bytes();
|
||||
let test_name = format!("boolean_key_sizes_{params_name}_bsk");
|
||||
|
||||
write_result(&mut file, &test_name, bsk_size);
|
||||
write_to_json(&test_name, *params, *params_name, "BSK", &operator);
|
||||
|
||||
println!(
|
||||
"Element in BSK: {}, size in bytes: {}",
|
||||
sks.bootstrapping_key_size_elements(),
|
||||
bsk_size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let work_dir = std::env::current_dir().unwrap();
|
||||
println!("work_dir: {}", std::env::current_dir().unwrap().display());
|
||||
// Change workdir so that the location of the keycache matches the one for tests
|
||||
let mut new_work_dir = work_dir;
|
||||
new_work_dir.push("tfhe");
|
||||
std::env::set_current_dir(new_work_dir).unwrap();
|
||||
|
||||
let results_file = Path::new("boolean_key_sizes.csv");
|
||||
client_server_key_sizes(results_file)
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
use tfhe::shortint::keycache::{FileStorage, NamedParam, PersistentStorage};
|
||||
|
||||
use tfhe::shortint::parameters::ALL_PARAMETER_VEC;
|
||||
use tfhe::shortint::{gen_keys, ClientKey, ServerKey};
|
||||
use tfhe::shortint::keycache::{NamedParam, KEY_CACHE, KEY_CACHE_WOPBS};
|
||||
use tfhe::shortint::parameters::parameters_wopbs_message_carry::{
|
||||
WOPBS_PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_3_CARRY_3,
|
||||
WOPBS_PARAM_MESSAGE_4_CARRY_4,
|
||||
};
|
||||
use tfhe::shortint::parameters::{
|
||||
Parameters, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2,
|
||||
PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_4_CARRY_4,
|
||||
};
|
||||
|
||||
fn client_server_keys() {
|
||||
let file_storage = FileStorage::new("keys/shortint/client_server".to_string());
|
||||
|
||||
println!("Generating (ClientKey, ServerKey)");
|
||||
println!("Generating shortint (ClientKey, ServerKey)");
|
||||
for (i, params) in ALL_PARAMETER_VEC.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
@@ -15,17 +18,55 @@ fn client_server_keys() {
|
||||
params.name()
|
||||
);
|
||||
|
||||
let keys: Option<(ClientKey, ServerKey)> = file_storage.load(params);
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
if keys.is_some() {
|
||||
continue;
|
||||
}
|
||||
let _ = KEY_CACHE.get_from_param(params);
|
||||
|
||||
let client_server_keys = gen_keys(params);
|
||||
file_storage.store(params, &client_server_keys);
|
||||
let stop = start.elapsed().as_secs();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
|
||||
const WOPBS_PARAMS: [(Parameters, Parameters); 4] = [
|
||||
(PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_1_CARRY_1),
|
||||
(PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_2_CARRY_2),
|
||||
(PARAM_MESSAGE_3_CARRY_3, WOPBS_PARAM_MESSAGE_3_CARRY_3),
|
||||
(PARAM_MESSAGE_4_CARRY_4, WOPBS_PARAM_MESSAGE_4_CARRY_4),
|
||||
];
|
||||
|
||||
println!("Generating woPBS keys");
|
||||
for (i, (params_shortint, params_wopbs)) in WOPBS_PARAMS.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}, {}",
|
||||
i + 1,
|
||||
WOPBS_PARAMS.len(),
|
||||
params_shortint.name(),
|
||||
params_wopbs.name(),
|
||||
);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let _ = KEY_CACHE_WOPBS.get_from_param((params_shortint, params_wopbs));
|
||||
|
||||
let stop = start.elapsed().as_secs();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE_WOPBS.clear_in_memory_cache()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let work_dir = std::env::current_dir().unwrap();
|
||||
println!("work_dir: {}", std::env::current_dir().unwrap().display());
|
||||
// Change workdir so that the location of the keycache matches the one for tests
|
||||
let mut new_work_dir = work_dir;
|
||||
new_work_dir.push("tfhe");
|
||||
std::env::set_current_dir(new_work_dir).unwrap();
|
||||
|
||||
client_server_keys()
|
||||
}
|
||||
|
||||
88
tfhe/examples/shortint_key_sizes.rs
Normal file
88
tfhe/examples/shortint_key_sizes.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
#[path = "../benches/utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use tfhe::shortint::keycache::{NamedParam, KEY_CACHE};
|
||||
use tfhe::shortint::parameters::{
|
||||
PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2, PARAM_MESSAGE_3_CARRY_3,
|
||||
PARAM_MESSAGE_4_CARRY_4,
|
||||
};
|
||||
|
||||
fn write_result(file: &mut File, name: &str, value: usize) {
|
||||
let line = format!("{name},{value}\n");
|
||||
let error_message = format!("cannot write {name} result into file");
|
||||
file.write_all(line.as_bytes()).expect(&error_message);
|
||||
}
|
||||
|
||||
fn client_server_key_sizes(results_file: &Path) {
|
||||
let shortint_params_vec = vec![
|
||||
PARAM_MESSAGE_1_CARRY_1,
|
||||
PARAM_MESSAGE_2_CARRY_2,
|
||||
PARAM_MESSAGE_3_CARRY_3,
|
||||
PARAM_MESSAGE_4_CARRY_4,
|
||||
];
|
||||
File::create(results_file).expect("create results file failed");
|
||||
let mut file = OpenOptions::new()
|
||||
.append(true)
|
||||
.open(results_file)
|
||||
.expect("cannot open results file");
|
||||
|
||||
let operator = OperatorType::Atomic;
|
||||
|
||||
println!("Generating shortint (ClientKey, ServerKey)");
|
||||
for (i, params) in shortint_params_vec.iter().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
shortint_params_vec.len(),
|
||||
params.name().to_lowercase()
|
||||
);
|
||||
|
||||
let keys = KEY_CACHE.get_from_param(*params);
|
||||
|
||||
// Client keys don't have public access to members, but the keys in there are small anyways
|
||||
// let cks = keys.client_key();
|
||||
let sks = keys.server_key();
|
||||
let ksk_size = sks.key_switching_key_size_bytes();
|
||||
let test_name = format!("shortint_key_sizes_{}_ksk", params.name());
|
||||
|
||||
write_result(&mut file, &test_name, ksk_size);
|
||||
write_to_json(&test_name, *params, params.name(), "KSK", &operator);
|
||||
|
||||
println!(
|
||||
"Element in KSK: {}, size in bytes: {}",
|
||||
sks.key_switching_key_size_elements(),
|
||||
ksk_size,
|
||||
);
|
||||
|
||||
let bsk_size = sks.bootstrapping_key_size_bytes();
|
||||
let test_name = format!("shortint_key_sizes_{}_bsk", params.name());
|
||||
|
||||
write_result(&mut file, &test_name, bsk_size);
|
||||
write_to_json(&test_name, *params, params.name(), "BSK", &operator);
|
||||
|
||||
println!(
|
||||
"Element in BSK: {}, size in bytes: {}",
|
||||
sks.bootstrapping_key_size_elements(),
|
||||
bsk_size,
|
||||
);
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let work_dir = std::env::current_dir().unwrap();
|
||||
println!("work_dir: {}", std::env::current_dir().unwrap().display());
|
||||
// Change workdir so that the location of the keycache matches the one for tests
|
||||
let mut new_work_dir = work_dir;
|
||||
new_work_dir.push("tfhe");
|
||||
std::env::set_current_dir(new_work_dir).unwrap();
|
||||
|
||||
let results_file = Path::new("shortint_key_sizes.csv");
|
||||
client_server_key_sizes(results_file)
|
||||
}
|
||||
233
tfhe/js_on_wasm_tests/test.js
Normal file
233
tfhe/js_on_wasm_tests/test.js
Normal file
@@ -0,0 +1,233 @@
|
||||
const crypto = require('crypto');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert').strict;
|
||||
const { Boolean, Shortint, BooleanParameterSet } = require("../pkg");
|
||||
|
||||
function genRandomBigIntWithBytes(byteCount) {
|
||||
return BigInt('0x' + crypto.randomBytes(byteCount).toString('hex'))
|
||||
}
|
||||
|
||||
// Boolean tests
|
||||
test('boolean_encrypt_decrypt', (t) => {
|
||||
let params = Boolean.get_parameters(BooleanParameterSet.Default);
|
||||
let cks = Boolean.new_client_key(params);
|
||||
let ct = Boolean.encrypt(cks, true);
|
||||
|
||||
let serialized_cks = Boolean.serialize_client_key(cks);
|
||||
let deserialized_cks = Boolean.deserialize_client_key(serialized_cks);
|
||||
|
||||
let serialized_ct = Boolean.serialize_ciphertext(ct);
|
||||
let deserialized_ct = Boolean.deserialize_ciphertext(serialized_ct);
|
||||
|
||||
let decrypted = Boolean.decrypt(deserialized_cks, deserialized_ct);
|
||||
assert.deepStrictEqual(decrypted, true);
|
||||
|
||||
let sks = Boolean.new_compressed_server_key(cks);
|
||||
|
||||
let serialized_sks = Boolean.serialize_compressed_server_key(sks);
|
||||
let deserialized_sks = Boolean.deserialize_compressed_server_key(serialized_sks);
|
||||
|
||||
// No equality tests here, as wasm stores pointers which will always differ
|
||||
});
|
||||
|
||||
test('boolean_compressed_encrypt_decrypt', (t) => {
|
||||
let params = Boolean.get_parameters(BooleanParameterSet.Default);
|
||||
let cks = Boolean.new_client_key(params);
|
||||
let ct = Boolean.encrypt_compressed(cks, true);
|
||||
|
||||
let serialized_cks = Boolean.serialize_client_key(cks);
|
||||
let deserialized_cks = Boolean.deserialize_client_key(serialized_cks);
|
||||
|
||||
let serialized_ct = Boolean.serialize_compressed_ciphertext(ct);
|
||||
let deserialized_ct = Boolean.deserialize_compressed_ciphertext(serialized_ct);
|
||||
|
||||
let decompressed_ct = Boolean.decompress_ciphertext(deserialized_ct);
|
||||
|
||||
let decrypted = Boolean.decrypt(deserialized_cks, decompressed_ct);
|
||||
assert.deepStrictEqual(decrypted, true);
|
||||
});
|
||||
|
||||
test('boolean_public_encrypt_decrypt', (t) => {
|
||||
let params = Boolean.get_parameters(BooleanParameterSet.Default);
|
||||
let cks = Boolean.new_client_key(params);
|
||||
let pk = Boolean.new_public_key(cks);
|
||||
|
||||
let serialized_pk = Boolean.serialize_public_key(pk);
|
||||
let deserialized_pk = Boolean.deserialize_public_key(serialized_pk);
|
||||
|
||||
let ct = Boolean.encrypt_with_public_key(deserialized_pk, true);
|
||||
|
||||
let serialized_ct = Boolean.serialize_ciphertext(ct);
|
||||
let deserialized_ct = Boolean.deserialize_ciphertext(serialized_ct);
|
||||
|
||||
let decrypted = Boolean.decrypt(cks, deserialized_ct);
|
||||
assert.deepStrictEqual(decrypted, true);
|
||||
});
|
||||
|
||||
test('boolean_deterministic_keygen', (t) => {
|
||||
const TEST_LOOP_COUNT = 128;
|
||||
|
||||
let seed_high_bytes = genRandomBigIntWithBytes(8);
|
||||
let seed_low_bytes = genRandomBigIntWithBytes(8);
|
||||
|
||||
let params = Boolean.get_parameters(BooleanParameterSet.Default);
|
||||
let cks = Boolean.new_client_key_from_seed_and_parameters(seed_high_bytes, seed_low_bytes, params);
|
||||
let other_cks = Boolean.new_client_key_from_seed_and_parameters(seed_high_bytes, seed_low_bytes, params);
|
||||
|
||||
for (let i = 0; i < TEST_LOOP_COUNT; i++) {
|
||||
let ct_true = Boolean.encrypt(cks, true);
|
||||
let decrypt_true_other = Boolean.decrypt(other_cks, ct_true);
|
||||
assert.deepStrictEqual(decrypt_true_other, true);
|
||||
|
||||
let ct_false = Boolean.encrypt(cks, false);
|
||||
let decrypt_false_other = Boolean.decrypt(other_cks, ct_false);
|
||||
assert.deepStrictEqual(decrypt_false_other, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Shortint tests
|
||||
test('shortint_encrypt_decrypt', (t) => {
|
||||
let params = Shortint.get_parameters(2, 2);
|
||||
let cks = Shortint.new_client_key(params);
|
||||
let ct = Shortint.encrypt(cks, BigInt(3));
|
||||
|
||||
let serialized_cks = Shortint.serialize_client_key(cks);
|
||||
let deserialized_cks = Shortint.deserialize_client_key(serialized_cks);
|
||||
|
||||
let serialized_ct = Shortint.serialize_ciphertext(ct);
|
||||
let deserialized_ct = Shortint.deserialize_ciphertext(serialized_ct);
|
||||
|
||||
let decrypted = Shortint.decrypt(deserialized_cks, deserialized_ct);
|
||||
assert.deepStrictEqual(decrypted, BigInt(3));
|
||||
|
||||
let sks = Shortint.new_compressed_server_key(cks);
|
||||
|
||||
let serialized_sks = Shortint.serialize_compressed_server_key(sks);
|
||||
let deserialized_sks = Shortint.deserialize_compressed_server_key(serialized_sks);
|
||||
|
||||
// No equality tests here, as wasm stores pointers which will always differ
|
||||
|
||||
// Encryption using small keys
|
||||
let params_small = Shortint.get_parameters_small(2, 2);
|
||||
let cks_small = Shortint.new_client_key(params_small);
|
||||
|
||||
let ct_small = Shortint.encrypt_small(cks_small, BigInt(3));
|
||||
|
||||
let serialized_ct_small = Shortint.serialize_ciphertext(ct_small);
|
||||
let deserialized_ct_small = Shortint.deserialize_ciphertext(serialized_ct_small);
|
||||
|
||||
let decrypted_small = Shortint.decrypt(cks_small, deserialized_ct_small);
|
||||
assert.deepStrictEqual(decrypted_small, BigInt(3));
|
||||
});
|
||||
|
||||
test('shortint_compressed_encrypt_decrypt', (t) => {
|
||||
let params = Shortint.get_parameters(2, 2);
|
||||
let cks = Shortint.new_client_key(params);
|
||||
let ct = Shortint.encrypt_compressed(cks, BigInt(3));
|
||||
|
||||
let serialized_cks = Shortint.serialize_client_key(cks);
|
||||
let deserialized_cks = Shortint.deserialize_client_key(serialized_cks);
|
||||
|
||||
let serialized_ct = Shortint.serialize_compressed_ciphertext(ct);
|
||||
let deserialized_ct = Shortint.deserialize_compressed_ciphertext(serialized_ct);
|
||||
|
||||
let decompressed_ct = Shortint.decompress_ciphertext(deserialized_ct);
|
||||
|
||||
let decrypted = Shortint.decrypt(deserialized_cks, decompressed_ct);
|
||||
assert.deepStrictEqual(decrypted, BigInt(3));
|
||||
|
||||
// Encryption using small keys
|
||||
let params_small = Shortint.get_parameters_small(2, 2);
|
||||
let cks_small = Shortint.new_client_key(params_small);
|
||||
|
||||
let ct_small = Shortint.encrypt_compressed_small(cks_small, BigInt(3));
|
||||
|
||||
let serialized_ct_small = Shortint.serialize_compressed_ciphertext(ct_small);
|
||||
let deserialized_ct_small = Shortint.deserialize_compressed_ciphertext(serialized_ct_small);
|
||||
|
||||
let decompressed_ct_small = Shortint.decompress_ciphertext(deserialized_ct_small);
|
||||
|
||||
let decrypted_small = Shortint.decrypt(cks_small, decompressed_ct_small);
|
||||
assert.deepStrictEqual(decrypted_small, BigInt(3));
|
||||
});
|
||||
|
||||
test('shortint_public_encrypt_decrypt', (t) => {
|
||||
let params = Shortint.get_parameters(2, 0);
|
||||
let cks = Shortint.new_client_key(params);
|
||||
let pk = Shortint.new_public_key(cks);
|
||||
|
||||
let ct = Shortint.encrypt_with_public_key(pk, BigInt(3));
|
||||
|
||||
let serialized_ct = Shortint.serialize_ciphertext(ct);
|
||||
let deserialized_ct = Shortint.deserialize_ciphertext(serialized_ct);
|
||||
|
||||
let decrypted = Shortint.decrypt(cks, deserialized_ct);
|
||||
assert.deepStrictEqual(decrypted, BigInt(3));
|
||||
|
||||
// Small
|
||||
let params_small = Shortint.get_parameters_small(2, 2);
|
||||
let cks_small = Shortint.new_client_key(params_small);
|
||||
|
||||
let pk_small = Shortint.new_public_key_small(cks_small);
|
||||
|
||||
let ct_small = Shortint.encrypt_with_public_key(pk_small, BigInt(3));
|
||||
|
||||
let serialized_ct_small = Shortint.serialize_ciphertext(ct_small);
|
||||
let deserialized_ct_small = Shortint.deserialize_ciphertext(serialized_ct_small);
|
||||
|
||||
let decrypted_small = Shortint.decrypt(cks_small, deserialized_ct_small);
|
||||
assert.deepStrictEqual(decrypted_small, BigInt(3));
|
||||
});
|
||||
|
||||
test('shortint_compressed_public_encrypt_decrypt', (t) => {
|
||||
let params = Shortint.get_parameters(1, 1);
|
||||
let cks = Shortint.new_client_key(params);
|
||||
let pk = Shortint.new_compressed_public_key(cks);
|
||||
|
||||
let serialized_pk = Shortint.serialize_compressed_public_key(pk);
|
||||
let deserialized_pk = Shortint.deserialize_compressed_public_key(serialized_pk);
|
||||
|
||||
let ct = Shortint.encrypt_with_compressed_public_key(deserialized_pk, BigInt(1));
|
||||
|
||||
let serialized_ct = Shortint.serialize_ciphertext(ct);
|
||||
let deserialized_ct = Shortint.deserialize_ciphertext(serialized_ct);
|
||||
|
||||
let decrypted = Shortint.decrypt(cks, deserialized_ct);
|
||||
assert.deepStrictEqual(decrypted, BigInt(1));
|
||||
|
||||
// Small
|
||||
let params_small = Shortint.get_parameters_small(2, 2);
|
||||
let cks_small = Shortint.new_client_key(params_small);
|
||||
|
||||
let pk_small = Shortint.new_compressed_public_key_small(cks_small);
|
||||
|
||||
let serialized_pk_small = Shortint.serialize_compressed_public_key(pk_small);
|
||||
let deserialized_pk_small = Shortint.deserialize_compressed_public_key(serialized_pk_small);
|
||||
|
||||
let ct_small = Shortint.encrypt_with_compressed_public_key(deserialized_pk_small, BigInt(1));
|
||||
|
||||
let serialized_ct_small = Shortint.serialize_ciphertext(ct_small);
|
||||
let deserialized_ct_small = Shortint.deserialize_ciphertext(serialized_ct_small);
|
||||
|
||||
let decrypted_small = Shortint.decrypt(cks_small, deserialized_ct_small);
|
||||
assert.deepStrictEqual(decrypted_small, BigInt(1));
|
||||
});
|
||||
|
||||
test('shortint_deterministic_keygen', (t) => {
|
||||
const TEST_LOOP_COUNT = 128;
|
||||
|
||||
let seed_high_bytes = genRandomBigIntWithBytes(8);
|
||||
let seed_low_bytes = genRandomBigIntWithBytes(8);
|
||||
|
||||
let params = Shortint.get_parameters(2, 2);
|
||||
let cks = Shortint.new_client_key_from_seed_and_parameters(seed_high_bytes, seed_low_bytes, params);
|
||||
let other_cks = Shortint.new_client_key_from_seed_and_parameters(seed_high_bytes, seed_low_bytes, params);
|
||||
|
||||
for (let i = 0; i < TEST_LOOP_COUNT; i++) {
|
||||
let random_message = genRandomBigIntWithBytes(4) % BigInt(4);
|
||||
let ct = Shortint.encrypt(cks, random_message);
|
||||
let decrypt_other = Shortint.decrypt(other_cks, ct);
|
||||
assert.deepStrictEqual(decrypt_other, random_message);
|
||||
}
|
||||
});
|
||||
15
tfhe/katex-header.html
Normal file
15
tfhe/katex-header.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/katex.min.css" integrity="sha384-KiWOvVjnN8qwAZbuQyWDIbfCLFhLXNETzBQjA/92pIowpC0d2O3nppDGQVgwd2nB" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/katex.min.js" integrity="sha384-0fdwu/T/EQMsQlrHCCHoH10pkPLlKA1jL5dFyUOvB3lfeT2540/2g6YgSi2BL14p" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/contrib/auto-render.min.js" integrity="sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
renderMathInElement(document.body, {
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -2,59 +2,28 @@
|
||||
//!
|
||||
//! This module implements the ciphertext structure containing an encryption of a Boolean message.
|
||||
|
||||
use crate::core_crypto::prelude::*;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use crate::core_crypto::entities::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A structure containing a ciphertext, meant to encrypt a Boolean message.
|
||||
///
|
||||
/// It is used to evaluate a Boolean circuits homomorphically.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Ciphertext {
|
||||
Encrypted(LweCiphertext32),
|
||||
Encrypted(LweCiphertextOwned<u32>),
|
||||
Trivial(bool),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum SerializableCiphertext {
|
||||
Encrypted(Vec<u8>),
|
||||
Trivial(bool),
|
||||
/// A structure containing a compressed ciphertext, meant to encrypt a Boolean message.
|
||||
///
|
||||
/// It has to be decompressed before evaluating a Boolean circuit.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CompressedCiphertext {
|
||||
pub(crate) ciphertext: SeededLweCiphertext<u32>,
|
||||
}
|
||||
|
||||
impl Serialize for Ciphertext {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
match self {
|
||||
Ciphertext::Encrypted(lwe) => {
|
||||
let ciphertext = ser_eng.serialize(lwe).map_err(serde::ser::Error::custom)?;
|
||||
SerializableCiphertext::Encrypted(ciphertext)
|
||||
}
|
||||
Ciphertext::Trivial(b) => SerializableCiphertext::Trivial(*b),
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Ciphertext {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let thing = SerializableCiphertext::deserialize(deserializer)?;
|
||||
|
||||
let mut de_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(match thing {
|
||||
SerializableCiphertext::Encrypted(data) => {
|
||||
let lwe = de_eng
|
||||
.deserialize(data.as_slice())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
Self::Encrypted(lwe)
|
||||
}
|
||||
SerializableCiphertext::Trivial(b) => Self::Trivial(b),
|
||||
})
|
||||
impl From<CompressedCiphertext> for Ciphertext {
|
||||
fn from(value: CompressedCiphertext) -> Self {
|
||||
Self::Encrypted(value.ciphertext.decompress_into_lwe_ciphertext())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
//! This module implements the generation of the client' secret keys, together with the
|
||||
//! encryption and decryption methods.
|
||||
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::boolean::engine::{CpuBooleanEngine, WithThreadLocalEngine};
|
||||
use crate::boolean::ciphertext::{Ciphertext, CompressedCiphertext};
|
||||
use crate::boolean::engine::{BooleanEngine, WithThreadLocalEngine};
|
||||
use crate::boolean::parameters::BooleanParameters;
|
||||
use crate::core_crypto::prelude::*;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use crate::core_crypto::entities::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
/// A structure containing the client key, which must be kept secret.
|
||||
@@ -18,10 +18,10 @@ use std::fmt::{Debug, Formatter};
|
||||
/// * `glwe_secret_key` - a GLWE secret key, used to generate the bootstrapping keys and key
|
||||
/// switching keys.
|
||||
/// * `parameters` - the cryptographic parameter set.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ClientKey {
|
||||
pub(crate) lwe_secret_key: LweSecretKey32,
|
||||
pub(crate) glwe_secret_key: GlweSecretKey32,
|
||||
pub(crate) lwe_secret_key: LweSecretKeyOwned<u32>,
|
||||
pub(crate) glwe_secret_key: GlweSecretKeyOwned<u32>,
|
||||
pub(crate) parameters: BooleanParameters,
|
||||
}
|
||||
|
||||
@@ -46,17 +46,16 @@ impl Debug for ClientKey {
|
||||
}
|
||||
|
||||
impl ClientKey {
|
||||
/// Encrypts a Boolean message using the client key.
|
||||
/// Encrypt a Boolean message using the client key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
/// // Generate the client key and the server key:
|
||||
/// let (cks, mut sks) = gen_keys();
|
||||
/// let (cks, sks) = gen_keys();
|
||||
///
|
||||
/// // Encryption of one message:
|
||||
/// let ct = cks.encrypt(true);
|
||||
@@ -65,24 +64,46 @@ impl ClientKey {
|
||||
/// let dec = cks.decrypt(&ct);
|
||||
/// assert_eq!(true, dec);
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn encrypt(&self, message: bool) -> Ciphertext {
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.encrypt(message, self))
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.encrypt(message, self))
|
||||
}
|
||||
|
||||
/// Decrypts a ciphertext encrypting a Boolean message using the client key.
|
||||
/// Encrypt a Boolean message using the client key returning a compressed ciphertext.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
/// // Generate the client key and the server key:
|
||||
/// let (cks, mut sks) = gen_keys();
|
||||
/// let (cks, sks) = gen_keys();
|
||||
///
|
||||
/// // Encryption of one message:
|
||||
/// let ct = cks.encrypt_compressed(true);
|
||||
///
|
||||
/// let ct: Ciphertext = ct.into();
|
||||
///
|
||||
/// // Decryption:
|
||||
/// let dec = cks.decrypt(&ct);
|
||||
/// assert_eq!(true, dec);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn encrypt_compressed(&self, message: bool) -> CompressedCiphertext {
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.encrypt_compressed(message, self))
|
||||
}
|
||||
|
||||
/// Decrypt a ciphertext encrypting a Boolean message using the client key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
/// // Generate the client key and the server key:
|
||||
/// let (cks, sks) = gen_keys();
|
||||
///
|
||||
/// // Encryption of one message:
|
||||
/// let ct = cks.encrypt(true);
|
||||
@@ -91,24 +112,16 @@ impl ClientKey {
|
||||
/// let dec = cks.decrypt(&ct);
|
||||
/// assert_eq!(true, dec);
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn decrypt(&self, ct: &Ciphertext) -> bool {
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.decrypt(ct, self))
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.decrypt(ct, self))
|
||||
}
|
||||
|
||||
/// Allocates and generates a client key.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This will panic when the "cuda" feature is enabled and the parameters
|
||||
/// uses a GlweDimension > 1 (as it is not yet supported by the cuda backend).
|
||||
/// Allocate and generate a client key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::client_key::ClientKey;
|
||||
/// use tfhe::boolean::parameters::TFHE_LIB_PARAMETERS;
|
||||
@@ -117,73 +130,8 @@ impl ClientKey {
|
||||
/// // Generate the client key:
|
||||
/// let cks = ClientKey::new(&TFHE_LIB_PARAMETERS);
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::client_key::ClientKey;
|
||||
/// use tfhe::boolean::parameters::GPU_DEFAULT_PARAMETERS;
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
/// // Generate the client key:
|
||||
/// let cks = ClientKey::new(&GPU_DEFAULT_PARAMETERS);}
|
||||
/// ```
|
||||
pub fn new(parameter_set: &BooleanParameters) -> ClientKey {
|
||||
#[cfg(feature = "cuda")]
|
||||
{
|
||||
if parameter_set.glwe_dimension.0 > 1 {
|
||||
panic!("the cuda backend does not support support GlweSize greater than one");
|
||||
}
|
||||
}
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.create_client_key(*parameter_set))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializableClientKey {
|
||||
lwe_secret_key: Vec<u8>,
|
||||
glwe_secret_key: Vec<u8>,
|
||||
parameters: BooleanParameters,
|
||||
}
|
||||
|
||||
impl Serialize for ClientKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
let lwe_secret_key = ser_eng
|
||||
.serialize(&self.lwe_secret_key)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
let glwe_secret_key = ser_eng
|
||||
.serialize(&self.glwe_secret_key)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
|
||||
SerializableClientKey {
|
||||
lwe_secret_key,
|
||||
glwe_secret_key,
|
||||
parameters: self.parameters,
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let thing =
|
||||
SerializableClientKey::deserialize(deserializer).map_err(serde::de::Error::custom)?;
|
||||
let mut de_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
lwe_secret_key: de_eng
|
||||
.deserialize(thing.lwe_secret_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
glwe_secret_key: de_eng
|
||||
.deserialize(thing.glwe_secret_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
parameters: thing.parameters,
|
||||
})
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.create_client_key(*parameter_set))
|
||||
}
|
||||
}
|
||||
|
||||
385
tfhe/src/boolean/engine/bootstrapping.rs
Normal file
385
tfhe/src/boolean/engine/bootstrapping.rs
Normal file
@@ -0,0 +1,385 @@
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::boolean::{ClientKey, PLAINTEXT_TRUE};
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
|
||||
use crate::core_crypto::commons::generators::{DeterministicSeeder, EncryptionRandomGenerator};
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
|
||||
use crate::core_crypto::commons::parameters::CiphertextModulus;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::fft_impl::fft64::math::fft::Fft;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
||||
/// Memory used as buffer for the bootstrap
|
||||
///
|
||||
/// It contains contiguous chunk which is then sliced and converted
|
||||
/// into core's View types.
|
||||
#[derive(Default)]
|
||||
struct Memory {
|
||||
buffer: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// Return a tuple with buffers that matches the server key.
|
||||
///
|
||||
/// - The first element is the accumulator for bootstrap step.
|
||||
/// - The second element is a lwe buffer where the result of the of the bootstrap should be
|
||||
/// written
|
||||
fn as_buffers(
|
||||
&mut self,
|
||||
server_key: &ServerKey,
|
||||
) -> (GlweCiphertextView<'_, u32>, LweCiphertextMutView<'_, u32>) {
|
||||
let num_elem_in_accumulator = server_key.bootstrapping_key.glwe_size().0
|
||||
* server_key.bootstrapping_key.polynomial_size().0;
|
||||
let num_elem_in_lwe = server_key
|
||||
.bootstrapping_key
|
||||
.output_lwe_dimension()
|
||||
.to_lwe_size()
|
||||
.0;
|
||||
let total_elem_needed = num_elem_in_accumulator + num_elem_in_lwe;
|
||||
|
||||
let all_elements = if self.buffer.len() < total_elem_needed {
|
||||
self.buffer.resize(total_elem_needed, 0u32);
|
||||
self.buffer.as_mut_slice()
|
||||
} else {
|
||||
&mut self.buffer[..total_elem_needed]
|
||||
};
|
||||
|
||||
let (accumulator_elements, lwe_elements) =
|
||||
all_elements.split_at_mut(num_elem_in_accumulator);
|
||||
|
||||
{
|
||||
let mut accumulator = GlweCiphertextMutView::from_container(
|
||||
accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
accumulator.get_mut_mask().as_mut().fill(0u32);
|
||||
accumulator.get_mut_body().as_mut().fill(PLAINTEXT_TRUE);
|
||||
}
|
||||
|
||||
let accumulator = GlweCiphertextView::from_container(
|
||||
accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
let lwe =
|
||||
LweCiphertextMutView::from_container(lwe_elements, CiphertextModulus::new_native());
|
||||
|
||||
(accumulator, lwe)
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure containing the server public key.
|
||||
///
|
||||
/// This server key data lives on the CPU.
|
||||
///
|
||||
/// The server key is generated by the client and is meant to be published: the client
|
||||
/// sends it to the server so it can compute homomorphic Boolean circuits.
|
||||
///
|
||||
/// In more details, it contains:
|
||||
/// * `bootstrapping_key` - a public key, used to perform the bootstrapping operation.
|
||||
/// * `key_switching_key` - a public key, used to perform the key-switching operation.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ServerKey {
|
||||
pub(crate) bootstrapping_key: FourierLweBootstrapKeyOwned,
|
||||
pub(crate) key_switching_key: LweKeyswitchKeyOwned<u32>,
|
||||
}
|
||||
|
||||
impl ServerKey {
|
||||
pub fn bootstrapping_key_size_elements(&self) -> usize {
|
||||
self.bootstrapping_key.as_view().data().as_ref().len()
|
||||
}
|
||||
|
||||
pub fn bootstrapping_key_size_bytes(&self) -> usize {
|
||||
self.bootstrapping_key_size_elements() * std::mem::size_of::<concrete_fft::c64>()
|
||||
}
|
||||
|
||||
pub fn key_switching_key_size_elements(&self) -> usize {
|
||||
self.key_switching_key.as_ref().len()
|
||||
}
|
||||
|
||||
pub fn key_switching_key_size_bytes(&self) -> usize {
|
||||
self.key_switching_key_size_elements() * std::mem::size_of::<u64>()
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure containing the compressed server public key.
|
||||
///
|
||||
/// This server key data lives on the CPU.
|
||||
///
|
||||
/// The server key is generated by the client and is meant to be published: the client
|
||||
/// sends it to the server so it can compute homomorphic Boolean circuits.
|
||||
///
|
||||
/// In more details, it contains:
|
||||
/// * `bootstrapping_key` - a public key, used to perform the bootstrapping operation.
|
||||
/// * `key_switching_key` - a public key, used to perform the key-switching operation.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct CompressedServerKey {
|
||||
pub(crate) bootstrapping_key: SeededLweBootstrapKeyOwned<u32>,
|
||||
pub(crate) key_switching_key: SeededLweKeyswitchKeyOwned<u32>,
|
||||
}
|
||||
|
||||
/// Perform ciphertext bootstraps on the CPU
|
||||
pub(crate) struct Bootstrapper {
|
||||
memory: Memory,
|
||||
/// A structure containing two CSPRNGs to generate material for encryption like public masks
|
||||
/// and secret errors.
|
||||
///
|
||||
/// The [`EncryptionRandomGenerator`] contains two CSPRNGs, one publicly seeded used to
|
||||
/// generate mask coefficients and one privately seeded used to generate errors during
|
||||
/// encryption.
|
||||
pub(crate) encryption_generator: EncryptionRandomGenerator<ActivatedRandomGenerator>,
|
||||
pub(crate) computation_buffers: ComputationBuffers,
|
||||
pub(crate) seeder: DeterministicSeeder<ActivatedRandomGenerator>,
|
||||
}
|
||||
|
||||
impl Bootstrapper {
|
||||
pub fn new(seeder: &mut dyn Seeder) -> Self {
|
||||
Bootstrapper {
|
||||
memory: Default::default(),
|
||||
encryption_generator: EncryptionRandomGenerator::<_>::new(seeder.seed(), seeder),
|
||||
computation_buffers: Default::default(),
|
||||
seeder: DeterministicSeeder::<_>::new(seeder.seed()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_server_key(
|
||||
&mut self,
|
||||
cks: &ClientKey,
|
||||
) -> Result<ServerKey, Box<dyn std::error::Error>> {
|
||||
let standard_bootstraping_key: LweBootstrapKeyOwned<u32> =
|
||||
par_allocate_and_generate_new_lwe_bootstrap_key(
|
||||
&cks.lwe_secret_key,
|
||||
&cks.glwe_secret_key,
|
||||
cks.parameters.pbs_base_log,
|
||||
cks.parameters.pbs_level,
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
// creation of the bootstrapping key in the Fourier domain
|
||||
let mut fourier_bsk = FourierLweBootstrapKey::new(
|
||||
standard_bootstraping_key.input_lwe_dimension(),
|
||||
standard_bootstraping_key.glwe_size(),
|
||||
standard_bootstraping_key.polynomial_size(),
|
||||
standard_bootstraping_key.decomposition_base_log(),
|
||||
standard_bootstraping_key.decomposition_level_count(),
|
||||
);
|
||||
|
||||
let fft = Fft::new(standard_bootstraping_key.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
self.computation_buffers.resize(
|
||||
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized_requirement(fft)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
let stack = self.computation_buffers.stack();
|
||||
|
||||
// Conversion to fourier domain
|
||||
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized(
|
||||
&standard_bootstraping_key,
|
||||
&mut fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
|
||||
// Convert the GLWE secret key into an LWE secret key:
|
||||
let big_lwe_secret_key = cks.glwe_secret_key.clone().into_lwe_secret_key();
|
||||
|
||||
// creation of the key switching key
|
||||
let ksk = allocate_and_generate_new_lwe_keyswitch_key(
|
||||
&big_lwe_secret_key,
|
||||
&cks.lwe_secret_key,
|
||||
cks.parameters.ks_base_log,
|
||||
cks.parameters.ks_level,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
Ok(ServerKey {
|
||||
bootstrapping_key: fourier_bsk,
|
||||
key_switching_key: ksk,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn new_compressed_server_key(
|
||||
&mut self,
|
||||
cks: &ClientKey,
|
||||
) -> Result<CompressedServerKey, Box<dyn std::error::Error>> {
|
||||
#[cfg(not(feature = "__wasm_api"))]
|
||||
let bootstrapping_key = par_allocate_and_generate_new_seeded_lwe_bootstrap_key(
|
||||
&cks.lwe_secret_key,
|
||||
&cks.glwe_secret_key,
|
||||
cks.parameters.pbs_base_log,
|
||||
cks.parameters.pbs_level,
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.seeder,
|
||||
);
|
||||
|
||||
#[cfg(feature = "__wasm_api")]
|
||||
let bootstrapping_key = allocate_and_generate_new_seeded_lwe_bootstrap_key(
|
||||
&cks.lwe_secret_key,
|
||||
&cks.glwe_secret_key,
|
||||
cks.parameters.pbs_base_log,
|
||||
cks.parameters.pbs_level,
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.seeder,
|
||||
);
|
||||
|
||||
let big_lwe_secret_key = cks.glwe_secret_key.clone().into_lwe_secret_key();
|
||||
|
||||
// creation of the key switching key
|
||||
let key_switching_key = allocate_and_generate_new_seeded_lwe_keyswitch_key(
|
||||
&big_lwe_secret_key,
|
||||
&cks.lwe_secret_key,
|
||||
cks.parameters.ks_base_log,
|
||||
cks.parameters.ks_level,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.seeder,
|
||||
);
|
||||
|
||||
Ok(CompressedServerKey {
|
||||
bootstrapping_key,
|
||||
key_switching_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap(
|
||||
&mut self,
|
||||
input: &LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<LweCiphertextOwned<u32>, Box<dyn Error>> {
|
||||
let (accumulator, mut buffer_after_pbs) = self.memory.as_buffers(server_key);
|
||||
|
||||
let fourier_bsk = &server_key.bootstrapping_key;
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
self.computation_buffers.resize(
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<u64>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
let stack = self.computation_buffers.stack();
|
||||
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
input,
|
||||
&mut buffer_after_pbs,
|
||||
&accumulator,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
|
||||
Ok(LweCiphertext::from_container(
|
||||
buffer_after_pbs.as_ref().to_owned(),
|
||||
input.ciphertext_modulus(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn keyswitch(
|
||||
&mut self,
|
||||
input: &LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<LweCiphertextOwned<u32>, Box<dyn Error>> {
|
||||
// Allocate the output of the KS
|
||||
let mut output = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
input.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
keyswitch_lwe_ciphertext(&server_key.key_switching_key, input, &mut output);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap_keyswitch(
|
||||
&mut self,
|
||||
mut ciphertext: LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<Ciphertext, Box<dyn Error>> {
|
||||
let (accumulator, mut buffer_lwe_after_pbs) = self.memory.as_buffers(server_key);
|
||||
|
||||
let fourier_bsk = &server_key.bootstrapping_key;
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
self.computation_buffers.resize(
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement::<u64>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
let stack = self.computation_buffers.stack();
|
||||
|
||||
// Compute a bootstrap
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
&ciphertext,
|
||||
&mut buffer_lwe_after_pbs,
|
||||
&accumulator,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
|
||||
// Compute a key switch to get back to input key
|
||||
keyswitch_lwe_ciphertext(
|
||||
&server_key.key_switching_key,
|
||||
&buffer_lwe_after_pbs,
|
||||
&mut ciphertext,
|
||||
);
|
||||
|
||||
Ok(Ciphertext::Encrypted(ciphertext))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CompressedServerKey> for ServerKey {
|
||||
fn from(compressed_server_key: CompressedServerKey) -> Self {
|
||||
let CompressedServerKey {
|
||||
key_switching_key,
|
||||
bootstrapping_key,
|
||||
} = compressed_server_key;
|
||||
|
||||
let key_switching_key = key_switching_key.decompress_into_lwe_keyswitch_key();
|
||||
let standard_bootstrapping_key = bootstrapping_key.decompress_into_lwe_bootstrap_key();
|
||||
|
||||
let mut bootstrapping_key = FourierLweBootstrapKeyOwned::new(
|
||||
standard_bootstrapping_key.input_lwe_dimension(),
|
||||
standard_bootstrapping_key.glwe_size(),
|
||||
standard_bootstrapping_key.polynomial_size(),
|
||||
standard_bootstrapping_key.decomposition_base_log(),
|
||||
standard_bootstrapping_key.decomposition_level_count(),
|
||||
);
|
||||
|
||||
convert_standard_lwe_bootstrap_key_to_fourier(
|
||||
&standard_bootstrapping_key,
|
||||
&mut bootstrapping_key,
|
||||
);
|
||||
|
||||
Self {
|
||||
key_switching_key,
|
||||
bootstrapping_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user