mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-04-28 03:01:21 -04:00
Compare commits
126 Commits
am/wip/sts
...
doc/wip0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b69222df1c | ||
|
|
934a78496a | ||
|
|
6d49993629 | ||
|
|
66a193eaca | ||
|
|
a5fa497096 | ||
|
|
7e3bf8e08a | ||
|
|
9cb98cca2e | ||
|
|
23d25a8281 | ||
|
|
ef26dc0e9e | ||
|
|
2e2f1200dc | ||
|
|
0a27ed44f7 | ||
|
|
37a47e63a4 | ||
|
|
417feb6a20 | ||
|
|
37be751188 | ||
|
|
2580a834af | ||
|
|
a029bd878e | ||
|
|
400e7930b6 | ||
|
|
40d07c6bc3 | ||
|
|
9dd2d39f1c | ||
|
|
4045a3bc2f | ||
|
|
b4ffeccd46 | ||
|
|
7fe3ad3b6e | ||
|
|
7fdd4f9532 | ||
|
|
81eef39ddb | ||
|
|
b6459e3cda | ||
|
|
f2ef78c348 | ||
|
|
aef8f31621 | ||
|
|
df78d178da | ||
|
|
9297a886a4 | ||
|
|
28b4f91a32 | ||
|
|
04fb46e41b | ||
|
|
53da809f37 | ||
|
|
723910c669 | ||
|
|
8ecf8879fb | ||
|
|
2427f744f8 | ||
|
|
422e1f23d5 | ||
|
|
30a5ade17f | ||
|
|
6cdd41c22f | ||
|
|
f369bec394 | ||
|
|
df4e9c69c7 | ||
|
|
0e3d129906 | ||
|
|
682e455c94 | ||
|
|
b553a68fa9 | ||
|
|
be95eadf79 | ||
|
|
0213a11a0c | ||
|
|
413fde3b3b | ||
|
|
40f8ac9adf | ||
|
|
2ab25c1084 | ||
|
|
86c62b70e5 | ||
|
|
18d790fc26 | ||
|
|
e9e3dae786 | ||
|
|
9b1dccbcb4 | ||
|
|
cef011dd91 | ||
|
|
19f7d5af5c | ||
|
|
95ca5a80dc | ||
|
|
b5fded34d1 | ||
|
|
0c3b09c83d | ||
|
|
85a19d30a9 | ||
|
|
f58132c391 | ||
|
|
099bff84aa | ||
|
|
42ad474a46 | ||
|
|
9f6827b803 | ||
|
|
d23c0df449 | ||
|
|
229bfeebe4 | ||
|
|
48aab9d494 | ||
|
|
e4769a8212 | ||
|
|
79bdaaba20 | ||
|
|
02a14fff7c | ||
|
|
72cce4c5b2 | ||
|
|
a317c4b9dd | ||
|
|
2e2bd5ba29 | ||
|
|
827d8d8708 | ||
|
|
bf434be347 | ||
|
|
ed83fbb460 | ||
|
|
0aad2e669b | ||
|
|
cd68a3bd1c | ||
|
|
b77286bcbc | ||
|
|
609f83bbff | ||
|
|
2a8ebb81d8 | ||
|
|
1a2a17a6ab | ||
|
|
0080caf95d | ||
|
|
c26238533b | ||
|
|
b29936d844 | ||
|
|
25914cc727 | ||
|
|
ca229e369b | ||
|
|
4a99e54c0d | ||
|
|
2383591351 | ||
|
|
dc464f398d | ||
|
|
ce70b5758a | ||
|
|
1c76a08373 | ||
|
|
9b19bd1e8b | ||
|
|
a3dde21240 | ||
|
|
005e1afe2f | ||
|
|
17c404b77d | ||
|
|
1403971d15 | ||
|
|
94ad69bfa3 | ||
|
|
bc129ba0ed | ||
|
|
462834a12e | ||
|
|
ebeee1d6f8 | ||
|
|
d0e1a582e1 | ||
|
|
546cb369a8 | ||
|
|
445af7ab97 | ||
|
|
23f8c69bae | ||
|
|
b8df207b68 | ||
|
|
03688aee4c | ||
|
|
5a3652f398 | ||
|
|
ae3c261d1e | ||
|
|
95dcf95e88 | ||
|
|
b92d6400f4 | ||
|
|
9ba27b4082 | ||
|
|
b164c90d75 | ||
|
|
ff893ca6ef | ||
|
|
2dd1e13dad | ||
|
|
4393fce861 | ||
|
|
4f10cfa6dd | ||
|
|
a6e4488de2 | ||
|
|
878f3fa448 | ||
|
|
90b887a56f | ||
|
|
7dc52cf4ef | ||
|
|
a69333ed37 | ||
|
|
ffad25449e | ||
|
|
6d471856c7 | ||
|
|
65749cb39b | ||
|
|
bf36316c12 | ||
|
|
d98bb0eb86 | ||
|
|
5747af6dce |
2
.github/workflows/aws_tfhe_fast_tests.yml
vendored
2
.github/workflows/aws_tfhe_fast_tests.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
|
||||
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: ${{ inputs.fork_repo }}
|
||||
ref: ${{ inputs.fork_git_sha }}
|
||||
|
||||
2
.github/workflows/aws_tfhe_integer_tests.yml
vendored
2
.github/workflows/aws_tfhe_integer_tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
|
||||
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: ${{ inputs.fork_repo }}
|
||||
ref: ${{ inputs.fork_git_sha }}
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
|
||||
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: ${{ inputs.fork_repo }}
|
||||
ref: ${{ inputs.fork_git_sha }}
|
||||
|
||||
2
.github/workflows/aws_tfhe_tests.yml
vendored
2
.github/workflows/aws_tfhe_tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
|
||||
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: ${{ inputs.fork_repo }}
|
||||
ref: ${{ inputs.fork_git_sha }}
|
||||
|
||||
2
.github/workflows/aws_tfhe_wasm_tests.yml
vendored
2
.github/workflows/aws_tfhe_wasm_tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
|
||||
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: ${{ inputs.fork_repo }}
|
||||
ref: ${{ inputs.fork_git_sha }}
|
||||
|
||||
6
.github/workflows/boolean_benchmark.yml
vendored
6
.github/workflows/boolean_benchmark.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -88,13 +88,13 @@ jobs:
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_boolean
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
12
.github/workflows/cargo_build.yml
vendored
12
.github/workflows/cargo_build.yml
vendored
@@ -21,7 +21,17 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
|
||||
- name: Install and run newline linter checks
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
wget https://github.com/fernandrone/linelint/releases/download/0.0.6/linelint-linux-amd64
|
||||
echo "16b70fb7b471d6f95cbdc0b4e5dc2b0ac9e84ba9ecdc488f7bdf13df823aca4b linelint-linux-amd64" > checksum
|
||||
sha256sum -c checksum || exit 1
|
||||
chmod +x linelint-linux-amd64
|
||||
mv linelint-linux-amd64 /usr/local/bin/linelint
|
||||
make check_newline
|
||||
|
||||
- name: Run pcc checks
|
||||
run: |
|
||||
|
||||
112
.github/workflows/code_coverage.yml
vendored
Normal file
112
.github/workflows/code_coverage.yml
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
name: Code Coverage
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
RUSTFLAGS: "-C target-cpu=native"
|
||||
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab as an alternative.
|
||||
workflow_dispatch:
|
||||
# All the inputs are provided by Slab
|
||||
inputs:
|
||||
instance_id:
|
||||
description: "AWS instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: "AWS instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: "AWS instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
fork_repo:
|
||||
description: 'Name of forked repo as user/repo'
|
||||
type: string
|
||||
fork_git_sha:
|
||||
description: 'Git SHA to checkout from fork'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
code-coverage:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ${{ inputs.runner_name }}
|
||||
steps:
|
||||
# Step used for log purpose.
|
||||
- name: Instance configuration used
|
||||
run: |
|
||||
echo "ID: ${{ inputs.instance_id }}"
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
echo "Fork repo: ${{ inputs.fork_repo }}"
|
||||
echo "Fork git sha: ${{ inputs.fork_git_sha }}"
|
||||
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: ${{ inputs.fork_repo }}
|
||||
ref: ${{ inputs.fork_git_sha }}
|
||||
|
||||
- name: Set up home
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install latest stable
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Check for file changes
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@8238a4103220c636f2dad328ead8a7c8dbe316a3
|
||||
with:
|
||||
files_yaml: |
|
||||
tfhe:
|
||||
- tfhe/src/**
|
||||
concrete_csprng:
|
||||
- concrete-csprng/src/**
|
||||
|
||||
- name: Generate Keys
|
||||
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
|
||||
run: |
|
||||
make GEN_KEY_CACHE_COVERAGE_ONLY=TRUE gen_key_cache
|
||||
|
||||
- name: Run coverage for boolean
|
||||
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
|
||||
run: |
|
||||
make test_boolean_cov
|
||||
|
||||
- name: Run coverage for shortint
|
||||
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
|
||||
run: |
|
||||
make test_shortint_cov
|
||||
|
||||
- name: Upload tfhe coverage to Codecov
|
||||
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
||||
if: steps.changed-files.outputs.tfhe_any_changed == 'true'
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
directory: ./coverage/
|
||||
fail_ci_if_error: true
|
||||
files: shortint/cobertura.xml,boolean/cobertura.xml
|
||||
|
||||
- name: Slack Notification
|
||||
if: ${{ failure() }}
|
||||
continue-on-error: true
|
||||
uses: rtCamp/action-slack-notify@b24d75fe0e728a4bf9fc42ee217caa686d141ee8
|
||||
env:
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
|
||||
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
|
||||
SLACK_MESSAGE: "Code coverage finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
|
||||
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
74
.github/workflows/csprng_randomness_testing.yml
vendored
Normal file
74
.github/workflows/csprng_randomness_testing.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: CSPRNG randomness testing Workflow
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
RUSTFLAGS: "-C target-cpu=native"
|
||||
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab as an alternative.
|
||||
workflow_dispatch:
|
||||
# All the inputs are provided by Slab
|
||||
inputs:
|
||||
instance_id:
|
||||
description: "AWS instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: "AWS instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: "AWS instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
fork_repo:
|
||||
description: 'Name of forked repo as user/repo'
|
||||
type: string
|
||||
fork_git_sha:
|
||||
description: 'Git SHA to checkout from fork'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
csprng-randomness-teting:
|
||||
name: CSPRNG randomness testing
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ${{ inputs.runner_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: ${{ inputs.fork_repo }}
|
||||
ref: ${{ inputs.fork_git_sha }}
|
||||
|
||||
- name: Set up home
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install latest stable
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Dieharder randomness test suite
|
||||
run: |
|
||||
make dieharder_csprng
|
||||
|
||||
- name: Slack Notification
|
||||
if: ${{ failure() }}
|
||||
continue-on-error: true
|
||||
uses: rtCamp/action-slack-notify@b24d75fe0e728a4bf9fc42ee217caa686d141ee8
|
||||
env:
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
|
||||
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
|
||||
SLACK_MESSAGE: "concrete-csprng randomness check finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
|
||||
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
8
.github/workflows/integer_benchmark.yml
vendored
8
.github/workflows/integer_benchmark.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
parse_integer_benches
|
||||
|
||||
- name: Upload csv results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_csv_integer
|
||||
path: ${{ env.PARSE_INTEGER_BENCH_CSV_FILE }}
|
||||
@@ -90,13 +90,13 @@ jobs:
|
||||
--throughput
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_integer
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
6
.github/workflows/integer_full_benchmark.yml
vendored
6
.github/workflows/integer_full_benchmark.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
override: true
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
--throughput
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_${{ matrix.command }}_${{ matrix.op_flavor }}
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
parse_integer_benches
|
||||
|
||||
- name: Upload csv results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_csv_integer
|
||||
path: ${{ env.PARSE_INTEGER_BENCH_CSV_FILE }}
|
||||
@@ -90,13 +90,13 @@ jobs:
|
||||
--throughput
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_integer
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
2
.github/workflows/m1_tests.yml
vendored
2
.github/workflows/m1_tests.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ["self-hosted", "m1mac"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
|
||||
- name: Install latest stable
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
|
||||
6
.github/workflows/make_release.yml
vendored
6
.github/workflows/make_release.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Publish web package
|
||||
if: ${{ inputs.push_web_package }}
|
||||
uses: JS-DevTools/npm-publish@5a85faf05d2ade2d5b6682bfe5359915d5159c6c
|
||||
uses: JS-DevTools/npm-publish@fe72237be0920f7a0cafd6a966c9b929c9466e9b
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: tfhe/pkg/package.json
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
- name: Publish Node package
|
||||
if: ${{ inputs.push_node_package }}
|
||||
uses: JS-DevTools/npm-publish@5a85faf05d2ade2d5b6682bfe5359915d5159c6c
|
||||
uses: JS-DevTools/npm-publish@fe72237be0920f7a0cafd6a966c9b929c9466e9b
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: tfhe/pkg/package.json
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
6
.github/workflows/parameters_check.yml
vendored
6
.github/workflows/parameters_check.yml
vendored
@@ -17,10 +17,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
|
||||
- name: Checkout lattice-estimator
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: malb/lattice-estimator
|
||||
path: lattice_estimator
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- name: Collect parameters
|
||||
run: |
|
||||
make write_params_to_file
|
||||
CARGO_PROFILE=devo make write_params_to_file
|
||||
|
||||
- name: Perform security check
|
||||
run: |
|
||||
|
||||
6
.github/workflows/pbs_benchmark.yml
vendored
6
.github/workflows/pbs_benchmark.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -78,13 +78,13 @@ jobs:
|
||||
--throughput
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_pbs
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
6
.github/workflows/shortint_benchmark.yml
vendored
6
.github/workflows/shortint_benchmark.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -88,13 +88,13 @@ jobs:
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_shortint
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
override: true
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_shortint_${{ matrix.op_flavor }}
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
6
.github/workflows/start_benchmarks.yml
vendored
6
.github/workflows/start_benchmarks.yml
vendored
@@ -42,13 +42,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for file changes
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@c860b5c47fa71f461da850094ef2f6e3d6514e44
|
||||
uses: tj-actions/changed-files@8238a4103220c636f2dad328ead8a7c8dbe316a3
|
||||
with:
|
||||
files_yaml: |
|
||||
common_benches:
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
- .github/workflows/wasm_client_benchmark.yml
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
4
.github/workflows/start_full_benchmarks.yml
vendored
4
.github/workflows/start_full_benchmarks.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout tfhe-rs
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
4
.github/workflows/sync_on_push.yml
vendored
4
.github/workflows/sync_on_push.yml
vendored
@@ -13,11 +13,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Save repo
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: repo-archive
|
||||
path: '.'
|
||||
|
||||
22
.github/workflows/trigger_aws_tests_on_pr.yml
vendored
22
.github/workflows/trigger_aws_tests_on_pr.yml
vendored
@@ -12,6 +12,16 @@ jobs:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Get current labels
|
||||
uses: snnaplab/get-labels-action@f426df40304808ace3b5282d4f036515f7609576
|
||||
|
||||
- name: Remove approved label
|
||||
if: ${{ github.event_name == 'pull_request' && contains(fromJSON(env.LABELS), 'approved') }}
|
||||
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: approved
|
||||
|
||||
- name: Launch fast tests
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
|
||||
@@ -20,8 +30,17 @@ jobs:
|
||||
message: |
|
||||
@slab-ci cpu_fast_test
|
||||
|
||||
- name: Add approved label
|
||||
uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf
|
||||
if: ${{ github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && !contains(fromJSON(env.LABELS), 'approved') }}
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: approved
|
||||
|
||||
# PR label 'approved' presence is checked to avoid running the full test suite several times
|
||||
# in case of multiple approvals without new commits in between.
|
||||
- name: Launch full tests suite
|
||||
if: ${{ github.event_name == 'pull_request_review' && github.event.review.state == 'approved' }}
|
||||
if: ${{ github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && !contains(fromJSON(env.LABELS), 'approved') }}
|
||||
uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
|
||||
with:
|
||||
allow-repeats: true
|
||||
@@ -32,3 +51,4 @@ jobs:
|
||||
@slab-ci cpu_integer_test
|
||||
@slab-ci cpu_multi_bit_test
|
||||
@slab-ci cpu_wasm_test
|
||||
@slab-ci csprng_randomness_testing
|
||||
|
||||
6
.github/workflows/wasm_client_benchmark.yml
vendored
6
.github/workflows/wasm_client_benchmark.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -89,13 +89,13 @@ jobs:
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
||||
with:
|
||||
name: ${{ github.sha }}_wasm
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -14,5 +14,8 @@ target/
|
||||
/tfhe/benchmarks_parameters
|
||||
**/*.csv
|
||||
|
||||
# Directory to use the NIST STS test suite built
|
||||
/sts_testing
|
||||
# dieharder run log
|
||||
dieharder_run.log
|
||||
|
||||
# Coverage reports
|
||||
./coverage/
|
||||
|
||||
14
.linelint.yml
Normal file
14
.linelint.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
ignore:
|
||||
- .git
|
||||
- target
|
||||
- tfhe/benchmarks_parameters
|
||||
- tfhe/web_wasm_parallel_tests/node_modules
|
||||
- tfhe/web_wasm_parallel_tests/dist
|
||||
- keys
|
||||
- coverage
|
||||
|
||||
rules:
|
||||
# checks if file ends in a newline character
|
||||
end-of-file:
|
||||
enable: true
|
||||
single-new-line: true
|
||||
104
Makefile
104
Makefile
@@ -11,6 +11,7 @@ AVX512_SUPPORT?=OFF
|
||||
WASM_RUSTFLAGS:=
|
||||
BIG_TESTS_INSTANCE?=FALSE
|
||||
GEN_KEY_CACHE_MULTI_BIT_ONLY?=FALSE
|
||||
GEN_KEY_CACHE_COVERAGE_ONLY?=FALSE
|
||||
PARSE_INTEGER_BENCH_CSV_FILE?=tfhe_rs_integer_benches.csv
|
||||
FAST_TESTS?=FALSE
|
||||
FAST_BENCH?=FALSE
|
||||
@@ -31,10 +32,32 @@ else
|
||||
MULTI_BIT_ONLY=
|
||||
endif
|
||||
|
||||
ifeq ($(GEN_KEY_CACHE_COVERAGE_ONLY),TRUE)
|
||||
COVERAGE_ONLY=--coverage-only
|
||||
else
|
||||
COVERAGE_ONLY=
|
||||
endif
|
||||
|
||||
# Variables used only for regex_engine example
|
||||
REGEX_STRING?=''
|
||||
REGEX_PATTERN?=''
|
||||
|
||||
# Exclude these files from coverage reports
|
||||
define COVERAGE_EXCLUDED_FILES
|
||||
--exclude-files apps/trivium/src/trivium/* \
|
||||
--exclude-files apps/trivium/src/kreyvium/* \
|
||||
--exclude-files apps/trivium/src/static_deque/* \
|
||||
--exclude-files apps/trivium/src/trans_ciphering/* \
|
||||
--exclude-files tasks/src/* \
|
||||
--exclude-files tfhe/benches/boolean/* \
|
||||
--exclude-files tfhe/benches/core_crypto/* \
|
||||
--exclude-files tfhe/benches/shortint/* \
|
||||
--exclude-files tfhe/benches/integer/* \
|
||||
--exclude-files tfhe/benches/* \
|
||||
--exclude-files tfhe/examples/regex_engine/* \
|
||||
--exclude-files tfhe/examples/utilities/*
|
||||
endef
|
||||
|
||||
.PHONY: rs_check_toolchain # Echo the rust toolchain used for checks
|
||||
rs_check_toolchain:
|
||||
@echo $(RS_CHECK_TOOLCHAIN)
|
||||
@@ -79,14 +102,42 @@ install_node:
|
||||
$(SHELL) -i -c 'nvm install node' || \
|
||||
( echo "Unable to install node, unknown error." && exit 1 )
|
||||
|
||||
.PHONY: install_dieharder # Install dieharder for apt distributions or macOS
|
||||
install_dieharder:
|
||||
@dieharder -h > /dev/null 2>&1 || \
|
||||
if [[ "$(OS)" == "Linux" ]]; then \
|
||||
sudo apt update && sudo apt install -y dieharder; \
|
||||
elif [[ "$(OS)" == "Darwin" ]]; then\
|
||||
brew install dieharder; \
|
||||
fi || ( echo "Unable to install dieharder, unknown error." && exit 1 )
|
||||
|
||||
.PHONY: install_tarpaulin # Install tarpaulin to perform code coverage
|
||||
install_tarpaulin: install_rs_build_toolchain
|
||||
@cargo tarpaulin --version > /dev/null 2>&1 || \
|
||||
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-tarpaulin --locked || \
|
||||
( echo "Unable to install cargo tarpaulin, unknown error." && exit 1 )
|
||||
|
||||
.PHONY: check_linelint_installed # Check if linelint newline linter is installed
|
||||
check_linelint_installed:
|
||||
@printf "\n" | linelint - > /dev/null 2>&1 || \
|
||||
( echo "Unable to locate linelint. Try installing it: https://github.com/fernandrone/linelint/releases" && exit 1 )
|
||||
|
||||
.PHONY: fmt # Format rust code
|
||||
fmt: install_rs_check_toolchain
|
||||
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt
|
||||
|
||||
.PHONT: check_fmt # Check rust code format
|
||||
.PHONY: check_fmt # Check rust code format
|
||||
check_fmt: install_rs_check_toolchain
|
||||
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt --check
|
||||
|
||||
.PHONY: fix_newline # Fix newline at end of file issues to be UNIX compliant
|
||||
fix_newline: check_linelint_installed
|
||||
linelint -a .
|
||||
|
||||
.PHONY: check_newline # Check for newline at end of file to be UNIX compliant
|
||||
check_newline: check_linelint_installed
|
||||
linelint .
|
||||
|
||||
.PHONY: clippy_core # Run clippy lints on core_crypto with and without experimental features
|
||||
clippy_core: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
@@ -137,10 +188,16 @@ clippy_tasks:
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
-p tasks -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_trivium # Run clippy lints on Trivium app
|
||||
clippy_trivium: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy -p tfhe-trivium \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_all_targets # Run clippy lints on all targets (benches, examples, etc.)
|
||||
clippy_all_targets:
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_concrete_csprng # Run clippy lints on concrete-csprng
|
||||
@@ -151,7 +208,7 @@ clippy_concrete_csprng:
|
||||
|
||||
.PHONY: clippy_all # Run all clippy targets
|
||||
clippy_all: clippy clippy_boolean clippy_shortint clippy_integer clippy_all_targets clippy_c_api \
|
||||
clippy_js_wasm_api clippy_tasks clippy_core clippy_concrete_csprng
|
||||
clippy_js_wasm_api clippy_tasks clippy_core clippy_concrete_csprng clippy_trivium
|
||||
|
||||
.PHONY: clippy_fast # Run main clippy targets
|
||||
clippy_fast: clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core \
|
||||
@@ -161,8 +218,8 @@ clippy_concrete_csprng
|
||||
gen_key_cache: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
|
||||
--example generates_test_keys \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- \
|
||||
$(MULTI_BIT_ONLY)
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache -p tfhe -- \
|
||||
$(MULTI_BIT_ONLY) $(COVERAGE_ONLY)
|
||||
|
||||
.PHONY: build_core # Build core_crypto without experimental features
|
||||
build_core: install_rs_build_toolchain install_rs_check_toolchain
|
||||
@@ -256,6 +313,14 @@ test_boolean: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
|
||||
|
||||
.PHONY: test_boolean_cov # Run the tests of the boolean module with code coverage
|
||||
test_boolean_cov: install_rs_check_toolchain install_tarpaulin
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) tarpaulin --profile $(CARGO_PROFILE) \
|
||||
--out xml --output-dir coverage/boolean --line --engine llvm --timeout 500 \
|
||||
$(COVERAGE_EXCLUDED_FILES) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache,__coverage \
|
||||
-p tfhe -- boolean::
|
||||
|
||||
.PHONY: test_c_api_rs # Run the rust tests for the C API
|
||||
test_c_api_rs: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
@@ -289,6 +354,14 @@ test_shortint: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- shortint::
|
||||
|
||||
.PHONY: test_shortint_cov # Run the tests of the shortint module with code coverage
|
||||
test_shortint_cov: install_rs_check_toolchain install_tarpaulin
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) tarpaulin --profile $(CARGO_PROFILE) \
|
||||
--out xml --output-dir coverage/shortint --line --engine llvm --timeout 500 \
|
||||
$(COVERAGE_EXCLUDED_FILES) \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,__coverage \
|
||||
-p tfhe -- shortint::
|
||||
|
||||
.PHONY: test_integer_ci # Run the tests for integer ci
|
||||
test_integer_ci: install_rs_build_toolchain install_cargo_nextest
|
||||
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
|
||||
@@ -338,14 +411,12 @@ test_examples: test_sha256_bool test_regex_engine
|
||||
.PHONY: test_trivium # Run tests for trivium
|
||||
test_trivium: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
trivium --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
|
||||
-- --test-threads=1
|
||||
-p tfhe-trivium -- --test-threads=1 trivium::
|
||||
|
||||
.PHONY: test_kreyvium # Run tests for kreyvium
|
||||
test_kreyvium: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
kreyvium --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \
|
||||
-- --test-threads=1
|
||||
-p tfhe-trivium -- --test-threads=1 kreyvium::
|
||||
|
||||
.PHONY: test_concrete_csprng # Run concrete-csprng tests
|
||||
test_concrete_csprng:
|
||||
@@ -428,6 +499,10 @@ no_tfhe_typo:
|
||||
no_dbg_log:
|
||||
@./scripts/no_dbg_calls.sh
|
||||
|
||||
.PHONY: dieharder_csprng # Run the dieharder test suite on our CSPRNG implementation
|
||||
dieharder_csprng: install_dieharder build_concrete_csprng
|
||||
./scripts/dieharder_test.sh
|
||||
|
||||
#
|
||||
# Benchmarks
|
||||
#
|
||||
@@ -454,6 +529,15 @@ bench_shortint: install_rs_check_toolchain
|
||||
--bench shortint-bench \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
|
||||
|
||||
.PHONY: bench_shortint_multi_bit # Run benchmarks for shortint using multi-bit parameters
|
||||
bench_shortint_multi_bit: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_TYPE=MULTI_BIT \
|
||||
__TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) \
|
||||
cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
|
||||
--bench shortint-bench \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe --
|
||||
|
||||
|
||||
.PHONY: bench_boolean # Run benchmarks for boolean
|
||||
bench_boolean: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
|
||||
@@ -548,7 +632,7 @@ pcc: no_tfhe_typo no_dbg_log check_fmt lint_doc clippy_all check_compile_tests
|
||||
fpcc: no_tfhe_typo no_dbg_log check_fmt lint_doc clippy_fast check_compile_tests
|
||||
|
||||
.PHONY: conformance # Automatically fix problems that can be fixed
|
||||
conformance: fmt
|
||||
conformance: fix_newline fmt
|
||||
|
||||
.PHONY: help # Generate list of targets with descriptions
|
||||
help:
|
||||
|
||||
@@ -47,7 +47,7 @@ tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64-un
|
||||
```toml
|
||||
tfhe = { version = "*", features = ["boolean", "shortint", "integer", "aarch64-unix"] }
|
||||
```
|
||||
Note: users with ARM devices must use `TFHE-rs` by compiling using the `nightly` toolchain.
|
||||
Note: users with ARM devices must compile `TFHE-rs` using a stable toolchain with version >= 1.72.
|
||||
|
||||
|
||||
+ For x86_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND)
|
||||
@@ -92,10 +92,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// On the server side:
|
||||
set_server_key(server_keys);
|
||||
|
||||
// Clear equivalent computations: 1344 * 8 = 10752
|
||||
// Clear equivalent computations: 1344 * 5 = 6720
|
||||
let encrypted_res_mul = &encrypted_a * &encrypted_b;
|
||||
|
||||
// Clear equivalent computations: 1344 >> 8 = 42
|
||||
// Clear equivalent computations: 1344 >> 5 = 42
|
||||
encrypted_a = &encrypted_res_mul >> &encrypted_b;
|
||||
|
||||
// Clear equivalent computations: let casted_a = a as u8;
|
||||
|
||||
@@ -17,7 +17,7 @@ path = "../../tfhe"
|
||||
features = [ "boolean", "shortint", "integer", "aarch64-unix" ]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.4", features = [ "html_reports" ]}
|
||||
criterion = { version = "0.5.1", features = [ "html_reports" ]}
|
||||
|
||||
[[bench]]
|
||||
name = "trivium"
|
||||
|
||||
@@ -120,7 +120,7 @@ fn main() {
|
||||
|
||||
# FHE byte Trivium implementation
|
||||
|
||||
The same objects have also been implemented to stream bytes insead of booleans. They can be constructed and used in the same way via the functions `TriviumStreamByte::<u8>::new` and
|
||||
The same objects have also been implemented to stream bytes instead of booleans. They can be constructed and used in the same way via the functions `TriviumStreamByte::<u8>::new` and
|
||||
`TriviumStreamByte::<FheUint8>::new` with the same arguments as before. The `FheUint8` version is significantly slower than the `FheBool` version, because not running
|
||||
with the same cryptographic parameters. Its interest lie in its trans-ciphering capabilities: `TriviumStreamByte<FheUint8>` implements the trait `TransCiphering`,
|
||||
meaning it implements the functions `trans_encrypt_64`. This function takes as input a `FheUint64` and outputs a `FheUint64`, the output being
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! This module implements the Kreyvium stream cipher, using booleans or FheBool
|
||||
//! for the representaion of the inner bits.
|
||||
//! for the representation of the inner bits.
|
||||
|
||||
use crate::static_deque::StaticDeque;
|
||||
|
||||
@@ -35,7 +35,7 @@ pub struct KreyviumStream<T> {
|
||||
}
|
||||
|
||||
impl KreyviumStream<bool> {
|
||||
/// Contructor for `KreyviumStream<bool>`: arguments are the secret key and the input vector.
|
||||
/// Constructor for `KreyviumStream<bool>`: arguments are the secret key and the input vector.
|
||||
/// Outputs a KreyviumStream object already initialized (1152 steps have been run before
|
||||
/// returning)
|
||||
pub fn new(mut key: [bool; 128], mut iv: [bool; 128]) -> KreyviumStream<bool> {
|
||||
@@ -80,9 +80,9 @@ impl KreyviumStream<FheBool> {
|
||||
|
||||
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
|
||||
// and c a few ones.
|
||||
let mut a_register = [false; 93].map(|x| FheBool::encrypt_trivial(x));
|
||||
let mut b_register = [false; 84].map(|x| FheBool::encrypt_trivial(x));
|
||||
let mut c_register = [false; 111].map(|x| FheBool::encrypt_trivial(x));
|
||||
let mut a_register = [false; 93].map(FheBool::encrypt_trivial);
|
||||
let mut b_register = [false; 84].map(FheBool::encrypt_trivial);
|
||||
let mut c_register = [false; 111].map(FheBool::encrypt_trivial);
|
||||
|
||||
for i in 0..93 {
|
||||
a_register[i] = key[128 - 93 + i].clone();
|
||||
@@ -99,7 +99,7 @@ impl KreyviumStream<FheBool> {
|
||||
|
||||
key.reverse();
|
||||
iv.reverse();
|
||||
let iv = iv.map(|x| FheBool::encrypt_trivial(x));
|
||||
let iv = iv.map(FheBool::encrypt_trivial);
|
||||
|
||||
unset_server_key();
|
||||
KreyviumStream::<FheBool>::new_from_registers(
|
||||
@@ -118,7 +118,7 @@ where
|
||||
T: KreyviumBoolInput<T> + std::marker::Send + std::marker::Sync,
|
||||
for<'a> &'a T: KreyviumBoolInput<T>,
|
||||
{
|
||||
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
|
||||
/// Internal generic constructor: arguments are already prepared registers, and an optional FHE
|
||||
/// server key
|
||||
fn new_from_registers(
|
||||
a_register: [T; 93],
|
||||
@@ -149,7 +149,7 @@ where
|
||||
}
|
||||
|
||||
/// Computes one turn of the stream, updating registers and outputting the new bit.
|
||||
pub fn next(&mut self) -> T {
|
||||
pub fn next_bool(&mut self) -> T {
|
||||
match &self.fhe_key {
|
||||
Some(sk) => set_server_key(sk.clone()),
|
||||
None => (),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! This module implements the Kreyvium stream cipher, using u8 or FheUint8
|
||||
//! for the representaion of the inner bits.
|
||||
//! for the representation of the inner bits.
|
||||
|
||||
use crate::static_deque::{StaticByteDeque, StaticByteDequeInput};
|
||||
|
||||
@@ -43,7 +43,7 @@ pub struct KreyviumStreamByte<T> {
|
||||
}
|
||||
|
||||
impl KreyviumStreamByte<u8> {
|
||||
/// Contructor for `KreyviumStreamByte<u8>`: arguments are the secret key and the input vector.
|
||||
/// Constructor for `KreyviumStreamByte<u8>`: arguments are the secret key and the input vector.
|
||||
/// Outputs a KreyviumStream object already initialized (1152 steps have been run before
|
||||
/// returning)
|
||||
pub fn new(key_bytes: [u8; 16], iv_bytes: [u8; 16]) -> KreyviumStreamByte<u8> {
|
||||
@@ -54,18 +54,15 @@ impl KreyviumStreamByte<u8> {
|
||||
let mut c_byte_reg = [0u8; 14];
|
||||
|
||||
// Copy key bits into a register
|
||||
for b in 0..12 {
|
||||
a_byte_reg[b] = key_bytes[b + 4];
|
||||
}
|
||||
a_byte_reg.copy_from_slice(&key_bytes[4..]);
|
||||
|
||||
// Copy iv bits into a register
|
||||
for b in 0..11 {
|
||||
b_byte_reg[b] = iv_bytes[b + 5];
|
||||
}
|
||||
b_byte_reg.copy_from_slice(&iv_bytes[5..]);
|
||||
|
||||
// Copy a lot of ones in the c register
|
||||
c_byte_reg[0] = 252;
|
||||
for b in 1..8 {
|
||||
c_byte_reg[b] = 255;
|
||||
}
|
||||
c_byte_reg[1..8].fill(255);
|
||||
|
||||
// Copy iv bits in the c register
|
||||
c_byte_reg[8] = (iv_bytes[0] << 4) | 31;
|
||||
for b in 9..14 {
|
||||
@@ -100,23 +97,22 @@ impl KreyviumStreamByte<FheUint8> {
|
||||
|
||||
// Initialization of Kreyvium registers: a has the secret key, b the input vector,
|
||||
// and c a few ones.
|
||||
let mut a_byte_reg = [0u8; 12].map(|x| FheUint8::encrypt_trivial(x));
|
||||
let mut b_byte_reg = [0u8; 11].map(|x| FheUint8::encrypt_trivial(x));
|
||||
let mut c_byte_reg = [0u8; 14].map(|x| FheUint8::encrypt_trivial(x));
|
||||
let mut a_byte_reg = [0u8; 12].map(FheUint8::encrypt_trivial);
|
||||
let mut b_byte_reg = [0u8; 11].map(FheUint8::encrypt_trivial);
|
||||
let mut c_byte_reg = [0u8; 14].map(FheUint8::encrypt_trivial);
|
||||
|
||||
// Copy key bits into a register
|
||||
for b in 0..12 {
|
||||
a_byte_reg[b] = key_bytes[b + 4].clone();
|
||||
}
|
||||
a_byte_reg.clone_from_slice(&key_bytes[4..]);
|
||||
|
||||
// Copy iv bits into a register
|
||||
for b in 0..11 {
|
||||
b_byte_reg[b] = FheUint8::encrypt_trivial(iv_bytes[b + 5]);
|
||||
}
|
||||
// Copy a lot of ones in the c register
|
||||
c_byte_reg[0] = FheUint8::encrypt_trivial(252u8);
|
||||
for b in 1..8 {
|
||||
c_byte_reg[b] = FheUint8::encrypt_trivial(255u8);
|
||||
}
|
||||
|
||||
c_byte_reg[1..8].fill_with(|| FheUint8::encrypt_trivial(255u8));
|
||||
|
||||
// Copy iv bits in the c register
|
||||
c_byte_reg[8] = FheUint8::encrypt_trivial((&iv_bytes[0] << 4u8) | 31u8);
|
||||
for b in 9..14 {
|
||||
@@ -150,7 +146,7 @@ where
|
||||
T: KreyviumByteInput<T> + Send,
|
||||
for<'a> &'a T: KreyviumByteInput<T>,
|
||||
{
|
||||
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
|
||||
/// Internal generic constructor: arguments are already prepared registers, and an optional FHE
|
||||
/// server key
|
||||
fn new_from_registers(
|
||||
a_register: [T; 12],
|
||||
@@ -292,6 +288,6 @@ where
|
||||
|
||||
impl KreyviumStreamByte<FheUint8> {
|
||||
pub fn get_server_key(&self) -> &ServerKey {
|
||||
&self.fhe_key.as_ref().unwrap()
|
||||
self.fhe_key.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct KreyviumStreamShortint {
|
||||
}
|
||||
|
||||
impl KreyviumStreamShortint {
|
||||
/// Contructor for KreyviumStreamShortint: arguments are the secret key and the input vector,
|
||||
/// Constructor for KreyviumStreamShortint: arguments are the secret key and the input vector,
|
||||
/// and a ServerKey reference. Outputs a KreyviumStream object already initialized (1152
|
||||
/// steps have been run before returning)
|
||||
pub fn new(
|
||||
@@ -75,7 +75,7 @@ impl KreyviumStreamShortint {
|
||||
}
|
||||
|
||||
/// Computes one turn of the stream, updating registers and outputting the new bit.
|
||||
pub fn next(&mut self) -> Ciphertext {
|
||||
pub fn next_ct(&mut self) -> Ciphertext {
|
||||
let [o, a, b, c] = self.get_output_and_values(0);
|
||||
|
||||
self.a.push(a);
|
||||
@@ -149,7 +149,7 @@ impl KreyviumStreamShortint {
|
||||
.unchecked_add_assign(&mut new_c, c5);
|
||||
self.internal_server_key
|
||||
.unchecked_add_assign(&mut new_c, &temp_b);
|
||||
self.internal_server_key.clear_carry_assign(&mut new_c);
|
||||
self.internal_server_key.message_extract_assign(&mut new_c);
|
||||
new_c
|
||||
},
|
||||
|| {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
mod kreyvium;
|
||||
pub use kreyvium::KreyviumStream;
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ fn get_hexadecimal_string_from_lsb_first_stream(a: Vec<bool>) -> String {
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
return hexadecimal;
|
||||
hexadecimal
|
||||
}
|
||||
|
||||
fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
|
||||
@@ -65,7 +65,7 @@ fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
|
||||
for test in a {
|
||||
hexadecimal.push_str(&format!("{:02X?}", test));
|
||||
}
|
||||
return hexadecimal;
|
||||
hexadecimal
|
||||
}
|
||||
|
||||
fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
|
||||
@@ -73,7 +73,7 @@ fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
|
||||
for test in a {
|
||||
hexadecimal.push_str(&format!("{:016X?}", test));
|
||||
}
|
||||
return hexadecimal;
|
||||
hexadecimal
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -86,7 +86,7 @@ fn kreyvium_test_1() {
|
||||
|
||||
let mut vec = Vec::<bool>::with_capacity(64);
|
||||
while vec.len() < 64 {
|
||||
vec.push(kreyvium.next());
|
||||
vec.push(kreyvium.next_bool());
|
||||
}
|
||||
|
||||
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
|
||||
@@ -105,7 +105,7 @@ fn kreyvium_test_2() {
|
||||
|
||||
let mut vec = Vec::<bool>::with_capacity(64);
|
||||
while vec.len() < 64 {
|
||||
vec.push(kreyvium.next());
|
||||
vec.push(kreyvium.next_bool());
|
||||
}
|
||||
|
||||
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
|
||||
@@ -124,7 +124,7 @@ fn kreyvium_test_3() {
|
||||
|
||||
let mut vec = Vec::<bool>::with_capacity(64);
|
||||
while vec.len() < 64 {
|
||||
vec.push(kreyvium.next());
|
||||
vec.push(kreyvium.next_bool());
|
||||
}
|
||||
|
||||
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
|
||||
@@ -161,7 +161,7 @@ fn kreyvium_test_4() {
|
||||
|
||||
let mut vec = Vec::<bool>::with_capacity(64);
|
||||
while vec.len() < 64 {
|
||||
vec.push(kreyvium.next());
|
||||
vec.push(kreyvium.next_bool());
|
||||
}
|
||||
|
||||
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
mod static_deque;
|
||||
pub use static_deque::StaticDeque;
|
||||
mod static_byte_deque;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! This module implements the StaticByteDeque struct: a deque of bytes. The idea
|
||||
//! is that this is a wrapper around StaticDeque, but StaticByteDeque has an additional
|
||||
//! functionnality: it can construct the "intermediate" bytes, made of parts of other bytes.
|
||||
//! functionality: it can construct the "intermediate" bytes, made of parts of other bytes.
|
||||
//! This is pretending to store bits, and allows accessing bits in chunks of 8 consecutive.
|
||||
|
||||
use crate::static_deque::StaticDeque;
|
||||
@@ -77,7 +77,7 @@ where
|
||||
}
|
||||
|
||||
let byte_next: &T = &self.deque[i / 8 + 1];
|
||||
return (byte << bit_idx) | (byte_next >> (8 - bit_idx as u8));
|
||||
(byte << bit_idx) | (byte_next >> (8 - bit_idx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ mod tests {
|
||||
assert!(deque.bit(7) == 0);
|
||||
|
||||
// second youngest: 128
|
||||
assert!(deque.bit(8 + 0) == 0);
|
||||
assert!(deque.bit(8) == 0);
|
||||
assert!(deque.bit(8 + 1) == 0);
|
||||
assert!(deque.bit(8 + 2) == 0);
|
||||
assert!(deque.bit(8 + 3) == 0);
|
||||
@@ -111,7 +111,7 @@ mod tests {
|
||||
assert!(deque.bit(8 + 7) > 0);
|
||||
|
||||
// oldest: 64
|
||||
assert!(deque.bit(16 + 0) == 0);
|
||||
assert!(deque.bit(16) == 0);
|
||||
assert!(deque.bit(16 + 1) == 0);
|
||||
assert!(deque.bit(16 + 2) == 0);
|
||||
assert!(deque.bit(16 + 3) == 0);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use core::ops::{Index, IndexMut};
|
||||
|
||||
/// StaticDeque: a struct implementing a deque whose size is known at compile time.
|
||||
/// It has 2 members: the static array conatining the data (never empty), and a cursor
|
||||
/// It has 2 members: the static array containing the data (never empty), and a cursor
|
||||
/// equal to the index of the oldest element (and the next one to be overwritten).
|
||||
#[derive(Clone)]
|
||||
pub struct StaticDeque<const N: usize, T> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod trivium;
|
||||
pub use trivium::TriviumStream;
|
||||
mod trivium_bool;
|
||||
pub use trivium_bool::TriviumStream;
|
||||
|
||||
mod trivium_byte;
|
||||
pub use trivium_byte::TriviumStreamByte;
|
||||
|
||||
@@ -56,7 +56,7 @@ fn get_hexadecimal_string_from_lsb_first_stream(a: Vec<bool>) -> String {
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
return hexadecimal;
|
||||
hexadecimal
|
||||
}
|
||||
|
||||
fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
|
||||
@@ -65,7 +65,7 @@ fn get_hexagonal_string_from_bytes(a: Vec<u8>) -> String {
|
||||
for test in a {
|
||||
hexadecimal.push_str(&format!("{:02X?}", test));
|
||||
}
|
||||
return hexadecimal;
|
||||
hexadecimal
|
||||
}
|
||||
|
||||
fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
|
||||
@@ -73,7 +73,7 @@ fn get_hexagonal_string_from_u64(a: Vec<u64>) -> String {
|
||||
for test in a {
|
||||
hexadecimal.push_str(&format!("{:016X?}", test));
|
||||
}
|
||||
return hexadecimal;
|
||||
hexadecimal
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -89,7 +89,7 @@ fn trivium_test_1() {
|
||||
|
||||
let mut vec = Vec::<bool>::with_capacity(512 * 8);
|
||||
while vec.len() < 512 * 8 {
|
||||
vec.push(trivium.next());
|
||||
vec.push(trivium.next_bool());
|
||||
}
|
||||
|
||||
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
|
||||
@@ -114,7 +114,7 @@ fn trivium_test_2() {
|
||||
|
||||
let mut vec = Vec::<bool>::with_capacity(512 * 8);
|
||||
while vec.len() < 512 * 8 {
|
||||
vec.push(trivium.next());
|
||||
vec.push(trivium.next_bool());
|
||||
}
|
||||
|
||||
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
|
||||
@@ -139,7 +139,7 @@ fn trivium_test_3() {
|
||||
|
||||
let mut vec = Vec::<bool>::with_capacity(512 * 8);
|
||||
while vec.len() < 512 * 8 {
|
||||
vec.push(trivium.next());
|
||||
vec.push(trivium.next_bool());
|
||||
}
|
||||
|
||||
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
|
||||
@@ -182,7 +182,7 @@ fn trivium_test_4() {
|
||||
|
||||
let mut vec = Vec::<bool>::with_capacity(131072 * 8);
|
||||
while vec.len() < 131072 * 8 {
|
||||
vec.push(trivium.next());
|
||||
vec.push(trivium.next_bool());
|
||||
}
|
||||
|
||||
let hexadecimal = get_hexadecimal_string_from_lsb_first_stream(vec);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! This module implements the Trivium stream cipher, using booleans or FheBool
|
||||
//! for the representaion of the inner bits.
|
||||
//! for the representation of the inner bits.
|
||||
|
||||
use crate::static_deque::StaticDeque;
|
||||
|
||||
@@ -33,7 +33,7 @@ pub struct TriviumStream<T> {
|
||||
}
|
||||
|
||||
impl TriviumStream<bool> {
|
||||
/// Contructor for `TriviumStream<bool>`: arguments are the secret key and the input vector.
|
||||
/// Constructor for `TriviumStream<bool>`: arguments are the secret key and the input vector.
|
||||
/// Outputs a TriviumStream object already initialized (1152 steps have been run before
|
||||
/// returning)
|
||||
pub fn new(key: [bool; 80], iv: [bool; 80]) -> TriviumStream<bool> {
|
||||
@@ -66,9 +66,9 @@ impl TriviumStream<FheBool> {
|
||||
|
||||
// Initialization of Trivium registers: a has the secret key, b the input vector,
|
||||
// and c a few ones.
|
||||
let mut a_register = [false; 93].map(|x| FheBool::encrypt_trivial(x));
|
||||
let mut b_register = [false; 84].map(|x| FheBool::encrypt_trivial(x));
|
||||
let mut c_register = [false; 111].map(|x| FheBool::encrypt_trivial(x));
|
||||
let mut a_register = [false; 93].map(FheBool::encrypt_trivial);
|
||||
let mut b_register = [false; 84].map(FheBool::encrypt_trivial);
|
||||
let mut c_register = [false; 111].map(FheBool::encrypt_trivial);
|
||||
|
||||
for i in 0..80 {
|
||||
a_register[93 - 80 + i] = key[i].clone();
|
||||
@@ -94,7 +94,7 @@ where
|
||||
T: TriviumBoolInput<T> + std::marker::Send + std::marker::Sync,
|
||||
for<'a> &'a T: TriviumBoolInput<T>,
|
||||
{
|
||||
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
|
||||
/// Internal generic constructor: arguments are already prepared registers, and an optional FHE
|
||||
/// server key
|
||||
fn new_from_registers(
|
||||
a_register: [T; 93],
|
||||
@@ -121,7 +121,7 @@ where
|
||||
}
|
||||
|
||||
/// Computes one turn of the stream, updating registers and outputting the new bit.
|
||||
pub fn next(&mut self) -> T {
|
||||
pub fn next_bool(&mut self) -> T {
|
||||
match &self.fhe_key {
|
||||
Some(sk) => set_server_key(sk.clone()),
|
||||
None => (),
|
||||
@@ -1,5 +1,5 @@
|
||||
//! This module implements the Trivium stream cipher, using u8 or FheUint8
|
||||
//! for the representaion of the inner bits.
|
||||
//! for the representation of the inner bits.
|
||||
|
||||
use crate::static_deque::{StaticByteDeque, StaticByteDequeInput};
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct TriviumStreamByte<T> {
|
||||
}
|
||||
|
||||
impl TriviumStreamByte<u8> {
|
||||
/// Contructor for `TriviumStreamByte<u8>`: arguments are the secret key and the input vector.
|
||||
/// Constructor for `TriviumStreamByte<u8>`: arguments are the secret key and the input vector.
|
||||
/// Outputs a TriviumStream object already initialized (1152 steps have been run before
|
||||
/// returning)
|
||||
pub fn new(key: [u8; 10], iv: [u8; 10]) -> TriviumStreamByte<u8> {
|
||||
@@ -81,9 +81,9 @@ impl TriviumStreamByte<FheUint8> {
|
||||
|
||||
// Initialization of Trivium registers: a has the secret key, b the input vector,
|
||||
// and c a few ones.
|
||||
let mut a_byte_reg = [0u8; 12].map(|x| FheUint8::encrypt_trivial(x));
|
||||
let mut b_byte_reg = [0u8; 11].map(|x| FheUint8::encrypt_trivial(x));
|
||||
let mut c_byte_reg = [0u8; 14].map(|x| FheUint8::encrypt_trivial(x));
|
||||
let mut a_byte_reg = [0u8; 12].map(FheUint8::encrypt_trivial);
|
||||
let mut b_byte_reg = [0u8; 11].map(FheUint8::encrypt_trivial);
|
||||
let mut c_byte_reg = [0u8; 14].map(FheUint8::encrypt_trivial);
|
||||
|
||||
for i in 0..10 {
|
||||
a_byte_reg[12 - 10 + i] = key[i].clone();
|
||||
@@ -111,7 +111,7 @@ where
|
||||
T: TriviumByteInput<T> + Send,
|
||||
for<'a> &'a T: TriviumByteInput<T>,
|
||||
{
|
||||
/// Internal generic contructor: arguments are already prepared registers, and an optional FHE
|
||||
/// Internal generic constructor: arguments are already prepared registers, and an optional FHE
|
||||
/// server key
|
||||
fn new_from_registers(
|
||||
a_register: [T; 12],
|
||||
@@ -236,6 +236,6 @@ where
|
||||
|
||||
impl TriviumStreamByte<FheUint8> {
|
||||
pub fn get_server_key(&self) -> &ServerKey {
|
||||
&self.fhe_key.as_ref().unwrap()
|
||||
self.fhe_key.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ pub struct TriviumStreamShortint {
|
||||
}
|
||||
|
||||
impl TriviumStreamShortint {
|
||||
/// Contructor for TriviumStreamShortint: arguments are the secret key and the input vector, and
|
||||
/// a ServerKey reference. Outputs a TriviumStream object already initialized (1152 steps
|
||||
/// have been run before returning)
|
||||
/// Constructor for TriviumStreamShortint: arguments are the secret key and the input vector,
|
||||
/// and a ServerKey reference. Outputs a TriviumStream object already initialized (1152
|
||||
/// steps have been run before returning)
|
||||
pub fn new(
|
||||
key: [Ciphertext; 80],
|
||||
iv: [u64; 80],
|
||||
@@ -63,7 +63,7 @@ impl TriviumStreamShortint {
|
||||
}
|
||||
|
||||
/// Computes one turn of the stream, updating registers and outputting the new bit.
|
||||
pub fn next(&mut self) -> Ciphertext {
|
||||
pub fn next_ct(&mut self) -> Ciphertext {
|
||||
let [o, a, b, c] = self.get_output_and_values(0);
|
||||
|
||||
self.a.push(a);
|
||||
@@ -113,7 +113,7 @@ impl TriviumStreamShortint {
|
||||
.unchecked_add_assign(&mut new_a, a5);
|
||||
self.internal_server_key
|
||||
.unchecked_add_assign(&mut new_a, &temp_c);
|
||||
self.internal_server_key.clear_carry_assign(&mut new_a);
|
||||
self.internal_server_key.message_extract_assign(&mut new_a);
|
||||
new_a
|
||||
},
|
||||
|| {
|
||||
@@ -122,7 +122,7 @@ impl TriviumStreamShortint {
|
||||
.unchecked_add_assign(&mut new_b, b5);
|
||||
self.internal_server_key
|
||||
.unchecked_add_assign(&mut new_b, &temp_a);
|
||||
self.internal_server_key.clear_carry_assign(&mut new_b);
|
||||
self.internal_server_key.message_extract_assign(&mut new_b);
|
||||
new_b
|
||||
},
|
||||
)
|
||||
@@ -135,7 +135,7 @@ impl TriviumStreamShortint {
|
||||
.unchecked_add_assign(&mut new_c, c5);
|
||||
self.internal_server_key
|
||||
.unchecked_add_assign(&mut new_c, &temp_b);
|
||||
self.internal_server_key.clear_carry_assign(&mut new_c);
|
||||
self.internal_server_key.message_extract_assign(&mut new_c);
|
||||
new_c
|
||||
},
|
||||
|| {
|
||||
|
||||
@@ -108,12 +108,25 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
|
||||
)
|
||||
)
|
||||
|
||||
# This is a special case where PBS are blasted as vector LWE ciphertext with
|
||||
# variable length to saturate the machine. To get the actual throughput we need to
|
||||
# multiply by the length of the vector.
|
||||
if "PBS_throughput" in test_name and "chunk" in test_name:
|
||||
try:
|
||||
multiplier = int(test_name.split("chunk")[0].split("_")[-1])
|
||||
except ValueError:
|
||||
parsing_failures.append((full_name,
|
||||
"failed to extract throughput multiplier"))
|
||||
continue
|
||||
else:
|
||||
multiplier = 1
|
||||
|
||||
if stat_name == "mean" and compute_throughput:
|
||||
test_suffix = "ops-per-sec"
|
||||
test_name_parts.append(test_suffix)
|
||||
result_values.append(
|
||||
_create_point(
|
||||
compute_ops_per_second(value),
|
||||
multiplier * compute_ops_per_second(value),
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"throughput",
|
||||
@@ -129,7 +142,7 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
|
||||
test_name_parts.append(test_suffix)
|
||||
result_values.append(
|
||||
_create_point(
|
||||
compute_ops_per_dollar(value, hardware_hourly_cost),
|
||||
multiplier * compute_ops_per_dollar(value, hardware_hourly_cost),
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"throughput",
|
||||
|
||||
@@ -36,23 +36,24 @@ def check_security(filename):
|
||||
try:
|
||||
# The lattice estimator is not able to manage such large dimension.
|
||||
# If we have the security for smaller `n` then we have security for larger ones.
|
||||
if param.n == 32768:
|
||||
if param.n > 16384:
|
||||
param = param.updated(n = 16384)
|
||||
|
||||
usvp_level = LWE.primal_usvp(param, red_cost_model = model)
|
||||
dual_level = LWE.dual_hybrid(param, red_cost_model = model)
|
||||
|
||||
estimator_level = log(min(usvp_level["rop"], dual_level["rop"]),2 )
|
||||
security_level = f"security level = {estimator_level} bits"
|
||||
if estimator_level < 127:
|
||||
print("FAIL")
|
||||
reason = f"attained security level = {estimator_level} bits target is 128 bits"
|
||||
print("FAIL\t({security_level})")
|
||||
reason = f"attained {security_level} target is 128 bits"
|
||||
to_update.append((param, reason))
|
||||
continue
|
||||
except Exception as err:
|
||||
print("FAIL")
|
||||
to_update.append((param, f"{repr(err)}"))
|
||||
else:
|
||||
print("OK")
|
||||
print(f"OK\t({security_level})")
|
||||
|
||||
return to_update
|
||||
|
||||
@@ -72,4 +73,4 @@ if __name__ == "__main__":
|
||||
print(f"[{param.tag}] reason: {reason} (param)")
|
||||
sys.exit(int(1)) # Explicit conversion is needed to make this call work
|
||||
else:
|
||||
print("All parameters passed the security check")
|
||||
print("All parameters passed the security check")
|
||||
|
||||
16
ci/slab.toml
16
ci/slab.toml
@@ -1,16 +1,16 @@
|
||||
[profile.cpu-big]
|
||||
region = "eu-west-3"
|
||||
image_id = "ami-0ab73f5bd11708a85"
|
||||
image_id = "ami-051942e4055555752"
|
||||
instance_type = "m6i.32xlarge"
|
||||
|
||||
[profile.cpu-small]
|
||||
region = "eu-west-3"
|
||||
image_id = "ami-0ab73f5bd11708a85"
|
||||
image_id = "ami-051942e4055555752"
|
||||
instance_type = "m6i.4xlarge"
|
||||
|
||||
[profile.bench]
|
||||
region = "eu-west-3"
|
||||
image_id = "ami-0ab73f5bd11708a85"
|
||||
image_id = "ami-051942e4055555752"
|
||||
instance_type = "m6i.metal"
|
||||
|
||||
[command.cpu_test]
|
||||
@@ -77,3 +77,13 @@ check_run_name = "PBS CPU AWS Benchmarks"
|
||||
workflow = "wasm_client_benchmark.yml"
|
||||
profile = "cpu-small"
|
||||
check_run_name = "WASM Client AWS Benchmarks"
|
||||
|
||||
[command.csprng_randomness_testing]
|
||||
workflow = "csprng_randomness_testing.yml"
|
||||
profile = "cpu-small"
|
||||
check_run_name = "CSPRNG randomness testing"
|
||||
|
||||
[command.code_coverage]
|
||||
workflow = "code_coverage.yml"
|
||||
profile = "cpu-small"
|
||||
check_run_name = "Code coverage"
|
||||
|
||||
4
codecov.yml
Normal file
4
codecov.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
coverage:
|
||||
status:
|
||||
# Disable patch checks in GitHub until all tfhe-rs layers have coverage implemented.
|
||||
patch: false
|
||||
@@ -19,8 +19,8 @@ libc = "0.2.133"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.3"
|
||||
criterion = "0.3"
|
||||
clap = "=4.2.7"
|
||||
criterion = "0.5.1"
|
||||
clap = "=4.4.4"
|
||||
|
||||
[features]
|
||||
parallel = ["rayon"]
|
||||
|
||||
@@ -29,37 +29,39 @@ use concrete_csprng::seeders::UnixSeeder as ActivatedSeeder;
|
||||
use concrete_csprng::seeders::Seeder;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::{stdout, Stdout};
|
||||
use std::io::{stdout, StdoutLock};
|
||||
|
||||
fn write_bytes(buffer: &mut [u8], generator: &mut ActivatedRandomGenerator, stdout: &mut Stdout) {
|
||||
fn write_bytes(
|
||||
buffer: &mut [u8],
|
||||
generator: &mut ActivatedRandomGenerator,
|
||||
stdout: &mut StdoutLock<'_>,
|
||||
) -> std::io::Result<()> {
|
||||
buffer.iter_mut().zip(generator).for_each(|(b, g)| *b = g);
|
||||
stdout.write_all(buffer).unwrap();
|
||||
stdout.write_all(buffer)
|
||||
}
|
||||
|
||||
fn infinite_bytes_generation(
|
||||
buffer: &mut [u8],
|
||||
generator: &mut ActivatedRandomGenerator,
|
||||
stdout: &mut Stdout,
|
||||
stdout: &mut StdoutLock<'_>,
|
||||
) {
|
||||
loop {
|
||||
write_bytes(buffer, generator, stdout);
|
||||
}
|
||||
while write_bytes(buffer, generator, stdout).is_ok() {}
|
||||
}
|
||||
|
||||
fn bytes_generation(
|
||||
bytes_total: usize,
|
||||
buffer: &mut [u8],
|
||||
generator: &mut ActivatedRandomGenerator,
|
||||
stdout: &mut Stdout,
|
||||
stdout: &mut StdoutLock<'_>,
|
||||
) {
|
||||
let quotient = bytes_total / buffer.len();
|
||||
let remaining = bytes_total % buffer.len();
|
||||
|
||||
for _ in 0..quotient {
|
||||
write_bytes(buffer, generator, stdout);
|
||||
write_bytes(buffer, generator, stdout).unwrap();
|
||||
}
|
||||
|
||||
write_bytes(&mut buffer[0..remaining], generator, stdout)
|
||||
write_bytes(&mut buffer[0..remaining], generator, stdout).unwrap()
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
@@ -90,10 +92,16 @@ pub fn main() {
|
||||
let new_seeder = || ActivatedSeeder;
|
||||
|
||||
let mut seeder = new_seeder();
|
||||
let mut generator = ActivatedRandomGenerator::new(seeder.seed());
|
||||
let mut stdout = stdout();
|
||||
let seed = seeder.seed();
|
||||
// Don't print on std out
|
||||
eprintln!("seed={seed:?}");
|
||||
let mut generator = ActivatedRandomGenerator::new(seed);
|
||||
let stdout = stdout();
|
||||
let mut buffer = [0u8; 16];
|
||||
|
||||
// lock stdout as there is a single thread running
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
match matches.get_one::<usize>("bytes_total") {
|
||||
Some(&total) => {
|
||||
bytes_generation(total, &mut buffer, &mut generator, &mut stdout);
|
||||
|
||||
@@ -35,17 +35,23 @@ impl AesBlockCipher for ArmAesBlockCipher {
|
||||
}
|
||||
|
||||
fn generate_batch(&mut self, AesIndex(aes_ctr): AesIndex) -> [u8; BYTES_PER_BATCH] {
|
||||
let mut output = [0u8; BYTES_PER_BATCH];
|
||||
// We want 128 bytes of output, the ctr gives 128 bit message (16 bytes)
|
||||
for (i, out) in output.chunks_exact_mut(16).enumerate() {
|
||||
let encrypted = unsafe {
|
||||
#[target_feature(enable = "aes,neon")]
|
||||
unsafe fn implementation(
|
||||
this: &ArmAesBlockCipher,
|
||||
AesIndex(aes_ctr): AesIndex,
|
||||
) -> [u8; BYTES_PER_BATCH] {
|
||||
let mut output = [0u8; BYTES_PER_BATCH];
|
||||
// We want 128 bytes of output, the ctr gives 128 bit message (16 bytes)
|
||||
for (i, out) in output.chunks_exact_mut(16).enumerate() {
|
||||
// Safe because we prevent the user from creating the Generator
|
||||
// on non-supported hardware
|
||||
encrypt(aes_ctr + (i as u128), &self.round_keys)
|
||||
};
|
||||
out.copy_from_slice(&encrypted.to_ne_bytes());
|
||||
let encrypted = encrypt(aes_ctr + (i as u128), &this.round_keys);
|
||||
out.copy_from_slice(&encrypted.to_ne_bytes());
|
||||
}
|
||||
output
|
||||
}
|
||||
output
|
||||
// SAFETY: we checked for aes and neon availability in `Self::new`
|
||||
unsafe { implementation(self, AesIndex(aes_ctr)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +61,7 @@ impl AesBlockCipher for ArmAesBlockCipher {
|
||||
///
|
||||
/// You must make sure the CPU's arch is`aarch64` and has
|
||||
/// `neon` and `aes` features.
|
||||
#[inline(always)]
|
||||
unsafe fn sub_word(word: u32) -> u32 {
|
||||
let data = vreinterpretq_u8_u32(vdupq_n_u32(word));
|
||||
let zero_key = vdupq_n_u8(0u8);
|
||||
@@ -68,14 +75,17 @@ unsafe fn sub_word(word: u32) -> u32 {
|
||||
vgetq_lane_u32::<0>(vreinterpretq_u32_u8(temp))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn uint8x16_t_to_u128(input: uint8x16_t) -> u128 {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn u128_to_uint8x16_t(input: u128) -> uint8x16_t {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
|
||||
#[target_feature(enable = "aes,neon")]
|
||||
unsafe fn generate_round_keys(key: AesKey) -> [uint8x16_t; NUM_ROUND_KEYS] {
|
||||
let mut round_keys: [uint8x16_t; NUM_ROUND_KEYS] = std::mem::zeroed();
|
||||
round_keys[0] = u128_to_uint8x16_t(key.0);
|
||||
@@ -109,6 +119,7 @@ unsafe fn generate_round_keys(key: AesKey) -> [uint8x16_t; NUM_ROUND_KEYS] {
|
||||
///
|
||||
/// You must make sure the CPU's arch is`aarch64` and has
|
||||
/// `neon` and `aes` features.
|
||||
#[inline(always)]
|
||||
unsafe fn encrypt(message: u128, keys: &[uint8x16_t; NUM_ROUND_KEYS]) -> u128 {
|
||||
// Notes:
|
||||
// According the [ARM Manual](https://developer.arm.com/documentation/ddi0487/gb/):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::generators::aes_ctr::{AesBlockCipher, AesIndex, AesKey, BYTES_PER_BATCH};
|
||||
use std::arch::x86_64::{
|
||||
__m128i, _mm_aesenc_si128, _mm_aesenclast_si128, _mm_aeskeygenassist_si128, _mm_load_si128,
|
||||
_mm_shuffle_epi32, _mm_slli_si128, _mm_store_si128, _mm_xor_si128,
|
||||
__m128i, _mm_aesenc_si128, _mm_aesenclast_si128, _mm_aeskeygenassist_si128, _mm_shuffle_epi32,
|
||||
_mm_slli_si128, _mm_store_si128, _mm_xor_si128,
|
||||
};
|
||||
use std::mem::transmute;
|
||||
|
||||
@@ -25,26 +25,36 @@ impl AesBlockCipher for AesniBlockCipher {
|
||||
)
|
||||
}
|
||||
|
||||
let round_keys = generate_round_keys(key);
|
||||
// SAFETY: we checked for aes and sse2 availability
|
||||
let round_keys = unsafe { generate_round_keys(key) };
|
||||
AesniBlockCipher { round_keys }
|
||||
}
|
||||
|
||||
fn generate_batch(&mut self, AesIndex(aes_ctr): AesIndex) -> [u8; BYTES_PER_BATCH] {
|
||||
si128arr_to_u8arr(aes_encrypt_many(
|
||||
&u128_to_si128(aes_ctr),
|
||||
&u128_to_si128(aes_ctr + 1),
|
||||
&u128_to_si128(aes_ctr + 2),
|
||||
&u128_to_si128(aes_ctr + 3),
|
||||
&u128_to_si128(aes_ctr + 4),
|
||||
&u128_to_si128(aes_ctr + 5),
|
||||
&u128_to_si128(aes_ctr + 6),
|
||||
&u128_to_si128(aes_ctr + 7),
|
||||
&self.round_keys,
|
||||
))
|
||||
#[target_feature(enable = "sse2,aes")]
|
||||
unsafe fn implementation(
|
||||
this: &AesniBlockCipher,
|
||||
AesIndex(aes_ctr): AesIndex,
|
||||
) -> [u8; BYTES_PER_BATCH] {
|
||||
si128arr_to_u8arr(aes_encrypt_many(
|
||||
u128_to_si128(aes_ctr),
|
||||
u128_to_si128(aes_ctr + 1),
|
||||
u128_to_si128(aes_ctr + 2),
|
||||
u128_to_si128(aes_ctr + 3),
|
||||
u128_to_si128(aes_ctr + 4),
|
||||
u128_to_si128(aes_ctr + 5),
|
||||
u128_to_si128(aes_ctr + 6),
|
||||
u128_to_si128(aes_ctr + 7),
|
||||
&this.round_keys,
|
||||
))
|
||||
}
|
||||
// SAFETY: we checked for aes and sse2 availability in `Self::new`
|
||||
unsafe { implementation(self, AesIndex(aes_ctr)) }
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_round_keys(key: AesKey) -> [__m128i; 11] {
|
||||
#[target_feature(enable = "sse2,aes")]
|
||||
unsafe fn generate_round_keys(key: AesKey) -> [__m128i; 11] {
|
||||
let key = u128_to_si128(key.0);
|
||||
let mut keys: [__m128i; 11] = [u128_to_si128(0); 11];
|
||||
aes_128_key_expansion(key, &mut keys);
|
||||
@@ -54,27 +64,19 @@ fn generate_round_keys(key: AesKey) -> [__m128i; 11] {
|
||||
// Uses aes to encrypt many values at once. This allows a substantial speedup (around 30%)
|
||||
// compared to the naive approach.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[inline(always)]
|
||||
fn aes_encrypt_many(
|
||||
message_1: &__m128i,
|
||||
message_2: &__m128i,
|
||||
message_3: &__m128i,
|
||||
message_4: &__m128i,
|
||||
message_5: &__m128i,
|
||||
message_6: &__m128i,
|
||||
message_7: &__m128i,
|
||||
message_8: &__m128i,
|
||||
message_1: __m128i,
|
||||
message_2: __m128i,
|
||||
message_3: __m128i,
|
||||
message_4: __m128i,
|
||||
message_5: __m128i,
|
||||
message_6: __m128i,
|
||||
message_7: __m128i,
|
||||
message_8: __m128i,
|
||||
keys: &[__m128i; 11],
|
||||
) -> [__m128i; 8] {
|
||||
unsafe {
|
||||
let message_1 = _mm_load_si128(message_1 as *const __m128i);
|
||||
let message_2 = _mm_load_si128(message_2 as *const __m128i);
|
||||
let message_3 = _mm_load_si128(message_3 as *const __m128i);
|
||||
let message_4 = _mm_load_si128(message_4 as *const __m128i);
|
||||
let message_5 = _mm_load_si128(message_5 as *const __m128i);
|
||||
let message_6 = _mm_load_si128(message_6 as *const __m128i);
|
||||
let message_7 = _mm_load_si128(message_7 as *const __m128i);
|
||||
let message_8 = _mm_load_si128(message_8 as *const __m128i);
|
||||
|
||||
let mut tmp_1 = _mm_xor_si128(message_1, keys[0]);
|
||||
let mut tmp_2 = _mm_xor_si128(message_2, keys[0]);
|
||||
let mut tmp_3 = _mm_xor_si128(message_3, keys[0]);
|
||||
@@ -125,6 +127,7 @@ fn aes_128_assist(temp1: __m128i, temp2: __m128i) -> __m128i {
|
||||
temp1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn aes_128_key_expansion(key: __m128i, keys: &mut [__m128i; 11]) {
|
||||
let (mut temp1, mut temp2): (__m128i, __m128i);
|
||||
temp1 = key;
|
||||
@@ -163,6 +166,7 @@ fn aes_128_key_expansion(key: __m128i, keys: &mut [__m128i; 11]) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn u128_to_si128(input: u128) -> __m128i {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
@@ -172,6 +176,7 @@ fn si128_to_u128(input: __m128i) -> u128 {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn si128arr_to_u8arr(input: [__m128i; 8]) -> [u8; BYTES_PER_BATCH] {
|
||||
unsafe { transmute(input) }
|
||||
}
|
||||
@@ -217,7 +222,7 @@ mod test {
|
||||
let mut keys: [__m128i; 11] = [u128_to_si128(0); 11];
|
||||
aes_128_key_expansion(key, &mut keys);
|
||||
let ciphertexts = aes_encrypt_many(
|
||||
&message, &message, &message, &message, &message, &message, &message, &message, &keys,
|
||||
message, message, message, message, message, message, message, message, &keys,
|
||||
);
|
||||
for ct in &ciphertexts {
|
||||
assert_eq!(CIPHERTEXT, si128_to_u128(*ct));
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct BytesPerChild(pub usize);
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct ByteCount(pub u128);
|
||||
|
||||
/// An error occuring during a generator fork.
|
||||
/// An error occurring during a generator fork.
|
||||
#[derive(Debug)]
|
||||
pub enum ForkError {
|
||||
ForkTooLarge,
|
||||
@@ -145,7 +145,7 @@ pub mod generator_generic_test {
|
||||
|
||||
/// Checks that the PRNG roughly generates uniform numbers.
|
||||
///
|
||||
/// To do that, we perform an histogram of the occurences of each byte value, over a fixed
|
||||
/// To do that, we perform an histogram of the occurrences of each byte value, over a fixed
|
||||
/// number of samples and check that the empirical probabilities of the bins are close to
|
||||
/// the theoretical probabilities.
|
||||
pub fn test_roughly_uniform<G: RandomGenerator>() {
|
||||
|
||||
@@ -8,7 +8,7 @@ pub struct RdseedSeeder;
|
||||
|
||||
impl Seeder for RdseedSeeder {
|
||||
fn seed(&mut self) -> Seed {
|
||||
Seed(rdseed_random_m128())
|
||||
Seed(unsafe { rdseed_random_m128() })
|
||||
}
|
||||
|
||||
fn is_available() -> bool {
|
||||
@@ -17,7 +17,8 @@ impl Seeder for RdseedSeeder {
|
||||
}
|
||||
|
||||
// Generates a random 128 bits value from rdseed
|
||||
fn rdseed_random_m128() -> u128 {
|
||||
#[target_feature(enable = "rdseed")]
|
||||
unsafe fn rdseed_random_m128() -> u128 {
|
||||
let mut rand1: u64 = 0;
|
||||
let mut rand2: u64 = 0;
|
||||
let mut output_bytes = [0u8; 16];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! When initializing a generator, one needs to provide a [`Seed`], which is then used as key to the
|
||||
//! AES blockcipher. As a consequence, the quality of the outputs of the generator is directly
|
||||
//! conditioned by the quality of this seed. This module proposes different mechanisms to deliver
|
||||
//! seeds that can accomodate varying scenarios.
|
||||
//! seeds that can accommodate varying scenarios.
|
||||
|
||||
/// A seed value, used to initialize a generator.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
@@ -15,7 +15,7 @@ pub trait Seeder {
|
||||
fn seed(&mut self) -> Seed;
|
||||
|
||||
/// Check whether the seeder can be used on the current machine. This function may check if some
|
||||
/// required CPU features are available or if some OS features are availble for example.
|
||||
/// required CPU features are available or if some OS features are available for example.
|
||||
fn is_available() -> bool
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
77
scripts/dieharder_test.sh
Executable file
77
scripts/dieharder_test.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# dieharder does not support running a subset of its tests, so we'll check which ones are not good
|
||||
# and ignore the output from those tests in the final log
|
||||
|
||||
set -e
|
||||
|
||||
DIEHARDER_RUN_LOG_FILE="dieharder_run.log"
|
||||
|
||||
bad_tests="$(dieharder -l | \
|
||||
# select lines with the -d
|
||||
grep -w '\-d' | \
|
||||
# forget about the good tests
|
||||
grep -v -i 'good' | \
|
||||
# get the test id
|
||||
cut -d ' ' -f 4 | \
|
||||
# nice formatting
|
||||
xargs)"
|
||||
|
||||
|
||||
bad_test_filter=""
|
||||
for bad_test in ${bad_tests}; do
|
||||
bad_test_filter="${bad_test_filter:+${bad_test_filter}|}$(dieharder -d "${bad_test}" -t 1 -p 1 -D test_name | xargs)"
|
||||
done
|
||||
|
||||
echo "The following tests will be ignored as they are marked as either 'suspect' or 'do not use': "
|
||||
echo ""
|
||||
echo "${bad_test_filter}"
|
||||
echo ""
|
||||
|
||||
# by default we may have no pv just forward the input
|
||||
pv="cat"
|
||||
if which pv > /dev/null; then
|
||||
pv="pv -t -a -b"
|
||||
fi
|
||||
|
||||
rm -f "${DIEHARDER_RUN_LOG_FILE}"
|
||||
|
||||
# ignore potential errors and parse the log afterwards
|
||||
set +e
|
||||
|
||||
# We are writing in both cases
|
||||
# shellcheck disable=SC2094
|
||||
./target/release/examples/generate 2>"${DIEHARDER_RUN_LOG_FILE}" | \
|
||||
$pv | \
|
||||
# -a: all tests
|
||||
# -g 200: get random bytes from input
|
||||
# -Y 1: disambiguate results, i.e. if a weak result appear check if it's a random failure/weakness
|
||||
# -k 2: better maths formulas to determine some test statistics
|
||||
dieharder -a -g 200 -Y 1 -k 2 | \
|
||||
tee -a "${DIEHARDER_RUN_LOG_FILE}"
|
||||
set -e
|
||||
|
||||
printf "\n\n"
|
||||
|
||||
cat "${DIEHARDER_RUN_LOG_FILE}"
|
||||
|
||||
if ! grep -q -i "failed" < "${DIEHARDER_RUN_LOG_FILE}"; then
|
||||
echo "All tests passed!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf "\n\n"
|
||||
|
||||
failed_tests="$(grep -i "failed" < "${DIEHARDER_RUN_LOG_FILE}")"
|
||||
true_failed_test="$(grep -i "failed" < "${DIEHARDER_RUN_LOG_FILE}" | { grep -v -E "${bad_test_filter}" || true; } | sed -z '$ s/\n$//')"
|
||||
|
||||
if [[ "${true_failed_test}" == "" ]]; then
|
||||
echo "There were test failures, but the tests were either marked as 'suspect' or 'do not use'"
|
||||
echo "${failed_tests}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "The following tests failed:"
|
||||
echo "${true_failed_test}"
|
||||
|
||||
exit 1
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p sts_testing
|
||||
cd sts_testing
|
||||
|
||||
if [[ ! -f sts-2_1_2.zip ]]; then
|
||||
wget "https://csrc.nist.gov/CSRC/media/Projects/Random-Bit-Generation/documents/sts-2_1_2.zip"
|
||||
echo "0238d2f1d26e120e3cc748ed2d4c674cdc636de37fc4027c76cc2a394fff9157 sts-2_1_2.zip" > checksum
|
||||
shasum -a 256 -c checksum
|
||||
fi
|
||||
|
||||
# q: quiet, o: overwrite
|
||||
unzip -q -o sts-2_1_2.zip
|
||||
|
||||
cd sts-2.1.2/sts-2.1.2/
|
||||
|
||||
make clean
|
||||
make -j GCCFLAGS="-c -Wall -O3"
|
||||
|
||||
rm -rf bytes.bin
|
||||
|
||||
echo "Generating bytes..."
|
||||
|
||||
# 1_000_000 bits = 125_000 bytes
|
||||
# recommended number of bit streams = 200
|
||||
# 125_000 * 200 = 25_000_000
|
||||
RUSTFLAGS="-C target-cpu=native" cargo run --profile release \
|
||||
--example generate --features=x86_64-unix -p concrete-csprng -- \
|
||||
--bytes_total 125000000 >> bytes.bin
|
||||
|
||||
echo "Running analysis... this may take a while"
|
||||
|
||||
# Loop if we get this shit: "igamc: UNDERFLOW"
|
||||
set +e # assess may return non 0 in case we get something wonky going on
|
||||
for retry in {1..10}; do
|
||||
# 0: use input file
|
||||
# Input file name
|
||||
# 1: Run all tests on sequences
|
||||
# 0: Confirm
|
||||
# 200: number of bit streams
|
||||
# 1: binary input mode (we wrote bytes to the bin file)
|
||||
printf "0\nbytes.bin\n1\n0\n1000\n1\n" | ./assess 1000000 > sts_run.log
|
||||
|
||||
cat sts_run.log
|
||||
|
||||
if ! grep -q -i 'underflow' sts_run.log; then
|
||||
# did not find any underflow, break out of retrying
|
||||
break
|
||||
fi
|
||||
|
||||
echo "Underflow detected in attempt ${retry}, retrying..."
|
||||
done
|
||||
# re-enable errors
|
||||
set -e
|
||||
|
||||
# Let's have a nice output
|
||||
cat experiments/AlgorithmTesting/finalAnalysisReport.txt
|
||||
|
||||
# Reports indicate failed tests with a * which does not appear in a report where everything worked
|
||||
# -F indicates we want to match the fixed string * (and not a regex)
|
||||
if ! grep -q -F '*' experiments/AlgorithmTesting/finalAnalysisReport.txt; then
|
||||
# Exit code was != 0 which means * was not found
|
||||
printf "\n\nStatistical tests passed!\n"
|
||||
exit 0
|
||||
else
|
||||
# * found, some tests failed
|
||||
printf "\n\nStatistical tests failed!\n"
|
||||
exit 1
|
||||
fi
|
||||
@@ -6,7 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = "3.1"
|
||||
clap = "=4.4.4"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
simplelog = "0.12"
|
||||
|
||||
2
tfhe/.gitignore
vendored
2
tfhe/.gitignore
vendored
@@ -1 +1 @@
|
||||
build/
|
||||
build/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tfhe"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
|
||||
@@ -17,7 +17,7 @@ exclude = [
|
||||
"/js_on_wasm_tests/",
|
||||
"/web_wasm_parallel_tests/",
|
||||
]
|
||||
rust-version = "1.67"
|
||||
rust-version = "1.72"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -26,26 +26,27 @@ rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
paste = "1.0.7"
|
||||
lazy_static = { version = "1.4.0" }
|
||||
criterion = "0.4.0"
|
||||
criterion = "0.5.1"
|
||||
doc-comment = "0.3.3"
|
||||
serde_json = "1.0.94"
|
||||
# clap has to be pinned as its minimum supported rust version
|
||||
# changes often between minor releases, which breaks our CI
|
||||
clap = { version = "=4.2.7", features = ["derive"] }
|
||||
clap = { version = "=4.4.4", features = ["derive"] }
|
||||
# Used in user documentation
|
||||
bincode = "1.3.3"
|
||||
fs2 = { version = "0.4.3" }
|
||||
itertools = "0.10.5"
|
||||
num_cpus = "1.15"
|
||||
itertools = "0.11.0"
|
||||
# For erf and normality test
|
||||
libm = "0.2.6"
|
||||
# Begin regex-engine deps
|
||||
test-case = "3.1.0"
|
||||
combine = "4.6.6"
|
||||
env_logger = "0.10.0"
|
||||
log = "0.4.19"
|
||||
# End regex-engine deps
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = { version = "0.24.3", optional = true }
|
||||
cbindgen = { version = "0.26.0", optional = true }
|
||||
|
||||
[dependencies]
|
||||
concrete-csprng = { version = "0.4.0", path= "../concrete-csprng", features = [
|
||||
@@ -56,15 +57,14 @@ lazy_static = { version = "1.4.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
rayon = { version = "1.5.0" }
|
||||
bincode = { version = "1.3.3", optional = true }
|
||||
concrete-fft = { version = "0.2.1", features = ["serde", "fft128"] }
|
||||
pulp = "0.11"
|
||||
concrete-fft = { version = "0.3.0", features = ["serde", "fft128"] }
|
||||
pulp = "0.13"
|
||||
aligned-vec = { version = "0.5", features = ["serde"] }
|
||||
dyn-stack = { version = "0.9" }
|
||||
once_cell = "1.13"
|
||||
paste = "1.0.7"
|
||||
paste = { version = "1.0.7", optional = true }
|
||||
fs2 = { version = "0.4.3", optional = true }
|
||||
# While we wait for repeat_n in rust standard library
|
||||
itertools = "0.10.5"
|
||||
itertools = "0.11.0"
|
||||
|
||||
# wasm deps
|
||||
wasm-bindgen = { version = "0.2.86", features = [
|
||||
@@ -73,22 +73,23 @@ wasm-bindgen = { version = "0.2.86", features = [
|
||||
wasm-bindgen-rayon = { version = "1.0", optional = true }
|
||||
js-sys = { version = "0.3", optional = true }
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
serde-wasm-bindgen = { version = "0.4", optional = true }
|
||||
serde-wasm-bindgen = { version = "0.6.0", optional = true }
|
||||
getrandom = { version = "0.2.8", optional = true }
|
||||
bytemuck = "1.13.1"
|
||||
|
||||
[features]
|
||||
boolean = []
|
||||
shortint = []
|
||||
integer = ["shortint"]
|
||||
internal-keycache = ["lazy_static", "fs2", "bincode"]
|
||||
# paste is used by the HL API
|
||||
boolean = ["dep:paste"]
|
||||
shortint = ["dep:paste"]
|
||||
integer = ["shortint", "dep:paste"]
|
||||
internal-keycache = ["lazy_static", "dep:fs2", "bincode", "dep:paste"]
|
||||
|
||||
# Experimental section
|
||||
experimental = []
|
||||
experimental-force_fft_algo_dif4 = []
|
||||
# End experimental section
|
||||
|
||||
__c_api = ["cbindgen", "bincode"]
|
||||
__c_api = ["cbindgen", "bincode", "dep:paste"]
|
||||
boolean-c-api = ["boolean", "__c_api"]
|
||||
shortint-c-api = ["shortint", "__c_api"]
|
||||
high-level-c-api = ["boolean-c-api", "shortint-c-api", "integer", "__c_api"]
|
||||
@@ -120,6 +121,7 @@ generator_aarch64_aes = ["concrete-csprng/generator_aarch64_aes"]
|
||||
|
||||
# Private features
|
||||
__profiling = []
|
||||
__coverage = []
|
||||
|
||||
seeder_unix = ["concrete-csprng/seeder_unix"]
|
||||
seeder_x86_64_rdseed = ["concrete-csprng/seeder_x86_64_rdseed"]
|
||||
@@ -180,6 +182,12 @@ path = "benches/integer/bench.rs"
|
||||
harness = false
|
||||
required-features = ["integer", "internal-keycache"]
|
||||
|
||||
[[bench]]
|
||||
name = "integer-signed-bench"
|
||||
path = "benches/integer/signed_bench.rs"
|
||||
harness = false
|
||||
required-features = ["integer", "internal-keycache"]
|
||||
|
||||
[[bench]]
|
||||
name = "keygen"
|
||||
path = "benches/keygen/bench.rs"
|
||||
|
||||
@@ -5,18 +5,20 @@ use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use tfhe::boolean::client_key::ClientKey;
|
||||
use tfhe::boolean::parameters::{
|
||||
BooleanParameters, DEFAULT_PARAMETERS, PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
|
||||
BooleanParameters, DEFAULT_PARAMETERS, DEFAULT_PARAMETERS_KS_PBS,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165, PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
|
||||
TFHE_LIB_PARAMETERS,
|
||||
};
|
||||
use tfhe::boolean::prelude::{BinaryBooleanGates, DEFAULT_PARAMETERS_KS_PBS};
|
||||
use tfhe::boolean::prelude::BinaryBooleanGates;
|
||||
use tfhe::boolean::server_key::ServerKey;
|
||||
|
||||
criterion_group!(
|
||||
gates_benches,
|
||||
bench_default_parameters,
|
||||
bench_tfhe_lib_parameters,
|
||||
bench_default_parameters_ks_pbs,
|
||||
bench_tfhe_lib_parameters_pbs,
|
||||
bench_low_prob_parameters,
|
||||
bench_low_prob_parameters_ks_pbs,
|
||||
bench_tfhe_lib_parameters,
|
||||
);
|
||||
|
||||
criterion_main!(gates_benches);
|
||||
@@ -41,7 +43,7 @@ pub fn write_to_json_boolean<T: Into<CryptoParametersRecord<u32>>>(
|
||||
|
||||
// Put all `bench_function` in one place
|
||||
// so the keygen is only run once per parameters saving time.
|
||||
fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
fn benches(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
let mut bench_group = c.benchmark_group("gates_benches");
|
||||
|
||||
let cks = ClientKey::new(¶ms);
|
||||
@@ -81,24 +83,29 @@ fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
}
|
||||
|
||||
fn bench_default_parameters(c: &mut Criterion) {
|
||||
benchs(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
|
||||
benches(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
|
||||
}
|
||||
|
||||
fn bench_default_parameters_ks_pbs(c: &mut Criterion) {
|
||||
benches(c, DEFAULT_PARAMETERS_KS_PBS, "DEFAULT_PARAMETERS_KS_PBS");
|
||||
}
|
||||
|
||||
fn bench_low_prob_parameters(c: &mut Criterion) {
|
||||
benches(
|
||||
c,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165",
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_low_prob_parameters_ks_pbs(c: &mut Criterion) {
|
||||
benches(
|
||||
c,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
|
||||
"PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS",
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
|
||||
benchs(
|
||||
c,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165,
|
||||
"TFHE_LIB_PARAMETERS",
|
||||
);
|
||||
}
|
||||
fn bench_default_parameters_ks_pbs(c: &mut Criterion) {
|
||||
benchs(c, DEFAULT_PARAMETERS_KS_PBS, "DEFAULT_PARAMETERS_KS_PBS");
|
||||
}
|
||||
|
||||
fn bench_tfhe_lib_parameters_pbs(c: &mut Criterion) {
|
||||
benchs(
|
||||
c,
|
||||
PARAMETERS_ERROR_PROB_2_POW_MINUS_165_KS_PBS,
|
||||
"TFHE_LIB_PARAMETERS_KS_PBS",
|
||||
);
|
||||
benches(c, TFHE_LIB_PARAMETERS, " TFHE_LIB_PARAMETERS");
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ criterion_group!(
|
||||
|
||||
criterion_main!(pbs_group, multi_bit_pbs_group, pbs_throughput_group);
|
||||
|
||||
fn benchmark_parameters<Scalar: UnsignedInteger>(
|
||||
) -> Vec<(&'static str, CryptoParametersRecord<Scalar>)> {
|
||||
fn benchmark_parameters<Scalar: UnsignedInteger>() -> Vec<(String, CryptoParametersRecord<Scalar>)>
|
||||
{
|
||||
if Scalar::BITS == 64 {
|
||||
SHORTINT_BENCH_PARAMS
|
||||
.iter()
|
||||
@@ -83,7 +83,7 @@ fn benchmark_parameters<Scalar: UnsignedInteger>(
|
||||
} else if Scalar::BITS == 32 {
|
||||
BOOLEAN_BENCH_PARAMS
|
||||
.iter()
|
||||
.map(|(name, params)| (*name, params.to_owned().into()))
|
||||
.map(|(name, params)| (name.to_string(), params.to_owned().into()))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
@@ -91,7 +91,7 @@ fn benchmark_parameters<Scalar: UnsignedInteger>(
|
||||
}
|
||||
|
||||
fn throughput_benchmark_parameters<Scalar: UnsignedInteger>(
|
||||
) -> Vec<(&'static str, CryptoParametersRecord<Scalar>)> {
|
||||
) -> Vec<(String, CryptoParametersRecord<Scalar>)> {
|
||||
if Scalar::BITS == 64 {
|
||||
vec![
|
||||
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
|
||||
@@ -111,18 +111,15 @@ fn throughput_benchmark_parameters<Scalar: UnsignedInteger>(
|
||||
} else if Scalar::BITS == 32 {
|
||||
BOOLEAN_BENCH_PARAMS
|
||||
.iter()
|
||||
.map(|(name, params)| (*name, params.to_owned().into()))
|
||||
.map(|(name, params)| (name.to_string(), params.to_owned().into()))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_bit_benchmark_parameters<Scalar: UnsignedInteger + Default>() -> Vec<(
|
||||
&'static str,
|
||||
CryptoParametersRecord<Scalar>,
|
||||
LweBskGroupingFactor,
|
||||
)> {
|
||||
fn multi_bit_benchmark_parameters<Scalar: UnsignedInteger + Default>(
|
||||
) -> Vec<(String, CryptoParametersRecord<Scalar>, LweBskGroupingFactor)> {
|
||||
if Scalar::BITS == 64 {
|
||||
vec![
|
||||
PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_2_KS_PBS,
|
||||
@@ -243,7 +240,7 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize> + Serialize>(c: &mu
|
||||
write_to_json(
|
||||
&id,
|
||||
*params,
|
||||
*name,
|
||||
name,
|
||||
"pbs",
|
||||
&OperatorType::Atomic,
|
||||
bit_size,
|
||||
@@ -332,7 +329,7 @@ fn multi_bit_pbs<
|
||||
write_to_json(
|
||||
&id,
|
||||
*params,
|
||||
*name,
|
||||
name,
|
||||
"pbs",
|
||||
&OperatorType::Atomic,
|
||||
bit_size,
|
||||
@@ -421,7 +418,7 @@ fn multi_bit_deterministic_pbs<
|
||||
write_to_json(
|
||||
&id,
|
||||
*params,
|
||||
*name,
|
||||
name,
|
||||
"pbs",
|
||||
&OperatorType::Atomic,
|
||||
bit_size,
|
||||
@@ -478,8 +475,6 @@ fn pbs_throughput<Scalar: UnsignedTorus + CastInto<usize> + Sync + Send + Serial
|
||||
params.ciphertext_modulus.unwrap(),
|
||||
);
|
||||
|
||||
let lwe_vec = lwe_vec;
|
||||
|
||||
let fft = Fft::new(params.polynomial_size.unwrap());
|
||||
let fft = fft.as_view();
|
||||
|
||||
@@ -543,7 +538,7 @@ fn pbs_throughput<Scalar: UnsignedTorus + CastInto<usize> + Sync + Send + Serial
|
||||
write_to_json(
|
||||
&id,
|
||||
*params,
|
||||
*name,
|
||||
name,
|
||||
"pbs",
|
||||
&OperatorType::Atomic,
|
||||
bit_size,
|
||||
|
||||
@@ -27,6 +27,9 @@ use tfhe::shortint::parameters::{
|
||||
/// It must be as big as the largest bit size tested
|
||||
type ScalarType = U256;
|
||||
|
||||
const FAST_BENCH_BIT_SIZES: [usize; 1] = [32];
|
||||
const BENCH_BIT_SIZES: [usize; 7] = [8, 16, 32, 40, 64, 128, 256];
|
||||
|
||||
fn gen_random_u256(rng: &mut ThreadRng) -> U256 {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
@@ -54,15 +57,14 @@ impl Default for ParamsAndNumBlocksIter {
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
let bit_sizes = if is_fast_bench {
|
||||
FAST_BENCH_BIT_SIZES.to_vec()
|
||||
} else {
|
||||
BENCH_BIT_SIZES.to_vec()
|
||||
};
|
||||
|
||||
if is_multi_bit {
|
||||
let params = vec![PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS.into()];
|
||||
|
||||
let bit_sizes = if is_fast_bench {
|
||||
vec![32]
|
||||
} else {
|
||||
vec![8, 16, 32, 40, 64]
|
||||
};
|
||||
|
||||
let params_and_bit_sizes = iproduct!(params, bit_sizes);
|
||||
Self {
|
||||
params_and_bit_sizes,
|
||||
@@ -75,13 +77,6 @@ impl Default for ParamsAndNumBlocksIter {
|
||||
// PARAM_MESSAGE_3_CARRY_3_KS_PBS.into(),
|
||||
// PARAM_MESSAGE_4_CARRY_4_KS_PBS.into(),
|
||||
];
|
||||
|
||||
let bit_sizes = if is_fast_bench {
|
||||
vec![32]
|
||||
} else {
|
||||
vec![8, 16, 32, 40, 64, 128, 256]
|
||||
};
|
||||
|
||||
let params_and_bit_sizes = iproduct!(params, bit_sizes);
|
||||
Self {
|
||||
params_and_bit_sizes,
|
||||
|
||||
811
tfhe/benches/integer/signed_bench.rs
Normal file
811
tfhe/benches/integer/signed_bench.rs
Normal file
@@ -0,0 +1,811 @@
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
use std::env;
|
||||
|
||||
use criterion::{criterion_group, Criterion};
|
||||
use itertools::iproduct;
|
||||
use rand::prelude::*;
|
||||
use rand::Rng;
|
||||
use std::vec::IntoIter;
|
||||
use tfhe::integer::keycache::KEY_CACHE;
|
||||
use tfhe::integer::{RadixCiphertext, ServerKey, SignedRadixCiphertext, I256};
|
||||
use tfhe::keycache::NamedParam;
|
||||
|
||||
use tfhe::shortint::parameters::{
|
||||
PARAM_MESSAGE_2_CARRY_2_KS_PBS, PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
|
||||
};
|
||||
|
||||
fn gen_random_i256(rng: &mut ThreadRng) -> I256 {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
|
||||
tfhe::integer::I256::from((clearlow, clearhigh))
|
||||
}
|
||||
|
||||
/// An iterator that yields a succession of combinations
|
||||
/// of parameters and a num_block to achieve a certain bit_size ciphertext
|
||||
/// in radix decomposition
|
||||
struct ParamsAndNumBlocksIter {
|
||||
params_and_bit_sizes:
|
||||
itertools::Product<IntoIter<tfhe::shortint::PBSParameters>, IntoIter<usize>>,
|
||||
}
|
||||
|
||||
impl Default for ParamsAndNumBlocksIter {
|
||||
fn default() -> Self {
|
||||
let is_multi_bit = match env::var("__TFHE_RS_BENCH_TYPE") {
|
||||
Ok(val) => val.to_lowercase() == "multi_bit",
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
let is_fast_bench = match env::var("__TFHE_RS_FAST_BENCH") {
|
||||
Ok(val) => val.to_lowercase() == "true",
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
if is_multi_bit {
|
||||
let params = vec![PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS.into()];
|
||||
|
||||
let bit_sizes = if is_fast_bench {
|
||||
vec![32]
|
||||
} else {
|
||||
vec![8, 16, 32, 40, 64]
|
||||
};
|
||||
|
||||
let params_and_bit_sizes = iproduct!(params, bit_sizes);
|
||||
Self {
|
||||
params_and_bit_sizes,
|
||||
}
|
||||
} else {
|
||||
// FIXME One set of parameter is tested since we want to benchmark only quickest
|
||||
// operations.
|
||||
let params = vec![
|
||||
PARAM_MESSAGE_2_CARRY_2_KS_PBS.into(),
|
||||
// PARAM_MESSAGE_3_CARRY_3_KS_PBS.into(),
|
||||
// PARAM_MESSAGE_4_CARRY_4_KS_PBS.into(),
|
||||
];
|
||||
|
||||
let bit_sizes = if is_fast_bench {
|
||||
vec![32]
|
||||
} else {
|
||||
vec![8, 16, 32, 40, 64, 128, 256]
|
||||
};
|
||||
|
||||
let params_and_bit_sizes = iproduct!(params, bit_sizes);
|
||||
Self {
|
||||
params_and_bit_sizes,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ParamsAndNumBlocksIter {
|
||||
type Item = (tfhe::shortint::PBSParameters, usize, usize);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (param, bit_size) = self.params_and_bit_sizes.next()?;
|
||||
let num_block =
|
||||
(bit_size as f64 / param.message_modulus().0.ilog2() as f64).ceil() as usize;
|
||||
|
||||
Some((param, num_block, bit_size))
|
||||
}
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a binary operation, input ciphertext will
|
||||
/// contain only zero carries
|
||||
fn bench_server_key_signed_binary_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
sample_size: usize,
|
||||
) where
|
||||
F: Fn(&ServerKey, &SignedRadixCiphertext, &SignedRadixCiphertext),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(sample_size)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_two_values = || {
|
||||
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
|
||||
let ct_1 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
|
||||
|
||||
(ct_0, ct_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_two_values,
|
||||
|(ct_0, ct_1)| {
|
||||
binary_op(&sks, &ct_0, &ct_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus().0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
/// Shifts and rotations require a special function as the rhs,
|
||||
/// i.e. the shift amount has to be a positive radix type.
|
||||
fn bench_server_key_signed_shift_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &SignedRadixCiphertext, &RadixCiphertext),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_two_values = || {
|
||||
let clear_1 = rng.gen_range(0u128..bit_size as u128);
|
||||
|
||||
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
|
||||
let ct_1 = cks.encrypt_radix(clear_1, num_block);
|
||||
|
||||
(ct_0, ct_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_two_values,
|
||||
|(ct_0, ct_1)| {
|
||||
binary_op(&sks, &ct_0, &ct_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus().0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a unary operation, input ciphertext will
|
||||
/// contain only zero carries
|
||||
fn bench_server_key_unary_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_fn: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &SignedRadixCiphertext),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_one_value =
|
||||
|| cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_one_value,
|
||||
|ct_0| {
|
||||
unary_fn(&sks, &ct_0);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus().0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn signed_if_then_else_parallelized(c: &mut Criterion) {
|
||||
let bench_name = "integer::signed::if_then_else_parallelized";
|
||||
let display_name = "if_then_else";
|
||||
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_tree_values = || {
|
||||
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
|
||||
let ct_1 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
|
||||
|
||||
let cond = sks.create_trivial_radix(rng.gen_bool(0.5) as u64, num_block);
|
||||
|
||||
(cond, ct_0, ct_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_tree_values,
|
||||
|(condition, true_ct, false_ct)| {
|
||||
sks.if_then_else_parallelized(&condition, &true_ct, &false_ct)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus().0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
macro_rules! define_server_key_bench_binary_signed_clean_inputs_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident $(,)?) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_signed_binary_function_clean_inputs(
|
||||
c,
|
||||
concat!("integer::signed::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
},
|
||||
15 /* sample_size */
|
||||
)
|
||||
}
|
||||
};
|
||||
(
|
||||
method_name: $server_key_method:ident,
|
||||
display_name:$name:ident,
|
||||
sample_size: $sample_size:expr $(,)?
|
||||
) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_signed_binary_function_clean_inputs(
|
||||
c,
|
||||
concat!("integer::signed::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
},
|
||||
$sample_size
|
||||
)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_unary_signed_clean_input_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident $(,)?) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_unary_function_clean_inputs(
|
||||
c,
|
||||
concat!("integer::signed::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs| {
|
||||
server_key.$server_key_method(lhs);
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_mul_parallelized,
|
||||
display_name: mul
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_bitand_parallelized,
|
||||
display_name: bitand
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_bitor_parallelized,
|
||||
display_name: bitand
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_bitxor_parallelized,
|
||||
display_name: bitand
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_eq_parallelized,
|
||||
display_name: eq
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_ne_parallelized,
|
||||
display_name: ne
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_le_parallelized,
|
||||
display_name: le
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_lt_parallelized,
|
||||
display_name: lt
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_ge_parallelized,
|
||||
display_name: ge
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_gt_parallelized,
|
||||
display_name: gt
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_max_parallelized,
|
||||
display_name: max
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_min_parallelized,
|
||||
display_name: min
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_div_rem_parallelized,
|
||||
display_name: div_rem,
|
||||
sample_size: 10,
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_signed_clean_inputs_fn!(
|
||||
method_name: unchecked_div_rem_floor_parallelized,
|
||||
display_name: div_rem_floor,
|
||||
sample_size: 10,
|
||||
);
|
||||
|
||||
fn unchecked_left_shift_parallelized(c: &mut Criterion) {
|
||||
bench_server_key_signed_shift_function_clean_inputs(
|
||||
c,
|
||||
concat!("integer::signed::", "unchecked_left_shift_parallelized"),
|
||||
"left_shift",
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.unchecked_left_shift_parallelized(lhs, rhs);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn unchecked_right_shift_parallelized(c: &mut Criterion) {
|
||||
bench_server_key_signed_shift_function_clean_inputs(
|
||||
c,
|
||||
concat!("integer::signed::", "unchecked_right_shift_parallelized"),
|
||||
"right_shift",
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.unchecked_right_shift_parallelized(lhs, rhs);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn unchecked_rotate_left_parallelized(c: &mut Criterion) {
|
||||
bench_server_key_signed_shift_function_clean_inputs(
|
||||
c,
|
||||
concat!("integer::signed::", "unchecked_rotate_left_parallelized"),
|
||||
"rotate_left",
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.unchecked_rotate_left_parallelized(lhs, rhs);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn unchecked_rotate_right_parallelized(c: &mut Criterion) {
|
||||
bench_server_key_signed_shift_function_clean_inputs(
|
||||
c,
|
||||
concat!("integer::signed::", "unchecked_rotate_right_parallelized"),
|
||||
"rotate_right",
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.unchecked_rotate_right_parallelized(lhs, rhs);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
define_server_key_bench_unary_signed_clean_input_fn!(
|
||||
method_name: unchecked_absolute_value,
|
||||
display_name: absolute_value,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
unchecked_ops,
|
||||
unchecked_mul_parallelized,
|
||||
unchecked_left_shift_parallelized,
|
||||
unchecked_right_shift_parallelized,
|
||||
unchecked_rotate_left_parallelized,
|
||||
unchecked_rotate_right_parallelized,
|
||||
unchecked_bitand_parallelized,
|
||||
unchecked_bitor_parallelized,
|
||||
unchecked_bitxor_parallelized,
|
||||
unchecked_absolute_value,
|
||||
unchecked_div_rem_parallelized,
|
||||
unchecked_div_rem_floor_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
unchecked_ops_comp,
|
||||
unchecked_eq_parallelized,
|
||||
unchecked_ne_parallelized,
|
||||
unchecked_ge_parallelized,
|
||||
unchecked_gt_parallelized,
|
||||
unchecked_le_parallelized,
|
||||
unchecked_lt_parallelized,
|
||||
unchecked_max_parallelized,
|
||||
unchecked_min_parallelized,
|
||||
);
|
||||
|
||||
//================================================================================
|
||||
// Scalar Benches
|
||||
//================================================================================
|
||||
|
||||
type ScalarType = I256;
|
||||
|
||||
fn bench_server_key_binary_scalar_function_clean_inputs<F, G>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
rng_func: G,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut SignedRadixCiphertext, ScalarType),
|
||||
G: Fn(&mut ThreadRng, usize) -> ScalarType,
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
if bit_size > ScalarType::BITS as usize {
|
||||
break;
|
||||
}
|
||||
let param_name = param.name();
|
||||
|
||||
let range = range_for_signed_bit_size(bit_size);
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits_scalar_{bit_size}");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_one_value = || {
|
||||
let ct_0 = cks.encrypt_signed_radix(gen_random_i256(&mut rng), num_block);
|
||||
|
||||
let clear_1 = rng_func(&mut rng, bit_size);
|
||||
assert!(
|
||||
range.contains(&clear_1),
|
||||
"{:?} is not within the range {:?}",
|
||||
clear_1,
|
||||
range
|
||||
);
|
||||
|
||||
(ct_0, clear_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_one_value,
|
||||
|(mut ct_0, clear_1)| {
|
||||
binary_op(&sks, &mut ct_0, clear_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus().0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn range_for_signed_bit_size(bit_size: usize) -> std::ops::RangeInclusive<ScalarType> {
|
||||
assert!(bit_size <= ScalarType::BITS as usize);
|
||||
assert!(bit_size > 0);
|
||||
let modulus = ScalarType::ONE << (bit_size - 1);
|
||||
// if clear_bit_size == ScalarType::BITS then modulus==T::MIN
|
||||
// -T::MIN = -T::MIN so we sill have our correct lower value
|
||||
// (in two's complement which rust uses)
|
||||
let lowest = modulus.wrapping_neg();
|
||||
// if clear_bit_size == 128 then modulus==T::MIN
|
||||
// T::MIN - 1 = T::MAX (in two's complement which rust uses)
|
||||
let highest = modulus.wrapping_sub(ScalarType::ONE);
|
||||
|
||||
lowest..=highest
|
||||
}
|
||||
|
||||
/// Creates a bitmask where bit_size bits are 1s, rest are 0s
|
||||
/// Only works if ScalarType in signed
|
||||
fn positive_bit_mask_for_bit_size(bit_size: usize) -> ScalarType {
|
||||
assert!(bit_size <= ScalarType::BITS as usize);
|
||||
assert!(bit_size > 0);
|
||||
let minus_one = -ScalarType::ONE; // (In two's complement this is full of 1s)
|
||||
// The last bit of bit_size can only be set for when value is positive
|
||||
let bitmask = (minus_one) >> (ScalarType::BITS as usize - bit_size - 1);
|
||||
// flib msb as they would still be one due to '>>' being arithmetic shift
|
||||
bitmask ^ ((minus_one) << (bit_size - 1))
|
||||
}
|
||||
|
||||
fn negative_bit_mask_for_bit_size(bit_size: usize) -> ScalarType {
|
||||
assert!(bit_size <= ScalarType::BITS as usize);
|
||||
assert!(bit_size > 0);
|
||||
let minus_one = -ScalarType::ONE; // (In two's complement this is full of 1s)
|
||||
let bitmask = (minus_one) >> (ScalarType::BITS as usize - bit_size);
|
||||
// flib msb as they would still be one due to '>>' being arithmetic shift
|
||||
bitmask ^ ((minus_one) << bit_size)
|
||||
}
|
||||
|
||||
// We have to do this complex stuff because we cannot impl
|
||||
// rand::distributions::Distribution<I256> because benches are considered out of the crate
|
||||
// so neither I256 nor rand::distributions::Distribution belong to the benches.
|
||||
//
|
||||
// rand::distributions::Distribution can't be implemented in tfhe sources
|
||||
// in a way that it becomes available to the benches, because rand is a dev dependency
|
||||
fn gen_random_i256_in_range(rng: &mut ThreadRng, bit_size: usize) -> I256 {
|
||||
let value = gen_random_i256(rng);
|
||||
if value >= I256::ZERO {
|
||||
value & positive_bit_mask_for_bit_size(bit_size)
|
||||
} else {
|
||||
(value & negative_bit_mask_for_bit_size(bit_size)) | -I256::ONE
|
||||
}
|
||||
}
|
||||
|
||||
// Functions used to apply different way of selecting a scalar based on the context.
|
||||
fn default_scalar(rng: &mut ThreadRng, clear_bit_size: usize) -> ScalarType {
|
||||
gen_random_i256_in_range(rng, clear_bit_size)
|
||||
}
|
||||
|
||||
fn shift_scalar(_rng: &mut ThreadRng, _clear_bit_size: usize) -> ScalarType {
|
||||
// Shifting by one is the worst case scenario.
|
||||
ScalarType::ONE
|
||||
}
|
||||
|
||||
fn div_scalar(rng: &mut ThreadRng, clear_bit_size: usize) -> ScalarType {
|
||||
loop {
|
||||
let scalar = gen_random_i256_in_range(rng, clear_bit_size);
|
||||
if scalar != ScalarType::ZERO {
|
||||
return scalar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_server_key_bench_binary_scalar_clean_inputs_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident, rng_func:$($rng_fn:tt)*) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_function_clean_inputs(
|
||||
c,
|
||||
concat!("integer::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
}, $($rng_fn)*)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_left_shift_parallelized,
|
||||
display_name: scalar_left_shift,
|
||||
rng_func: shift_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_right_shift_parallelized,
|
||||
display_name: scalar_right_shift,
|
||||
rng_func: shift_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_rotate_right_parallelized,
|
||||
display_name: scalar_rotate_right,
|
||||
rng_func: shift_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_rotate_left_parallelized,
|
||||
display_name: scalar_rotate_left,
|
||||
rng_func: shift_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_mul_parallelized,
|
||||
display_name: scalar_mul,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_bitand_parallelized,
|
||||
display_name: scalar_bitand,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_bitor_parallelized,
|
||||
display_name: scalar_bitor,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_bitxor_parallelized,
|
||||
display_name: scalar_bitxor,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_eq_parallelized,
|
||||
display_name: scalar_eq,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_ne_parallelized,
|
||||
display_name: scalar_ne,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_le_parallelized,
|
||||
display_name: scalar_le,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_lt_parallelized,
|
||||
display_name: scalar_lt,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_ge_parallelized,
|
||||
display_name: scalar_ge,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_gt_parallelized,
|
||||
display_name: scalar_gt,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_max_parallelized,
|
||||
display_name: scalar_max,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_scalar_min_parallelized,
|
||||
display_name: scalar_min,
|
||||
rng_func: default_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_signed_scalar_div_rem_parallelized,
|
||||
display_name: scalar_div_rem,
|
||||
rng_func: div_scalar
|
||||
);
|
||||
|
||||
define_server_key_bench_binary_scalar_clean_inputs_fn!(
|
||||
method_name: unchecked_signed_scalar_div_parallelized,
|
||||
display_name: scalar_div,
|
||||
rng_func: div_scalar
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
unchecked_scalar_ops,
|
||||
unchecked_scalar_left_shift_parallelized,
|
||||
unchecked_scalar_right_shift_parallelized,
|
||||
unchecked_scalar_rotate_right_parallelized,
|
||||
unchecked_scalar_rotate_left_parallelized,
|
||||
unchecked_scalar_bitand_parallelized,
|
||||
unchecked_scalar_bitor_parallelized,
|
||||
unchecked_scalar_bitxor_parallelized,
|
||||
unchecked_scalar_mul_parallelized,
|
||||
unchecked_signed_scalar_div_rem_parallelized,
|
||||
unchecked_signed_scalar_div_parallelized,
|
||||
unchecked_div_rem_floor_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
unchecked_scalar_ops_comp,
|
||||
unchecked_scalar_eq_parallelized,
|
||||
unchecked_scalar_ne_parallelized,
|
||||
unchecked_scalar_le_parallelized,
|
||||
unchecked_scalar_lt_parallelized,
|
||||
unchecked_scalar_ge_parallelized,
|
||||
unchecked_scalar_gt_parallelized,
|
||||
unchecked_scalar_max_parallelized,
|
||||
unchecked_scalar_min_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(default_ops, signed_if_then_else_parallelized,);
|
||||
|
||||
fn main() {
|
||||
match env::var("__TFHE_RS_BENCH_OP_FLAVOR") {
|
||||
Ok(val) => {
|
||||
match val.to_lowercase().as_str() {
|
||||
"unchecked" => unchecked_ops(),
|
||||
"unchecked_comp" => unchecked_ops_comp(),
|
||||
"unchecked_scalar" => unchecked_scalar_ops(),
|
||||
"unchecked_scalar_comp" => unchecked_scalar_ops_comp(),
|
||||
_ => panic!("unknown benchmark operations flavor"),
|
||||
};
|
||||
}
|
||||
Err(_) => {
|
||||
unchecked_ops();
|
||||
unchecked_ops_comp();
|
||||
unchecked_scalar_ops();
|
||||
unchecked_scalar_ops_comp();
|
||||
}
|
||||
};
|
||||
|
||||
Criterion::default().configure_from_args().final_summary();
|
||||
}
|
||||
@@ -7,7 +7,9 @@ use std::env;
|
||||
use criterion::{criterion_group, Criterion};
|
||||
use tfhe::keycache::NamedParam;
|
||||
use tfhe::shortint::parameters::*;
|
||||
use tfhe::shortint::{Ciphertext, ClassicPBSParameters, ServerKey, ShortintParameterSet};
|
||||
use tfhe::shortint::{
|
||||
Ciphertext, ClassicPBSParameters, CompressedServerKey, ServerKey, ShortintParameterSet,
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
use tfhe::shortint::keycache::{KEY_CACHE, KEY_CACHE_WOPBS};
|
||||
@@ -39,20 +41,59 @@ const SERVER_KEY_BENCH_PARAMS_EXTENDED: [ClassicPBSParameters; 15] = [
|
||||
PARAM_MESSAGE_8_CARRY_0_KS_PBS,
|
||||
];
|
||||
|
||||
const SERVER_KEY_MULTI_BIT_BENCH_PARAMS: [MultiBitPBSParameters; 2] = [
|
||||
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
|
||||
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
|
||||
];
|
||||
|
||||
const SERVER_KEY_MULTI_BIT_BENCH_PARAMS_EXTENDED: [MultiBitPBSParameters; 6] = [
|
||||
PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_2_KS_PBS,
|
||||
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
|
||||
PARAM_MULTI_BIT_MESSAGE_3_CARRY_3_GROUP_2_KS_PBS,
|
||||
PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_3_KS_PBS,
|
||||
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
|
||||
PARAM_MULTI_BIT_MESSAGE_3_CARRY_3_GROUP_3_KS_PBS,
|
||||
];
|
||||
|
||||
enum BenchParamsSet {
|
||||
Standard,
|
||||
Extended,
|
||||
}
|
||||
|
||||
fn benchmark_parameters(params_set: BenchParamsSet) -> Vec<PBSParameters> {
|
||||
let is_multi_bit = match env::var("__TFHE_RS_BENCH_TYPE") {
|
||||
Ok(val) => val.to_lowercase() == "multi_bit",
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
if is_multi_bit {
|
||||
let params = match params_set {
|
||||
BenchParamsSet::Standard => SERVER_KEY_MULTI_BIT_BENCH_PARAMS.to_vec(),
|
||||
BenchParamsSet::Extended => SERVER_KEY_MULTI_BIT_BENCH_PARAMS_EXTENDED.to_vec(),
|
||||
};
|
||||
params.iter().map(|p| (*p).into()).collect()
|
||||
} else {
|
||||
let params = match params_set {
|
||||
BenchParamsSet::Standard => SERVER_KEY_BENCH_PARAMS.to_vec(),
|
||||
BenchParamsSet::Extended => SERVER_KEY_BENCH_PARAMS_EXTENDED.to_vec(),
|
||||
};
|
||||
params.iter().map(|p| (*p).into()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_server_key_unary_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_op: F,
|
||||
params: &[ClassicPBSParameters],
|
||||
params_set: BenchParamsSet,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut Ciphertext),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
for param in params.iter() {
|
||||
let param: PBSParameters = (*param).into();
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
for param in benchmark_parameters(params_set).iter() {
|
||||
let keys = KEY_CACHE.get_from_param(*param);
|
||||
let (cks, sks) = (keys.client_key(), keys.server_key());
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -72,7 +113,7 @@ fn bench_server_key_unary_function<F>(
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
@@ -89,15 +130,14 @@ fn bench_server_key_binary_function<F>(
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[ClassicPBSParameters],
|
||||
params_set: BenchParamsSet,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut Ciphertext, &mut Ciphertext),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
for param in params.iter() {
|
||||
let param: PBSParameters = (*param).into();
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
for param in benchmark_parameters(params_set).iter() {
|
||||
let keys = KEY_CACHE.get_from_param(*param);
|
||||
let (cks, sks) = (keys.client_key(), keys.server_key());
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -119,7 +159,7 @@ fn bench_server_key_binary_function<F>(
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
@@ -136,15 +176,14 @@ fn bench_server_key_binary_scalar_function<F>(
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[ClassicPBSParameters],
|
||||
params_set: BenchParamsSet,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut Ciphertext, u8),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
for param in params {
|
||||
let param: PBSParameters = (*param).into();
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
for param in benchmark_parameters(params_set).iter() {
|
||||
let keys = KEY_CACHE.get_from_param(*param);
|
||||
let (cks, sks) = (keys.client_key(), keys.server_key());
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -165,7 +204,7 @@ fn bench_server_key_binary_scalar_function<F>(
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
@@ -182,15 +221,14 @@ fn bench_server_key_binary_scalar_division_function<F>(
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[ClassicPBSParameters],
|
||||
params_set: BenchParamsSet,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut Ciphertext, u8),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
for param in params {
|
||||
let param: PBSParameters = (*param).into();
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
for param in benchmark_parameters(params_set).iter() {
|
||||
let keys = KEY_CACHE.get_from_param(*param);
|
||||
let (cks, sks) = (keys.client_key(), keys.server_key());
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -215,7 +253,7 @@ fn bench_server_key_binary_scalar_division_function<F>(
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
@@ -227,12 +265,11 @@ fn bench_server_key_binary_scalar_division_function<F>(
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn carry_extract(c: &mut Criterion) {
|
||||
fn carry_extract_bench(c: &mut Criterion, params_set: BenchParamsSet) {
|
||||
let mut bench_group = c.benchmark_group("carry_extract");
|
||||
|
||||
for param in SERVER_KEY_BENCH_PARAMS {
|
||||
let param: PBSParameters = param.into();
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
for param in benchmark_parameters(params_set).iter() {
|
||||
let keys = KEY_CACHE.get_from_param(*param);
|
||||
let (cks, sks) = (keys.client_key(), keys.server_key());
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -252,7 +289,7 @@ fn carry_extract(c: &mut Criterion) {
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
*param,
|
||||
param.name(),
|
||||
"carry_extract",
|
||||
&OperatorType::Atomic,
|
||||
@@ -264,12 +301,11 @@ fn carry_extract(c: &mut Criterion) {
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn programmable_bootstrapping(c: &mut Criterion) {
|
||||
fn programmable_bootstrapping_bench(c: &mut Criterion, params_set: BenchParamsSet) {
|
||||
let mut bench_group = c.benchmark_group("programmable_bootstrap");
|
||||
|
||||
for param in SERVER_KEY_BENCH_PARAMS {
|
||||
let param: PBSParameters = param.into();
|
||||
let keys = KEY_CACHE.get_from_param(param);
|
||||
for param in benchmark_parameters(params_set).iter() {
|
||||
let keys = KEY_CACHE.get_from_param(*param);
|
||||
let (cks, sks) = (keys.client_key(), keys.server_key());
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -292,7 +328,7 @@ fn programmable_bootstrapping(c: &mut Criterion) {
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
param,
|
||||
*param,
|
||||
param.name(),
|
||||
"pbs",
|
||||
&OperatorType::Atomic,
|
||||
@@ -304,6 +340,54 @@ fn programmable_bootstrapping(c: &mut Criterion) {
|
||||
bench_group.finish();
|
||||
}
|
||||
|
||||
fn server_key_from_compressed_key(c: &mut Criterion) {
|
||||
let mut bench_group = c.benchmark_group("uncompress_key");
|
||||
bench_group
|
||||
.sample_size(10)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
|
||||
let mut params = SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
.iter()
|
||||
.map(|p| (*p).into())
|
||||
.collect::<Vec<PBSParameters>>();
|
||||
let multi_bit_params = SERVER_KEY_MULTI_BIT_BENCH_PARAMS_EXTENDED
|
||||
.iter()
|
||||
.map(|p| (*p).into())
|
||||
.collect::<Vec<PBSParameters>>();
|
||||
params.extend(&multi_bit_params);
|
||||
|
||||
for param in params.iter() {
|
||||
let keys = KEY_CACHE.get_from_param(*param);
|
||||
let sks_compressed = CompressedServerKey::new(keys.client_key());
|
||||
|
||||
let bench_id = format!("shortint::uncompress_key::{}", param.name());
|
||||
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let clone_compressed_key = || sks_compressed.clone();
|
||||
|
||||
b.iter_batched(
|
||||
clone_compressed_key,
|
||||
|sks_cloned| {
|
||||
let _ = ServerKey::from(sks_cloned);
|
||||
},
|
||||
criterion::BatchSize::PerIteration,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json::<u64, _>(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
"uncompress_key",
|
||||
&OperatorType::Atomic,
|
||||
param.message_modulus().0.ilog2(),
|
||||
vec![param.message_modulus().0.ilog2()],
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish();
|
||||
}
|
||||
|
||||
// TODO: remove?
|
||||
fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
|
||||
let mut bench_group = c.benchmark_group("programmable_bootstrap");
|
||||
@@ -333,7 +417,7 @@ fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
macro_rules! define_server_key_unary_bench_fn (
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params_set:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_unary_function(
|
||||
c,
|
||||
@@ -341,13 +425,13 @@ macro_rules! define_server_key_unary_bench_fn (
|
||||
stringify!($name),
|
||||
|server_key, lhs| {
|
||||
let _ = server_key.$server_key_method(lhs);},
|
||||
$params)
|
||||
$params_set)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_fn (
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params_set:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_function(
|
||||
c,
|
||||
@@ -355,13 +439,13 @@ macro_rules! define_server_key_bench_fn (
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
$params_set)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_scalar_bench_fn (
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params_set:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_function(
|
||||
c,
|
||||
@@ -369,13 +453,13 @@ macro_rules! define_server_key_scalar_bench_fn (
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
$params_set)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_scalar_div_bench_fn (
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params_set:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_division_function(
|
||||
c,
|
||||
@@ -383,7 +467,19 @@ macro_rules! define_server_key_scalar_div_bench_fn (
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
$params_set)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_custom_bench_fn (
|
||||
(function_name:$function:ident, $params_set:expr) => {
|
||||
fn $function(c: &mut Criterion) {
|
||||
::paste::paste! {
|
||||
[<$function _bench>](
|
||||
c,
|
||||
$params_set)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -391,251 +487,258 @@ macro_rules! define_server_key_scalar_div_bench_fn (
|
||||
define_server_key_unary_bench_fn!(
|
||||
method_name: unchecked_neg,
|
||||
display_name: negation,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
BenchParamsSet::Extended
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
BenchParamsSet::Extended
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_mul_lsb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
BenchParamsSet::Extended
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_mul_msb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
BenchParamsSet::Extended
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitand,
|
||||
display_name: bitand,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitor,
|
||||
display_name: bitor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitxor,
|
||||
display_name: bitxor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_mul_lsb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitand,
|
||||
display_name: bitand,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitor,
|
||||
display_name: bitor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitxor,
|
||||
display_name: bitxor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: greater,
|
||||
display_name: greater,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: greater_or_equal,
|
||||
display_name: greater_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: less,
|
||||
display_name: less,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: less_or_equal,
|
||||
display_name: less_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: not_equal,
|
||||
display_name: not_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_unary_bench_fn!(
|
||||
method_name: neg,
|
||||
display_name: negation,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_greater,
|
||||
display_name: greater_than,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_less,
|
||||
display_name: less_than,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
BenchParamsSet::Extended
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
BenchParamsSet::Extended
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
BenchParamsSet::Extended
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_left_shift,
|
||||
display_name: left_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_right_shift,
|
||||
display_name: right_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: unchecked_scalar_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
BenchParamsSet::Extended
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: unchecked_scalar_mod,
|
||||
display_name: modulo,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_left_shift,
|
||||
display_name: left_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_right_shift,
|
||||
display_name: right_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_mod,
|
||||
display_name: modulo,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_greater,
|
||||
display_name: greater,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_greater_or_equal,
|
||||
display_name: greater_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_less,
|
||||
display_name: less,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_less_or_equal,
|
||||
display_name: less_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_not_equal,
|
||||
display_name: not_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
|
||||
define_custom_bench_fn!(function_name: carry_extract, BenchParamsSet::Standard);
|
||||
|
||||
define_custom_bench_fn!(
|
||||
function_name: programmable_bootstrapping,
|
||||
BenchParamsSet::Standard
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
@@ -709,6 +812,8 @@ criterion_group!(
|
||||
scalar_not_equal
|
||||
);
|
||||
|
||||
criterion_group!(misc, server_key_from_compressed_key);
|
||||
|
||||
mod casting;
|
||||
criterion_group!(
|
||||
casting,
|
||||
@@ -722,6 +827,7 @@ fn main() {
|
||||
casting();
|
||||
default_ops();
|
||||
default_scalar_ops();
|
||||
misc();
|
||||
}
|
||||
|
||||
match env::var("__TFHE_RS_BENCH_OP_FLAVOR") {
|
||||
|
||||
@@ -37,4 +37,3 @@ foreach (testsourcefile ${TEST_CASES})
|
||||
# Enabled asserts even in release mode
|
||||
add_definitions(-UNDEBUG)
|
||||
endforeach (testsourcefile ${TEST_CASES})
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ int uint256_public_key(const ClientKey *client_key,
|
||||
lhs = expand_output[0];
|
||||
rhs = expand_output[1];
|
||||
// We can destroy the compact list
|
||||
// The expanded ciphertext are independant from it
|
||||
// The expanded ciphertext are independent from it
|
||||
compact_fhe_uint256_list_destroy(list);
|
||||
|
||||
ok = fhe_uint256_sub(lhs, rhs, &result);
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
## Getting Started
|
||||
* [Installation](getting_started/installation.md)
|
||||
* [Quick Start](getting_started/quick_start.md)
|
||||
* [Operations](getting_started/operations.md)
|
||||
* [Types & Operations](getting_started/operations.md)
|
||||
* [Benchmarks](getting_started/benchmarks.md)
|
||||
* [Security and Cryptography](getting_started/security_and_cryptography.md)
|
||||
|
||||
## Tutorials
|
||||
* [Homomorphic Parity Bit](tutorials/parity_bit.md)
|
||||
* [Homomorphic Case Changing on Latin String](tutorials/latin_fhe_string.md)
|
||||
* [Homomorphic Case Changing on Ascii String](tutorials/ascii_fhe_string.md)
|
||||
|
||||
## How To
|
||||
* [Configure Rust](how_to/rust_configuration.md)
|
||||
@@ -19,6 +19,7 @@
|
||||
* [Compress Ciphertexts/Keys](how_to/compress.md)
|
||||
* [Use Public Key Encryption](how_to/public_key.md)
|
||||
* [Use Trivial Ciphertext](how_to/trivial_ciphertext.md)
|
||||
* [Generic Function Bounds](how_to/trait_bounds.md)
|
||||
* [Use Parallelized PBS](how_to/parallelized_pbs.md)
|
||||
* [Use the C API](how_to/c_api.md)
|
||||
* [Use the JS on WASM API](how_to/js_on_wasm_api.md)
|
||||
@@ -55,4 +56,3 @@
|
||||
|
||||
## API references
|
||||
* [docs.rs](https://docs.rs/tfhe/)
|
||||
|
||||
|
||||
@@ -102,25 +102,23 @@ for buy_order in buy_orders.iter_mut() {
|
||||
#### The complete algorithm in plain Rust:
|
||||
|
||||
```rust
|
||||
fn volume_match_plain(sell_orders: &mut Vec<u16>, buy_orders: &mut Vec<u16>) {
|
||||
fn fill_orders(orders: &mut [u16], total_volume: u16) {
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for order in orders {
|
||||
let filled_amount = std::cmp::min(volume_left_to_transact, *order);
|
||||
*order = filled_amount;
|
||||
volume_left_to_transact -= filled_amount;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn volume_match(sell_orders: &mut [u16], buy_orders: &mut [u16]) {
|
||||
let total_sell_volume: u16 = sell_orders.iter().sum();
|
||||
let total_buy_volume: u16 = buy_orders.iter().sum();
|
||||
|
||||
let total_volume = std::cmp::min(total_buy_volume, total_sell_volume);
|
||||
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for sell_order in sell_orders.iter_mut() {
|
||||
let filled_amount = std::cmp::min(volume_left_to_transact, *sell_order);
|
||||
*sell_order = filled_amount;
|
||||
volume_left_to_transact -= filled_amount;
|
||||
}
|
||||
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for buy_order in buy_orders.iter_mut() {
|
||||
let filled_amount = std::cmp::min(volume_left_to_transact, *buy_order);
|
||||
*buy_order = filled_amount;
|
||||
volume_left_to_transact -= filled_amount;
|
||||
}
|
||||
fill_orders(sell_orders, total_volume);
|
||||
fill_orders(buy_orders, total_volume);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -155,15 +153,17 @@ Now, we can start implementing the algorithm with FHE:
|
||||
1. Calculate the total sell volume and the total buy volume.
|
||||
|
||||
```rust
|
||||
let mut total_sell_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for sell_order in sell_orders.iter_mut() {
|
||||
server_key.smart_add_assign(&mut total_sell_volume, sell_order);
|
||||
fn vector_sum(server_key: &ServerKey, orders: &mut [RadixCiphertext]) -> RadixCiphertext {
|
||||
let mut total_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for order in orders {
|
||||
server_key.smart_add_assign(&mut total_volume, order);
|
||||
}
|
||||
total_volume
|
||||
}
|
||||
|
||||
let mut total_buy_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for buy_order in buy_orders.iter_mut() {
|
||||
server_key.smart_add_assign(&mut total_buy_volume, buy_order);
|
||||
}
|
||||
let mut total_sell_volume = vector_sum(server_key, sell_orders);
|
||||
let mut total_buy_volume = vector_sum(server_key, buy_orders);
|
||||
|
||||
```
|
||||
|
||||
2. Find the total volume that will be transacted by taking the minimum of the total sell volume and the total buy
|
||||
@@ -177,17 +177,21 @@ let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_v
|
||||
reduce code duplication since the code for filling buy orders and sell orders are the same.
|
||||
|
||||
```rust
|
||||
let fill_orders = |orders: &mut [RadixCiphertext]| {
|
||||
let mut volume_left_to_transact = total_volume.clone();
|
||||
for mut order in orders.iter_mut() {
|
||||
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, &mut order);
|
||||
fn fill_orders(
|
||||
server_key: &ServerKey,
|
||||
orders: &mut [RadixCiphertext],
|
||||
total_volume: RadixCiphertext,
|
||||
) {
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for order in orders {
|
||||
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, order);
|
||||
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fill_orders(sell_orders);
|
||||
fill_orders(buy_orders);
|
||||
fill_orders(server_key, sell_orders, total_volume.clone());
|
||||
fill_orders(server_key, buy_orders, total_volume);
|
||||
```
|
||||
|
||||
#### The complete algorithm in TFHE-rs:
|
||||
@@ -195,36 +199,40 @@ fill_orders(buy_orders);
|
||||
```rust
|
||||
const NUMBER_OF_BLOCKS: usize = 8;
|
||||
|
||||
fn volume_match_fhe(
|
||||
fn vector_sum(server_key: &ServerKey, orders: &mut [RadixCiphertext]) -> RadixCiphertext {
|
||||
let mut total_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for order in orders {
|
||||
server_key.smart_add_assign(&mut total_volume, order);
|
||||
}
|
||||
total_volume
|
||||
}
|
||||
|
||||
fn fill_orders(
|
||||
server_key: &ServerKey,
|
||||
orders: &mut [RadixCiphertext],
|
||||
total_volume: RadixCiphertext,
|
||||
) {
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for order in orders {
|
||||
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, order);
|
||||
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn volume_match(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let mut total_sell_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for sell_order in sell_orders.iter_mut() {
|
||||
server_key.smart_add_assign(&mut total_sell_volume, sell_order);
|
||||
}
|
||||
|
||||
let mut total_buy_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for buy_order in buy_orders.iter_mut() {
|
||||
server_key.smart_add_assign(&mut total_buy_volume, buy_order);
|
||||
}
|
||||
let mut total_sell_volume = vector_sum(server_key, sell_orders);
|
||||
let mut total_buy_volume = vector_sum(server_key, buy_orders);
|
||||
|
||||
let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_volume);
|
||||
|
||||
let fill_orders = |orders: &mut [RadixCiphertext]| {
|
||||
let mut volume_left_to_transact = total_volume.clone();
|
||||
for mut order in orders.iter_mut() {
|
||||
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, &mut order);
|
||||
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
};
|
||||
|
||||
fill_orders(sell_orders);
|
||||
fill_orders(buy_orders);
|
||||
fill_orders(server_key, sell_orders, total_volume.clone());
|
||||
fill_orders(server_key, buy_orders, total_volume);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Optimizing the implementation
|
||||
@@ -235,63 +243,73 @@ fn volume_match_fhe(
|
||||
|
||||
* We can parallelize vector sum with Rayon and `reduce` operation.
|
||||
```rust
|
||||
let parallel_vector_sum = |vec: &mut [RadixCiphertext]| {
|
||||
vec.to_vec().into_par_iter().reduce(
|
||||
fn vector_sum(server_key: &ServerKey, orders: Vec<RadixCiphertext>) -> RadixCiphertext {
|
||||
orders.into_par_iter().reduce(
|
||||
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|
||||
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
|
||||
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
|
||||
server_key.smart_add_parallelized(&mut acc, &mut ele)
|
||||
},
|
||||
)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
* We can run vector summation on `buy_orders` and `sell_orders` in parallel since these operations do not depend on each other.
|
||||
```rust
|
||||
let (mut total_sell_volume, mut total_buy_volume) =
|
||||
rayon::join(|| vector_sum(sell_orders), || vector_sum(buy_orders));
|
||||
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|
||||
|| vector_sum(server_key, sell_orders.to_owned()),
|
||||
|| vector_sum(server_key, buy_orders.to_owned()),
|
||||
);
|
||||
```
|
||||
|
||||
* We can match sell and buy orders in parallel since the matching does not depend on each other.
|
||||
```rust
|
||||
rayon::join(|| fill_orders(sell_orders), || fill_orders(buy_orders));
|
||||
rayon::join(
|
||||
|| fill_orders(server_key, sell_orders, total_volume.clone()),
|
||||
|| fill_orders(server_key, buy_orders, total_volume.clone()),
|
||||
);
|
||||
```
|
||||
|
||||
#### Optimized algorithm
|
||||
```rust
|
||||
fn volume_match_fhe_parallelized(
|
||||
fn vector_sum(server_key: &ServerKey, orders: Vec<RadixCiphertext>) -> RadixCiphertext {
|
||||
orders.into_par_iter().reduce(
|
||||
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|
||||
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
|
||||
server_key.smart_add_parallelized(&mut acc, &mut ele)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn fill_orders(
|
||||
server_key: &ServerKey,
|
||||
orders: &mut [RadixCiphertext],
|
||||
total_volume: RadixCiphertext,
|
||||
) {
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for order in orders {
|
||||
let mut filled_amount =
|
||||
server_key.smart_min_parallelized(&mut volume_left_to_transact, order);
|
||||
server_key.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn volume_match(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let parallel_vector_sum = |vec: &mut [RadixCiphertext]| {
|
||||
vec.to_vec().into_par_iter().reduce(
|
||||
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|
||||
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
|
||||
server_key.smart_add_parallelized(&mut acc, &mut ele)
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|
||||
|| parallel_vector_sum(sell_orders),
|
||||
|| parallel_vector_sum(buy_orders),
|
||||
|| vector_sum(server_key, sell_orders.to_owned()),
|
||||
|| vector_sum(server_key, buy_orders.to_owned()),
|
||||
);
|
||||
|
||||
let total_volume =
|
||||
server_key.smart_min_parallelized(&mut total_sell_volume, &mut total_buy_volume);
|
||||
|
||||
let fill_orders = |orders: &mut [RadixCiphertext]| {
|
||||
let mut volume_left_to_transact = total_volume.clone();
|
||||
for mut order in orders.iter_mut() {
|
||||
let mut filled_amount =
|
||||
server_key.smart_min_parallelized(&mut volume_left_to_transact, &mut order);
|
||||
server_key
|
||||
.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
};
|
||||
|
||||
rayon::join(|| fill_orders(sell_orders), || fill_orders(buy_orders));
|
||||
rayon::join(
|
||||
|| fill_orders(server_key, sell_orders, total_volume.clone()),
|
||||
|| fill_orders(server_key, buy_orders, total_volume.clone()),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -312,14 +330,19 @@ We will call the new list the "prefix sum" of the array.
|
||||
|
||||
The new version for the plain `fill_orders` is as follows:
|
||||
```rust
|
||||
let fill_orders = |orders: &mut [u64], prefix_sum: &[u64], total_orders: u64|{
|
||||
fn fill_orders(total_orders: u16, orders: &mut [u16], prefix_sum_arr: &[u16]) {
|
||||
orders.iter().for_each(|order : &mut u64| {
|
||||
if (total_orders >= prefix_sum[i]) {
|
||||
continue;
|
||||
} else if total_orders >= prefix_sum.get(i-1).unwrap_or(0) {
|
||||
*order = total_orders - prefix_sum.get(i-1).unwrap_or(0);
|
||||
} else {
|
||||
let previous_prefix_sum = if i == 0 { 0 } else { prefix_sum_arr[i - 1] };
|
||||
|
||||
let diff = total_orders as i64 - previous_prefix_sum as i64;
|
||||
|
||||
if (diff < 0) {
|
||||
*order = 0;
|
||||
} else if diff < order {
|
||||
*order = diff as u16;
|
||||
} else {
|
||||
// *order = *order;
|
||||
continue;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -328,11 +351,15 @@ let fill_orders = |orders: &mut [u64], prefix_sum: &[u64], total_orders: u64|{
|
||||
To write this new function we need transform the conditional code into a mathematical expression since FHE does not support conditional operations.
|
||||
```rust
|
||||
|
||||
let fill_orders = |orders: &mut [u64], prefix_sum: &[u64], total_orders: u64| {
|
||||
orders.iter().for_each(|order| : &mut){
|
||||
*order = *order + ((total_orders >= prefix_sum - std::cmp::min(total_orders, prefix_sum.get(i - 1).unwrap_or(&0).clone()) - *order);
|
||||
fn fill_orders(total_orders: u16, orders: &mut [u16], prefix_sum_arr: &[u16]) {
|
||||
for (i, order) in orders.iter_mut().enumerate() {
|
||||
let previous_prefix_sum = if i == 0 { 0 } else { prefix_sum_arr[i - 1] };
|
||||
|
||||
*order = (total_orders as i64 - previous_prefix_sum as i64)
|
||||
.max(0)
|
||||
.min(*order as i64) as u16;
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
New `fill_order` function requires a prefix sum array. We are going to calculate this prefix sum array in parallel
|
||||
@@ -345,108 +372,129 @@ So we modify how the algorithm is implemented, but we don't change the algorithm
|
||||
|
||||
Here is the modified version of the algorithm in TFHE-rs:
|
||||
```rust
|
||||
fn volume_match_fhe_modified(
|
||||
fn compute_prefix_sum(server_key: &ServerKey, arr: &[RadixCiphertext]) -> Vec<RadixCiphertext> {
|
||||
if arr.is_empty() {
|
||||
return arr.to_vec();
|
||||
}
|
||||
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
|
||||
.into_par_iter()
|
||||
.map(|i| {
|
||||
if i < arr.len() {
|
||||
arr[i].clone()
|
||||
} else {
|
||||
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
for d in 0..prefix_sum.len().ilog2() {
|
||||
prefix_sum
|
||||
.par_chunks_exact_mut(2_usize.pow(d + 1))
|
||||
.for_each(move |chunk| {
|
||||
let length = chunk.len();
|
||||
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
|
||||
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
|
||||
});
|
||||
}
|
||||
let last = prefix_sum.last().unwrap().clone();
|
||||
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for d in (0..prefix_sum.len().ilog2()).rev() {
|
||||
prefix_sum
|
||||
.par_chunks_exact_mut(2_usize.pow(d + 1))
|
||||
.for_each(move |chunk| {
|
||||
let length = chunk.len();
|
||||
let temp = chunk.last().unwrap().clone();
|
||||
let mut mid = chunk.get((length - 1) / 2).unwrap().clone();
|
||||
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut mid);
|
||||
chunk[(length - 1) / 2] = temp;
|
||||
});
|
||||
}
|
||||
prefix_sum.push(last);
|
||||
prefix_sum[1..=arr.len()].to_vec()
|
||||
}
|
||||
|
||||
fn fill_orders(
|
||||
server_key: &ServerKey,
|
||||
total_orders: &RadixCiphertext,
|
||||
orders: &mut [RadixCiphertext],
|
||||
prefix_sum_arr: &[RadixCiphertext],
|
||||
) {
|
||||
orders
|
||||
.into_par_iter()
|
||||
.enumerate()
|
||||
.for_each(move |(i, order)| {
|
||||
// (total_orders - previous_prefix_sum).max(0)
|
||||
let mut diff = if i == 0 {
|
||||
total_orders.clone()
|
||||
} else {
|
||||
let previous_prefix_sum = &prefix_sum_arr[i - 1];
|
||||
|
||||
// total_orders - previous_prefix_sum
|
||||
let mut diff = server_key.smart_sub_parallelized(
|
||||
&mut total_orders.clone(),
|
||||
&mut previous_prefix_sum.clone(),
|
||||
);
|
||||
|
||||
// total_orders > prefix_sum
|
||||
let mut cond = server_key.smart_gt_parallelized(
|
||||
&mut total_orders.clone(),
|
||||
&mut previous_prefix_sum.clone(),
|
||||
);
|
||||
|
||||
// (total_orders - previous_prefix_sum) * (total_orders > previous_prefix_sum)
|
||||
// = (total_orders - previous_prefix_sum).max(0)
|
||||
server_key.smart_mul_parallelized(&mut cond, &mut diff)
|
||||
};
|
||||
|
||||
// (total_orders - previous_prefix_sum).max(0).min(*order);
|
||||
*order = server_key.smart_min_parallelized(&mut diff, order);
|
||||
});
|
||||
}
|
||||
|
||||
/// FHE implementation of the volume matching algorithm.
|
||||
///
|
||||
/// In this function, the implemented algorithm is modified to utilize more concurrency.
|
||||
///
|
||||
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
|
||||
/// [server_key]. The amount of the orders that are successfully filled is written over the original
|
||||
/// order count.
|
||||
pub fn volume_match(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let compute_prefix_sum = |arr: &[RadixCiphertext]| {
|
||||
if arr.is_empty() {
|
||||
return arr.to_vec();
|
||||
}
|
||||
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
|
||||
.into_par_iter()
|
||||
.map(|i| {
|
||||
if i < arr.len() {
|
||||
arr[i].clone()
|
||||
} else {
|
||||
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// Up sweep
|
||||
for d in 0..(prefix_sum.len().ilog2() as u32) {
|
||||
prefix_sum
|
||||
.par_chunks_exact_mut(2_usize.pow(d + 1))
|
||||
.for_each(move |chunk| {
|
||||
let length = chunk.len();
|
||||
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
|
||||
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
|
||||
});
|
||||
}
|
||||
// Down sweep
|
||||
let last = prefix_sum.last().unwrap().clone();
|
||||
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for d in (0..(prefix_sum.len().ilog2() as u32)).rev() {
|
||||
prefix_sum
|
||||
.par_chunks_exact_mut(2_usize.pow(d + 1))
|
||||
.for_each(move |chunk| {
|
||||
let length = chunk.len();
|
||||
let t = chunk.last().unwrap().clone();
|
||||
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
|
||||
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left);
|
||||
chunk[(length - 1) / 2] = t;
|
||||
});
|
||||
}
|
||||
prefix_sum.push(last);
|
||||
prefix_sum[1..=arr.len()].to_vec()
|
||||
};
|
||||
|
||||
println!("Creating prefix sum arrays...");
|
||||
let time = Instant::now();
|
||||
let (prefix_sum_sell_orders, prefix_sum_buy_orders) = rayon::join(
|
||||
|| compute_prefix_sum(sell_orders),
|
||||
|| compute_prefix_sum(buy_orders),
|
||||
|| compute_prefix_sum(server_key, sell_orders),
|
||||
|| compute_prefix_sum(server_key, buy_orders),
|
||||
);
|
||||
println!("Created prefix sum arrays in {:?}", time.elapsed());
|
||||
|
||||
let fill_orders = |total_orders: &RadixCiphertext,
|
||||
orders: &mut [RadixCiphertext],
|
||||
prefix_sum_arr: &[RadixCiphertext]| {
|
||||
orders
|
||||
.into_par_iter()
|
||||
.enumerate()
|
||||
.for_each(move |(i, order)| {
|
||||
server_key.smart_add_assign_parallelized(
|
||||
order,
|
||||
&mut server_key.smart_mul_parallelized(
|
||||
&mut server_key
|
||||
.smart_ge_parallelized(&mut order.clone(), &mut total_orders.clone()),
|
||||
&mut server_key.smart_sub_parallelized(
|
||||
&mut server_key.smart_sub_parallelized(
|
||||
&mut total_orders.clone(),
|
||||
&mut server_key.smart_min_parallelized(
|
||||
&mut total_orders.clone(),
|
||||
&mut prefix_sum_arr
|
||||
.get(i - 1)
|
||||
.unwrap_or(
|
||||
&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|
||||
)
|
||||
.clone(),
|
||||
),
|
||||
),
|
||||
&mut order.clone(),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
};
|
||||
let zero = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
|
||||
let total_buy_orders = &mut prefix_sum_buy_orders
|
||||
.last()
|
||||
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
|
||||
.clone();
|
||||
let total_buy_orders = prefix_sum_buy_orders.last().unwrap_or(&zero);
|
||||
|
||||
let total_sell_orders = &mut prefix_sum_sell_orders
|
||||
.last()
|
||||
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
|
||||
.clone();
|
||||
let total_sell_orders = prefix_sum_sell_orders.last().unwrap_or(&zero);
|
||||
|
||||
println!("Matching orders...");
|
||||
let time = Instant::now();
|
||||
rayon::join(
|
||||
|| fill_orders(total_sell_orders, buy_orders, &prefix_sum_buy_orders),
|
||||
|| fill_orders(total_buy_orders, sell_orders, &prefix_sum_sell_orders),
|
||||
|| {
|
||||
fill_orders(
|
||||
server_key,
|
||||
total_sell_orders,
|
||||
buy_orders,
|
||||
&prefix_sum_buy_orders,
|
||||
)
|
||||
},
|
||||
|| {
|
||||
fill_orders(
|
||||
server_key,
|
||||
total_buy_orders,
|
||||
sell_orders,
|
||||
&prefix_sum_sell_orders,
|
||||
)
|
||||
},
|
||||
);
|
||||
println!("Matched orders in {:?}", time.elapsed());
|
||||
}
|
||||
|
||||
@@ -504,7 +504,7 @@ Pattern | Description
|
||||
`/^abc$/` | Matches with content that equals exactly `abc` (case sensitive)
|
||||
`/^abc$/i` | Matches with content that equals `abc` (case insensitive)
|
||||
`/abc/` | Matches with content that contains somewhere `abc`
|
||||
`/ab?c/` | Matches with content that contains somewhere `abc` or somwhere `ab`
|
||||
`/ab?c/` | Matches with content that contains somewhere `abc` or somewhere `ab`
|
||||
`/^ab*c$/` | For example, matches with: `ac`, `abc`, `abbbbc`
|
||||
`/^[a-c]b\|cd$/` | Matches with: `ab`, `bb`, `cb`, `cd`
|
||||
`/^[a-c]b\|cd$/i` | Matches with: `ab`, `Ab`, `aB`, ..., `cD`, `CD`
|
||||
|
||||
@@ -136,7 +136,11 @@ fn sha256(padded_input: Vec<bool>) -> [bool; 256] {
|
||||
|
||||
The key idea is that we can replace each bit of ```padded_input``` with a Fully Homomorphic Encryption of the same bit value, and operate over the encrypted values using homomorphic operations. To achieve this we need to change the function signatures and deal with the borrowing rules of the Ciphertext type (which represents an encrypted bit) but the structure of the sha256 function remains the same. The part of the code that requires more consideration is the implementation of the sha256 operations, since they will use homomorphic boolean operations internally.
|
||||
|
||||
Homomorphic operations are really expensive, so we have to remove their unnecessary use and maximize parallelization in order to speed up the program. To simplify our code we use the Rayon crate which provides parallel iterators and efficiently manages threads. Let's now take a look at each sha256 operation!
|
||||
Homomorphic operations are really expensive, so we have to remove their unnecessary use and maximize parallelization in order to speed up the program. To simplify our code we use the Rayon crate which provides parallel iterators and efficiently manages threads.
|
||||
|
||||
The final code is available at https://github.com/zama-ai/tfhe-rs/tree/main/tfhe/examples/sha256_bool
|
||||
|
||||
Let's now take a look at each sha256 operation!
|
||||
|
||||
#### Rotate Right and Shift Right
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Welcome to this tutorial about `TFHE-rs` `core_crypto` module.
|
||||
To use `TFHE-rs`, it first has to be added as a dependency in the `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = [ "x86_64-unix" ] }
|
||||
tfhe = { version = "0.4.0", features = [ "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available (like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available). To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
|
||||
@@ -19,19 +19,19 @@ For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `a
|
||||
In short: For `x86_64`-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
|
||||
tfhe = { version = "0.4.0", features = ["x86_64-unix"] }
|
||||
```
|
||||
|
||||
For Apple Silicon or aarch64-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = ["aarch64-unix"] }
|
||||
tfhe = { version = "0.4.0", features = ["aarch64-unix"] }
|
||||
```
|
||||
|
||||
For `x86_64`-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = ["x86_64"] }
|
||||
tfhe = { version = "0.4.0", features = ["x86_64"] }
|
||||
```
|
||||
|
||||
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.
|
||||
@@ -249,7 +249,7 @@ pub fn main() {
|
||||
println!("Checking result...");
|
||||
assert_eq!(6, pbs_multiplication_result);
|
||||
println!(
|
||||
"Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
|
||||
"Multiplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -86,7 +86,7 @@ fn main() {
|
||||
// Don't consider the following line; you should follow the procedure above.
|
||||
let (client_key, _) = gen_keys();
|
||||
|
||||
//---------------------------- SERVER SIDE
|
||||
//---------------------------- CLIENT SIDE
|
||||
|
||||
// We use the client key to encrypt the messages:
|
||||
let ct_1 = client_key.encrypt(true);
|
||||
@@ -104,7 +104,8 @@ fn main() {
|
||||
|
||||
## Encrypting inputs using a public key
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client simply needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be utilized:
|
||||
Anyone (the server or a third party) with the public key can also encrypt some (or all) of the inputs.
|
||||
The public key can only be used to encrypt, not to decrypt.
|
||||
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
@@ -114,18 +115,18 @@ fn main() {
|
||||
let (client_key, _) = gen_keys();
|
||||
let public_key = PublicKey::new(&client_key);
|
||||
|
||||
//---------------------------- SERVER SIDE
|
||||
//---------------------------- SERVER or THIRD_PARTY SIDE
|
||||
|
||||
// We use the public key to encrypt the messages:
|
||||
let ct_1 = public_key.encrypt(true);
|
||||
let ct_2 = public_key.encrypt(false);
|
||||
|
||||
// We serialize the ciphertexts:
|
||||
// We serialize the ciphertexts (if not on the server already):
|
||||
let encoded_1: Vec<u8> = bincode::serialize(&ct_1).unwrap();
|
||||
let encoded_2: Vec<u8> = bincode::serialize(&ct_2).unwrap();
|
||||
|
||||
// ...
|
||||
// And we send them to the server somehow
|
||||
// And we send them to the server to be deserialized (if not on the server already)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ This crate implements two ways to represent an integer:
|
||||
|
||||
The first possibility to represent a large integer is to use a Radix-based decomposition on the plaintexts. Let $$B \in \mathbb{N}$$ be a basis such that the size of $$B$$ is smaller than (or equal to) 4 bits. Then, an integer $$m \in \mathbb{N}$$ can be written as $$m = m_0 + m_1*B + m_2*B^2 + ...$$, where each $$m_i$$ is strictly smaller than $$B$$. Each $$m_i$$ is then independently encrypted. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
|
||||
|
||||
The definition of an integer requires a basis and a number of blocks. This is done at key generation. Below, the keys are dedicated to unsigned integers encrypting messages over 8 bits, using a basis over 2 bits (i.e., $$B=2^2$$) and 4 blocks.
|
||||
The definition of an integer requires a basis and a number of blocks. This is done at key generation. Below, the keys are dedicated to integers encrypting messages over 8 bits, using a basis over 2 bits (i.e., $$B=2^2$$) and 4 blocks.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
@@ -162,12 +162,15 @@ fn main() {
|
||||
assert!(result.is_ok());
|
||||
|
||||
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let result = server_key.checked_add_assign(&mut ct_1, &ct_3);
|
||||
assert!(result.is_err());
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
// Only the scalar multiplication could be done
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, (msg1 * scalar) % modulus as u64);
|
||||
assert_eq!(output, ((msg1 * scalar) - msg2) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tutorial
|
||||
|
||||
`tfhe::integer` is dedicated to unsigned integers smaller than 256 bits. The steps to homomorphically evaluate an integer circuit are described here.
|
||||
`tfhe::integer` is dedicated to integers smaller than 256 bits. The steps to homomorphically evaluate an integer circuit are described here.
|
||||
|
||||
## Key Types
|
||||
|
||||
@@ -25,7 +25,7 @@ To generate the keys, a user needs two parameters:
|
||||
* A set of `shortint` cryptographic parameters.
|
||||
* The number of ciphertexts used to encrypt an integer (we call them "shortint blocks").
|
||||
|
||||
We are now going to build a pair of keys that can encrypt an **8-bit** integer by using **4** shortint blocks that store **2** bits of message each.
|
||||
We are now going to build a pair of keys that can encrypt **8-bit** integers (signed or unsigned) by using **4** shortint blocks that store **2** bits of message each.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tutorial
|
||||
|
||||
`tfhe::shortint` is dedicated to small unsigned integers smaller than 8 bits. The steps to homomorphically evaluate a circuit are described below.
|
||||
`tfhe::shortint` is dedicated to small integers smaller than 8 bits. The steps to homomorphically evaluate a circuit are described below.
|
||||
|
||||
## Key generation
|
||||
|
||||
|
||||
@@ -6,47 +6,22 @@ Due to their nature, homomorphic operations are naturally slower than their clea
|
||||
All benchmarks were launched on an AWS m6i.metal with the following specifications: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
|
||||
{% endhint %}
|
||||
|
||||
## Boolean
|
||||
|
||||
This measures the execution time of a single binary Boolean gate.
|
||||
|
||||
### tfhe-rs::boolean.
|
||||
|
||||
| Parameter set | Concrete FFT | Concrete FFT + AVX-512 |
|
||||
| --------------------- | ------------ | ---------------------- |
|
||||
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
|
||||
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
|
||||
|
||||
### tfhe-lib.
|
||||
|
||||
| Parameter set | fftw | spqlios-fma |
|
||||
| ------------------------------------------------ | ------ | ----------- |
|
||||
| default\_128bit\_gate\_bootstrapping\_parameters | 28.9ms | 15.7ms |
|
||||
|
||||
### OpenFHE.
|
||||
|
||||
| Parameter set | GINX | GINX (Intel HEXL) |
|
||||
| ------------- | ----- | ----------------- |
|
||||
| STD\_128 | 172ms | 78ms |
|
||||
| MEDIUM | 113ms | 50.2ms |
|
||||
|
||||
|
||||
## Integer
|
||||
This measures the execution time for some operation sets of tfhe-rs::integer.
|
||||
|
||||
This measures the execution time for some operation sets of tfhe-rs::integer. Note that the timings with `FheInt` are similar.
|
||||
|
||||
| Operation \ Size | `FheUint8` | `FheUint16` | `FheUint32` | ` FheUint64` | `FheUint128` | `FheUint256` |
|
||||
|--------------------------------------------------------|------------|-------------|-------------|--------------|--------------|--------------|
|
||||
| Negation (`-`) | 80.4 ms | 106 ms | 132 ms | 193 ms | 257 ms | 348 ms |
|
||||
| Add / Sub (`+`,`-`) | 81.5 ms | 110 ms | 139 ms | 200 ms | 262 ms | 355 ms |
|
||||
| Mul (`x`) | 150 ms | 221 ms | 361 ms | 928 ms | 2.90 s | 10.97 s |
|
||||
| Equal / Not Equal (`eq`, `ne`) | 39.4 ms | 40.2 ms | 61.1 ms | 66.4 ms | 74.5 ms | 85.7 ms |
|
||||
| Comparisons (`ge`, `gt`, `le`, `lt`) | 57.5 ms | 79.6 ms | 105 ms | 136 ms | 174 ms | 219 ms |
|
||||
| Max / Min (`max`,`min`) | 100 ms | 130 ms | 163 ms | 204 ms | 245 ms | 338 ms |
|
||||
| Bitwise operations (`&`, `|`, `^`) | 20.7 ms | 21.1 ms | 22.6 ms | 30.2 ms | 34.1 ms | 42.1 ms |
|
||||
| Div / Rem (`/`, `%`) | 1.37 s | 3.50 s | 9.12 s | 23.9 s | 59.9 s | 149.2 s |
|
||||
| Left / Right Shifts (`<<`, `>>`) | 106 ms | 140 ms | 202 ms | 262 ms | 403 ms | 827 ms |
|
||||
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 105 ms | 140 ms | 199 ms | 263 ms | 403 ms | 829 ms |
|
||||
|
||||
| Negation (`-`) | 70.9 ms | 99.3 ms | 129 ms | 180 ms | 239 ms | 333 ms |
|
||||
| Add / Sub (`+`,`-`) | 70.5 ms | 100 ms | 132 ms | 186 ms | 249 ms | 334 ms |
|
||||
| Mul (`x`) | 144 ms | 216 ms | 333 ms | 832 ms | 2.50 s | 8.85 s |
|
||||
| Equal / Not Equal (`eq`, `ne`) | 36.1 ms | 36.5 ms | 57.4 ms | 64.2 ms | 67.3 ms | 78.1 ms |
|
||||
| Comparisons (`ge`, `gt`, `le`, `lt`) | 52.6 ms | 73.1 ms | 98.8 ms | 124 ms | 165 ms | 201 ms |
|
||||
| Max / Min (`max`,`min`) | 76.2 ms | 102 ms | 135 ms | 171 ms | 212 ms | 301 ms |
|
||||
| Bitwise operations (`&`, `\|`, `^`) | 19.4 ms | 20.3 ms | 21.0 ms | 27.2 ms | 31.6 ms | 40.2 ms |
|
||||
| Div / Rem (`/`, `%`) | 1.10 s | 2.97 s | 7.17 s | 19.7 s | 50.2 s | 131 s |
|
||||
| Left / Right Shifts (`<<`, `>>`) | 99.4 ms | 129 ms | 180 ms | 243 ms | 372 ms | 762 ms |
|
||||
| Left / Right Rotations (`left_rotate`, `right_rotate`) | 103 ms | 128 ms | 182 ms | 241 ms | 374 ms | 763 ms |
|
||||
|
||||
|
||||
All timings are related to parallelized Radix-based integer operations, where each block is encrypted using the default parameters (i.e., PARAM\_MESSAGE\_2\_CARRY\_2, more information about parameters can be found [here](../fine_grained_api/shortint/parameters.md)).
|
||||
@@ -54,29 +29,65 @@ To ensure predictable timings, the operation flavor is the `default` one: the ca
|
||||
|
||||
|
||||
## Shortint
|
||||
This measures the execution time for some operations using various parameter sets of tfhe-rs::shortint.
|
||||
This measures the execution time for some operations using various parameter sets of tfhe-rs::shortint. Except from the `unchecked_add`, all timings are related to the `default` operations. This flavor ensures predictable timings of an operation along the entire circuit by clearing the carry space after each operation.
|
||||
|
||||
This uses the Concrete FFT + AVX-512 configuration.
|
||||
|
||||
|
||||
| Parameter set | unchecked\_add | unchecked\_mul\_lsb | keyswitch\_programmable\_bootstrap |
|
||||
|-----------------------------|----------------|---------------------|------------------------------------|
|
||||
| PARAM\_MESSAGE\_1\_CARRY\_1 | 338 ns | 8.3 ms | 8.1 ms |
|
||||
| PARAM\_MESSAGE\_2\_CARRY\_2 | 406 ns | 18.4 ms | 18.4 ms |
|
||||
| PARAM\_MESSAGE\_3\_CARRY\_3 | 3.06 µs | 134 ms | 129.4 ms |
|
||||
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 828.1 ms |
|
||||
| Parameter set | PARAM\_MESSAGE\_1\_CARRY\_1 | PARAM\_MESSAGE\_2\_CARRY\_2 | PARAM\_MESSAGE\_3\_CARRY\_3 | PARAM\_MESSAGE\_4\_CARRY\_4 |
|
||||
|------------------------------------|-----------------------------|-----------------------------|-----------------------------|-----------------------------|
|
||||
| unchecked\_add | 348 ns | 413 ns | 2.95 µs | 12.1 µs |
|
||||
| add | 7.59 ms | 17.0 ms | 121 ms | 835 ms |
|
||||
| mul\_lsb | 8.13 ms | 16.8 ms | 121 ms | 827 ms |
|
||||
| keyswitch\_programmable\_bootstrap | 7.28 ms | 16.6 ms | 121 ms | 811 ms |
|
||||
|
||||
Next, the timings for the operation flavor `default` are given. This flavor ensures predictable timings of an operation along the entire circuit by clearing the carry space after each operation.
|
||||
|
||||
| Parameter set | add | mul\_lsb | keyswitch\_programmable\_bootstrap |
|
||||
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
|
||||
| PARAM\_MESSAGE\_1\_CARRY\_1 | 7.90 ms | 8.00 ms | 8.10 ms |
|
||||
| PARAM\_MESSAGE\_2\_CARRY\_2 | 18.4 ms | 18.1 ms | 18.4 ms |
|
||||
| PARAM\_MESSAGE\_3\_CARRY\_3 | 131.5 ms | 129.5 ms | 129.4 ms |
|
||||
| PARAM\_MESSAGE\_4\_CARRY\_4 | 852.5 ms | 839.7 ms | 828.1 ms |
|
||||
## Boolean
|
||||
|
||||
## How to reproduce benchmarks
|
||||
This measures the execution time of a single binary Boolean gate.
|
||||
|
||||
TFHE-rs benchmarks can easily be reproduced from the [sources](https://github.com/zama-ai/tfhe-rs).
|
||||
### tfhe-rs::boolean.
|
||||
|
||||
| Parameter set | Concrete FFT + AVX-512 |
|
||||
|------------------------------------------------------|------------------------|
|
||||
| DEFAULT\_PARAMETERS\_KS\_PBS | 9.19 ms |
|
||||
| PARAMETERS\_ERROR\_PROB\_2\_POW\_MINUS\_165\_KS\_PBS | 14.1 ms |
|
||||
| TFHE\_LIB\_PARAMETERS | 10.0 ms |
|
||||
|
||||
### tfhe-lib.
|
||||
|
||||
| Parameter set | spqlios-fma |
|
||||
|--------------------------------------------------|-------------|
|
||||
| default\_128bit\_gate\_bootstrapping\_parameters | 15.4 ms |
|
||||
|
||||
### OpenFHE (v1.1.1).
|
||||
|
||||
Following the instructions from OpenFHE maintainers, `clang14` is used and this command to setup the project:
|
||||
`cmake -DNATIVE_SIZE=32 -DWITH_NATIVEOPT=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DWITH_OPENMP=OFF ..`
|
||||
|
||||
To use the HEXL library, the configuration file is:
|
||||
```bash
|
||||
export CXX=clang++
|
||||
export CC=clang
|
||||
|
||||
scripts/configure.sh
|
||||
Release -> y
|
||||
hexl -> y
|
||||
|
||||
scripts/build-openfhe-development-hexl.sh
|
||||
```
|
||||
|
||||
Using the same m6i machine as previous benchmarks, the timings are:
|
||||
|
||||
| Parameter set | GINX | GINX w/ Intel HEXL |
|
||||
|----------------------------------|---------|--------------------|
|
||||
| FHEW\_BINGATE/STD128\_OR | 40.2 ms | 31.0 ms |
|
||||
| FHEW\_BINGATE/STD128\_LMKCDEY_OR | 38.6 ms | 28.4 ms |
|
||||
|
||||
|
||||
## How to reproduce TFHE-rs benchmarks
|
||||
|
||||
TFHE-rs benchmarks can be easily reproduced from the [sources](https://github.com/zama-ai/tfhe-rs).
|
||||
|
||||
```shell
|
||||
#Boolean benchmarks:
|
||||
@@ -95,7 +106,3 @@ If the host machine supports AVX-512, then the argument `AVX512_SUPPORT=ON' shou
|
||||
#Integer benchmarks:
|
||||
make AVX512_SUPPORT=ON bench_integer
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
|
||||
tfhe = { version = "0.4.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
{% hint style="success" %}
|
||||
@@ -27,6 +27,5 @@ TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (
|
||||
| Windows | `x86_64` | Unsupported |
|
||||
|
||||
{% hint style="info" %}
|
||||
Users who have ARM devices can use TFHE-rs by compiling using the `nightly` toolchain (see
|
||||
[Configuration](../how_to/rust_configuration.md) for more details).
|
||||
Users who have ARM devices can compile TFHE-rs using a stable toolchain with version >= 1.72 (see [Configuration](../how_to/rust_configuration.md) for more details).
|
||||
{% endhint %}
|
||||
|
||||
@@ -1,43 +1,325 @@
|
||||
# Operations
|
||||
# Homomorphic Types and Operations
|
||||
|
||||
The table below contains an overview of the available operations in `TFHE-rs`. More details, and further examples, are given in the following sections.
|
||||
## Types
|
||||
`TFHE-rs` includes two main types to represent encrypted data:
|
||||
- `FheUint`: this is the homomorphic equivalent of Rust `uint`
|
||||
- `FheInt`: this is the homomorphic equivalent of Rust `int`
|
||||
|
||||
| name | symbol | FheUint/FheUint | FheUint/Uint | Uint/FheUint |
|
||||
|-----------------------|-------------|--------------------|--------------------------|--------------------------|
|
||||
| Neg | `-` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Add | `+` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Sub | `-` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Mul | `*` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Div | `/` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Rem | `%` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Not | `!` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| BitAnd | `&` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| BitOr | `\|` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| BitXor | `^` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Shr | `>>` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Shl | `<<` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Min | `min` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Max | `max` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Greater than | `gt` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Greater or equal than | `ge` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Lower than | `lt` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Lower or equal than | `le` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Equal | `eq` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Cast (into dest type) | `cast_into` | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |
|
||||
| Cast (from src type) | `cast_from` | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: |
|
||||
In the same manner than many programming languages, the number of bits used to represent the data must be chosen when declaring a variable. For instance:
|
||||
|
||||
## Boolean Operations
|
||||
```Rust
|
||||
|
||||
Native homomorphic Booleans support common Boolean operations.
|
||||
// let clear_a: u64 = 7;
|
||||
let mut a = FheUint64::try_encrypt(clear_a, &keys)?;
|
||||
|
||||
// let clear_b: i8 = 3;
|
||||
let mut b = FheInt8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
// let clear_c: u128 = 2;
|
||||
let mut c = FheUint128::try_encrypt(clear_c, &keys)?;
|
||||
```
|
||||
|
||||
## Operation list
|
||||
The table below contains an overview of the available operations in `TFHE-rs`. The notation `Enc` (for Encypted Data) either refers to `FheInt` or `FheUint`, for any size between 1 and 256-bits.
|
||||
|
||||
More details, and further examples, are given in the following sections.
|
||||
|
||||
| name | symbol | `Enc`/`Enc` | `Enc`/ `Int` |
|
||||
|-----------------------|-------------|--------------------|--------------------------|
|
||||
| Neg | `-` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Add | `+` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Sub | `-` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Mul | `*` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Div | `/` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Rem | `%` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Not | `!` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| BitAnd | `&` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| BitOr | `\|` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| BitXor | `^` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Shr | `>>` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Shl | `<<` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Min | `min` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Max | `max` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Greater than | `gt` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Greater or equal than | `ge` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Lower than | `lt` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Lower or equal than | `le` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Equal | `eq` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Cast (into dest type) | `cast_into` | :heavy_check_mark: | :heavy_multiplication_x: |
|
||||
| Cast (from src type) | `cast_from` | :heavy_check_mark: | :heavy_multiplication_x: |
|
||||
|
||||
|
||||
## Integer
|
||||
|
||||
In TFHE-rs, integers are used to encrypt any messages larger than 4 bits. All supported operations are listed below.
|
||||
|
||||
### Arithmetic operations.
|
||||
|
||||
Homomorphic integer types support arithmetic operations.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
| ------------------------------------------------------------- | ------ | ------ |
|
||||
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
|
||||
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
|
||||
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
|
||||
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
|
||||
| name | symbol | type |
|
||||
|----------------------------------------------------------|--------|--------|
|
||||
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `-` | Unary |
|
||||
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
|
||||
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
|
||||
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
|
||||
| [Div](https://doc.rust-lang.org/std/ops/trait.Div.html)* | `/` | Binary |
|
||||
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html)* | `%` | Binary |
|
||||
|
||||
For division by $$0$$, the convention is to return $$modulus - 1$$. For instance, for FheUint8, the modulus is $$2^8=256$$, so a division by $$0$$ will return an encryption of $$255$$.
|
||||
For the remainder operator, the convention is to return the first input without any modification. For instance, for $$ct1 = FheUint8(63)$$ and $$ct2 = FheUint8(0)$$, then $$ct1 % ct2$$ will return $$FheUint8(63)$$.
|
||||
|
||||
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt8, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 15_u64;
|
||||
let clear_b = 27_u64;
|
||||
let clear_c = 43_u64;
|
||||
let clear_d = -87_i64;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
let mut c = FheUint8::try_encrypt(clear_c, &keys)?;
|
||||
let mut d = FheInt8::try_encrypt(clear_d, &keys)?;
|
||||
|
||||
|
||||
a = a * &b; // Clear equivalent computations: 15 * 27 mod 256 = 149
|
||||
b = &b + &c; // Clear equivalent computations: 27 + 43 mod 256 = 70
|
||||
b = b - 76u8; // Clear equivalent computations: 70 - 76 mod 256 = 250
|
||||
d = d - 13i8; // Clear equivalent computations: -87 - 13 = 100 in [-128, 128]
|
||||
|
||||
let dec_a: u8 = a.decrypt(&keys);
|
||||
let dec_b: u8 = b.decrypt(&keys);
|
||||
let dec_d: i8 = d.decrypt(&keys);
|
||||
|
||||
assert_eq!(dec_a, ((clear_a * clear_b) % 256_u64) as u8);
|
||||
assert_eq!(dec_b, (((clear_b + clear_c).wrapping_sub(76_u64)) % 256_u64) as u8);
|
||||
assert_eq!(dec_d, (clear_d - 13) as i8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Bitwise operations.
|
||||
|
||||
Homomorphic integer types support some bitwise operations.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
|--------------------------------------------------------------------------------------|----------------|--------|
|
||||
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
|
||||
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
|
||||
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
|
||||
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
|
||||
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
|
||||
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
|
||||
| [Rotate Right](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_right) | `rotate_right` | Binary |
|
||||
| [Rotate Left](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_left) | `rotate_left` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 164;
|
||||
let clear_b = 212;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
a = a ^ &b;
|
||||
b = b ^ &a;
|
||||
a = a ^ &b;
|
||||
|
||||
let dec_a: u8 = a.decrypt(&keys);
|
||||
let dec_b: u8 = b.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_a, clear_b);
|
||||
assert_eq!(dec_b, clear_a);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Comparisons.
|
||||
|
||||
Homomorphic integers support comparison operations. Since Rust does not allow the overloading of these operations, a simple function has been associated to each one.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
|-----------------------------------------------------------------------------|--------|--------|
|
||||
| [Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `eq` | Binary |
|
||||
| [Not Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `ne` | Binary |
|
||||
| [Greater Than ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `gt` | Binary |
|
||||
| [Greater or Equal](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `ge` | Binary |
|
||||
| [Lower ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `lt` | Binary |
|
||||
| [Lower or Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `le` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a:i8 = -121;
|
||||
let clear_b:i8 = 87;
|
||||
|
||||
let mut a = FheInt8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheInt8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
let greater = a.gt(&b);
|
||||
let greater_or_equal = a.ge(&b);
|
||||
let lower = a.lt(&b);
|
||||
let lower_or_equal = a.le(&b);
|
||||
let equal = a.eq(&b);
|
||||
|
||||
let dec_gt : i8 = greater.decrypt(&keys);
|
||||
let dec_ge : i8 = greater_or_equal.decrypt(&keys);
|
||||
let dec_lt : i8 = lower.decrypt(&keys);
|
||||
let dec_le : i8 = lower_or_equal.decrypt(&keys);
|
||||
let dec_eq : i8 = equal.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_gt, (clear_a > clear_b ) as i8);
|
||||
assert_eq!(dec_ge, (clear_a >= clear_b) as i8);
|
||||
assert_eq!(dec_lt, (clear_a < clear_b ) as i8);
|
||||
assert_eq!(dec_le, (clear_a <= clear_b) as i8);
|
||||
assert_eq!(dec_eq, (clear_a == clear_b) as i8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Min/Max.
|
||||
|
||||
Homomorphic integers support the min/max operations.
|
||||
|
||||
| name | symbol | type |
|
||||
| ---- | ------ | ------ |
|
||||
| Min | `min` | Binary |
|
||||
| Max | `max` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a:u8 = 164;
|
||||
let clear_b:u8 = 212;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
let min = a.min(&b);
|
||||
let max = a.max(&b);
|
||||
|
||||
let dec_min : u8 = min.decrypt(&keys);
|
||||
let dec_max : u8 = max.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_min, u8::min(clear_a, clear_b));
|
||||
assert_eq!(dec_max, u8::max(clear_a, clear_b));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Casting.
|
||||
Casting between integer types is possible via the `cast_from` associated function
|
||||
or the `cast_into` method.
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheInt16, FheUint8, FheUint32, FheUint16};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
// Casting requires server_key to set
|
||||
// (encryptions/decryptions do not need server_key to be set)
|
||||
set_server_key(server_key);
|
||||
|
||||
{
|
||||
let clear = 12_837u16;
|
||||
let a = FheUint16::encrypt(clear, &client_key);
|
||||
|
||||
// Downcasting
|
||||
let a: FheUint8 = a.cast_into();
|
||||
let da: u8 = a.decrypt(&client_key);
|
||||
assert_eq!(da, clear as u8);
|
||||
|
||||
// Upcasting
|
||||
let a: FheUint32 = a.cast_into();
|
||||
let da: u32 = a.decrypt(&client_key);
|
||||
assert_eq!(da, (clear as u8) as u32);
|
||||
}
|
||||
|
||||
{
|
||||
let clear = 12_837u16;
|
||||
let a = FheUint16::encrypt(clear, &client_key);
|
||||
|
||||
// Upcasting
|
||||
let a = FheUint32::cast_from(a);
|
||||
let da: u32 = a.decrypt(&client_key);
|
||||
assert_eq!(da, clear as u32);
|
||||
|
||||
// Downcasting
|
||||
let a = FheUint8::cast_from(a);
|
||||
let da: u8 = a.decrypt(&client_key);
|
||||
assert_eq!(da, (clear as u32) as u8);
|
||||
}
|
||||
|
||||
{
|
||||
let clear = 12_837i16;
|
||||
let a = FheInt16::encrypt(clear, &client_key);
|
||||
|
||||
// Casting from FheInt16 to FheUint16
|
||||
let a = FheUint16::cast_from(a);
|
||||
let da: u16 = a.decrypt(&client_key);
|
||||
assert_eq!(da, clear as u16);
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## ShortInt Operations
|
||||
|
||||
@@ -252,257 +534,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
## Integer
|
||||
|
||||
In TFHE-rs, integers are used to encrypt any messages larger than 4 bits. All supported operations are listed below.
|
||||
|
||||
### Arithmetic operations.
|
||||
## Boolean Operations
|
||||
|
||||
Homomorphic integer types support arithmetic operations.
|
||||
Native homomorphic Booleans support common Boolean operations.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
|----------------------------------------------------------|--------|--------|
|
||||
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `-` | Unary |
|
||||
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
|
||||
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
|
||||
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
|
||||
| [Div](https://doc.rust-lang.org/std/ops/trait.Div.html)* | `/` | Binary |
|
||||
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html)* | `%` | Binary |
|
||||
|
||||
For the division operator, the convention is to return the modulus - 1. For instance, for FheUint8, the modulus is $$2^8=256$$, so a division by 0 will return an encryption of 255.
|
||||
For the remainder operator, the convention is to return the first input without any modification. For instance, for ct1 = FheUint8(63) and ct2 = FheUint8(0), then ct1 % ct2 will return FheUint8(63).
|
||||
|
||||
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 15_u64;
|
||||
let clear_b = 27_u64;
|
||||
let clear_c = 43_u64;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
let mut c = FheUint8::try_encrypt(clear_c, &keys)?;
|
||||
|
||||
|
||||
a = a * &b; // Clear equivalent computations: 15 * 27 mod 256 = 149
|
||||
b = &b + &c; // Clear equivalent computations: 27 + 43 mod 256 = 70
|
||||
b = b - 76u8; // Clear equivalent computations: 70 - 76 mod 256 = 250
|
||||
|
||||
let dec_a: u8 = a.decrypt(&keys);
|
||||
let dec_b: u8 = b.decrypt(&keys);
|
||||
|
||||
assert_eq!(dec_a, ((clear_a * clear_b) % 256_u64) as u8);
|
||||
assert_eq!(dec_b, (((clear_b + clear_c).wrapping_sub(76_u64)) % 256_u64) as u8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Bitwise operations.
|
||||
|
||||
Homomorphic integer types support some bitwise operations.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
|--------------------------------------------------------------------------------------|----------------|--------|
|
||||
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
|
||||
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
|
||||
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
|
||||
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
|
||||
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
|
||||
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
|
||||
| [Rotate Right](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_right) | `rotate_right` | Binary |
|
||||
| [Rotate Left](https://doc.rust-lang.org/std/primitive.u32.html#method.rotate_left) | `rotate_left` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a = 164;
|
||||
let clear_b = 212;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
a = a ^ &b;
|
||||
b = b ^ &a;
|
||||
a = a ^ &b;
|
||||
|
||||
let dec_a: u8 = a.decrypt(&keys);
|
||||
let dec_b: u8 = b.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_a, clear_b);
|
||||
assert_eq!(dec_b, clear_a);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Comparisons.
|
||||
|
||||
Homomorphic integers support comparison operations. Since Rust does not allow the overloading of these operations, a simple function has been associated to each one.
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| name | symbol | type |
|
||||
|-----------------------------------------------------------------------------|--------|--------|
|
||||
| [Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `eq` | Binary |
|
||||
| [Not Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) | `ne` | Binary |
|
||||
| [Greater Than ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `gt` | Binary |
|
||||
| [Greater or Equal](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `ge` | Binary |
|
||||
| [Lower ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `lt` | Binary |
|
||||
| [Lower or Equal ](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) | `le` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a:u8 = 164;
|
||||
let clear_b:u8 = 212;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
let greater = a.gt(&b);
|
||||
let greater_or_equal = a.ge(&b);
|
||||
let lower = a.lt(&b);
|
||||
let lower_or_equal = a.le(&b);
|
||||
let equal = a.eq(&b);
|
||||
|
||||
let dec_gt : u8 = greater.decrypt(&keys);
|
||||
let dec_ge : u8 = greater_or_equal.decrypt(&keys);
|
||||
let dec_lt : u8 = lower.decrypt(&keys);
|
||||
let dec_le : u8 = lower_or_equal.decrypt(&keys);
|
||||
let dec_eq : u8 = equal.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_gt, (clear_a > clear_b ) as u8);
|
||||
assert_eq!(dec_ge, (clear_a >= clear_b) as u8);
|
||||
assert_eq!(dec_lt, (clear_a < clear_b ) as u8);
|
||||
assert_eq!(dec_le, (clear_a <= clear_b) as u8);
|
||||
assert_eq!(dec_eq, (clear_a == clear_b) as u8);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Min/Max.
|
||||
|
||||
Homomorphic integers support the min/max operations.
|
||||
|
||||
| name | symbol | type |
|
||||
| ---- | ------ | ------ |
|
||||
| Min | `min` | Binary |
|
||||
| Max | `max` | Binary |
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
let clear_a:u8 = 164;
|
||||
let clear_b:u8 = 212;
|
||||
|
||||
let mut a = FheUint8::try_encrypt(clear_a, &keys)?;
|
||||
let mut b = FheUint8::try_encrypt(clear_b, &keys)?;
|
||||
|
||||
let min = a.min(&b);
|
||||
let max = a.max(&b);
|
||||
|
||||
let dec_min : u8 = min.decrypt(&keys);
|
||||
let dec_max : u8 = max.decrypt(&keys);
|
||||
|
||||
// We homomorphically swapped values using bitwise operations
|
||||
assert_eq!(dec_min, u8::min(clear_a, clear_b));
|
||||
assert_eq!(dec_max, u8::max(clear_a, clear_b));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Casting.
|
||||
|
||||
Casting between integer types is possible via the `cast_from` associated function
|
||||
or the `cast_into` method.
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheUint32, FheUint16};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
// Casting requires server_key to set
|
||||
// (encryptions/decryptions do not need server_key to be set)
|
||||
set_server_key(server_key);
|
||||
|
||||
{
|
||||
let clear = 12_837u16;
|
||||
let a = FheUint16::encrypt(clear, &client_key);
|
||||
|
||||
// Downcasting
|
||||
let a: FheUint8 = a.cast_into();
|
||||
let da: u8 = a.decrypt(&client_key);
|
||||
assert_eq!(da, clear as u8);
|
||||
|
||||
// Upcasting
|
||||
let a: FheUint32 = a.cast_into();
|
||||
let da: u32 = a.decrypt(&client_key);
|
||||
assert_eq!(da, (clear as u8) as u32);
|
||||
}
|
||||
|
||||
{
|
||||
let clear = 12_837u16;
|
||||
let a = FheUint16::encrypt(clear, &client_key);
|
||||
|
||||
// Upcasting
|
||||
let a = FheUint32::cast_from(a);
|
||||
let da: u32 = a.decrypt(&client_key);
|
||||
assert_eq!(da, clear as u32);
|
||||
|
||||
// Downcasting
|
||||
let a = FheUint8::cast_from(a);
|
||||
let da: u8 = a.decrypt(&client_key);
|
||||
assert_eq!(da, (clear as u32) as u8);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
| name | symbol | type |
|
||||
| ------------------------------------------------------------- | ------ | ------ |
|
||||
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
|
||||
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
|
||||
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
|
||||
| [Not](https://doc.rust-lang.org/std/ops/trait.Not.html) | `!` | Unary |
|
||||
|
||||
@@ -46,7 +46,7 @@ fn main() {
|
||||
|
||||
The default configuration for x86 Unix machines:
|
||||
```toml
|
||||
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
|
||||
tfhe = { version = "0.4.0", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Configuration options for different platforms can be found [here](../getting_started/installation.md). Other rust and homomorphic types features can be found [here](../how_to/rust_configuration.md).
|
||||
@@ -139,6 +139,3 @@ let clear_result = clear_a + clear_b;
|
||||
|
||||
assert_eq!(decrypted_result, clear_result);
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ The figure below illustrates this problem in the case of an addition, where an e
|
||||
|
||||
### Programmable BootStrapping (PBS)
|
||||
|
||||
The bootstrapping of TFHE has the particularity of being programmable: this means that any function can be homomorphically computed over an encrypted input, while also reducing the noise. These functions are represented by look-up tables. The computation of a PBS is in general either preceded or followed by a keyswitch, which is an operation used to change the encryption key. The output ciphertext is then encrypted with the same key as the input one. To do this, two (public) evaluation keys are required: a boostrapping key and a keyswitching key. These operations are quite complex to describe, more information about these operations (or about TFHE in general) can be found here [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
|
||||
The bootstrapping of TFHE has the particularity of being programmable: this means that any function can be homomorphically computed over an encrypted input, while also reducing the noise. These functions are represented by look-up tables. The computation of a PBS is in general either preceded or followed by a keyswitch, which is an operation used to change the encryption key. The output ciphertext is then encrypted with the same key as the input one. To do this, two (public) evaluation keys are required: a bootstrapping key and a keyswitching key. These operations are quite complex to describe, more information about these operations (or about TFHE in general) can be found here [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
|
||||
|
||||
|
||||
### Carry.
|
||||
|
||||
@@ -3,6 +3,7 @@ TFHE-rs includes features to reduce the size of both keys and ciphertexts, by co
|
||||
|
||||
In the library, entities that can be compressed are prefixed by `Compressed`. For instance, the type of a compressed `FheUint256` is `CompressedFheUint256`.
|
||||
|
||||
In the following example code, we use the `bincode` crate dependency to serialize in a binary format and compare serialized sizes.
|
||||
|
||||
## Compressed ciphertexts
|
||||
This example shows how to compress a ciphertext encypting messages over 16 bits.
|
||||
@@ -19,7 +20,18 @@ fn main() {
|
||||
|
||||
let clear = 12_837u16;
|
||||
let compressed = CompressedFheUint16::try_encrypt(clear, &client_key).unwrap();
|
||||
println!(
|
||||
"compressed size : {}",
|
||||
bincode::serialize(&compressed).unwrap().len()
|
||||
);
|
||||
|
||||
let decompressed = compressed.decompress();
|
||||
|
||||
println!(
|
||||
"decompressed size: {}",
|
||||
bincode::serialize(&decompressed).unwrap().len()
|
||||
);
|
||||
|
||||
let clear_decompressed: u16 = decompressed.decrypt(&client_key);
|
||||
assert_eq!(clear_decompressed, clear);
|
||||
}
|
||||
@@ -31,7 +43,9 @@ This example shows how to compress the server keys.
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, CompressedServerKey, ClientKey};
|
||||
use tfhe::{
|
||||
generate_keys, set_server_key, ClientKey, CompressedServerKey, ConfigBuilder, FheUint8,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
@@ -40,8 +54,19 @@ fn main() {
|
||||
|
||||
let cks = ClientKey::generate(config);
|
||||
let compressed_sks = CompressedServerKey::new(&cks);
|
||||
|
||||
println!(
|
||||
"compressed size : {}",
|
||||
bincode::serialize(&compressed_sks).unwrap().len()
|
||||
);
|
||||
|
||||
let sks = compressed_sks.decompress();
|
||||
|
||||
println!(
|
||||
"decompressed size: {}",
|
||||
bincode::serialize(&sks).unwrap().len()
|
||||
);
|
||||
|
||||
set_server_key(sks);
|
||||
|
||||
let clear_a = 12u8;
|
||||
@@ -51,6 +76,7 @@ fn main() {
|
||||
let decrypted: u8 = c.decrypt(&cks);
|
||||
assert_eq!(decrypted, clear_a.wrapping_add(234));
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -58,7 +84,7 @@ fn main() {
|
||||
This example shows how to compress the classical public keys.
|
||||
|
||||
{% hint style="warning" %}
|
||||
It is not currently recommended to use the CompressedPublicKey to encrypt ciphertexts without first decompressing it. In case the resulting PublicKey is too large to fit in memory the encryption with the CompressedPublicKey will be very slow, this is a known problem and will be adressed in future releases.
|
||||
It is not currently recommended to use the CompressedPublicKey to encrypt ciphertexts without first decompressing it. In case the resulting PublicKey is too large to fit in memory the encryption with the CompressedPublicKey will be very slow, this is a known problem and will be addressed in future releases.
|
||||
{% endhint %}
|
||||
|
||||
```rust
|
||||
@@ -71,7 +97,14 @@ fn main() {
|
||||
.build();
|
||||
let (client_key, _) = generate_keys(config);
|
||||
|
||||
let public_key = CompressedPublicKey::new(&client_key);
|
||||
let compressed_public_key = CompressedPublicKey::new(&client_key);
|
||||
|
||||
println!("compressed size : {}", bincode::serialize(&compressed_public_key).unwrap().len());
|
||||
|
||||
let public_key = compressed_public_key.decompress();
|
||||
|
||||
println!("decompressed size: {}", bincode::serialize(&public_key).unwrap().len());
|
||||
|
||||
|
||||
let a = FheUint8::try_encrypt(213u8, &public_key).unwrap();
|
||||
let clear: u8 = a.decrypt(&client_key);
|
||||
@@ -86,10 +119,10 @@ This example shows how to use compressed compact public keys.
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8, CompressedCompactPublicKey};
|
||||
use tfhe::{generate_keys, set_server_key, CompressedCompactPublicKey, ConfigBuilder, FheUint8};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_custom_integers(
|
||||
tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS,
|
||||
None,
|
||||
@@ -98,10 +131,22 @@ fn main() {
|
||||
let (client_key, _) = generate_keys(config);
|
||||
|
||||
let public_key_compressed = CompressedCompactPublicKey::new(&client_key);
|
||||
|
||||
println!(
|
||||
"compressed size : {}",
|
||||
bincode::serialize(&public_key_compressed).unwrap().len()
|
||||
);
|
||||
|
||||
let public_key = public_key_compressed.decompress();
|
||||
|
||||
|
||||
println!(
|
||||
"decompressed size: {}",
|
||||
bincode::serialize(&public_key).unwrap().len()
|
||||
);
|
||||
|
||||
let a = FheUint8::try_encrypt(255u8, &public_key).unwrap();
|
||||
let clear: u8 = a.decrypt(&client_key);
|
||||
assert_eq!(clear, 255u8);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Parallelized Programmable Bootstrapping
|
||||
|
||||
The [Programmable Bootstrapping](../getting_started/security_and_cryptography.md)(PBS) is a sequential operation by nature. However, some [recent results](https://marcjoye.github.io/papers/JP22ternary.pdf) showed that parallelism could be added at the cost of having larger keys. Overall, the performance of the PBS are improved.
|
||||
The [Programmable Bootstrapping](../getting_started/security_and_cryptography.md)(PBS) is a sequential operation by nature. However, some [recent results](https://marcjoye.github.io/papers/JP22ternary.pdf) showed that parallelism could be added at the cost of having larger keys. Overall, the performance of the PBS are improved. This new PBS is called a multi bit PBS.
|
||||
In TFHE-rs, since integer homomorphic operations are already parallelized, activating this feature may improve performance in the case of high core count CPUs if enough cores are available, or for small input message precision.
|
||||
|
||||
In what follows, an example on how to use the parallelized bootstrapping:
|
||||
In what follows, an example on how to use the parallelized bootstrapping by choosing multi bit PBS parameters:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
@@ -63,6 +63,3 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -49,4 +49,3 @@ fn main() {
|
||||
assert_eq!(clear, 255u8);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# Using TFHE-rs with nightly toolchain.
|
||||
# Using the right toolchain for TFHE-rs.
|
||||
|
||||
TFHE-rs only requires a nightly toolchain for building the C API and using advanced SIMD instructions, otherwise you can use a stable toolchain (with version >= 1.72 for ARM devices)
|
||||
Install the needed Rust toolchain:
|
||||
|
||||
```shell
|
||||
# If you don't need the C API or the advanced still unstable SIMD instructions use this
|
||||
rustup toolchain install stable
|
||||
# Otherwise install a nightly toolchain
|
||||
rustup toolchain install nightly
|
||||
```
|
||||
|
||||
@@ -11,12 +15,23 @@ Then, you can either:
|
||||
* Manually specify the toolchain to use in each of the cargo commands:
|
||||
|
||||
```shell
|
||||
# By default the +stable should not be needed, but we add it here for completeness
|
||||
cargo +stable build
|
||||
cargo +stable test
|
||||
# Or
|
||||
cargo +nightly build
|
||||
cargo +nightly test
|
||||
```
|
||||
|
||||
* Or override the toolchain to use for the current project:
|
||||
|
||||
```shell
|
||||
# This should not be necessary by default, but if you want to make sure your configuration is
|
||||
# correct you can still set the overridden toolchain to stable
|
||||
rustup override set stable
|
||||
# cargo will use the `stable` toolchain.
|
||||
cargo build
|
||||
# Or
|
||||
rustup override set nightly
|
||||
# cargo will use the `nightly` toolchain.
|
||||
cargo build
|
||||
@@ -37,16 +52,16 @@ rustup show
|
||||
|
||||
This crate exposes two kinds of data types. Each kind is enabled by activating its corresponding feature in the TOML line. Each kind may have multiple types:
|
||||
|
||||
| Kind | Features | Type(s) |
|
||||
| --------- | ---------- | --------------------------------- |
|
||||
| Booleans | `boolean` | Booleans |
|
||||
| ShortInts | `shortint` | Short unsigned integers |
|
||||
| Integers | `integer` | Arbitrary-sized unsigned integers |
|
||||
| Kind | Features | Type(s) |
|
||||
|-----------|------------|---------------------------|
|
||||
| Booleans | `boolean` | Booleans |
|
||||
| ShortInts | `shortint` | Short integers |
|
||||
| Integers | `integer` | Arbitrary-sized integers |
|
||||
|
||||
|
||||
## AVX-512
|
||||
|
||||
In general, the library automatically chooses the best instruction sets available by the host. However, in the case of 'AVX-512', this has to explicitly chosen as a feature. This requires to use the [nightly toolchain](#using-tfhe-rs-with-nightly-toolchain) along with the feature `nightly-avx512`.
|
||||
In general, the library automatically chooses the best instruction sets available by the host. However, in the case of 'AVX-512', this has to be explicitly chosen as a feature. This requires to use a [nightly toolchain](#using-tfhe-rs-with-nightly-toolchain) along with the feature `nightly-avx512`.
|
||||
|
||||
```shell
|
||||
cargo +nightly build --features=nightly-avx512
|
||||
|
||||
@@ -11,7 +11,7 @@ To serialize our data, a [data format](https://serde.rs/#data-formats) should be
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.3.0", features = ["integer","x86_64-unix"]}
|
||||
tfhe = { version = "0.4.0", features = ["integer","x86_64-unix"]}
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
|
||||
131
tfhe/docs/how_to/trait_bounds.md
Normal file
131
tfhe/docs/how_to/trait_bounds.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Generic Bounds
|
||||
|
||||
If you wish to write generic functions which use operators with mixed reference and non-reference,
|
||||
it might get tricky at first to specify the trait [bounds](https://doc.rust-lang.org/rust-by-example/generics/bounds.html).
|
||||
This page should serve as a _cookbook_ to help you.
|
||||
|
||||
Operators (+, *, >>, etc) are tied to traits in `std:::ops`, e.g. `+` is `std::ops::Add`,
|
||||
so to write a generic function which uses the `+` operator, you need to use add `std::ops::Add`
|
||||
as a trait bound.
|
||||
|
||||
Then, depending on if the left hand side / right hand side is an owned value or a reference, the trait bound
|
||||
is slightly different. The table below shows the possibilities.
|
||||
|
||||
| operation | trait bound |
|
||||
| ----------- | ------------------------------------- |
|
||||
| `T $op T` | `T: $Op<T, Output=T>` |
|
||||
| `T $op &T` | `T: for<'a> $Op<&'a T, Output=T>` |
|
||||
| `&T $op T` | `for<'a> &'a T: $Op<T, Output=T>` |
|
||||
| `&T $op &T` | `for<'a> &'a T: $Op<&'a T, Output=T>` |
|
||||
|
||||
{% hint style="info" %}
|
||||
The `for<'a>` syntax is something called [Higher-Rank Trait Bounds](https://doc.rust-lang.org/nomicon/hrtb.html), often shortened as __HRTB__
|
||||
{% endhint %}
|
||||
|
||||
{% hint style="info" %}
|
||||
Writing generic functions will also allow you to call them using clear inputs,
|
||||
only allowing easier debugging.
|
||||
{% endhint %}
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use std::ops::{Add, Mul};
|
||||
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint32, FheUint64};
|
||||
|
||||
pub fn ex1<'a, FheType, ClearType>(ct: &'a FheType, pt: ClearType) -> FheType
|
||||
where
|
||||
&'a FheType: Add<ClearType, Output = FheType>,
|
||||
{
|
||||
ct + pt
|
||||
}
|
||||
|
||||
pub fn ex2<'a, FheType, ClearType>(a: &'a FheType, b: &'a FheType, pt: ClearType) -> FheType
|
||||
where
|
||||
&'a FheType: Mul<&'a FheType, Output = FheType>,
|
||||
FheType: Add<ClearType, Output = FheType>,
|
||||
{
|
||||
(a * b) + pt
|
||||
}
|
||||
|
||||
pub fn ex3<FheType, ClearType>(a: FheType, b: FheType, pt: ClearType) -> FheType
|
||||
where
|
||||
for<'a> &'a FheType: Add<&'a FheType, Output = FheType>,
|
||||
FheType: Add<FheType, Output = FheType> + Add<ClearType, Output = FheType>,
|
||||
{
|
||||
let tmp = (&a + &b) + (&a + &b);
|
||||
tmp + pt
|
||||
}
|
||||
|
||||
pub fn ex4<FheType, ClearType>(a: FheType, b: FheType, pt: ClearType) -> FheType
|
||||
where
|
||||
FheType: Clone + Add<FheType, Output = FheType> + Add<ClearType, Output = FheType>,
|
||||
{
|
||||
let tmp = (a.clone() + b.clone()) + (a.clone() + b.clone());
|
||||
tmp + pt
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let (client_key, server_keys) = generate_keys(config);
|
||||
|
||||
set_server_key(server_keys);
|
||||
|
||||
// Use FheUint32
|
||||
{
|
||||
let clear_a = 46546u32;
|
||||
let clear_b = 6469u32;
|
||||
let clear_c = 64u32;
|
||||
|
||||
let a = FheUint32::try_encrypt(clear_a, &client_key).unwrap();
|
||||
let b = FheUint32::try_encrypt(clear_b, &client_key).unwrap();
|
||||
assert_eq!(
|
||||
ex1(&clear_a, clear_c),
|
||||
ex1(&a, clear_c).decrypt(&client_key)
|
||||
);
|
||||
assert_eq!(
|
||||
ex2(&clear_a, &clear_b, clear_c),
|
||||
ex2(&a, &b, clear_c).decrypt(&client_key)
|
||||
);
|
||||
assert_eq!(
|
||||
ex3(clear_a, clear_b, clear_c),
|
||||
ex3(a.clone(), b.clone(), clear_c).decrypt(&client_key)
|
||||
);
|
||||
assert_eq!(
|
||||
ex4(clear_a, clear_b, clear_c),
|
||||
ex4(a, b, clear_c).decrypt(&client_key)
|
||||
);
|
||||
}
|
||||
|
||||
// Use FheUint64
|
||||
{
|
||||
let clear_a = 46544866u64;
|
||||
let clear_b = 6469446677u64;
|
||||
let clear_c = 647897u64;
|
||||
|
||||
let a = FheUint64::try_encrypt(clear_a, &client_key).unwrap();
|
||||
let b = FheUint64::try_encrypt(clear_b, &client_key).unwrap();
|
||||
assert_eq!(
|
||||
ex1(&clear_a, clear_c),
|
||||
ex1(&a, clear_c).decrypt(&client_key)
|
||||
);
|
||||
assert_eq!(
|
||||
ex2(&clear_a, &clear_b, clear_c),
|
||||
ex2(&a, &b, clear_c).decrypt(&client_key)
|
||||
);
|
||||
assert_eq!(
|
||||
ex3(clear_a, clear_b, clear_c),
|
||||
ex3(a.clone(), b.clone(), clear_c).decrypt(&client_key)
|
||||
);
|
||||
assert_eq!(
|
||||
ex4(clear_a, clear_b, clear_c),
|
||||
ex4(a, b, clear_c).decrypt(&client_key)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
# Trival Ciphertext
|
||||
# Trivial Ciphertext
|
||||
|
||||
Sometimes, the server side needs to initialize a value.
|
||||
For example, when computing the sum of a list of ciphertext,
|
||||
|
||||
156
tfhe/docs/tutorials/ascii_fhe_string.md
Normal file
156
tfhe/docs/tutorials/ascii_fhe_string.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# A first complete example: FheAsciiString (Integer)
|
||||
|
||||
The goal of this tutorial is to build a data type that represents a ASCII string in FHE while implementing the `to_lower` and `to_upper` functions.
|
||||
|
||||
An ASCII character is stored in 7 bits.
|
||||
To store an encrypted ASCII we use the `FheUint8`.
|
||||
|
||||
* The uppercase letters are in the range \[65, 90]
|
||||
* The lowercase letters are in the range \[97, 122]
|
||||
|
||||
`lower_case = upper_case + UP_LOW_DISTANCE` <=> `upper_case = lower_case - UP_LOW_DISTANCE`
|
||||
|
||||
Where `UP_LOW_DISTANCE = 32`
|
||||
|
||||
|
||||
## Types and methods.
|
||||
|
||||
This type will hold the encrypted characters as a `Vec<FheUint8>` to implement the functions that change the case.
|
||||
|
||||
To use the `FheUint8` type, the `integer` feature must be activated:
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.4.0", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
|
||||
|
||||
In the `FheAsciiString::encrypt` function, some data validation is done:
|
||||
|
||||
* The input string can only contain ascii characters.
|
||||
|
||||
It is not possible to branch on an encrypted value, however it is possible to evaluate a boolean condition and use it to get the desired result.
|
||||
Checking if the 'char' is an uppercase letter to modify it to a lowercase can be done without using a branch, like this:
|
||||
|
||||
```rust
|
||||
pub const UP_LOW_DISTANCE: u8 = 32;
|
||||
|
||||
fn to_lower(c: u8) -> u8 {
|
||||
if c > 64 && c < 91 {
|
||||
c + UP_LOW_DISTANCE
|
||||
} else {
|
||||
c
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can remove the branch this way:
|
||||
|
||||
```rust
|
||||
pub const UP_LOW_DISTANCE: u8 = 32;
|
||||
|
||||
fn to_lower(c: u8) -> u8 {
|
||||
c + ((c > 64) as u8 & (c < 91) as u8) * UP_LOW_DISTANCE
|
||||
}
|
||||
```
|
||||
|
||||
On an homomorphic integer, this gives
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::FheUint8;
|
||||
|
||||
pub const UP_LOW_DISTANCE: u8 = 32;
|
||||
|
||||
fn to_lower(c: &FheUint8) -> FheUint8 {
|
||||
c + (c.gt(64) & c.lt(91)) * UP_LOW_DISTANCE
|
||||
}
|
||||
```
|
||||
|
||||
The whole code is:
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ClientKey, ConfigBuilder, FheUint8};
|
||||
|
||||
pub const UP_LOW_DISTANCE: u8 = 32;
|
||||
|
||||
struct FheAsciiString {
|
||||
bytes: Vec<FheUint8>,
|
||||
}
|
||||
|
||||
fn to_upper(c: &FheUint8) -> FheUint8 {
|
||||
c - (c.gt(96) & c.lt(123)) * UP_LOW_DISTANCE
|
||||
}
|
||||
|
||||
fn to_lower(c: &FheUint8) -> FheUint8 {
|
||||
c + (c.gt(64) & c.lt(91)) * UP_LOW_DISTANCE
|
||||
}
|
||||
|
||||
impl FheAsciiString {
|
||||
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
|
||||
assert!(
|
||||
string.chars().all(|char| char.is_ascii()),
|
||||
"The input string must only contain ascii letters"
|
||||
);
|
||||
|
||||
let fhe_bytes: Vec<FheUint8> = string
|
||||
.bytes()
|
||||
.map(|b| FheUint8::encrypt(b, client_key))
|
||||
.collect();
|
||||
|
||||
Self { bytes: fhe_bytes }
|
||||
}
|
||||
|
||||
fn decrypt(&self, client_key: &ClientKey) -> String {
|
||||
let ascii_bytes: Vec<u8> = self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|fhe_b| fhe_b.decrypt(client_key))
|
||||
.collect();
|
||||
String::from_utf8(ascii_bytes).unwrap()
|
||||
}
|
||||
|
||||
fn to_upper(&self) -> Self {
|
||||
Self {
|
||||
bytes: self.bytes.iter().map(to_upper).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lower(&self) -> Self {
|
||||
Self {
|
||||
bytes: self.bytes.iter().map(to_lower).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let my_string = FheAsciiString::encrypt("Hello Zama, how is it going?", &client_key);
|
||||
let verif_string = my_string.decrypt(&client_key);
|
||||
println!("Start string: {verif_string}");
|
||||
|
||||
let my_string_upper = my_string.to_upper();
|
||||
let verif_string = my_string_upper.decrypt(&client_key);
|
||||
println!("Upper string: {verif_string}");
|
||||
assert_eq!(verif_string, "HELLO ZAMA, HOW IS IT GOING?");
|
||||
|
||||
let my_string_lower = my_string_upper.to_lower();
|
||||
let verif_string = my_string_lower.decrypt(&client_key);
|
||||
println!("Lower string: {verif_string}");
|
||||
assert_eq!(verif_string, "hello zama, how is it going?");
|
||||
}
|
||||
```
|
||||
@@ -1,155 +0,0 @@
|
||||
# A first complete example: FheLatinString (Integer)
|
||||
|
||||
The goal of this tutorial is to build a data type that represents a Latin string in FHE while implementing the `to_lower` and `to_upper` functions.
|
||||
|
||||
The allowed characters in a Latin string are:
|
||||
|
||||
* Uppercase letters: `A B C D E F G H I J K L M N O P Q R S T U V W X Y Z`
|
||||
* Lowercase letters: `a b c d e f g h i j k l m n o p q r s t u v w x y z`
|
||||
|
||||
For the code point of the letters,`ascii` codes are used:
|
||||
|
||||
* The uppercase letters are in the range \[65, 90]
|
||||
* The lowercase letters are in the range \[97, 122]
|
||||
|
||||
`lower_case` = `upper_case` + 32 <=> `upper_case` = `lower_case` - 32
|
||||
|
||||
For this type, the `FheUint8` type is used.
|
||||
|
||||
## Types and methods.
|
||||
|
||||
This type will hold the encrypted characters as a `Vec<FheUint8>`, as well as the encrypted constant `32` to implement the functions that change the case.
|
||||
|
||||
In the `FheLatinString::encrypt` function, some data validation is done:
|
||||
|
||||
* The input string can only contain ascii letters (no digit, no special characters).
|
||||
* The input string cannot mix lower and upper case letters.
|
||||
|
||||
These two points are to work around a limitation of FHE. It is not possible to create branches, meaning the function cannot use conditional statements. Checking if the 'char' is an uppercase letter to modify it to a lowercase one cannot be done, like in the example below.
|
||||
|
||||
```rust
|
||||
fn to_lower(string: &String) -> String {
|
||||
let mut result = String::with_capacity(string.len());
|
||||
for char in string.chars() {
|
||||
if char.is_uppercase() {
|
||||
result.extend(char.to_lowercase().to_string().chars())
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
```
|
||||
|
||||
With these preconditions checked, implementing `to_lower` and `to_upper` is rather simple.
|
||||
|
||||
To use the `FheUint8` type, the `integer` feature must be activated:
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
|
||||
```rust
|
||||
use tfhe::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
struct FheLatinString{
|
||||
bytes: Vec<FheUint8>,
|
||||
// Constant used to switch lower case <=> upper case
|
||||
cst: FheUint8,
|
||||
}
|
||||
|
||||
impl FheLatinString {
|
||||
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
|
||||
assert!(
|
||||
string.chars().all(|char| char.is_ascii_alphabetic()),
|
||||
"The input string must only contain ascii letters"
|
||||
);
|
||||
|
||||
let has_mixed_case = string.as_bytes().windows(2).any(|window| {
|
||||
let first = char::from(*window.first().unwrap());
|
||||
let second = char::from(*window.last().unwrap());
|
||||
|
||||
(first.is_ascii_lowercase() && second.is_ascii_uppercase())
|
||||
|| (first.is_ascii_uppercase() && second.is_ascii_lowercase())
|
||||
});
|
||||
|
||||
assert!(
|
||||
!has_mixed_case,
|
||||
"The input string cannot mix lower case and upper case letters"
|
||||
);
|
||||
|
||||
let fhe_bytes = string
|
||||
.bytes()
|
||||
.map(|b| FheUint8::encrypt(b, client_key))
|
||||
.collect::<Vec<FheUint8>>();
|
||||
let cst = FheUint8::encrypt(32u8, client_key);
|
||||
|
||||
Self {
|
||||
bytes: fhe_bytes,
|
||||
cst,
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt(&self, client_key: &ClientKey) -> String {
|
||||
let ascii_bytes = self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|fhe_b| fhe_b.decrypt(client_key))
|
||||
.collect::<Vec<u8>>();
|
||||
String::from_utf8(ascii_bytes).unwrap()
|
||||
}
|
||||
|
||||
fn to_upper(&self) -> Self {
|
||||
Self {
|
||||
bytes: self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|b| b - &self.cst)
|
||||
.collect::<Vec<FheUint8>>(),
|
||||
cst: self.cst.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lower(&self) -> Self {
|
||||
Self {
|
||||
bytes: self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|b| b + &self.cst)
|
||||
.collect::<Vec<FheUint8>>(),
|
||||
cst: self.cst.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let my_string = FheLatinString::encrypt("zama", &client_key);
|
||||
let verif_string = my_string.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
|
||||
let my_string_upper = my_string.to_upper();
|
||||
let verif_string = my_string_upper.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
assert_eq!(verif_string, "ZAMA");
|
||||
|
||||
let my_string_lower = my_string_upper.to_lower();
|
||||
let verif_string = my_string_lower.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
assert_eq!(verif_string, "zama");
|
||||
}
|
||||
```
|
||||
@@ -21,7 +21,7 @@ To use Booleans, the `booleans` feature in our Cargo.toml must be enabled:
|
||||
# Cargo.toml
|
||||
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.3.0", features = ["boolean", "x86_64-unix"]}
|
||||
tfhe = { version = "0.4.0", features = ["boolean", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
@@ -1,418 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
use tfhe::integer::ciphertext::RadixCiphertext;
|
||||
use tfhe::integer::keycache::IntegerKeyCache;
|
||||
use tfhe::integer::ServerKey;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
|
||||
|
||||
/// The number of blocks to be used in the Radix.
|
||||
const NUMBER_OF_BLOCKS: usize = 8;
|
||||
|
||||
/// Plain implementation of the volume matching algorithm.
|
||||
///
|
||||
/// Matches the given [sell_orders] with [buy_orders].
|
||||
/// The amount of the orders that are successfully filled is written over the original order count.
|
||||
fn volume_match_plain(sell_orders: &mut [u16], buy_orders: &mut [u16]) {
|
||||
let total_sell_volume: u16 = sell_orders.iter().sum();
|
||||
let total_buy_volume: u16 = buy_orders.iter().sum();
|
||||
|
||||
let total_volume = std::cmp::min(total_buy_volume, total_sell_volume);
|
||||
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for sell_order in sell_orders.iter_mut() {
|
||||
let filled_amount = std::cmp::min(volume_left_to_transact, *sell_order);
|
||||
*sell_order = filled_amount;
|
||||
volume_left_to_transact -= filled_amount;
|
||||
}
|
||||
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for buy_order in buy_orders.iter_mut() {
|
||||
let filled_amount = std::cmp::min(volume_left_to_transact, *buy_order);
|
||||
*buy_order = filled_amount;
|
||||
volume_left_to_transact -= filled_amount;
|
||||
}
|
||||
}
|
||||
|
||||
/// FHE implementation of the volume matching algorithm.
|
||||
///
|
||||
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
|
||||
/// [server_key]. The amount of the orders that are successfully filled is written over the original
|
||||
/// order count.
|
||||
fn volume_match_fhe(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
println!("Calculating total sell and buy volumes...");
|
||||
let time = Instant::now();
|
||||
let mut total_sell_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for sell_order in sell_orders.iter_mut() {
|
||||
server_key.smart_add_assign(&mut total_sell_volume, sell_order);
|
||||
}
|
||||
|
||||
let mut total_buy_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for buy_order in buy_orders.iter_mut() {
|
||||
server_key.smart_add_assign(&mut total_buy_volume, buy_order);
|
||||
}
|
||||
println!(
|
||||
"Total sell and buy volumes are calculated in {:?}",
|
||||
time.elapsed()
|
||||
);
|
||||
|
||||
println!("Calculating total volume to be matched...");
|
||||
let time = Instant::now();
|
||||
let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_volume);
|
||||
println!(
|
||||
"Calculated total volume to be matched in {:?}",
|
||||
time.elapsed()
|
||||
);
|
||||
|
||||
let fill_orders = |orders: &mut [RadixCiphertext]| {
|
||||
let mut volume_left_to_transact = total_volume.clone();
|
||||
for order in orders.iter_mut() {
|
||||
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, order);
|
||||
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
};
|
||||
|
||||
println!("Filling orders...");
|
||||
let time = Instant::now();
|
||||
fill_orders(sell_orders);
|
||||
fill_orders(buy_orders);
|
||||
println!("Filled orders in {:?}", time.elapsed());
|
||||
}
|
||||
|
||||
/// FHE implementation of the volume matching algorithm.
|
||||
///
|
||||
/// This version of the algorithm utilizes parallelization to speed up the computation.
|
||||
///
|
||||
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
|
||||
/// [server_key]. The amount of the orders that are successfully filled is written over the original
|
||||
/// order count.
|
||||
fn volume_match_fhe_parallelized(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
// Calculate the element sum of the given vector in parallel
|
||||
let parallel_vector_sum = |vec: &mut [RadixCiphertext]| {
|
||||
vec.to_vec().into_par_iter().reduce(
|
||||
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|
||||
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
|
||||
server_key.smart_add_parallelized(&mut acc, &mut ele)
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
println!("Calculating total sell and buy volumes...");
|
||||
let time = Instant::now();
|
||||
// Total sell and buy volumes can be calculated in parallel because they have no dependency on
|
||||
// each other.
|
||||
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|
||||
|| parallel_vector_sum(sell_orders),
|
||||
|| parallel_vector_sum(buy_orders),
|
||||
);
|
||||
println!(
|
||||
"Total sell and buy volumes are calculated in {:?}",
|
||||
time.elapsed()
|
||||
);
|
||||
|
||||
println!("Calculating total volume to be matched...");
|
||||
let time = Instant::now();
|
||||
let total_volume =
|
||||
server_key.smart_min_parallelized(&mut total_sell_volume, &mut total_buy_volume);
|
||||
println!(
|
||||
"Calculated total volume to be matched in {:?}",
|
||||
time.elapsed()
|
||||
);
|
||||
|
||||
let fill_orders = |orders: &mut [RadixCiphertext]| {
|
||||
let mut volume_left_to_transact = total_volume.clone();
|
||||
for order in orders.iter_mut() {
|
||||
let mut filled_amount =
|
||||
server_key.smart_min_parallelized(&mut volume_left_to_transact, order);
|
||||
server_key
|
||||
.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
};
|
||||
println!("Filling orders...");
|
||||
let time = Instant::now();
|
||||
rayon::join(|| fill_orders(sell_orders), || fill_orders(buy_orders));
|
||||
println!("Filled orders in {:?}", time.elapsed());
|
||||
}
|
||||
|
||||
/// FHE implementation of the volume matching algorithm.
|
||||
///
|
||||
/// In this function, the implemented algorithm is modified to utilize more concurrency.
|
||||
///
|
||||
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
|
||||
/// [server_key]. The amount of the orders that are successfully filled is written over the original
|
||||
/// order count.
|
||||
fn volume_match_fhe_modified(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let compute_prefix_sum = |arr: &[RadixCiphertext]| {
|
||||
if arr.is_empty() {
|
||||
return arr.to_vec();
|
||||
}
|
||||
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
|
||||
.into_par_iter()
|
||||
.map(|i| {
|
||||
if i < arr.len() {
|
||||
arr[i].clone()
|
||||
} else {
|
||||
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
for d in 0..prefix_sum.len().ilog2() {
|
||||
prefix_sum
|
||||
.par_chunks_exact_mut(2_usize.pow(d + 1))
|
||||
.for_each(move |chunk| {
|
||||
let length = chunk.len();
|
||||
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
|
||||
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
|
||||
});
|
||||
}
|
||||
let last = prefix_sum.last().unwrap().clone();
|
||||
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for d in (0..prefix_sum.len().ilog2()).rev() {
|
||||
prefix_sum
|
||||
.par_chunks_exact_mut(2_usize.pow(d + 1))
|
||||
.for_each(move |chunk| {
|
||||
let length = chunk.len();
|
||||
let temp = chunk.last().unwrap().clone();
|
||||
let mut mid = chunk.get((length - 1) / 2).unwrap().clone();
|
||||
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut mid);
|
||||
chunk[(length - 1) / 2] = temp;
|
||||
});
|
||||
}
|
||||
prefix_sum.push(last);
|
||||
prefix_sum[1..=arr.len()].to_vec()
|
||||
};
|
||||
|
||||
println!("Creating prefix sum arrays...");
|
||||
let time = Instant::now();
|
||||
let (prefix_sum_sell_orders, prefix_sum_buy_orders) = rayon::join(
|
||||
|| compute_prefix_sum(sell_orders),
|
||||
|| compute_prefix_sum(buy_orders),
|
||||
);
|
||||
println!("Created prefix sum arrays in {:?}", time.elapsed());
|
||||
|
||||
let fill_orders = |total_orders: &RadixCiphertext,
|
||||
orders: &mut [RadixCiphertext],
|
||||
prefix_sum_arr: &[RadixCiphertext]| {
|
||||
orders
|
||||
.into_par_iter()
|
||||
.enumerate()
|
||||
.for_each(move |(i, order)| {
|
||||
server_key.smart_add_assign_parallelized(
|
||||
order,
|
||||
&mut server_key.smart_mul_parallelized(
|
||||
&mut server_key
|
||||
.smart_ge_parallelized(&mut order.clone(), &mut total_orders.clone()),
|
||||
&mut server_key.smart_sub_parallelized(
|
||||
&mut server_key.smart_sub_parallelized(
|
||||
&mut total_orders.clone(),
|
||||
&mut server_key.smart_min_parallelized(
|
||||
&mut total_orders.clone(),
|
||||
&mut prefix_sum_arr
|
||||
.get(i - 1)
|
||||
.unwrap_or(
|
||||
&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|
||||
)
|
||||
.clone(),
|
||||
),
|
||||
),
|
||||
&mut order.clone(),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
let total_buy_orders = &mut prefix_sum_buy_orders
|
||||
.last()
|
||||
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
|
||||
.clone();
|
||||
|
||||
let total_sell_orders = &mut prefix_sum_sell_orders
|
||||
.last()
|
||||
.unwrap_or(&server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS))
|
||||
.clone();
|
||||
|
||||
println!("Matching orders...");
|
||||
let time = Instant::now();
|
||||
rayon::join(
|
||||
|| fill_orders(total_sell_orders, buy_orders, &prefix_sum_buy_orders),
|
||||
|| fill_orders(total_buy_orders, sell_orders, &prefix_sum_sell_orders),
|
||||
);
|
||||
println!("Matched orders in {:?}", time.elapsed());
|
||||
}
|
||||
|
||||
/// Runs the given [tester] function with the test cases for volume matching algorithm.
|
||||
fn run_test_cases<F: Fn(&[u16], &[u16], &[u16], &[u16])>(tester: F) {
|
||||
println!("Testing empty sell orders...");
|
||||
tester(
|
||||
&[],
|
||||
&(1..11).collect::<Vec<_>>(),
|
||||
&[],
|
||||
&(1..11).map(|_| 0).collect::<Vec<_>>(),
|
||||
);
|
||||
println!();
|
||||
|
||||
println!("Testing empty buy orders...");
|
||||
tester(
|
||||
&(1..11).collect::<Vec<_>>(),
|
||||
&[],
|
||||
&(1..11).map(|_| 0).collect::<Vec<_>>(),
|
||||
&[],
|
||||
);
|
||||
println!();
|
||||
|
||||
println!("Testing exact matching of sell and buy orders...");
|
||||
tester(
|
||||
&(1..11).collect::<Vec<_>>(),
|
||||
&(1..11).collect::<Vec<_>>(),
|
||||
&(1..11).collect::<Vec<_>>(),
|
||||
&(1..11).collect::<Vec<_>>(),
|
||||
);
|
||||
println!();
|
||||
|
||||
println!("Testing the case where there are more buy orders than sell orders...");
|
||||
tester(
|
||||
&(1..11).map(|_| 10).collect::<Vec<_>>(),
|
||||
&[200],
|
||||
&(1..11).map(|_| 10).collect::<Vec<_>>(),
|
||||
&[100],
|
||||
);
|
||||
println!();
|
||||
|
||||
println!("Testing the case where there are more sell orders than buy orders...");
|
||||
tester(
|
||||
&[200],
|
||||
&(1..11).map(|_| 10).collect::<Vec<_>>(),
|
||||
&[100],
|
||||
&(1..11).map(|_| 10).collect::<Vec<_>>(),
|
||||
);
|
||||
println!();
|
||||
|
||||
println!("Testing maximum input size for sell and buy orders...");
|
||||
tester(
|
||||
&(1..=500).map(|_| 100).collect::<Vec<_>>(),
|
||||
&(1..=500).map(|_| 100).collect::<Vec<_>>(),
|
||||
&(1..=500).map(|_| 100).collect::<Vec<_>>(),
|
||||
&(1..=500).map(|_| 100).collect::<Vec<_>>(),
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
||||
/// Runs the test cases for the plain implementation of the volume matching algorithm.
|
||||
fn test_volume_match_plain() {
|
||||
let tester = |input_sell_orders: &[u16],
|
||||
input_buy_orders: &[u16],
|
||||
expected_filled_sells: &[u16],
|
||||
expected_filled_buys: &[u16]| {
|
||||
let mut sell_orders = input_sell_orders.to_vec();
|
||||
let mut buy_orders = input_buy_orders.to_vec();
|
||||
|
||||
println!("Running plain implementation...");
|
||||
let time = Instant::now();
|
||||
volume_match_plain(&mut sell_orders, &mut buy_orders);
|
||||
println!("Ran plain implementation in {:?}", time.elapsed());
|
||||
|
||||
assert_eq!(sell_orders, expected_filled_sells);
|
||||
assert_eq!(buy_orders, expected_filled_buys);
|
||||
};
|
||||
|
||||
println!("Running test cases for the plain implementation");
|
||||
run_test_cases(tester);
|
||||
}
|
||||
|
||||
/// Runs the test cases for the fhe implementation of the volume matching algorithm.
|
||||
///
|
||||
/// [parallelized] indicates whether the fhe implementation should be run in parallel.
|
||||
fn test_volume_match_fhe(
|
||||
fhe_function: fn(&mut [RadixCiphertext], &mut [RadixCiphertext], &ServerKey),
|
||||
) {
|
||||
let working_dir = std::env::current_dir().unwrap();
|
||||
if working_dir.file_name().unwrap() != std::path::Path::new("tfhe") {
|
||||
std::env::set_current_dir(working_dir.join("tfhe")).unwrap();
|
||||
}
|
||||
|
||||
println!("Generating keys...");
|
||||
let time = Instant::now();
|
||||
let (client_key, server_key) = IntegerKeyCache.get_from_params(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
|
||||
println!("Keys generated in {:?}", time.elapsed());
|
||||
|
||||
let tester = |input_sell_orders: &[u16],
|
||||
input_buy_orders: &[u16],
|
||||
expected_filled_sells: &[u16],
|
||||
expected_filled_buys: &[u16]| {
|
||||
let mut encrypted_sell_orders = input_sell_orders
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|pt| client_key.encrypt_radix(pt as u64, NUMBER_OF_BLOCKS))
|
||||
.collect::<Vec<RadixCiphertext>>();
|
||||
let mut encrypted_buy_orders = input_buy_orders
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|pt| client_key.encrypt_radix(pt as u64, NUMBER_OF_BLOCKS))
|
||||
.collect::<Vec<RadixCiphertext>>();
|
||||
|
||||
println!("Running FHE implementation...");
|
||||
let time = Instant::now();
|
||||
fhe_function(
|
||||
&mut encrypted_sell_orders,
|
||||
&mut encrypted_buy_orders,
|
||||
&server_key,
|
||||
);
|
||||
println!("Ran FHE implementation in {:?}", time.elapsed());
|
||||
|
||||
let decrypted_filled_sells = encrypted_sell_orders
|
||||
.iter()
|
||||
.map(|ct| client_key.decrypt_radix::<u64>(ct) as u16)
|
||||
.collect::<Vec<u16>>();
|
||||
let decrypted_filled_buys = encrypted_buy_orders
|
||||
.iter()
|
||||
.map(|ct| client_key.decrypt_radix::<u64>(ct) as u16)
|
||||
.collect::<Vec<u16>>();
|
||||
|
||||
assert_eq!(decrypted_filled_sells, expected_filled_sells);
|
||||
assert_eq!(decrypted_filled_buys, expected_filled_buys);
|
||||
};
|
||||
|
||||
println!("Running test cases for the FHE implementation");
|
||||
run_test_cases(tester);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
for argument in std::env::args() {
|
||||
if argument == "fhe-modified" {
|
||||
println!("Running modified fhe version");
|
||||
test_volume_match_fhe(volume_match_fhe_modified);
|
||||
println!();
|
||||
}
|
||||
if argument == "fhe-parallel" {
|
||||
println!("Running parallelized fhe version");
|
||||
test_volume_match_fhe(volume_match_fhe_parallelized);
|
||||
println!();
|
||||
}
|
||||
if argument == "plain" {
|
||||
println!("Running plain version");
|
||||
test_volume_match_plain();
|
||||
println!();
|
||||
}
|
||||
if argument == "fhe" {
|
||||
println!("Running fhe version");
|
||||
test_volume_match_fhe(volume_match_fhe);
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
102
tfhe/examples/dark_market/fhe.rs
Normal file
102
tfhe/examples/dark_market/fhe.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use std::time::Instant;
|
||||
use tfhe::integer::ciphertext::RadixCiphertext;
|
||||
use tfhe::integer::{ClientKey, ServerKey};
|
||||
|
||||
use crate::NUMBER_OF_BLOCKS;
|
||||
|
||||
fn vector_sum(server_key: &ServerKey, orders: &mut [RadixCiphertext]) -> RadixCiphertext {
|
||||
let mut total_volume = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for order in orders {
|
||||
server_key.smart_add_assign(&mut total_volume, order);
|
||||
}
|
||||
total_volume
|
||||
}
|
||||
|
||||
fn fill_orders(
|
||||
server_key: &ServerKey,
|
||||
orders: &mut [RadixCiphertext],
|
||||
total_volume: RadixCiphertext,
|
||||
) {
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for order in orders {
|
||||
let mut filled_amount = server_key.smart_min(&mut volume_left_to_transact, order);
|
||||
server_key.smart_sub_assign(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
}
|
||||
|
||||
/// FHE implementation of the volume matching algorithm.
|
||||
///
|
||||
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
|
||||
/// [server_key]. The amount of the orders that are successfully filled is written over the original
|
||||
/// order count.
|
||||
pub fn volume_match(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
println!("Calculating total sell and buy volumes...");
|
||||
let time = Instant::now();
|
||||
|
||||
let mut total_sell_volume = vector_sum(server_key, sell_orders);
|
||||
let mut total_buy_volume = vector_sum(server_key, buy_orders);
|
||||
|
||||
println!(
|
||||
"Total sell and buy volumes are calculated in {:?}",
|
||||
time.elapsed()
|
||||
);
|
||||
|
||||
println!("Calculating total volume to be matched...");
|
||||
let time = Instant::now();
|
||||
let total_volume = server_key.smart_min(&mut total_sell_volume, &mut total_buy_volume);
|
||||
println!(
|
||||
"Calculated total volume to be matched in {:?}",
|
||||
time.elapsed()
|
||||
);
|
||||
|
||||
println!("Filling orders...");
|
||||
let time = Instant::now();
|
||||
fill_orders(server_key, sell_orders, total_volume.clone());
|
||||
fill_orders(server_key, buy_orders, total_volume);
|
||||
println!("Filled orders in {:?}", time.elapsed());
|
||||
}
|
||||
|
||||
pub fn tester(
|
||||
client_key: &ClientKey,
|
||||
server_key: &ServerKey,
|
||||
input_sell_orders: &[u16],
|
||||
input_buy_orders: &[u16],
|
||||
expected_filled_sells: &[u16],
|
||||
expected_filled_buys: &[u16],
|
||||
fhe_function: fn(&mut [RadixCiphertext], &mut [RadixCiphertext], &ServerKey),
|
||||
) {
|
||||
let encrypt = |pt: u16| client_key.encrypt_radix(pt as u64, NUMBER_OF_BLOCKS);
|
||||
|
||||
let mut encrypted_sell_orders = input_sell_orders
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(encrypt)
|
||||
.collect::<Vec<RadixCiphertext>>();
|
||||
let mut encrypted_buy_orders = input_buy_orders
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(encrypt)
|
||||
.collect::<Vec<RadixCiphertext>>();
|
||||
|
||||
println!("Running FHE implementation...");
|
||||
let time = Instant::now();
|
||||
fhe_function(
|
||||
&mut encrypted_sell_orders,
|
||||
&mut encrypted_buy_orders,
|
||||
server_key,
|
||||
);
|
||||
println!("Ran FHE implementation in {:?}", time.elapsed());
|
||||
|
||||
let decrypt = |ct| client_key.decrypt_radix::<u64>(ct) as u16;
|
||||
|
||||
let decrypted_filled_sells: Vec<u16> = encrypted_sell_orders.iter().map(decrypt).collect();
|
||||
let decrypted_filled_buys: Vec<u16> = encrypted_buy_orders.iter().map(decrypt).collect();
|
||||
|
||||
assert_eq!(decrypted_filled_sells, expected_filled_sells);
|
||||
assert_eq!(decrypted_filled_buys, expected_filled_buys);
|
||||
}
|
||||
135
tfhe/examples/dark_market/improved_parallel_fhe.rs
Normal file
135
tfhe/examples/dark_market/improved_parallel_fhe.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
use tfhe::integer::ciphertext::RadixCiphertext;
|
||||
use tfhe::integer::ServerKey;
|
||||
|
||||
use crate::NUMBER_OF_BLOCKS;
|
||||
|
||||
fn compute_prefix_sum(server_key: &ServerKey, arr: &[RadixCiphertext]) -> Vec<RadixCiphertext> {
|
||||
if arr.is_empty() {
|
||||
return arr.to_vec();
|
||||
}
|
||||
let mut prefix_sum: Vec<RadixCiphertext> = (0..arr.len().next_power_of_two())
|
||||
.into_par_iter()
|
||||
.map(|i| {
|
||||
if i < arr.len() {
|
||||
arr[i].clone()
|
||||
} else {
|
||||
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
for d in 0..prefix_sum.len().ilog2() {
|
||||
prefix_sum
|
||||
.par_chunks_exact_mut(2_usize.pow(d + 1))
|
||||
.for_each(move |chunk| {
|
||||
let length = chunk.len();
|
||||
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
|
||||
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
|
||||
});
|
||||
}
|
||||
let last = prefix_sum.last().unwrap().clone();
|
||||
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
for d in (0..prefix_sum.len().ilog2()).rev() {
|
||||
prefix_sum
|
||||
.par_chunks_exact_mut(2_usize.pow(d + 1))
|
||||
.for_each(move |chunk| {
|
||||
let length = chunk.len();
|
||||
let temp = chunk.last().unwrap().clone();
|
||||
let mut mid = chunk.get((length - 1) / 2).unwrap().clone();
|
||||
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut mid);
|
||||
chunk[(length - 1) / 2] = temp;
|
||||
});
|
||||
}
|
||||
prefix_sum.push(last);
|
||||
prefix_sum[1..=arr.len()].to_vec()
|
||||
}
|
||||
|
||||
fn fill_orders(
|
||||
server_key: &ServerKey,
|
||||
total_orders: &RadixCiphertext,
|
||||
orders: &mut [RadixCiphertext],
|
||||
prefix_sum_arr: &[RadixCiphertext],
|
||||
) {
|
||||
orders
|
||||
.into_par_iter()
|
||||
.enumerate()
|
||||
.for_each(move |(i, order)| {
|
||||
// (total_orders - previous_prefix_sum).max(0)
|
||||
let mut diff = if i == 0 {
|
||||
total_orders.clone()
|
||||
} else {
|
||||
let previous_prefix_sum = &prefix_sum_arr[i - 1];
|
||||
|
||||
// total_orders - previous_prefix_sum
|
||||
let mut diff = server_key.smart_sub_parallelized(
|
||||
&mut total_orders.clone(),
|
||||
&mut previous_prefix_sum.clone(),
|
||||
);
|
||||
|
||||
// total_orders > prefix_sum
|
||||
let mut cond = server_key.smart_gt_parallelized(
|
||||
&mut total_orders.clone(),
|
||||
&mut previous_prefix_sum.clone(),
|
||||
);
|
||||
|
||||
// (total_orders - previous_prefix_sum) * (total_orders > previous_prefix_sum)
|
||||
// = (total_orders - previous_prefix_sum).max(0)
|
||||
server_key.smart_mul_parallelized(&mut cond, &mut diff)
|
||||
};
|
||||
|
||||
// (total_orders - previous_prefix_sum).max(0).min(*order);
|
||||
*order = server_key.smart_min_parallelized(&mut diff, order);
|
||||
});
|
||||
}
|
||||
|
||||
/// FHE implementation of the volume matching algorithm.
|
||||
///
|
||||
/// In this function, the implemented algorithm is modified to utilize more concurrency.
|
||||
///
|
||||
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
|
||||
/// [server_key]. The amount of the orders that are successfully filled is written over the original
|
||||
/// order count.
|
||||
pub fn volume_match(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
println!("Creating prefix sum arrays...");
|
||||
let time = Instant::now();
|
||||
let (prefix_sum_sell_orders, prefix_sum_buy_orders) = rayon::join(
|
||||
|| compute_prefix_sum(server_key, sell_orders),
|
||||
|| compute_prefix_sum(server_key, buy_orders),
|
||||
);
|
||||
println!("Created prefix sum arrays in {:?}", time.elapsed());
|
||||
|
||||
let zero = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
|
||||
|
||||
let total_buy_orders = prefix_sum_buy_orders.last().unwrap_or(&zero);
|
||||
|
||||
let total_sell_orders = prefix_sum_sell_orders.last().unwrap_or(&zero);
|
||||
|
||||
println!("Matching orders...");
|
||||
let time = Instant::now();
|
||||
rayon::join(
|
||||
|| {
|
||||
fill_orders(
|
||||
server_key,
|
||||
total_sell_orders,
|
||||
buy_orders,
|
||||
&prefix_sum_buy_orders,
|
||||
)
|
||||
},
|
||||
|| {
|
||||
fill_orders(
|
||||
server_key,
|
||||
total_buy_orders,
|
||||
sell_orders,
|
||||
&prefix_sum_sell_orders,
|
||||
)
|
||||
},
|
||||
);
|
||||
println!("Matched orders in {:?}", time.elapsed());
|
||||
}
|
||||
32
tfhe/examples/dark_market/improved_plain.rs
Normal file
32
tfhe/examples/dark_market/improved_plain.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
fn compute_prefix_sum(arr: &[u16]) -> Vec<u16> {
|
||||
let mut sum = 0;
|
||||
arr.iter()
|
||||
.map(|a| {
|
||||
sum += a;
|
||||
sum
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn fill_orders(total_orders: u16, orders: &mut [u16], prefix_sum_arr: &[u16]) {
|
||||
for (i, order) in orders.iter_mut().enumerate() {
|
||||
let previous_prefix_sum = if i == 0 { 0 } else { prefix_sum_arr[i - 1] };
|
||||
|
||||
*order = (total_orders as i64 - previous_prefix_sum as i64)
|
||||
.max(0)
|
||||
.min(*order as i64) as u16;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn volume_match(sell_orders: &mut [u16], buy_orders: &mut [u16]) {
|
||||
let prefix_sum_sell_orders = compute_prefix_sum(sell_orders);
|
||||
|
||||
let prefix_sum_buy_orders = compute_prefix_sum(buy_orders);
|
||||
|
||||
let total_buy_orders = *prefix_sum_buy_orders.last().unwrap_or(&0);
|
||||
|
||||
let total_sell_orders = *prefix_sum_sell_orders.last().unwrap_or(&0);
|
||||
|
||||
fill_orders(total_sell_orders, buy_orders, &prefix_sum_buy_orders);
|
||||
fill_orders(total_buy_orders, sell_orders, &prefix_sum_sell_orders);
|
||||
}
|
||||
111
tfhe/examples/dark_market/main.rs
Normal file
111
tfhe/examples/dark_market/main.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use tfhe::integer::ciphertext::RadixCiphertext;
|
||||
use tfhe::integer::keycache::IntegerKeyCache;
|
||||
use tfhe::integer::ServerKey;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
|
||||
|
||||
mod fhe;
|
||||
mod improved_parallel_fhe;
|
||||
mod improved_plain;
|
||||
mod parallel_fhe;
|
||||
mod plain;
|
||||
|
||||
/// The number of blocks to be used in the Radix.
|
||||
const NUMBER_OF_BLOCKS: usize = 8;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn test_cases() -> Vec<(String, (Vec<u16>, Vec<u16>, Vec<u16>, Vec<u16>))> {
|
||||
vec![
|
||||
(
|
||||
"empty sell orders".to_owned(),
|
||||
(vec![], (1..11).collect::<Vec<_>>(), vec![], vec![0; 10]),
|
||||
),
|
||||
(
|
||||
"empty buy orders".to_owned(),
|
||||
((1..11).collect::<Vec<_>>(), vec![], vec![0; 10], vec![]),
|
||||
),
|
||||
(
|
||||
"exact matching of sell and buy orders".to_owned(),
|
||||
(
|
||||
(1..11).collect::<Vec<_>>(),
|
||||
(1..11).collect::<Vec<_>>(),
|
||||
(1..11).collect::<Vec<_>>(),
|
||||
(1..11).collect::<Vec<_>>(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"a case where there are more buy orders than sell orders".to_owned(),
|
||||
(vec![10; 10], vec![200], vec![10; 10], vec![100]),
|
||||
),
|
||||
(
|
||||
"a case where there are more sell orders than buy orders".to_owned(),
|
||||
(vec![200], vec![10; 10], vec![100], vec![10; 10]),
|
||||
),
|
||||
(
|
||||
"maximum input size for sell and buy orders".to_owned(),
|
||||
(
|
||||
vec![100; 499],
|
||||
vec![100; 499],
|
||||
vec![100; 499],
|
||||
vec![100; 499],
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Runs the given [tester] function with the test cases for volume matching algorithm.
|
||||
fn run_test_cases(tester: impl Fn(&[u16], &[u16], &[u16], &[u16])) {
|
||||
for (test_name, test_case) in &test_cases() {
|
||||
println!("Testing {test_name}...");
|
||||
tester(&test_case.0, &test_case.1, &test_case.2, &test_case.3);
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
fn test_volume_match_plain(function: fn(&mut [u16], &mut [u16])) {
|
||||
println!("Running test cases for the plain implementation");
|
||||
run_test_cases(|a, b, c, d| plain::tester(a, b, c, d, function));
|
||||
}
|
||||
|
||||
fn test_volume_match_fhe(
|
||||
fhe_function: fn(&mut [RadixCiphertext], &mut [RadixCiphertext], &ServerKey),
|
||||
) {
|
||||
println!("Generating keys...");
|
||||
let time = Instant::now();
|
||||
let (client_key, server_key) = IntegerKeyCache.get_from_params(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
|
||||
println!("Keys generated in {:?}", time.elapsed());
|
||||
|
||||
println!("Running test cases for the FHE implementation");
|
||||
run_test_cases(|a, b, c, d| fhe::tester(&client_key, &server_key, a, b, c, d, fhe_function));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
for argument in std::env::args() {
|
||||
if argument == "plain" {
|
||||
println!("Running plain version");
|
||||
test_volume_match_plain(plain::volume_match);
|
||||
println!();
|
||||
}
|
||||
if argument == "plain-improved" {
|
||||
println!("Running plain improved version");
|
||||
test_volume_match_plain(improved_plain::volume_match);
|
||||
println!();
|
||||
}
|
||||
if argument == "fhe" {
|
||||
println!("Running fhe version");
|
||||
test_volume_match_fhe(fhe::volume_match);
|
||||
println!();
|
||||
}
|
||||
if argument == "fhe-parallel" {
|
||||
println!("Running parallelized fhe version");
|
||||
test_volume_match_fhe(parallel_fhe::volume_match);
|
||||
println!();
|
||||
}
|
||||
if argument == "fhe-improved" {
|
||||
println!("Running improved parallelized fhe fhe version");
|
||||
test_volume_match_fhe(improved_parallel_fhe::volume_match);
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
tfhe/examples/dark_market/parallel_fhe.rs
Normal file
75
tfhe/examples/dark_market/parallel_fhe.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
use tfhe::integer::ciphertext::RadixCiphertext;
|
||||
use tfhe::integer::ServerKey;
|
||||
|
||||
use crate::NUMBER_OF_BLOCKS;
|
||||
|
||||
// Calculate the element sum of the given vector in parallel
|
||||
fn vector_sum(server_key: &ServerKey, orders: Vec<RadixCiphertext>) -> RadixCiphertext {
|
||||
orders.into_par_iter().reduce(
|
||||
|| server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS),
|
||||
|mut acc: RadixCiphertext, mut ele: RadixCiphertext| {
|
||||
server_key.smart_add_parallelized(&mut acc, &mut ele)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn fill_orders(
|
||||
server_key: &ServerKey,
|
||||
orders: &mut [RadixCiphertext],
|
||||
total_volume: RadixCiphertext,
|
||||
) {
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for order in orders {
|
||||
let mut filled_amount =
|
||||
server_key.smart_min_parallelized(&mut volume_left_to_transact, order);
|
||||
server_key.smart_sub_assign_parallelized(&mut volume_left_to_transact, &mut filled_amount);
|
||||
*order = filled_amount;
|
||||
}
|
||||
}
|
||||
|
||||
/// FHE implementation of the volume matching algorithm.
|
||||
///
|
||||
/// This version of the algorithm utilizes parallelization to speed up the computation.
|
||||
///
|
||||
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
|
||||
/// [server_key]. The amount of the orders that are successfully filled is written over the original
|
||||
/// order count.
|
||||
pub fn volume_match(
|
||||
sell_orders: &mut [RadixCiphertext],
|
||||
buy_orders: &mut [RadixCiphertext],
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
println!("Calculating total sell and buy volumes...");
|
||||
let time = Instant::now();
|
||||
// Total sell and buy volumes can be calculated in parallel because they have no dependency on
|
||||
// each other.
|
||||
let (mut total_sell_volume, mut total_buy_volume) = rayon::join(
|
||||
|| vector_sum(server_key, sell_orders.to_owned()),
|
||||
|| vector_sum(server_key, buy_orders.to_owned()),
|
||||
);
|
||||
println!(
|
||||
"Total sell and buy volumes are calculated in {:?}",
|
||||
time.elapsed()
|
||||
);
|
||||
|
||||
println!("Calculating total volume to be matched...");
|
||||
let time = Instant::now();
|
||||
let total_volume =
|
||||
server_key.smart_min_parallelized(&mut total_sell_volume, &mut total_buy_volume);
|
||||
println!(
|
||||
"Calculated total volume to be matched in {:?}",
|
||||
time.elapsed()
|
||||
);
|
||||
|
||||
println!("Filling orders...");
|
||||
let time = Instant::now();
|
||||
rayon::join(
|
||||
|| fill_orders(server_key, sell_orders, total_volume.clone()),
|
||||
|| fill_orders(server_key, buy_orders, total_volume.clone()),
|
||||
);
|
||||
println!("Filled orders in {:?}", time.elapsed());
|
||||
}
|
||||
43
tfhe/examples/dark_market/plain.rs
Normal file
43
tfhe/examples/dark_market/plain.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use std::time::Instant;
|
||||
|
||||
fn fill_orders(orders: &mut [u16], total_volume: u16) {
|
||||
let mut volume_left_to_transact = total_volume;
|
||||
for order in orders {
|
||||
let filled_amount = std::cmp::min(volume_left_to_transact, *order);
|
||||
*order = filled_amount;
|
||||
volume_left_to_transact -= filled_amount;
|
||||
}
|
||||
}
|
||||
|
||||
/// Plain implementation of the volume matching algorithm.
|
||||
///
|
||||
/// Matches the given [sell_orders] with [buy_orders].
|
||||
/// The amount of the orders that are successfully filled is written over the original order count.
|
||||
pub fn volume_match(sell_orders: &mut [u16], buy_orders: &mut [u16]) {
|
||||
let total_sell_volume: u16 = sell_orders.iter().sum();
|
||||
let total_buy_volume: u16 = buy_orders.iter().sum();
|
||||
|
||||
let total_volume = std::cmp::min(total_buy_volume, total_sell_volume);
|
||||
|
||||
fill_orders(sell_orders, total_volume);
|
||||
fill_orders(buy_orders, total_volume);
|
||||
}
|
||||
|
||||
pub fn tester(
|
||||
input_sell_orders: &[u16],
|
||||
input_buy_orders: &[u16],
|
||||
expected_filled_sells: &[u16],
|
||||
expected_filled_buys: &[u16],
|
||||
function: fn(&mut [u16], &mut [u16]),
|
||||
) {
|
||||
let mut sell_orders = input_sell_orders.to_vec();
|
||||
let mut buy_orders = input_buy_orders.to_vec();
|
||||
|
||||
println!("Running plain implementation...");
|
||||
let time = Instant::now();
|
||||
function(&mut sell_orders, &mut buy_orders);
|
||||
println!("Ran plain implementation in {:?}", time.elapsed());
|
||||
|
||||
assert_eq!(sell_orders, expected_filled_sells);
|
||||
assert_eq!(buy_orders, expected_filled_buys);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ fn write_result(file: &mut File, name: &str, value: usize) {
|
||||
}
|
||||
|
||||
fn client_server_key_sizes(results_file: &Path) {
|
||||
let boolean_params_vec = vec![
|
||||
let boolean_params_vec = [
|
||||
(DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS"),
|
||||
(PARAMETERS_ERROR_PROB_2_POW_MINUS_165, "TFHE_LIB_PARAMETERS"),
|
||||
];
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use tfhe::boolean;
|
||||
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, DEFAULT_PARAMETERS_KS_PBS};
|
||||
use tfhe::keycache::NamedParam;
|
||||
use tfhe::shortint::keycache::{KEY_CACHE, KEY_CACHE_WOPBS};
|
||||
use tfhe::shortint::keycache::{KEY_CACHE, KEY_CACHE_KSK, KEY_CACHE_WOPBS};
|
||||
use tfhe::shortint::parameters::key_switching::{
|
||||
ShortintKeySwitchingParameters, PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
|
||||
};
|
||||
use tfhe::shortint::parameters::multi_bit::{
|
||||
MultiBitPBSParameters, PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
|
||||
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
|
||||
};
|
||||
use tfhe::shortint::parameters::parameters_compact_pk::{
|
||||
PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS, PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS,
|
||||
};
|
||||
use tfhe::shortint::parameters::parameters_wopbs_message_carry::{
|
||||
WOPBS_PARAM_MESSAGE_1_CARRY_1_KS_PBS, WOPBS_PARAM_MESSAGE_2_CARRY_2_KS_PBS,
|
||||
WOPBS_PARAM_MESSAGE_3_CARRY_3_KS_PBS, WOPBS_PARAM_MESSAGE_4_CARRY_4_KS_PBS,
|
||||
@@ -17,56 +29,75 @@ fn client_server_keys() {
|
||||
Arg::new("multi_bit_only")
|
||||
.long("multi-bit-only")
|
||||
.help("Set to generate only multi bit keys, otherwise only PBS and WoPBS keys are generated")
|
||||
.action(ArgAction::SetTrue),
|
||||
.action(ArgAction::SetTrue)
|
||||
.exclusive(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("coverage_only")
|
||||
.long("coverage-only")
|
||||
.help("Set to generate only coverage keys, a very small subset of keys")
|
||||
.action(ArgAction::SetTrue)
|
||||
.exclusive(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// If set using the command line flag "--ladner-fischer" this algorithm will be used in
|
||||
// additions
|
||||
let multi_bit_only: bool = matches.get_flag("multi_bit_only");
|
||||
let coverage_only: bool = matches.get_flag("coverage_only");
|
||||
|
||||
if multi_bit_only {
|
||||
println!("Generating shortint multibit (ClientKey, ServerKey)");
|
||||
for (i, params) in ALL_MULTI_BIT_PARAMETER_VEC.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
ALL_MULTI_BIT_PARAMETER_VEC.len(),
|
||||
params.name()
|
||||
);
|
||||
generate_pbs_multi_bit_keys(&ALL_MULTI_BIT_PARAMETER_VEC);
|
||||
} else if coverage_only {
|
||||
println!("Generating keys (ClientKey, ServerKey) for coverage");
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
const PBS_PARAMS: [ClassicPBSParameters; 5] = [
|
||||
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
|
||||
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
|
||||
PARAM_MESSAGE_3_CARRY_3_KS_PBS,
|
||||
PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_KS_PBS,
|
||||
PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS,
|
||||
];
|
||||
|
||||
let _ = KEY_CACHE.get_from_param(params.with_non_deterministic_execution());
|
||||
generate_pbs_keys(&PBS_PARAMS);
|
||||
|
||||
let stop = start.elapsed().as_secs();
|
||||
const MULTI_BIT_PARAMS: [MultiBitPBSParameters; 2] = [
|
||||
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2_KS_PBS,
|
||||
PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_3_KS_PBS,
|
||||
];
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
generate_pbs_multi_bit_keys(&MULTI_BIT_PARAMS);
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
const KSK_PARAMS: [(
|
||||
ClassicPBSParameters,
|
||||
ClassicPBSParameters,
|
||||
ShortintKeySwitchingParameters,
|
||||
); 2] = [
|
||||
(
|
||||
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
|
||||
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
|
||||
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
|
||||
),
|
||||
(
|
||||
PARAM_MESSAGE_1_CARRY_1_KS_PBS,
|
||||
PARAM_MESSAGE_3_CARRY_3_KS_PBS,
|
||||
PARAM_KEYSWITCH_1_1_KS_PBS_TO_2_2_KS_PBS,
|
||||
),
|
||||
];
|
||||
|
||||
generate_ksk_keys(&KSK_PARAMS);
|
||||
|
||||
const WOPBS_PARAMS: [(ClassicPBSParameters, WopbsParameters); 1] = [(
|
||||
PARAM_MESSAGE_2_CARRY_2_KS_PBS,
|
||||
WOPBS_PARAM_MESSAGE_2_CARRY_2_KS_PBS,
|
||||
)];
|
||||
generate_wopbs_keys(&WOPBS_PARAMS);
|
||||
|
||||
const BOOLEAN_PARAMS: [BooleanParameters; 2] =
|
||||
[DEFAULT_PARAMETERS, DEFAULT_PARAMETERS_KS_PBS];
|
||||
generate_boolean_keys(&BOOLEAN_PARAMS);
|
||||
} else {
|
||||
println!("Generating shortint (ClientKey, ServerKey)");
|
||||
for (i, params) in ALL_PARAMETER_VEC.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
ALL_PARAMETER_VEC.len(),
|
||||
params.name()
|
||||
);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let _ = KEY_CACHE.get_from_param(params);
|
||||
|
||||
let stop = start.elapsed().as_secs();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
generate_pbs_keys(&ALL_PARAMETER_VEC);
|
||||
|
||||
const WOPBS_PARAMS: [(ClassicPBSParameters, WopbsParameters); 4] = [
|
||||
(
|
||||
@@ -87,27 +118,136 @@ fn client_server_keys() {
|
||||
),
|
||||
];
|
||||
|
||||
println!("Generating woPBS keys");
|
||||
for (i, (params_shortint, params_wopbs)) in WOPBS_PARAMS.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}, {}",
|
||||
i + 1,
|
||||
WOPBS_PARAMS.len(),
|
||||
params_shortint.name(),
|
||||
params_wopbs.name(),
|
||||
);
|
||||
generate_wopbs_keys(&WOPBS_PARAMS);
|
||||
}
|
||||
}
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
fn generate_pbs_keys(params: &[ClassicPBSParameters]) {
|
||||
println!("Generating shortint (ClientKey, ServerKey)");
|
||||
|
||||
let _ = KEY_CACHE_WOPBS.get_from_param((params_shortint, params_wopbs));
|
||||
for (i, param) in params.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
params.len(),
|
||||
param.name()
|
||||
);
|
||||
|
||||
let stop = start.elapsed().as_secs();
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
let _ = KEY_CACHE.get_from_param(param);
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE_WOPBS.clear_in_memory_cache()
|
||||
}
|
||||
let stop = start.elapsed().as_secs();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_pbs_multi_bit_keys(params: &[MultiBitPBSParameters]) {
|
||||
println!("Generating shortint multibit (ClientKey, ServerKey)");
|
||||
|
||||
for (i, param) in params.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
params.len(),
|
||||
param.name()
|
||||
);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let _ = KEY_CACHE.get_from_param(param.with_non_deterministic_execution());
|
||||
|
||||
let stop = start.elapsed().as_secs();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_ksk_keys(
|
||||
params: &[(
|
||||
ClassicPBSParameters,
|
||||
ClassicPBSParameters,
|
||||
ShortintKeySwitchingParameters,
|
||||
)],
|
||||
) {
|
||||
println!("Generating shortint Key Switching keys (ClientKey, ServerKey)");
|
||||
|
||||
for (i, (param_1, param_2, param_ksk)) in params.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}__{}__{}",
|
||||
i + 1,
|
||||
params.len(),
|
||||
param_1.name(),
|
||||
param_2.name(),
|
||||
param_ksk.name()
|
||||
);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let _ = KEY_CACHE_KSK.get_from_param((param_1, param_2, param_ksk));
|
||||
|
||||
let stop = start.elapsed().as_secs();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE_KSK.clear_in_memory_cache()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_wopbs_keys(params: &[(ClassicPBSParameters, WopbsParameters)]) {
|
||||
println!("Generating woPBS keys");
|
||||
|
||||
for (i, (params_shortint, params_wopbs)) in params.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}, {}",
|
||||
i + 1,
|
||||
params.len(),
|
||||
params_shortint.name(),
|
||||
params_wopbs.name(),
|
||||
);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let _ = KEY_CACHE_WOPBS.get_from_param((params_shortint, params_wopbs));
|
||||
|
||||
let stop = start.elapsed().as_secs();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE_WOPBS.clear_in_memory_cache()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_boolean_keys(params: &[BooleanParameters]) {
|
||||
println!("Generating boolean (ClientKey, ServerKey)");
|
||||
|
||||
for (i, param) in params.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
params.len(),
|
||||
param.name()
|
||||
);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let _ = boolean::keycache::KEY_CACHE.get_from_param(param);
|
||||
|
||||
let stop = start.elapsed().as_secs();
|
||||
|
||||
println!("Generation took {stop} seconds");
|
||||
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
boolean::keycache::KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use tfhe::core_crypto::commons::dispersion::StandardDev;
|
||||
use tfhe::core_crypto::commons::parameters::{GlweDimension, LweDimension, PolynomialSize};
|
||||
use tfhe::keycache::NamedParam;
|
||||
use tfhe::shortint::parameters::multi_bit::ALL_MULTI_BIT_PARAMETER_VEC;
|
||||
use tfhe::shortint::parameters::parameters_compact_pk::ALL_PARAMETER_VEC_COMPACT_PK;
|
||||
use tfhe::shortint::parameters::{
|
||||
ShortintParameterSet, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1_PBS_KS,
|
||||
PARAM_MESSAGE_2_CARRY_2_PBS_KS, PARAM_MESSAGE_3_CARRY_3_PBS_KS, PARAM_MESSAGE_4_CARRY_4_PBS_KS,
|
||||
@@ -153,7 +154,12 @@ fn main() {
|
||||
PARAM_MESSAGE_3_CARRY_3_PBS_KS,
|
||||
PARAM_MESSAGE_4_CARRY_4_PBS_KS,
|
||||
];
|
||||
let all_classic_pbs = [ALL_PARAMETER_VEC.to_vec(), classic_params_small].concat();
|
||||
let all_classic_pbs = [
|
||||
ALL_PARAMETER_VEC.to_vec(),
|
||||
ALL_PARAMETER_VEC_COMPACT_PK.to_vec(),
|
||||
classic_params_small,
|
||||
]
|
||||
.concat();
|
||||
let classic_pbs = all_classic_pbs
|
||||
.iter()
|
||||
.map(|p| ShortintParameterSet::from(*p))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user