Compare commits

..

2 Commits

Author SHA1 Message Date
Carl-Zama
ccc4410515 fix custom modulus keyswitch and error generation 2023-04-19 00:30:48 +01:00
Arthur Meyre
31348cb1f8 refactor(tfhe): wip dynamic modulus for LWE ciphertexts 2023-03-24 17:34:40 +01:00
489 changed files with 14360 additions and 63131 deletions

View File

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

View File

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

View File

@@ -25,35 +25,26 @@ on:
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'
matrix_item:
description: 'Build matrix item'
type: string
jobs:
integer-tests:
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
group: ${{ github.workflow }}_${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ inputs.runner_name }}
runs-on: ${{ github.event.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 }}"
echo "ID: ${{ github.event.inputs.instance_id }}"
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
echo "Type: ${{ github.event.inputs.instance_type }}"
echo "Request ID: ${{ github.event.inputs.request_id }}"
- name: Checkout tfhe-rs
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
- name: Set up home
run: |

View File

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

View File

@@ -25,35 +25,26 @@ on:
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'
matrix_item:
description: 'Build matrix item'
type: string
jobs:
shortint-tests:
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}_${{ inputs.instance_image_id }}_${{ inputs.instance_type }}
group: ${{ github.workflow }}_${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
cancel-in-progress: true
runs-on: ${{ inputs.runner_name }}
runs-on: ${{ github.event.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 }}"
echo "ID: ${{ github.event.inputs.instance_id }}"
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
echo "Type: ${{ github.event.inputs.instance_type }}"
echo "Request ID: ${{ github.event.inputs.request_id }}"
- name: Checkout tfhe-rs
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: ${{ inputs.fork_repo }}
ref: ${{ inputs.fork_git_sha }}
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
- name: Set up home
run: |
@@ -67,7 +58,7 @@ jobs:
- name: Run core tests
run: |
AVX512_SUPPORT=ON make test_core_crypto
make test_core_crypto
- name: Run boolean tests
run: |
@@ -81,6 +72,10 @@ jobs:
run: |
make test_user_doc
- name: Run js on wasm API tests
run: |
make test_nodejs_wasm_api_in_docker
- name: Gen Keys if required
run: |
make gen_key_cache
@@ -89,10 +84,6 @@ jobs:
run: |
BIG_TESTS_INSTANCE=TRUE make test_shortint_ci
- name: Run high-level API tests
run: |
BIG_TESTS_INSTANCE=TRUE make test_high_level_api
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true

View File

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

View File

@@ -5,20 +5,24 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: "Instance ID"
description: 'Instance ID'
type: string
instance_image_id:
description: "Instance AMI ID"
description: 'Instance AMI ID'
type: string
instance_type:
description: "Instance product type"
description: 'Instance product type'
type: string
runner_name:
description: "Action runner name"
description: 'Action runner name'
type: string
request_id:
description: "Slab request ID"
description: 'Slab request ID'
type: string
matrix_item:
description: 'Build matrix item'
type: string
env:
CARGO_TERM_COLOR: always
@@ -36,13 +40,14 @@ jobs:
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Matrix item: ${{ inputs.matrix_item }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
with:
fetch-depth: 0
@@ -57,25 +62,39 @@ jobs:
toolchain: nightly
override: true
- name: Run benchmarks with AVX512
- name: Run benchmarks
run: |
make AVX512_SUPPORT=ON bench_boolean
make bench_boolean
- name: Parse results
run: |
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs \
--database tfhe_rs_benchmarks \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--name-suffix avx512 \
--throughput
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_boolean
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--hardware ${{ inputs.instance_type }} \
--name-suffix avx512 \
--throughput \
--append-results
- name: Measure key sizes
run: |
make measure_boolean_key_sizes
@@ -85,7 +104,7 @@ jobs:
python3 ./ci/benchmark_parser.py tfhe/boolean_key_sizes.csv ${{ env.RESULTS_FILENAME }} \
--key-sizes \
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
@@ -93,7 +112,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
with:
repository: zama-ai/slab
path: slab
@@ -102,25 +121,13 @@ jobs:
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on results file"
echo "Computing HMac on downloaded artifact"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
echo "Sending results to Slab..."
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data_v2" \
-H "X-Slab-Command: store_data" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Boolean benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -17,11 +17,16 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest]
fail-fast: false
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
- name: Get rust toolchain to use for checks and lints
id: toolchain
run: |
echo "rs-toolchain=$(make rs_toolchain)" >> "${GITHUB_OUTPUT}"
- name: Run pcc checks
run: |
@@ -29,8 +34,7 @@ jobs:
- name: Build Release core
run: |
make build_core AVX512_SUPPORT=ON
make build_core_experimental AVX512_SUPPORT=ON
make build_core
- name: Build Release boolean
run: |

View File

@@ -1,128 +0,0 @@
# Run integer benchmarks on an AWS instance and return parsed results to Slab CI bot.
name: Integer benchmarks
on:
workflow_dispatch:
inputs:
instance_id:
description: "Instance ID"
type: string
instance_image_id:
description: "Instance AMI ID"
type: string
instance_type:
description: "Instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
request_id:
description: "Slab request ID"
type: string
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
PARSE_INTEGER_BENCH_CSV_FILE: tfhe_rs_integer_benches_${{ github.sha }}.csv
jobs:
run-integer-benchmarks:
name: Execute integer benchmarks in EC2
runs-on: ${{ github.event.inputs.runner_name }}
if: ${{ !cancelled() }}
steps:
- name: Instance configuration used
run: |
echo "IDs: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
- name: Set up home
# "Install rust" step require root user to have a HOME directory which is not set.
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install rust
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: nightly
override: true
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_integer
- name: Parse benchmarks to csv
run: |
make PARSE_INTEGER_BENCH_CSV_FILE=${{ env.PARSE_INTEGER_BENCH_CSV_FILE }} \
parse_integer_benches
- name: Upload csv results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_csv_integer
path: ${{ env.PARSE_INTEGER_BENCH_CSV_FILE }}
- name: Parse results
run: |
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--name-suffix avx512 \
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_integer
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on results file"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
echo "Sending results to Slab..."
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data_v2" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Integer benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: ["self-hosted", "m1mac"]
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af

View File

@@ -1,52 +0,0 @@
# Publish new release of tfhe-rs on various platform.
name: Publish release
on:
workflow_dispatch:
inputs:
dry_run:
description: "Dry-run"
type: boolean
default: true
jobs:
publish_release:
name: Publish Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
- name: Publish crate.io package
env:
CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
DRY_RUN: ${{ inputs.dry_run && '--dry-run' || '' }}
run: |
cargo publish -p tfhe --token ${{ env.CRATES_TOKEN }} ${{ env.DRY_RUN }}
- name: Build web package
run: |
make build_web_js_api
- name: Publish web package
uses: JS-DevTools/npm-publish@a25b4180b728b0279fca97d4e5bccf391685aead
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json
dry-run: ${{ inputs.dry_run }}
- name: Build Node package
run: |
rm -rf tfhe/pkg
make build_node_js_api
sed -i 's/"tfhe"/"node-tfhe"/g' tfhe/pkg/package.json
- name: Publish Node package
uses: JS-DevTools/npm-publish@a25b4180b728b0279fca97d4e5bccf391685aead
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json
dry-run: ${{ inputs.dry_run }}

View File

@@ -5,21 +5,22 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: "Instance ID"
description: 'Instance ID'
type: string
instance_image_id:
description: "Instance AMI ID"
description: 'Instance AMI ID'
type: string
instance_type:
description: "Instance product type"
description: 'Instance product type'
type: string
runner_name:
description: "Action runner name"
description: 'Action runner name'
type: string
request_id:
description: "Slab request ID"
description: 'Slab request ID'
type: string
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
@@ -42,7 +43,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
with:
fetch-depth: 0
@@ -66,14 +67,13 @@ jobs:
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs \
--database tfhe_rs_benchmarks \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--name-suffix avx512 \
--walk-subdirs \
--throughput
- name: Upload parsed results artifact
@@ -83,7 +83,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
with:
repository: zama-ai/slab
path: slab
@@ -98,19 +98,7 @@ jobs:
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data_v2" \
-H "X-Slab-Command: store_data" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "PBS benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -5,20 +5,24 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: "Instance ID"
description: 'Instance ID'
type: string
instance_image_id:
description: "Instance AMI ID"
description: 'Instance AMI ID'
type: string
instance_type:
description: "Instance product type"
description: 'Instance product type'
type: string
runner_name:
description: "Action runner name"
description: 'Action runner name'
type: string
request_id:
description: "Slab request ID"
description: 'Slab request ID'
type: string
matrix_item:
description: 'Build matrix item'
type: string
env:
CARGO_TERM_COLOR: always
@@ -36,13 +40,14 @@ jobs:
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
echo "Matrix item: ${{ inputs.matrix_item }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
with:
fetch-depth: 0
@@ -57,25 +62,41 @@ jobs:
toolchain: nightly
override: true
- name: Run benchmarks with AVX512
- name: Run benchmarks
run: |
make AVX512_SUPPORT=ON bench_shortint
make bench_shortint
- name: Parse results
run: |
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs \
--database tfhe_rs_benchmarks \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--name-suffix avx512 \
--throughput
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_shortint
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--hardware ${{ inputs.instance_type }} \
--walk-subdirs \
--name-suffix avx512 \
--throughput \
--append-results
- name: Measure key sizes
run: |
make measure_shortint_key_sizes
@@ -93,7 +114,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
with:
repository: zama-ai/slab
path: slab
@@ -102,25 +123,13 @@ jobs:
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on results file"
echo "Computing HMac on downloaded artifact"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
echo "Sending results to Slab..."
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data_v2" \
-H "X-Slab-Command: store_data" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "Shortint benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -4,19 +4,18 @@ name: Start all benchmarks
on:
push:
branches:
- "main"
- 'main'
workflow_dispatch:
jobs:
start-benchmarks:
if: ${{ (github.event_name == 'push' && github.repository == 'zama-ai/tfhe-rs') || github.event_name == 'workflow_dispatch' }}
strategy:
matrix:
command: [boolean_bench, shortint_bench, integer_bench, pbs_bench, wasm_client_bench]
command: [boolean_bench, shortint_bench, pbs_bench]
runs-on: ubuntu-latest
steps:
- name: Checkout Slab repo
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
with:
repository: zama-ai/slab
path: slab
@@ -24,11 +23,11 @@ jobs:
- name: Start AWS job in Slab
shell: bash
# TODO: step result must be correlated to HTTP return code.
run: |
echo -n '{"command": "${{ matrix.command }}", "git_ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' > command.json
SIGNATURE="$(slab/scripts/hmac_calculator.sh command.json '${{ secrets.JOB_SECRET }}')"
curl -v -k \
--fail-with-body \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: start_aws" \

View File

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

View File

@@ -3,32 +3,16 @@ name: PR AWS build trigger
on:
pull_request:
pull_request_review:
types: [submitted]
jobs:
trigger-tests:
test:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Launch fast tests
if: ${{ github.event_name == 'pull_request' }}
uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
- uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
with:
allow-repeats: true
message: |
@slab-ci cpu_fast_test
- name: Launch full tests suite
if: ${{ github.event_name == 'pull_request_review' && github.event.review.state == 'approved' }}
uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
with:
allow-repeats: true
message: |
Pull Request has been approved :tada:
Launching full test suite...
@slab-ci cpu_test
@slab-ci cpu_integer_test
@slab-ci cpu_multi_bit_test
@slab-ci cpu_wasm_test

View File

@@ -1,127 +0,0 @@
# Run WASM client benchmarks on an AWS instance and return parsed results to Slab CI bot.
name: WASM client benchmarks
on:
workflow_dispatch:
inputs:
instance_id:
description: "Instance ID"
type: string
instance_image_id:
description: "Instance AMI ID"
type: string
instance_type:
description: "Instance product type"
type: string
runner_name:
description: "Action runner name"
type: string
request_id:
description: "Slab request ID"
type: string
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
jobs:
run-wasm-client-benchmarks:
name: Execute WASM client benchmarks in EC2
runs-on: ${{ github.event.inputs.runner_name }}
if: ${{ !cancelled() }}
steps:
- name: Instance configuration used
run: |
echo "IDs: ${{ inputs.instance_id }}"
echo "AMI: ${{ inputs.instance_image_id }}"
echo "Type: ${{ inputs.instance_type }}"
echo "Request ID: ${{ inputs.request_id }}"
- name: Get benchmark date
run: |
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
fetch-depth: 0
- name: Set up home
# "Install rust" step require root user to have a HOME directory which is not set.
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install rust
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
with:
toolchain: nightly
override: true
- name: Run benchmarks
run: |
make install_node
make ci_bench_web_js_api_parallel
- name: Parse results
run: |
make parse_wasm_benchmarks
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py tfhe/wasm_pk_gen.csv ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--key-gen
- name: Measure public key and ciphertext sizes in HL Api
run: |
make measure_hlapi_compact_pk_ct_sizes
- name: Parse key and ciphertext sizes results
run: |
python3 ./ci/benchmark_parser.py tfhe/hlapi_cpk_and_cctl_sizes.csv ${{ env.RESULTS_FILENAME }} \
--key-gen \
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_wasm
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on results file"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
echo "Sending results to Slab..."
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data_v2" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_COLOR: ${{ job.status }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_MESSAGE: "WASM benchmarks failed. (${{ env.ACTION_RUN_URL }})"
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

4
.gitignore vendored
View File

@@ -9,7 +9,3 @@ target/
**/Cargo.lock
**/*.bin
# Some of our bench outputs
/tfhe/benchmarks_parameters
**/*.csv

View File

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

View File

@@ -7,9 +7,3 @@ lto = "fat"
[profile.release]
lto = "fat"
# Compiles much faster for tests and allows reasonable performance for iterating
[profile.devo]
inherits = "dev"
opt-level = 3
lto = "off"

273
Makefile
View File

@@ -1,19 +1,14 @@
SHELL:=$(shell /usr/bin/env which bash)
OS:=$(shell uname)
RS_CHECK_TOOLCHAIN:=$(shell cat toolchain.txt | tr -d '\n')
CARGO_RS_CHECK_TOOLCHAIN:=+$(RS_CHECK_TOOLCHAIN)
TARGET_ARCH_FEATURE:=$(shell ./scripts/get_arch_feature.sh)
RS_BUILD_TOOLCHAIN:=$(shell \
( (echo $(TARGET_ARCH_FEATURE) | grep -q x86) && echo stable) || echo $(RS_CHECK_TOOLCHAIN))
CARGO_RS_BUILD_TOOLCHAIN:=+$(RS_BUILD_TOOLCHAIN)
CARGO_PROFILE?=release
MIN_RUST_VERSION:=$(shell grep rust-version tfhe/Cargo.toml | cut -d '=' -f 2 | xargs)
MIN_RUST_VERSION:=1.65
AVX512_SUPPORT?=OFF
WASM_RUSTFLAGS:=
BIG_TESTS_INSTANCE?=FALSE
GEN_KEY_CACHE_MULTI_BIT_ONLY?=FALSE
PARSE_INTEGER_BENCH_CSV_FILE?=tfhe_rs_integer_benches.csv
FAST_TESTS?=FALSE
# This is done to avoid forgetting it, we still precise the RUSTFLAGS in the commands to be able to
# copy paste the command in the terminal and change them if required without forgetting the flags
export RUSTFLAGS?=-C target-cpu=native
@@ -24,16 +19,6 @@ else
AVX512_FEATURE=
endif
ifeq ($(GEN_KEY_CACHE_MULTI_BIT_ONLY),TRUE)
MULTI_BIT_ONLY=--multi-bit-only
else
MULTI_BIT_ONLY=
endif
# Variables used only for regex_engine example
REGEX_STRING?=''
REGEX_PATTERN?=''
.PHONY: rs_check_toolchain # Echo the rust toolchain used for checks
rs_check_toolchain:
@echo $(RS_CHECK_TOOLCHAIN)
@@ -65,19 +50,6 @@ install_cargo_nextest: install_rs_build_toolchain
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-nextest --locked || \
( echo "Unable to install cargo nextest, unknown error." && exit 1 )
.PHONY: install_wasm_pack # Install wasm-pack to build JS packages
install_wasm_pack: install_rs_build_toolchain
@wasm-pack --version > /dev/null 2>&1 || \
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install wasm-pack || \
( echo "Unable to install cargo wasm-pack, unknown error." && exit 1 )
.PHONY: install_node # Install last version of NodeJS via nvm
install_node:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | $(SHELL)
source ~/.bashrc
$(SHELL) -i -c 'nvm install node' || \
( echo "Unable to install node, unknown error." && exit 1 )
.PHONY: fmt # Format rust code
fmt: install_rs_check_toolchain
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt
@@ -125,10 +97,10 @@ clippy_c_api: install_rs_check_toolchain
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint, integer and the js wasm API
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint and the js wasm API
clippy_js_wasm_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_tasks # Run clippy lints on helper tasks crate.
@@ -146,179 +118,104 @@ clippy_all_targets:
clippy_all: clippy clippy_boolean clippy_shortint clippy_integer clippy_all_targets clippy_c_api \
clippy_js_wasm_api clippy_tasks clippy_core
.PHONY: clippy_fast # Run main clippy targets
clippy_fast: clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core
.PHONY: gen_key_cache # Run the script to generate keys and cache them for shortint tests
gen_key_cache: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --release \
--example generates_test_keys \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- \
$(MULTI_BIT_ONLY)
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe
.PHONY: build_core # Build core_crypto without experimental features
build_core: install_rs_build_toolchain install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
.PHONY: build_core # Build core_crypto with and without experimental features
build_core: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
--features=$(TARGET_ARCH_FEATURE) -p tfhe
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),$(AVX512_FEATURE) -p tfhe; \
fi
.PHONY: build_core_experimental # Build core_crypto with experimental features
build_core_experimental: install_rs_build_toolchain install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
--features=$(TARGET_ARCH_FEATURE),experimental -p tfhe
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe; \
fi
.PHONY: build_boolean # Build with boolean enabled
build_boolean: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe --all-targets
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe
.PHONY: build_shortint # Build with shortint enabled
build_shortint: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),shortint -p tfhe --all-targets
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
--features=$(TARGET_ARCH_FEATURE),shortint -p tfhe
.PHONY: build_integer # Build with integer enabled
build_integer: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),integer -p tfhe --all-targets
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
--features=$(TARGET_ARCH_FEATURE),integer -p tfhe
.PHONY: build_tfhe_full # Build with boolean, shortint and integer enabled
build_tfhe_full: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer -p tfhe --all-targets
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer -p tfhe
.PHONY: build_c_api # Build the C API for boolean, shortint and integer
build_c_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api \
-p tfhe
.PHONY: build_c_api_experimental_deterministic_fft # Build the C API for boolean, shortint and integer with experimental deterministic FFT
build_c_api_experimental_deterministic_fft: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,experimental-force_fft_algo_dif4 \
-p tfhe
.PHONY: build_c_api # Build the C API for boolean and shortint
build_c_api: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api -p tfhe
.PHONY: build_web_js_api # Build the js API targeting the web browser
build_web_js_api: install_rs_build_toolchain install_wasm_pack
build_web_js_api: install_rs_build_toolchain
cd tfhe && \
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
wasm-pack build --release --target=web \
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api
.PHONY: build_web_js_api_parallel # Build the js API targeting the web browser with parallelism support
build_web_js_api_parallel: install_rs_check_toolchain install_wasm_pack
cd tfhe && \
rustup component add rust-src --toolchain $(RS_CHECK_TOOLCHAIN) && \
RUSTFLAGS="$(WASM_RUSTFLAGS) -C target-feature=+atomics,+bulk-memory,+mutable-globals" rustup run $(RS_CHECK_TOOLCHAIN) \
wasm-pack build --release --target=web \
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api,parallel-wasm-api \
-Z build-std=panic_abort,std
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
.PHONY: build_node_js_api # Build the js API targeting nodejs
build_node_js_api: install_rs_build_toolchain install_wasm_pack
build_node_js_api: install_rs_build_toolchain
cd tfhe && \
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
wasm-pack build --release --target=nodejs \
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
.PHONY: test_core_crypto # Run the tests of the core_crypto module including experimental ones
test_core_crypto: install_rs_build_toolchain install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
test_core_crypto: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
--features=$(TARGET_ARCH_FEATURE),experimental -p tfhe -- core_crypto::
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe -- core_crypto::; \
fi
.PHONY: test_boolean # Run the tests of the boolean module
test_boolean: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
.PHONY: test_c_api # Run the tests for the C API
test_c_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api \
-p tfhe \
c_api
"$(MAKE)" build_c_api
test_c_api: build_c_api
./scripts/c_api_tests.sh
.PHONY: test_shortint_ci # Run the tests for shortint ci
test_shortint_ci: install_rs_build_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/shortint-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN)
.PHONY: test_shortint_multi_bit_ci # Run the tests for shortint ci running only multibit tests
test_shortint_multi_bit_ci: install_rs_build_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/shortint-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) --multi-bit
./scripts/shortint-tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
.PHONY: test_shortint # Run all the tests for shortint
test_shortint: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- shortint::
.PHONY: test_integer_ci # Run the tests for integer ci
test_integer_ci: install_rs_build_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN)
.PHONY: test_integer_multi_bit_ci # Run the tests for integer ci running only multibit tests
test_integer_multi_bit_ci: install_rs_build_toolchain install_cargo_nextest
BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \
FAST_TESTS="$(FAST_TESTS)" \
./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) --multi-bit
./scripts/integer-tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
.PHONY: test_integer # Run all the tests for integer
test_integer: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache -p tfhe -- integer::
.PHONY: test_high_level_api # Run all the tests for high_level_api
test_high_level_api: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe -- high_level_api::
.PHONY: test_user_doc # Run tests from the .md documentation
test_user_doc: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) --doc \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release --doc \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \
-- test_user_docs::
.PHONY: test_regex_engine # Run tests for regex_engine example
test_regex_engine: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--example regex_engine \
--features=$(TARGET_ARCH_FEATURE),integer
.PHONY: test_sha256_bool # Run tests for sha256_bool example
test_sha256_bool: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--example sha256_bool \
--features=$(TARGET_ARCH_FEATURE),boolean
.PHONY: doc # Build rust doc
doc: install_rs_check_toolchain
RUSTDOCFLAGS="--html-in-header katex-header.html -Dwarnings" \
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" doc \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer --no-deps
.PHONY: docs # Build rust doc alias for doc
docs: doc
.PHONY: format_doc_latex # Format the documentation latex equations to avoid broken rendering.
format_doc_latex:
cargo xtask format_latex_doc
@@ -329,15 +226,11 @@ format_doc_latex:
@printf "\n===============================\n"
.PHONY: check_compile_tests # Build tests in debug without running them
check_compile_tests:
check_compile_tests: build_c_api
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --no-run \
--features=$(TARGET_ARCH_FEATURE),experimental,boolean,shortint,integer,internal-keycache \
-p tfhe
@if [[ "$(OS)" == "Linux" || "$(OS)" == "Darwin" ]]; then \
"$(MAKE)" build_c_api; \
./scripts/c_api_tests.sh --build-only; \
fi
-p tfhe && \
./scripts/c_api_tests.sh --build-only
.PHONY: build_nodejs_test_docker # Build a docker image with tools to run nodejs tests for wasm API
build_nodejs_test_docker:
@@ -359,38 +252,10 @@ test_nodejs_wasm_api_in_docker: build_nodejs_test_docker
test_nodejs_wasm_api: build_node_js_api
cd tfhe && node --test js_on_wasm_tests
.PHONY: test_web_js_api_parallel # Run tests for the web wasm api
test_web_js_api_parallel: build_web_js_api_parallel
$(MAKE) -C tfhe/web_wasm_parallel_tests test
.PHONY: ci_test_web_js_api_parallel # Run tests for the web wasm api
ci_test_web_js_api_parallel: build_web_js_api_parallel
# Auto-retry since WASM tests can be flaky
@for i in 1 2 3 ; do \
source ~/.nvm/nvm.sh && \
nvm use node && \
$(MAKE) -C tfhe/web_wasm_parallel_tests test-ci | tee web_js_tests_output; \
if grep -q -i "timeout" web_js_tests_output; then \
echo "Timeout occurred starting attempt #${i}"; \
else \
break; \
fi; \
done
.PHONY: no_tfhe_typo # Check we did not invert the h and f in tfhe
no_tfhe_typo:
@./scripts/no_tfhe_typo.sh
#
# Benchmarks
#
.PHONY: bench_integer # Run benchmarks for integer
bench_integer: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
--bench integer-bench \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p tfhe
.PHONY: bench_shortint # Run benchmarks for shortint
bench_shortint: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
@@ -409,81 +274,21 @@ bench_pbs: install_rs_check_toolchain
--bench pbs-bench \
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe
.PHONY: bench_web_js_api_parallel # Run benchmarks for the web wasm api
bench_web_js_api_parallel: build_web_js_api_parallel
$(MAKE) -C tfhe/web_wasm_parallel_tests bench
.PHONY: ci_bench_web_js_api_parallel # Run benchmarks for the web wasm api
ci_bench_web_js_api_parallel: build_web_js_api_parallel
source ~/.nvm/nvm.sh && \
nvm use node && \
$(MAKE) -C tfhe/web_wasm_parallel_tests bench-ci
#
# Utility tools
#
.PHONY: measure_hlapi_compact_pk_ct_sizes # Measure sizes of public keys and ciphertext for high-level API
measure_hlapi_compact_pk_ct_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example hlapi_compact_pk_ct_sizes \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache
.PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint
measure_shortint_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
--example shortint_key_sizes \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache
.PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean
measure_boolean_key_sizes: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \
--example boolean_key_sizes \
--features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache
.PHONY: parse_integer_benches # Run python parser to output a csv containing integer benches data
parse_integer_benches:
python3 ./ci/parse_integer_benches_to_csv.py \
--criterion-dir target/criterion \
--output-file "$(PARSE_INTEGER_BENCH_CSV_FILE)"
.PHONY: parse_wasm_benchmarks # Parse benchmarks performed with WASM web client into a CSV file
parse_wasm_benchmarks: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example wasm_benchmarks_parser \
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache \
-- web_wasm_parallel_tests/test/benchmark_results
#
# Real use case examples
#
.PHONY: regex_engine # Run regex_engine example
regex_engine: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example regex_engine \
--features=$(TARGET_ARCH_FEATURE),integer \
-- $(REGEX_STRING) $(REGEX_PATTERN)
.PHONY: dark_market # Run dark market example
dark_market: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example dark_market \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache \
-- fhe-modified fhe-parallel plain fhe
.PHONY: sha256_bool # Run sha256_bool example
sha256_bool: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
--example sha256_bool \
--features=$(TARGET_ARCH_FEATURE),boolean
.PHONY: pcc # pcc stands for pre commit checks
pcc: no_tfhe_typo check_fmt doc clippy_all check_compile_tests
.PHONY: fpcc # pcc stands for pre commit checks, the f stands for fast
fpcc: no_tfhe_typo check_fmt doc clippy_fast check_compile_tests
.PHONY: conformance # Automatically fix problems that can be fixed
conformance: fmt

View File

@@ -1,25 +1,31 @@
<p align="center">
<!-- product name logo -->
<img width=600 src="https://user-images.githubusercontent.com/5758427/231206749-8f146b97-3c5a-4201-8388-3ffa88580415.png">
</p>
<hr/>
<p align="center">
<a href="https://docs.zama.ai/tfhe-rs"> 📒 Read documentation</a> | <a href="https://zama.ai/community"> 💛 Community support</a>
<img width=600 src="https://user-images.githubusercontent.com/86411313/201107820-b1b861be-6b3f-46cc-bccd-ed051201781a.png">
</p>
<p align="center">
<!-- Version badge using shields.io -->
<a href="https://github.com/zama-ai/tfhe-rs/releases">
<img src="https://img.shields.io/github/v/release/zama-ai/tfhe-rs?style=flat-square">
</a>
<!-- Zama Bounty Program -->
<a href="https://github.com/zama-ai/bounty-program">
<img src="https://img.shields.io/badge/Contribute-Zama%20Bounty%20Program-yellow?style=flat-square">
<!-- Link to docs badge using shields.io -->
<a href="https://docs.zama.ai/tfhe-rs">
<img src="https://img.shields.io/badge/read-documentation-yellow?style=flat-square">
</a>
<!-- Community forum badge using shields.io -->
<a href="https://community.zama.ai">
<img src="https://img.shields.io/badge/community%20forum-online-brightgreen?style=flat-square">
</a>
<!-- Open source badge using shields.io -->
<a href="https://docs.zama.ai/tfhe-rs/developers/contributing">
<img src="https://img.shields.io/badge/we're%20open%20source-contributing.md-blue?style=flat-square">
</a>
<!-- Follow on twitter badge using shields.io -->
<a href="https://twitter.com/zama_fhe">
<img src="https://img.shields.io/badge/follow-zama_fhe-blue?logo=twitter&style=flat-square">
</a>
</p>
<hr/>
**TFHE-rs** is a pure Rust implementation of TFHE for boolean and integer
**TFHE-rs** is a pure Rust implementation of TFHE for boolean and small integer
arithmetics over encrypted data. It includes:
- a **Rust** API
- a **C** API
@@ -57,31 +63,27 @@ tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64"]
Note: aarch64-based machines are not yet supported for Windows as it's currently missing an entropy source to be able to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs
Note that when running code that uses `tfhe-rs`, it is highly recommended
to run in release mode with cargo's `--release` flag to have the best performances possible,
eg: `cargo run --release`.
Here is a full example evaluating a Boolean circuit:
```rust
use tfhe::boolean::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys();
// We generate a set of client/server keys, using the default parameters:
let (mut client_key, mut server_key) = gen_keys();
// We use the client secret key to encrypt two messages:
// We use the client secret key to encrypt two messages:
let ct_1 = client_key.encrypt(true);
let ct_2 = client_key.encrypt(false);
// We use the server public key to execute a boolean circuit:
// if ((NOT ct_2) NAND (ct_1 AND ct_2)) then (NOT ct_2) else (ct_1 AND ct_2)
// We use the server public key to execute a boolean circuit:
// if ((NOT ct_2) NAND (ct_1 AND ct_2)) then (NOT ct_2) else (ct_1 AND ct_2)
let ct_3 = server_key.not(&ct_2);
let ct_4 = server_key.and(&ct_1, &ct_2);
let ct_5 = server_key.nand(&ct_3, &ct_4);
let ct_6 = server_key.mux(&ct_5, &ct_3, &ct_4);
// We use the client key to decrypt the output of the circuit:
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_6);
assert_eq!(output, true);
}
@@ -93,9 +95,8 @@ Another example of how the library can be used with shortints:
use tfhe::shortint::prelude::*;
fn main() {
// Generate a set of client/server keys
// with 2 bits of message and 2 bits of carry
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
// Generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(Parameters::default());
let msg1 = 3;
let msg2 = 2;
@@ -111,8 +112,8 @@ fn main() {
// f: x -> sum of the bits of x
let f = |x:u64| x.count_ones() as u64;
// Generate the lookup table for the function
let acc = server_key.generate_lookup_table(f);
// Generate the accumulator for the function
let acc = server_key.generate_accumulator(f);
// Compute the function over the ciphertext using the PBS
let ct_res = server_key.apply_lookup_table(&ct_add, &acc);
@@ -123,30 +124,6 @@ fn main() {
}
```
An example using integer:
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We create keys to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, 8);
let clear_a = 2382u16;
let clear_b = 29374u16;
let mut a = cks.encrypt(clear_a as u64);
let mut b = cks.encrypt(clear_b as u64);
let encrypted_max = sks.smart_max_parallelized(&mut a, &mut b);
let decrypted_max: u64 = cks.decrypt(&encrypted_max);
assert_eq!(decrypted_max as u16, clear_a.max(clear_b))
}
```
## Contributing
There are two ways to contribute to TFHE-rs:
@@ -162,24 +139,6 @@ Only approved contributors can send pull requests, so please make sure to get in
This library uses several dependencies and we would like to thank the contributors of those
libraries.
## Need support?
<a target="_blank" href="https://community.zama.ai">
<img src="https://user-images.githubusercontent.com/5758427/231115030-21195b55-2629-4c01-9809-be5059243999.png">
</a>
## Citing TFHE-rs
To cite TFHE-rs in academic papers, please use the following entry:
```text
@Misc{TFHE-rs,
title={{TFHE-rs: A Pure Rust Implementation of the TFHE Scheme for Boolean and Integer Arithmetics Over Encrypted Data}},
author={Zama},
year={2022},
note={\url{https://github.com/zama-ai/tfhe-rs}},
}
```
## License
This software is distributed under the BSD-3-Clause-Clear license. If you have any questions,

View File

@@ -39,13 +39,9 @@ parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true',
help='Check for results in subdirectories')
parser.add_argument('--key-sizes', dest='key_sizes', action='store_true',
help='Parse only the results regarding keys size measurements')
parser.add_argument('--key-gen', dest='key_gen', action='store_true',
help='Parse only the results regarding keys generation time measurements')
parser.add_argument('--throughput', dest='throughput', action='store_true',
help='Compute and append number of operations per second and'
help='Compute and append number of operations per millisecond and'
'operations per dollar')
parser.add_argument('--backend', dest='backend', default='cpu',
help='Backend on which benchmarks have run')
def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throughput=False,
@@ -57,99 +53,43 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
:param directory: path to directory that contains raw results as :class:`pathlib.Path`
:param walk_subdirs: traverse results subdirectories if parameters changes for benchmark case.
:param name_suffix: a :class:`str` suffix to apply to each test name found
:param compute_throughput: compute number of operations per second and operations per
:param compute_throughput: compute number of operations per millisecond and operations per
dollar
:param hardware_hourly_cost: hourly cost of the hardware used in dollar
:return: tuple of :class:`list` as (data points, parsing failures)
:return: :class:`list` of data points
"""
excluded_directories = ["child_generate", "fork", "parent_generate", "report"]
result_values = []
parsing_failures = []
bench_class = "evaluate"
for dire in directory.iterdir():
if dire.name in excluded_directories or not dire.is_dir():
continue
for subdir in dire.iterdir():
if walk_subdirs:
if subdir.name == "new":
pass
else:
subdir = subdir.joinpath("new")
if not subdir.exists():
continue
subdir = subdir.joinpath("new")
if not subdir.exists():
continue
elif subdir.name != "new":
continue
full_name, test_name = parse_benchmark_file(subdir)
if test_name is None:
parsing_failures.append((full_name, "'function_id' field is null in report"))
continue
try:
params, display_name, operator = get_parameters(test_name)
except Exception as err:
parsing_failures.append((full_name, f"failed to get parameters: {err}"))
continue
test_name = parse_benchmark_file(subdir)
for stat_name, value in parse_estimate_file(subdir).items():
test_name_parts = list(filter(None, [test_name, stat_name, name_suffix]))
result_values.append(
_create_point(
value,
"_".join(test_name_parts),
bench_class,
"latency",
operator,
params,
display_name=display_name
)
)
result_values.append({"value": value, "test": "_".join(test_name_parts)})
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),
"_".join(test_name_parts),
bench_class,
"throughput",
operator,
params,
display_name="_".join([display_name, test_suffix])
)
)
test_name_parts.append("ops-per-ms")
result_values.append({"value": compute_ops_per_millisecond(value),
"test": "_".join(test_name_parts)})
test_name_parts.pop()
if hardware_hourly_cost is not None:
test_suffix = "ops-per-dollar"
test_name_parts.append(test_suffix)
result_values.append(
_create_point(
compute_ops_per_dollar(value, hardware_hourly_cost),
"_".join(test_name_parts),
bench_class,
"throughput",
operator,
params,
display_name="_".join([display_name, test_suffix])
)
)
test_name_parts.append("ops-per-dollar")
result_values.append({
"value": compute_ops_per_dollar(value, hardware_hourly_cost),
"test": "_".join(test_name_parts)})
return result_values, parsing_failures
def _create_point(value, test_name, bench_class, bench_type, operator, params, display_name=None):
return {
"value": value,
"test": test_name,
"name": display_name,
"class": bench_class,
"type": bench_type,
"operator": operator,
"params": params}
return result_values
def parse_benchmark_file(directory):
@@ -161,7 +101,7 @@ def parse_benchmark_file(directory):
:return: name of the test as :class:`str`
"""
raw_res = _parse_file_to_json(directory, "benchmark.json")
return raw_res["full_id"], raw_res["function_id"]
return raw_res["full_id"].replace(" ", "_")
def parse_estimate_file(directory):
@@ -179,79 +119,21 @@ def parse_estimate_file(directory):
}
def _parse_key_results(result_file, bench_type):
"""
Parse file containing results about operation on keys. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
:return: tuple of :class:`list` as (data points, parsing failures)
"""
result_values = []
parsing_failures = []
with result_file.open() as csv_file:
reader = csv.reader(csv_file)
for (test_name, value) in reader:
try:
params, display_name, operator = get_parameters(test_name)
except Exception as err:
parsing_failures.append((test_name, f"failed to get parameters: {err}"))
continue
result_values.append({
"value": int(value),
"test": test_name,
"name": display_name,
"class": "keygen",
"type": bench_type,
"operator": operator,
"params": params})
return result_values, parsing_failures
def parse_key_sizes(result_file):
"""
Parse file containing key sizes results. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
:return: tuple of :class:`list` as (data points, parsing failures)
:return: :class:`list` of data points
"""
return _parse_key_results(result_file, "keysize")
result_values = []
with result_file.open() as csv_file:
reader = csv.reader(csv_file)
for (test_name, value) in reader:
result_values.append({"value": int(value), "test": test_name})
def parse_key_gen_time(result_file):
"""
Parse file containing key generation time results. The file must be formatted as CSV.
:param result_file: results file as :class:`pathlib.Path`
:return: tuple of :class:`list` as (data points, parsing failures)
"""
return _parse_key_results(result_file, "latency")
def get_parameters(bench_id):
"""
Get benchmarks parameters recorded for a given benchmark case.
:param bench_id: function name used for the benchmark case
:return: :class:`tuple` as ``(benchmark parameters, display name, operator type)``
"""
params_dir = pathlib.Path("tfhe", "benchmarks_parameters", bench_id)
params = _parse_file_to_json(params_dir, "parameters.json")
display_name = params.pop("display_name")
operator = params.pop("operator_type")
# Put cryptographic parameters at the same level as the others parameters
crypto_params = params.pop("crypto_parameters")
params.update(crypto_params)
return params, display_name, operator
return result_values
def compute_ops_per_dollar(data_point, product_hourly_cost):
@@ -266,15 +148,15 @@ def compute_ops_per_dollar(data_point, product_hourly_cost):
return ONE_HOUR_IN_NANOSECONDS / (product_hourly_cost * data_point)
def compute_ops_per_second(data_point):
def compute_ops_per_millisecond(data_point):
"""
Compute numbers of operations per second for a given ``data_point``.
Compute numbers of operations per millisecond for a given ``data_point``.
:param data_point: timing value measured during benchmark in nanoseconds
:return: number of operations per second
:return: number of operations per millisecond
"""
return 1E9 / data_point
return 1E6 / data_point
def _parse_file_to_json(directory, filename):
@@ -290,9 +172,6 @@ def dump_results(parsed_results, filename, input_args):
:param filename: filename for dump file as :class:`pathlib.Path`
:param input_args: CLI input arguments
"""
for point in parsed_results:
point["backend"] = input_args.backend
if input_args.append_results:
parsed_content = json.loads(filename.read_text())
parsed_content["points"].extend(parsed_results)
@@ -325,7 +204,7 @@ def check_mandatory_args(input_args):
for arg_name in vars(input_args):
if arg_name in ["results_dir", "output_file", "name_suffix",
"append_results", "walk_subdirs", "key_sizes",
"key_gen", "throughput"]:
"throughput"]:
continue
if not getattr(input_args, arg_name):
missing_args.append(arg_name)
@@ -340,17 +219,8 @@ if __name__ == "__main__":
args = parser.parse_args()
check_mandatory_args(args)
#failures = []
raw_results = pathlib.Path(args.results)
if args.key_sizes or args.key_gen:
if args.key_sizes:
print("Parsing key sizes results... ")
results, failures = parse_key_sizes(raw_results)
if args.key_gen:
print("Parsing key generation time results... ")
results, failures = parse_key_gen_time(raw_results)
else:
if not args.key_sizes:
print("Parsing benchmark results... ")
hardware_cost = None
if args.throughput:
@@ -364,9 +234,11 @@ if __name__ == "__main__":
print(f"Cannot find hardware hourly cost for '{args.hardware}'")
sys.exit(1)
results, failures = recursive_parse(raw_results, args.walk_subdirs, args.name_suffix,
args.throughput, hardware_cost)
results = recursive_parse(raw_results, args.walk_subdirs, args.name_suffix, args.throughput,
hardware_cost)
else:
print("Parsing key sizes results... ")
results = parse_key_sizes(raw_results)
print("Parsing results done")
output_file = pathlib.Path(args.output_file)
@@ -374,10 +246,3 @@ if __name__ == "__main__":
dump_results(results, output_file, args)
print("Done")
if failures:
print("\nParsing failed for some results")
print("-------------------------------")
for name, error in failures:
print(f"[{name}] {error}")
sys.exit(1)

View File

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

View File

@@ -3,11 +3,6 @@ region = "eu-west-3"
image_id = "ami-04deffe45b5b236fd"
instance_type = "m6i.32xlarge"
[profile.cpu-small]
region = "eu-west-3"
image_id = "ami-04deffe45b5b236fd"
instance_type = "m6i.4xlarge"
[profile.bench]
region = "eu-west-3"
image_id = "ami-04deffe45b5b236fd"
@@ -23,26 +18,6 @@ workflow = "aws_tfhe_integer_tests.yml"
profile = "cpu-big"
check_run_name = "CPU Integer AWS Tests"
[command.cpu_multi_bit_test]
workflow = "aws_tfhe_multi_bit_tests.yml"
profile = "cpu-big"
check_run_name = "CPU AWS Multi Bit Tests"
[command.cpu_wasm_test]
workflow = "aws_tfhe_wasm_tests.yml"
profile = "cpu-small"
check_run_name = "CPU AWS WASM Tests"
[command.cpu_fast_test]
workflow = "aws_tfhe_fast_tests.yml"
profile = "cpu-big"
check_run_name = "CPU AWS Fast Tests"
[command.integer_bench]
workflow = "integer_benchmark.yml"
profile = "bench"
check_run_name = "Integer CPU AWS Benchmarks"
[command.shortint_bench]
workflow = "shortint_benchmark.yml"
profile = "bench"
@@ -57,8 +32,3 @@ check_run_name = "Boolean CPU AWS Benchmarks"
workflow = "pbs_benchmark.yml"
profile = "bench"
check_run_name = "PBS CPU AWS Benchmarks"
[command.wasm_client_bench]
workflow = "wasm_client_benchmark.yml"
profile = "cpu-small"
check_run_name = "WASM Client AWS Benchmarks"

View File

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

View File

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

View File

@@ -2,49 +2,6 @@
set -e
function usage() {
echo "$0: shortint test runner"
echo
echo "--help Print this message"
echo "--rust-toolchain The toolchain to run the tests with default: stable"
echo "--multi-bit Run multi-bit tests only: default off"
echo
}
RUST_TOOLCHAIN="+stable"
multi_bit=""
not_multi_bit="_multi_bit"
while [ -n "$1" ]
do
case "$1" in
"--help" | "-h" )
usage
exit 0
;;
"--rust-toolchain" )
shift
RUST_TOOLCHAIN="$1"
;;
"--multi-bit" )
multi_bit="_multi_bit"
not_multi_bit=""
;;
*)
echo "Unknown param : $1"
exit 1
;;
esac
shift
done
if [[ "${RUST_TOOLCHAIN::1}" != "+" ]]; then
RUST_TOOLCHAIN="+${RUST_TOOLCHAIN}"
fi
CURR_DIR="$(dirname "$0")"
ARCH_FEATURE="$("${CURR_DIR}/get_arch_feature.sh")"
@@ -68,31 +25,20 @@ else
fi
if [[ "${BIG_TESTS_INSTANCE}" != TRUE ]]; then
if [[ "${FAST_TESTS}" != TRUE ]]; then
# block pbs are too slow for high params
# mul_crt_4_4 is extremely flaky (~80% failure)
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
# test_integer_smart_mul_param_message_4_carry_4 is too slow
# so is test_integer_default_add_sequence_multi_thread_param_message_4_carry_4
filter_expression="""\
test(/^integer::.*${multi_bit}/) \
${not_multi_bit:+"and not test(~${not_multi_bit})"} \
and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]$/) \
and not test(~mul_crt_param_message_4_carry_4) \
and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]$/) \
and not test(/.*test_integer_smart_mul_param_message_4_carry_4$/) \
and not test(/.*test_integer_default_add_sequence_multi_thread_param_message_4_carry_4$/)"""
else
# test only fast default operations with only two set of parameters
filter_expression="""\
test(/^integer::.*${multi_bit}/) \
${not_multi_bit:+"and not test(~${not_multi_bit})"} \
and test(/.*_default_.*/) \
and not test(/.*_param_message_[14]_carry_[14]$/) \
and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3$/)"""
fi
# block pbs are too slow for high params
# mul_crt_4_4 is extremely flaky (~80% failure)
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
# same for test_wopbs_bivariate_radix_wopbs_param_message_4_carry_4
# test_integer_smart_mul_param_message_4_carry_4 is too slow
filter_expression=''\
'test(/^integer::.*$/)'\
'and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]$/)'\
'and not test(~mul_crt_param_message_4_carry_4)'\
'and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]$/)'\
'and not test(/.*test_wopbs_bivariate_radix_wopbs_param_message_4_carry_4$/)'\
'and not test(/.*test_integer_smart_mul_param_message_4_carry_4$/)'
cargo "${RUST_TOOLCHAIN}" nextest run \
cargo ${1:+"${1}"} nextest run \
--tests \
--release \
--package tfhe \
@@ -101,58 +47,41 @@ and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3$/)"""
--test-threads "${n_threads}" \
-E "$filter_expression"
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
integer::
fi
cargo ${1:+"${1}"} test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
integer::
else
if [[ "${FAST_TESTS}" != TRUE ]]; then
# block pbs are too slow for high params
# mul_crt_4_4 is extremely flaky (~80% failure)
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
# test_integer_smart_mul_param_message_4_carry_4 is too slow
# so is test_integer_default_add_sequence_multi_thread_param_message_4_carry_4
filter_expression="""\
test(/^integer::.*${multi_bit}/) \
${not_multi_bit:+"and not test(~${not_multi_bit})"} \
and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]$/) \
and not test(~mul_crt_param_message_4_carry_4) \
and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]$/) \
and not test(/.*test_integer_smart_mul_param_message_4_carry_4$/) \
and not test(/.*test_integer_default_add_sequence_multi_thread_param_message_4_carry_4$/)"""
else
# test only fast default operations with only two set of parameters
filter_expression="""\
test(/^integer::.*${multi_bit}/) \
${not_multi_bit:+"and not test(~${not_multi_bit})"} \
and test(/.*_default_.*/) \
and not test(/.*_param_message_[14]_carry_[14]$/) \
and not test(/.*default_add_sequence_multi_thread_param_message_3_carry_3$/)"""
fi
# block pbs are too slow for high params
# mul_crt_4_4 is extremely flaky (~80% failure)
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
# same for test_wopbs_bivariate_radix_wopbs_param_message_4_carry_4
# test_integer_smart_mul_param_message_4_carry_4 is too slow
filter_expression=''\
'test(/^integer::.*$/)'\
'and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]$/)'\
'and not test(~mul_crt_param_message_4_carry_4)'\
'and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]$/)'\
'and not test(/.*test_wopbs_bivariate_radix_wopbs_param_message_4_carry_4$/)'\
'and not test(/.*test_integer_smart_mul_param_message_4_carry_4$/)'
num_cpu_threads="$(${nproc_bin})"
num_threads=$((num_cpu_threads * 2 / 3))
cargo "${RUST_TOOLCHAIN}" nextest run \
cargo ${1:+"${1}"} nextest run \
--tests \
--release \
--package tfhe \
--profile ci \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--test-threads $num_threads \
--test-threads "$(${nproc_bin})" \
-E "$filter_expression"
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
integer:: -- --test-threads="$(${nproc_bin})"
fi
cargo ${1:+"${1}"} test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",integer,internal-keycache \
--doc \
integer:: -- --test-threads="$(${nproc_bin})"
fi
echo "Test ran in $SECONDS seconds"

View File

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

View File

@@ -2,47 +2,6 @@
set -e
function usage() {
echo "$0: shortint test runner"
echo
echo "--help Print this message"
echo "--rust-toolchain The toolchain to run the tests with default: stable"
echo "--multi-bit Run multi-bit tests only: default off"
echo
}
RUST_TOOLCHAIN="+stable"
multi_bit=""
while [ -n "$1" ]
do
case "$1" in
"--help" | "-h" )
usage
exit 0
;;
"--rust-toolchain" )
shift
RUST_TOOLCHAIN="$1"
;;
"--multi-bit" )
multi_bit="_multi_bit"
;;
*)
echo "Unknown param : $1"
exit 1
;;
esac
shift
done
if [[ "${RUST_TOOLCHAIN::1}" != "+" ]]; then
RUST_TOOLCHAIN="+${RUST_TOOLCHAIN}"
fi
CURR_DIR="$(dirname "$0")"
ARCH_FEATURE="$("${CURR_DIR}/get_arch_feature.sh")"
@@ -72,35 +31,25 @@ else
fi
if [[ "${BIG_TESTS_INSTANCE}" != TRUE ]]; then
if [[ "${FAST_TESTS}" != TRUE ]]; then
filter_expression_small_params="""\
(\
test(/^shortint::.*_param${multi_bit}_message_1_carry_1/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_2/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_3/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_4/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_5/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_6/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_1/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_2/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_3/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_1/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_2/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_3/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
else
filter_expression_small_params="""\
(\
test(/^shortint::.*_param${multi_bit}_message_2_carry_1/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_2/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_3/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
fi
filter_expression_small_params=''\
'('\
' test(/^shortint::.*_param_message_1_carry_1$/)'\
'or test(/^shortint::.*_param_message_1_carry_2$/)'\
'or test(/^shortint::.*_param_message_1_carry_3$/)'\
'or test(/^shortint::.*_param_message_1_carry_4$/)'\
'or test(/^shortint::.*_param_message_1_carry_5$/)'\
'or test(/^shortint::.*_param_message_1_carry_6$/)'\
'or test(/^shortint::.*_param_message_2_carry_1$/)'\
'or test(/^shortint::.*_param_message_2_carry_2$/)'\
'or test(/^shortint::.*_param_message_2_carry_3$/)'\
'or test(/^shortint::.*_param_message_3_carry_1$/)'\
'or test(/^shortint::.*_param_message_3_carry_2$/)'\
'or test(/^shortint::.*_param_message_3_carry_3$/)'\
')'\
'and not test(~smart_add_and_mul)' # This test is too slow
# Run tests only no examples or benches with small params and more threads
cargo "${RUST_TOOLCHAIN}" nextest run \
cargo ${1:+"${1}"} nextest run \
--tests \
--release \
--package tfhe \
@@ -109,15 +58,14 @@ and not test(~smart_add_and_mul)""" # This test is too slow
--test-threads "${n_threads_small}" \
-E "${filter_expression_small_params}"
if [[ "${FAST_TESTS}" != TRUE ]]; then
filter_expression_big_params="""\
(\
test(/^shortint::.*_param${multi_bit}_message_4_carry_4/) \
) \
and not test(~smart_add_and_mul)"""
filter_expression_big_params=''\
'('\
' test(/^shortint::.*_param_message_4_carry_4$/)'\
')'\
'and not test(~smart_add_and_mul)'
# Run tests only no examples or benches with big params and less threads
cargo "${RUST_TOOLCHAIN}" nextest run \
cargo ${1:+"${1}"} nextest run \
--tests \
--release \
--package tfhe \
@@ -126,46 +74,33 @@ and not test(~smart_add_and_mul)"""
--test-threads "${n_threads_big}" \
-E "${filter_expression_big_params}"
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--doc \
shortint::
fi
fi
cargo ${1:+"${1}"} test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--doc \
shortint::
else
if [[ "${FAST_TESTS}" != TRUE ]]; then
filter_expression="""\
(\
test(/^shortint::.*_param${multi_bit}_message_1_carry_1/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_2/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_3/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_4/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_5/) \
or test(/^shortint::.*_param${multi_bit}_message_1_carry_6/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_1/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_2/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_3/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_1/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_2/) \
or test(/^shortint::.*_param${multi_bit}_message_3_carry_3/) \
or test(/^shortint::.*_param${multi_bit}_message_4_carry_4/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
else
filter_expression="""\
(\
test(/^shortint::.*_param${multi_bit}_message_2_carry_1/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_2/) \
or test(/^shortint::.*_param${multi_bit}_message_2_carry_3/) \
)\
and not test(~smart_add_and_mul)""" # This test is too slow
fi
filter_expression=''\
'('\
' test(/^shortint::.*_param_message_1_carry_1$/)'\
'or test(/^shortint::.*_param_message_1_carry_2$/)'\
'or test(/^shortint::.*_param_message_1_carry_3$/)'\
'or test(/^shortint::.*_param_message_1_carry_4$/)'\
'or test(/^shortint::.*_param_message_1_carry_5$/)'\
'or test(/^shortint::.*_param_message_1_carry_6$/)'\
'or test(/^shortint::.*_param_message_2_carry_1$/)'\
'or test(/^shortint::.*_param_message_2_carry_2$/)'\
'or test(/^shortint::.*_param_message_2_carry_3$/)'\
'or test(/^shortint::.*_param_message_3_carry_1$/)'\
'or test(/^shortint::.*_param_message_3_carry_2$/)'\
'or test(/^shortint::.*_param_message_3_carry_3$/)'\
'or test(/^shortint::.*_param_message_4_carry_4$/)'\
')'\
'and not test(~smart_add_and_mul)' # This test is too slow
# Run tests only no examples or benches with small params and more threads
cargo "${RUST_TOOLCHAIN}" nextest run \
cargo ${1:+"${1}"} nextest run \
--tests \
--release \
--package tfhe \
@@ -174,14 +109,12 @@ and not test(~smart_add_and_mul)""" # This test is too slow
--test-threads "$(${nproc_bin})" \
-E "${filter_expression}"
if [[ "${multi_bit}" == "" ]]; then
cargo "${RUST_TOOLCHAIN}" test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--doc \
shortint:: -- --test-threads="$(${nproc_bin})"
fi
cargo ${1:+"${1}"} test \
--release \
--package tfhe \
--features="${ARCH_FEATURE}",shortint,internal-keycache \
--doc \
shortint:: -- --test-threads="$(${nproc_bin})"
fi
echo "Test ran in $SECONDS seconds"

View File

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "0.3.0-beta.0"
version = "0.2.0"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
@@ -10,37 +10,23 @@ repository = "https://github.com/zama-ai/tfhe-rs"
license = "BSD-3-Clause-Clear"
description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
build = "build.rs"
exclude = [
"/docs/",
"/c_api_tests/",
"/CMakeLists.txt",
"/js_on_wasm_tests/",
"/web_wasm_parallel_tests/",
]
rust-version = "1.67"
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt", "/js_on_wasm_tests/"]
rust-version = "1.65"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
rand = "0.8.5"
rand_distr = "0.4.3"
kolmogorov_smirnov = "1.1.0"
paste = "1.0.7"
lazy_static = { version = "1.4.0" }
criterion = "0.4.0"
doc-comment = "0.3.3"
serde_json = "1.0.94"
clap = { version = "4.2.7", features = ["derive"] }
# Used in user documentation
bincode = "1.3.3"
fs2 = { version = "0.4.3" }
itertools = "0.10.5"
num_cpus = "1.15"
# For erf and normality test
libm = "0.2.6"
test-case = "3.1.0"
combine = "4.6.6"
env_logger = "0.10.0"
log = "0.4.19"
[build-dependencies]
cbindgen = { version = "0.24.3", optional = true }
@@ -54,10 +40,9 @@ 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.1", features = ["serde"] }
aligned-vec = { version = "0.5", features = ["serde"] }
dyn-stack = { version = "0.9" }
dyn-stack = { version = "0.8" }
once_cell = "1.13"
paste = "1.0.7"
fs2 = { version = "0.4.3", optional = true }
@@ -65,15 +50,13 @@ fs2 = { version = "0.4.3", optional = true }
itertools = "0.10.5"
# wasm deps
wasm-bindgen = { version = "0.2.86", features = [
wasm-bindgen = { version = "0.2.63", features = [
"serde-serialize",
], optional = true }
wasm-bindgen-rayon = { version = "1.0", optional = true }
js-sys = { version = "0.3", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
serde-wasm-bindgen = { version = "0.4", optional = true }
getrandom = { version = "0.2.8", optional = true }
bytemuck = "1.13.1"
[features]
boolean = []
@@ -82,14 +65,13 @@ integer = ["shortint"]
internal-keycache = ["lazy_static", "fs2", "bincode"]
# Experimental section
experimental = []
experimental-force_fft_algo_dif4 = []
experimental = ["experimental-multi_bit_pbs"]
experimental-multi_bit_pbs = []
# End experimental section
__c_api = ["cbindgen", "bincode"]
boolean-c-api = ["boolean", "__c_api"]
shortint-c-api = ["shortint", "__c_api"]
high-level-c-api = ["boolean-c-api", "shortint-c-api", "integer", "__c_api"]
__wasm_api = [
"wasm-bindgen",
@@ -102,11 +84,8 @@ __wasm_api = [
]
boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
integer-client-js-wasm-api = ["integer", "__wasm_api"]
high-level-client-js-wasm-api = ["boolean", "shortint", "integer", "__wasm_api"]
parallel-wasm-api = ["wasm-bindgen-rayon"]
nightly-avx512 = ["concrete-fft/nightly", "pulp/nightly"]
nightly-avx512 = ["concrete-fft/nightly"]
# Enable the x86_64 specific accelerated implementation of the random generator for the default
# backend
@@ -155,11 +134,6 @@ path = "benches/core_crypto/dev_bench.rs"
harness = false
required-features = ["experimental", "internal-keycache"]
[[bench]]
name = "pbs128-bench"
path = "benches/core_crypto/pbs128_bench.rs"
harness = false
[[bench]]
name = "boolean-bench"
path = "benches/boolean/bench.rs"
@@ -182,58 +156,21 @@ required-features = ["integer", "internal-keycache"]
name = "keygen"
path = "benches/keygen/bench.rs"
harness = false
required-features = ["shortint", "internal-keycache"]
[[bench]]
name = "utilities"
path = "benches/utilities.rs"
harness = false
required-features = ["boolean", "shortint", "integer", "internal-keycache"]
# Examples used as tools
[[example]]
name = "wasm_benchmarks_parser"
path = "examples/utilities/wasm_benchmarks_parser.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "generates_test_keys"
path = "examples/utilities/generates_test_keys.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "boolean_key_sizes"
path = "examples/utilities/boolean_key_sizes.rs"
required-features = ["boolean", "internal-keycache"]
[[example]]
name = "shortint_key_sizes"
path = "examples/utilities/shortint_key_sizes.rs"
required-features = ["shortint", "internal-keycache"]
[[example]]
name = "hlapi_compact_pk_ct_sizes"
path = "examples/utilities/hlapi_compact_pk_ct_sizes.rs"
required-features = ["integer", "internal-keycache"]
[[example]]
name = "micro_bench_and"
path = "examples/utilities/micro_bench_and.rs"
required-features = ["boolean"]
# Real use-case examples
[[example]]
name = "dark_market"
required-features = ["integer", "internal-keycache"]
[[example]]
name = "regex_engine"
required-features = ["integer"]
[[example]]
name = "sha256_bool"
required-features = ["boolean"]
[lib]

View File

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

View File

@@ -72,8 +72,6 @@ fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Syn
thread_count,
) = get_bench_params::<Scalar>();
let ciphertext_modulus = CiphertextModulus::new_native();
while input_lwe_dimension.0 % grouping_factor.0 != 0 {
input_lwe_dimension = LweDimension(input_lwe_dimension.0 + 1);
}
@@ -111,22 +109,16 @@ fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Syn
&input_lwe_secret_key,
Plaintext(Scalar::ZERO),
lwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
let accumulator = GlweCiphertext::new(
Scalar::ZERO,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
let accumulator =
GlweCiphertext::new(Scalar::ZERO, glwe_dimension.to_glwe_size(), polynomial_size);
// Allocate the LweCiphertext to store the result of the PBS
let mut out_pbs_ct = LweCiphertext::new(
Scalar::ZERO,
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
ciphertext_modulus,
);
let id = format!("Multi Bit PBS {}", Scalar::BITS);
@@ -163,8 +155,6 @@ fn pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
_,
) = get_bench_params::<Scalar>();
let ciphertext_modulus = CiphertextModulus::new_native();
// Create the PRNG
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
@@ -198,22 +188,16 @@ fn pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
&input_lwe_secret_key,
Plaintext(Scalar::ZERO),
lwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
let accumulator = GlweCiphertext::new(
Scalar::ZERO,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
let accumulator =
GlweCiphertext::new(Scalar::ZERO, glwe_dimension.to_glwe_size(), polynomial_size);
// Allocate the LweCiphertext to store the result of the PBS
let mut out_pbs_ct = LweCiphertext::new(
Scalar::ZERO,
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
ciphertext_modulus,
);
let id = format!("PBS {}", Scalar::BITS);
@@ -281,21 +265,15 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
&input_lwe_secret_key,
Plaintext(Scalar::ZERO),
lwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
let accumulator = GlweCiphertext::new(
Scalar::ZERO,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
let accumulator =
GlweCiphertext::new(Scalar::ZERO, glwe_dimension.to_glwe_size(), polynomial_size);
// Allocate the LweCiphertext to store the result of the PBS
let mut out_pbs_ct = LweCiphertext::new(
Scalar::ZERO,
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
ciphertext_modulus,
);
let mut buffers = ComputationBuffers::new();

View File

@@ -1,108 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use dyn_stack::PodStack;
fn sqr(x: f64) -> f64 {
x * x
}
fn criterion_bench(c: &mut Criterion) {
{
use tfhe::core_crypto::fft_impl::fft128::crypto::bootstrap::bootstrap_scratch;
use tfhe::core_crypto::prelude::*;
type Scalar = u128;
let small_lwe_dimension = LweDimension(742);
let glwe_dimension = GlweDimension(1);
let polynomial_size = PolynomialSize(2048);
let lwe_modular_std_dev = StandardDev(sqr(0.000007069849454709433));
let pbs_base_log = DecompositionBaseLog(23);
let pbs_level = DecompositionLevelCount(1);
let ciphertext_modulus = CiphertextModulus::new_native();
let mut boxed_seeder = new_seeder();
let seeder = boxed_seeder.as_mut();
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let small_lwe_sk =
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
let glwe_sk = GlweSecretKey::<Vec<Scalar>>::generate_new_binary(
glwe_dimension,
polynomial_size,
&mut secret_generator,
);
let big_lwe_sk = glwe_sk.into_lwe_secret_key();
let fourier_bsk = Fourier128LweBootstrapKey::new(
small_lwe_dimension,
glwe_dimension.to_glwe_size(),
polynomial_size,
pbs_base_log,
pbs_level,
);
let fft = Fft128::new(polynomial_size);
let fft = fft.as_view();
let message_modulus: Scalar = 1 << 4;
let input_message: Scalar = 3;
let delta: Scalar = (1 << (Scalar::BITS - 1)) / message_modulus;
let plaintext = Plaintext(input_message * delta);
let lwe_ciphertext_in: LweCiphertextOwned<Scalar> = allocate_and_encrypt_new_lwe_ciphertext(
&small_lwe_sk,
plaintext,
lwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
let accumulator: GlweCiphertextOwned<Scalar> = GlweCiphertextOwned::new(
Scalar::ONE,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
let mut pbs_out: LweCiphertext<Vec<Scalar>> = LweCiphertext::new(
0,
big_lwe_sk.lwe_dimension().to_lwe_size(),
ciphertext_modulus,
);
let mut buf = vec![
0u8;
bootstrap_scratch::<Scalar>(
fourier_bsk.glwe_size(),
fourier_bsk.polynomial_size(),
fft
)
.unwrap()
.unaligned_bytes_required()
];
c.bench_function("pbs128", |b| {
b.iter(|| {
fourier_bsk.bootstrap(
&mut pbs_out,
&lwe_ciphertext_in,
&accumulator,
fft,
PodStack::new(&mut buf),
)
});
});
}
}
criterion_group!(benches, criterion_bench);
criterion_main!(benches);

View File

@@ -1,15 +1,11 @@
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
use tfhe::core_crypto::prelude::*;
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::*;
use tfhe::shortint::ClassicPBSParameters;
use tfhe::shortint::Parameters;
const SHORTINT_BENCH_PARAMS: [ClassicPBSParameters; 15] = [
const SHORTINT_BENCH_PARAMS: [Parameters; 15] = [
PARAM_MESSAGE_1_CARRY_0,
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_0,
@@ -28,8 +24,8 @@ const SHORTINT_BENCH_PARAMS: [ClassicPBSParameters; 15] = [
];
const BOOLEAN_BENCH_PARAMS: [(&str, BooleanParameters); 2] = [
("BOOLEAN_DEFAULT_PARAMS", DEFAULT_PARAMETERS),
("BOOLEAN_TFHE_LIB_PARAMS", TFHE_LIB_PARAMETERS),
("boolean_default_params", DEFAULT_PARAMETERS),
("boolean_tfhe_lib_params", TFHE_LIB_PARAMETERS),
];
criterion_group!(
@@ -38,200 +34,82 @@ criterion_group!(
targets = mem_optimized_pbs::<u64>, mem_optimized_pbs::<u32>
);
criterion_group!(
name = multi_bit_pbs_group;
config = Criterion::default().sample_size(2000);
targets = multi_bit_pbs::<u64>,
multi_bit_pbs::<u32>,
multi_bit_deterministic_pbs::<u64>,
multi_bit_deterministic_pbs::<u32>,
);
criterion_main!(pbs_group);
criterion_main!(pbs_group, multi_bit_pbs_group);
struct BenchmarkPbsParameters<Scalar: UnsignedInteger> {
input_lwe_dimension: LweDimension,
lwe_modular_std_dev: StandardDev,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
ciphertext_modulus: CoreCiphertextModulus<Scalar>,
}
fn benchmark_parameters<Scalar: Numeric>() -> Vec<(String, CryptoParametersRecord)> {
if Scalar::BITS == 64 {
SHORTINT_BENCH_PARAMS
.iter()
.map(|params| (params.name(), params.to_owned().into()))
.collect()
} else if Scalar::BITS == 32 {
BOOLEAN_BENCH_PARAMS
.iter()
.map(|(name, params)| (name.to_string(), params.to_owned().into()))
.collect()
} else {
vec![]
impl From<BooleanParameters> for BenchmarkPbsParameters<u32> {
fn from(params: BooleanParameters) -> Self {
BenchmarkPbsParameters {
input_lwe_dimension: params.lwe_dimension,
lwe_modular_std_dev: params.lwe_modular_std_dev,
decomp_base_log: params.pbs_base_log,
decomp_level_count: params.pbs_level,
glwe_dimension: params.glwe_dimension,
polynomial_size: params.polynomial_size,
ciphertext_modulus: CoreCiphertextModulus::<u32>::new_native(),
}
}
}
fn multi_bit_benchmark_parameters<Scalar: Numeric>(
) -> Vec<(String, (CryptoParametersRecord, LweBskGroupingFactor))> {
impl From<Parameters> for BenchmarkPbsParameters<u64> {
fn from(params: Parameters) -> Self {
BenchmarkPbsParameters {
input_lwe_dimension: params.lwe_dimension,
lwe_modular_std_dev: params.lwe_modular_std_dev,
decomp_base_log: params.pbs_base_log,
decomp_level_count: params.pbs_level,
glwe_dimension: params.glwe_dimension,
polynomial_size: params.polynomial_size,
ciphertext_modulus: params.ciphertext_modulus,
}
}
}
fn benchmark_parameters<Scalar: UnsignedInteger>() -> Vec<(String, BenchmarkPbsParameters<Scalar>)>
{
if Scalar::BITS == 64 {
vec![
(
"2_bits_multi_bit_group_2".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(764)),
lwe_modular_std_dev: Some(StandardDev(0.000006025673585415336)),
pbs_base_log: Some(DecompositionBaseLog(18)),
pbs_level: Some(DecompositionLevelCount(1)),
glwe_dimension: Some(GlweDimension(3)),
glwe_modular_std_dev: Some(StandardDev(0.0000000000039666089171633006)),
polynomial_size: Some(PolynomialSize(1 << 9)),
message_modulus: Some(1),
carry_modulus: Some(1),
..Default::default()
},
LweBskGroupingFactor(2),
),
),
(
"2_bits_multi_bit_group_3".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(765)),
lwe_modular_std_dev: Some(StandardDev(0.000005915594083804978)),
pbs_base_log: Some(DecompositionBaseLog(18)),
pbs_level: Some(DecompositionLevelCount(1)),
glwe_dimension: Some(GlweDimension(3)),
glwe_modular_std_dev: Some(StandardDev(0.0000000000039666089171633006)),
polynomial_size: Some(PolynomialSize(1 << 9)),
message_modulus: Some(1),
carry_modulus: Some(1),
..Default::default()
},
LweBskGroupingFactor(3),
),
),
(
"4_bits_multi_bit_group_2".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(818)),
lwe_modular_std_dev: Some(StandardDev(0.000002226459789930014)),
pbs_base_log: Some(DecompositionBaseLog(22)),
pbs_level: Some(DecompositionLevelCount(1)),
glwe_dimension: Some(GlweDimension(1)),
glwe_modular_std_dev: Some(StandardDev(0.0000000000000003152931493498455)),
polynomial_size: Some(PolynomialSize(1 << 11)),
message_modulus: Some(2),
carry_modulus: Some(2),
..Default::default()
},
LweBskGroupingFactor(2),
),
),
(
"4_bits_multi_bit_group_3".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(888)),
lwe_modular_std_dev: Some(StandardDev(0.0000006125031601933181)),
pbs_base_log: Some(DecompositionBaseLog(21)),
pbs_level: Some(DecompositionLevelCount(1)),
glwe_dimension: Some(GlweDimension(1)),
glwe_modular_std_dev: Some(StandardDev(0.0000000000000003152931493498455)),
polynomial_size: Some(PolynomialSize(1 << 11)),
message_modulus: Some(2),
carry_modulus: Some(2),
..Default::default()
},
LweBskGroupingFactor(3),
),
),
(
"6_bits_multi_bit_group_2".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(922)),
lwe_modular_std_dev: Some(StandardDev(0.0000003272369292345697)),
pbs_base_log: Some(DecompositionBaseLog(14)),
pbs_level: Some(DecompositionLevelCount(2)),
glwe_dimension: Some(GlweDimension(1)),
glwe_modular_std_dev: Some(StandardDev(
0.0000000000000000002168404344971009,
)),
polynomial_size: Some(PolynomialSize(1 << 13)),
message_modulus: Some(3),
carry_modulus: Some(3),
..Default::default()
},
LweBskGroupingFactor(2),
),
),
(
"6_bits_multi_bit_group_3".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(972)),
lwe_modular_std_dev: Some(StandardDev(0.00000013016688349592805)),
pbs_base_log: Some(DecompositionBaseLog(14)),
pbs_level: Some(DecompositionLevelCount(2)),
glwe_dimension: Some(GlweDimension(1)),
glwe_modular_std_dev: Some(StandardDev(
0.0000000000000000002168404344971009,
)),
polynomial_size: Some(PolynomialSize(1 << 13)),
message_modulus: Some(3),
carry_modulus: Some(3),
..Default::default()
},
LweBskGroupingFactor(3),
),
),
(
"8_bits_multi_bit_group_2".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(1052)),
lwe_modular_std_dev: Some(StandardDev(0.000000029779789543501806)),
pbs_base_log: Some(DecompositionBaseLog(14)),
pbs_level: Some(DecompositionLevelCount(2)),
glwe_dimension: Some(GlweDimension(1)),
glwe_modular_std_dev: Some(StandardDev(
0.0000000000000000002168404344971009,
)),
polynomial_size: Some(PolynomialSize(1 << 15)),
message_modulus: Some(4),
carry_modulus: Some(4),
..Default::default()
},
LweBskGroupingFactor(2),
),
),
(
"8_bits_multi_bit_group_3".to_string(),
(
CryptoParametersRecord {
lwe_dimension: Some(LweDimension(1098)),
lwe_modular_std_dev: Some(StandardDev(0.000000012752307213087621)),
pbs_base_log: Some(DecompositionBaseLog(14)),
pbs_level: Some(DecompositionLevelCount(2)),
glwe_dimension: Some(GlweDimension(1)),
glwe_modular_std_dev: Some(StandardDev(
0.0000000000000000002168404344971009,
)),
polynomial_size: Some(PolynomialSize(1 << 15)),
message_modulus: Some(4),
carry_modulus: Some(4),
..Default::default()
},
LweBskGroupingFactor(3),
),
),
]
unsafe {
std::mem::transmute(
SHORTINT_BENCH_PARAMS
.iter()
.map(|params| {
(
format!("shortint_{}", params.name().to_lowercase()),
BenchmarkPbsParameters::<u64>::from(params.to_owned()),
)
})
.collect::<Vec<_>>(),
)
}
} else if Scalar::BITS == 32 {
unsafe {
std::mem::transmute(
BOOLEAN_BENCH_PARAMS
.iter()
.map(|(name, params)| {
(
name.to_string(),
BenchmarkPbsParameters::<u32>::from(params.to_owned()),
)
})
.collect::<Vec<_>>(),
)
}
} else {
// For now there are no parameters available to test multi bit PBS on 32 bits.
vec![]
}
}
fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
let bench_name = "PBS_mem-optimized";
let mut bench_group = c.benchmark_group(bench_name);
// Create the PRNG
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
@@ -243,47 +121,46 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
for (name, params) in benchmark_parameters::<Scalar>().iter() {
// Create the LweSecretKey
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
params.lwe_dimension.unwrap(),
params.input_lwe_dimension,
&mut secret_generator,
);
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
allocate_and_generate_new_binary_glwe_secret_key(
params.glwe_dimension.unwrap(),
params.polynomial_size.unwrap(),
params.glwe_dimension,
params.polynomial_size,
&mut secret_generator,
);
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
// Create the empty bootstrapping key in the Fourier domain
let fourier_bsk = FourierLweBootstrapKey::new(
params.lwe_dimension.unwrap(),
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
params.pbs_base_log.unwrap(),
params.pbs_level.unwrap(),
params.input_lwe_dimension,
params.glwe_dimension.to_glwe_size(),
params.polynomial_size,
params.decomp_base_log,
params.decomp_level_count,
);
// Allocate a new LweCiphertext and encrypt our plaintext
let lwe_ciphertext_in: LweCiphertextOwned<Scalar> = allocate_and_encrypt_new_lwe_ciphertext(
&input_lwe_secret_key,
Plaintext(Scalar::ZERO),
params.lwe_modular_std_dev.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
params.lwe_modular_std_dev,
params.ciphertext_modulus,
&mut encryption_generator,
);
let accumulator = GlweCiphertext::new(
Scalar::ZERO,
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
params.glwe_dimension.to_glwe_size(),
params.polynomial_size,
);
// Allocate the LweCiphertext to store the result of the PBS
let mut out_pbs_ct = LweCiphertext::new(
Scalar::ZERO,
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
params.ciphertext_modulus,
);
let mut buffers = ComputationBuffers::new();
@@ -301,9 +178,9 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
.unaligned_bytes_required(),
);
let id = format!("{bench_name}_{name}");
let id = format!("PBS_mem-optimized_{name}");
{
bench_group.bench_function(&id, |b| {
c.bench_function(&id, |b| {
b.iter(|| {
programmable_bootstrap_lwe_ciphertext_mem_optimized(
&lwe_ciphertext_in,
@@ -317,190 +194,5 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
})
});
}
let bit_size = (params.message_modulus.unwrap_or(2) as u32).ilog2();
write_to_json(
&id,
*params,
name,
"pbs",
&OperatorType::Atomic,
bit_size,
vec![bit_size],
);
}
}
fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Sync>(
c: &mut Criterion,
) {
let bench_name = "multi_bits_PBS";
let mut bench_group = c.benchmark_group(bench_name);
// Create the PRNG
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
for (name, (params, grouping_factor)) in multi_bit_benchmark_parameters::<Scalar>().iter() {
// Create the LweSecretKey
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
params.lwe_dimension.unwrap(),
&mut secret_generator,
);
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
allocate_and_generate_new_binary_glwe_secret_key(
params.glwe_dimension.unwrap(),
params.polynomial_size.unwrap(),
&mut secret_generator,
);
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
let multi_bit_bsk = FourierLweMultiBitBootstrapKey::new(
params.lwe_dimension.unwrap(),
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
params.pbs_base_log.unwrap(),
params.pbs_level.unwrap(),
*grouping_factor,
);
// Allocate a new LweCiphertext and encrypt our plaintext
let lwe_ciphertext_in = allocate_and_encrypt_new_lwe_ciphertext(
&input_lwe_secret_key,
Plaintext(Scalar::ZERO),
params.lwe_modular_std_dev.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
&mut encryption_generator,
);
let accumulator = GlweCiphertext::new(
Scalar::ZERO,
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
);
// Allocate the LweCiphertext to store the result of the PBS
let mut out_pbs_ct = LweCiphertext::new(
Scalar::ZERO,
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
);
let id = format!("{bench_name}_{name}_parallelized");
bench_group.bench_function(&id, |b| {
b.iter(|| {
multi_bit_programmable_bootstrap_lwe_ciphertext(
&lwe_ciphertext_in,
&mut out_pbs_ct,
&accumulator.as_view(),
&multi_bit_bsk,
ThreadCount(10),
);
black_box(&mut out_pbs_ct);
})
});
let bit_size = params.message_modulus.unwrap().ilog2();
write_to_json(
&id,
*params,
name,
"pbs",
&OperatorType::Atomic,
bit_size,
vec![bit_size],
);
}
}
fn multi_bit_deterministic_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Sync>(
c: &mut Criterion,
) {
let bench_name = "multi_bits_deterministic_PBS";
let mut bench_group = c.benchmark_group(bench_name);
// Create the PRNG
let mut seeder = new_seeder();
let seeder = seeder.as_mut();
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
for (name, (params, grouping_factor)) in multi_bit_benchmark_parameters::<Scalar>().iter() {
// Create the LweSecretKey
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
params.lwe_dimension.unwrap(),
&mut secret_generator,
);
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
allocate_and_generate_new_binary_glwe_secret_key(
params.glwe_dimension.unwrap(),
params.polynomial_size.unwrap(),
&mut secret_generator,
);
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
let multi_bit_bsk = FourierLweMultiBitBootstrapKey::new(
params.lwe_dimension.unwrap(),
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
params.pbs_base_log.unwrap(),
params.pbs_level.unwrap(),
*grouping_factor,
);
// Allocate a new LweCiphertext and encrypt our plaintext
let lwe_ciphertext_in = allocate_and_encrypt_new_lwe_ciphertext(
&input_lwe_secret_key,
Plaintext(Scalar::ZERO),
params.lwe_modular_std_dev.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
&mut encryption_generator,
);
let accumulator = GlweCiphertext::new(
Scalar::ZERO,
params.glwe_dimension.unwrap().to_glwe_size(),
params.polynomial_size.unwrap(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
);
// Allocate the LweCiphertext to store the result of the PBS
let mut out_pbs_ct = LweCiphertext::new(
Scalar::ZERO,
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
);
let id = format!("{bench_name}_{name}_parallelized");
bench_group.bench_function(&id, |b| {
b.iter(|| {
multi_bit_deterministic_programmable_bootstrap_lwe_ciphertext(
&lwe_ciphertext_in,
&mut out_pbs_ct,
&accumulator.as_view(),
&multi_bit_bsk,
ThreadCount(10),
);
black_box(&mut out_pbs_ct);
})
});
let bit_size = params.message_modulus.unwrap().ilog2();
write_to_json(
&id,
*params,
name,
"pbs",
&OperatorType::Atomic,
bit_size,
vec![bit_size],
);
}
}

View File

@@ -1,9 +1,5 @@
#![allow(dead_code)]
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use criterion::{criterion_group, criterion_main, Criterion};
use itertools::iproduct;
use rand::Rng;
@@ -23,16 +19,15 @@ use tfhe::shortint::parameters::{
/// in radix decomposition
struct ParamsAndNumBlocksIter {
params_and_bit_sizes:
itertools::Product<IntoIter<tfhe::shortint::ClassicPBSParameters, 1>, IntoIter<usize, 7>>,
itertools::Product<IntoIter<tfhe::shortint::Parameters, 3>, IntoIter<usize, 7>>,
}
impl Default for ParamsAndNumBlocksIter {
fn default() -> Self {
// FIXME One set of parameter is tested since we want to benchmark only quickest operations.
const PARAMS: [tfhe::shortint::ClassicPBSParameters; 1] = [
const PARAMS: [tfhe::shortint::Parameters; 3] = [
PARAM_MESSAGE_2_CARRY_2,
// PARAM_MESSAGE_3_CARRY_3,
// PARAM_MESSAGE_4_CARRY_4,
PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
];
const BIT_SIZES: [usize; 7] = [8, 16, 32, 40, 64, 128, 256];
let params_and_bit_sizes = iproduct!(PARAMS, BIT_SIZES);
@@ -42,7 +37,7 @@ impl Default for ParamsAndNumBlocksIter {
}
}
impl Iterator for ParamsAndNumBlocksIter {
type Item = (tfhe::shortint::ClassicPBSParameters, usize, usize);
type Item = (tfhe::shortint::Parameters, usize, usize);
fn next(&mut self) -> Option<Self::Item> {
let (param, bit_size) = self.params_and_bit_sizes.next()?;
@@ -53,14 +48,9 @@ impl Iterator for ParamsAndNumBlocksIter {
}
}
/// Base function to bench a server key function that is a binary operation, input ciphertexts will
/// contain non zero carries
fn bench_server_key_binary_function_dirty_inputs<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
) where
/// Base function to bench a server key function that is a binary operation
fn bench_server_key_binary_function<F>(c: &mut Criterion, bench_name: &str, binary_op: F)
where
F: Fn(&ServerKey, &mut RadixCiphertext, &mut RadixCiphertext),
{
let mut bench_group = c.benchmark_group(bench_name);
@@ -72,7 +62,7 @@ fn bench_server_key_binary_function_dirty_inputs<F>(
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");
let bench_id = format!("{param_name}/{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
@@ -112,102 +102,24 @@ fn bench_server_key_binary_function_dirty_inputs<F>(
criterion::BatchSize::SmallInput,
)
});
write_to_json(
&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 binary operation, input ciphertext will
/// contain only zero carries
fn bench_server_key_binary_function_clean_inputs<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
) where
F: Fn(&ServerKey, &mut RadixCiphertext, &mut 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 clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
let ct_0 = cks.encrypt_radix(clear_0, num_block);
let clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
let clear_1 = tfhe::integer::U256::from((clearlow, clearhigh));
let ct_1 = cks.encrypt_radix(clear_1, num_block);
(ct_0, ct_1)
};
b.iter_batched(
encrypt_two_values,
|(mut ct_0, mut ct_1)| {
binary_op(&sks, &mut ct_0, &mut ct_1);
},
criterion::BatchSize::SmallInput,
)
});
write_to_json(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
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 ciphertexts will
/// contain non zero carries
fn bench_server_key_unary_function_dirty_inputs<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
unary_fn: F,
) where
/// Base function to bench a server key function that is a unary operation
fn bench_server_key_unary_function<F>(c: &mut Criterion, group_name: &str, unary_fn: F)
where
F: Fn(&ServerKey, &mut 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 bench_group = c.benchmark_group(group_name);
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");
let bench_id = format!("{param_name}/{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
@@ -243,164 +155,22 @@ fn bench_server_key_unary_function_dirty_inputs<F>(
criterion::BatchSize::SmallInput,
)
});
write_to_json(
&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, &mut 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_one_value = || {
let clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
cks.encrypt_radix(clear_0, num_block)
};
b.iter_batched(
encrypt_one_value,
|mut ct_0| {
unary_fn(&sks, &mut ct_0);
},
criterion::BatchSize::SmallInput,
)
});
write_to_json(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus.0.ilog2(); num_block],
);
}
bench_group.finish()
}
fn bench_server_key_binary_scalar_function_dirty_inputs<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
) where
fn bench_server_key_binary_scalar_function<F>(c: &mut Criterion, bench_name: &str, binary_op: F)
where
F: Fn(&ServerKey, &mut RadixCiphertext, u64),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
.sample_size(15)
.measurement_time(std::time::Duration::from_secs(60));
let mut rng = rand::thread_rng();
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
let param_name = param.name();
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let encrypt_one_value = || {
let clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
let mut ct_0 = cks.encrypt_radix(clear_0, num_block);
// Raise the degree, so as to ensure worst case path in operations
let mut carry_mod = param.carry_modulus.0;
while carry_mod > 0 {
// Raise the degree, so as to ensure worst case path in operations
let clearlow = rng.gen::<u128>();
let clearhigh = rng.gen::<u128>();
let clear_2 = tfhe::integer::U256::from((clearlow, clearhigh));
let ct_2 = cks.encrypt_radix(clear_2, num_block);
sks.unchecked_add_assign(&mut ct_0, &ct_2);
carry_mod -= 1;
}
let clear_1 = rng.gen::<u64>();
(ct_0, clear_1)
};
b.iter_batched(
encrypt_one_value,
|(mut ct_0, clear_1)| {
binary_op(&sks, &mut ct_0, clear_1);
},
criterion::BatchSize::SmallInput,
)
});
write_to_json(
&bench_id,
param,
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus.0.ilog2(); num_block],
);
}
bench_group.finish()
}
fn bench_server_key_binary_scalar_function_clean_inputs<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
) where
F: Fn(&ServerKey, &mut RadixCiphertext, u64),
{
let mut bench_group = c.benchmark_group(bench_name);
bench_group
.sample_size(15)
.measurement_time(std::time::Duration::from_secs(60));
let mut rng = rand::thread_rng();
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
let param_name = param.name();
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
let bench_id = format!("{param_name}/{bit_size}_bits");
bench_group.bench_function(&bench_id, |b| {
let (cks, sks) = KEY_CACHE.get_from_params(param);
@@ -424,56 +194,30 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F>(
criterion::BatchSize::SmallInput,
)
});
write_to_json(
&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_unary_fn (
(method_name: $server_key_method:ident, display_name:$name:ident) => {
($server_key_method:ident) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_unary_function_dirty_inputs(
bench_server_key_unary_function(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs| {
server_key.$server_key_method(lhs);
})
}
}
);
macro_rules! define_server_key_bench_unary_default_fn (
(method_name: $server_key_method:ident, display_name:$name:ident) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_unary_function_clean_inputs(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs| {
server_key.$server_key_method(lhs);
})
}
}
);
);
macro_rules! define_server_key_bench_fn (
(method_name: $server_key_method:ident, display_name:$name:ident) => {
($server_key_method:ident) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_function_dirty_inputs(
bench_server_key_binary_function(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
server_key.$server_key_method(lhs, rhs);
})
@@ -481,27 +225,12 @@ macro_rules! define_server_key_bench_fn (
}
);
macro_rules! define_server_key_bench_default_fn (
(method_name: $server_key_method:ident, display_name:$name:ident) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_function_clean_inputs(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
server_key.$server_key_method(lhs, rhs);
})
}
}
);
macro_rules! define_server_key_bench_scalar_fn (
(method_name: $server_key_method:ident, display_name:$name:ident) => {
($server_key_method:ident) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_scalar_function_dirty_inputs(
bench_server_key_binary_scalar_function(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
server_key.$server_key_method(lhs, rhs);
})
@@ -509,215 +238,76 @@ macro_rules! define_server_key_bench_scalar_fn (
}
);
macro_rules! define_server_key_bench_scalar_default_fn (
(method_name: $server_key_method:ident, display_name:$name:ident) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_scalar_function_clean_inputs(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
server_key.$server_key_method(lhs, rhs);
})
}
}
);
define_server_key_bench_fn!(smart_add);
define_server_key_bench_fn!(smart_sub);
define_server_key_bench_fn!(smart_mul);
define_server_key_bench_fn!(smart_bitand);
define_server_key_bench_fn!(smart_bitor);
define_server_key_bench_fn!(smart_bitxor);
define_server_key_bench_fn!(method_name: smart_add, display_name: add);
define_server_key_bench_fn!(method_name: smart_sub, display_name: sub);
define_server_key_bench_fn!(method_name: smart_mul, display_name: mul);
define_server_key_bench_fn!(method_name: smart_bitand, display_name: bitand);
define_server_key_bench_fn!(method_name: smart_bitor, display_name: bitor);
define_server_key_bench_fn!(method_name: smart_bitxor, display_name: bitxor);
define_server_key_bench_fn!(smart_add_parallelized);
define_server_key_bench_fn!(smart_sub_parallelized);
define_server_key_bench_fn!(smart_mul_parallelized);
define_server_key_bench_fn!(smart_bitand_parallelized);
define_server_key_bench_fn!(smart_bitxor_parallelized);
define_server_key_bench_fn!(smart_bitor_parallelized);
define_server_key_bench_fn!(method_name: smart_add_parallelized, display_name: add);
define_server_key_bench_fn!(method_name: smart_sub_parallelized, display_name: sub);
define_server_key_bench_fn!(method_name: smart_mul_parallelized, display_name: mul);
define_server_key_bench_fn!(method_name: smart_bitand_parallelized, display_name: bitand);
define_server_key_bench_fn!(method_name: smart_bitxor_parallelized, display_name: bitxor);
define_server_key_bench_fn!(method_name: smart_bitor_parallelized, display_name: bitor);
define_server_key_bench_fn!(unchecked_add);
define_server_key_bench_fn!(unchecked_sub);
define_server_key_bench_fn!(unchecked_mul);
define_server_key_bench_fn!(unchecked_bitand);
define_server_key_bench_fn!(unchecked_bitor);
define_server_key_bench_fn!(unchecked_bitxor);
define_server_key_bench_default_fn!(method_name: add_parallelized, display_name: add);
define_server_key_bench_default_fn!(method_name: sub_parallelized, display_name: sub);
define_server_key_bench_default_fn!(method_name: mul_parallelized, display_name: mul);
define_server_key_bench_default_fn!(method_name: bitand_parallelized, display_name: bitand);
define_server_key_bench_default_fn!(method_name: bitxor_parallelized, display_name: bitxor);
define_server_key_bench_default_fn!(method_name: bitor_parallelized, display_name: bitor);
define_server_key_bench_fn!(unchecked_mul_parallelized);
define_server_key_bench_fn!(method_name: unchecked_add, display_name: add);
define_server_key_bench_fn!(method_name: unchecked_sub, display_name: sub);
define_server_key_bench_fn!(method_name: unchecked_mul, display_name: mul);
define_server_key_bench_fn!(method_name: unchecked_bitand, display_name: bitand);
define_server_key_bench_fn!(method_name: unchecked_bitor, display_name: bitor);
define_server_key_bench_fn!(method_name: unchecked_bitxor, display_name: bitxor);
define_server_key_bench_scalar_fn!(smart_scalar_add);
define_server_key_bench_scalar_fn!(smart_scalar_sub);
define_server_key_bench_scalar_fn!(smart_scalar_mul);
define_server_key_bench_fn!(method_name: unchecked_mul_parallelized, display_name: mul);
define_server_key_bench_fn!(
method_name: unchecked_bitand_parallelized,
display_name: bitand
);
define_server_key_bench_fn!(
method_name: unchecked_bitor_parallelized,
display_name: bitor
);
define_server_key_bench_fn!(
method_name: unchecked_bitxor_parallelized,
display_name: bitxor
);
define_server_key_bench_scalar_fn!(smart_scalar_add_parallelized);
define_server_key_bench_scalar_fn!(smart_scalar_sub_parallelized);
define_server_key_bench_scalar_fn!(smart_scalar_mul_parallelized);
define_server_key_bench_scalar_fn!(method_name: smart_scalar_add, display_name: add);
define_server_key_bench_scalar_fn!(method_name: smart_scalar_sub, display_name: sub);
define_server_key_bench_scalar_fn!(method_name: smart_scalar_mul, display_name: mul);
define_server_key_bench_scalar_fn!(unchecked_scalar_add);
define_server_key_bench_scalar_fn!(unchecked_scalar_sub);
define_server_key_bench_scalar_fn!(unchecked_small_scalar_mul);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_add_parallelized,
display_name: add
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_sub_parallelized,
display_name: sub
);
define_server_key_bench_scalar_fn!(
method_name: smart_scalar_mul_parallelized,
display_name: mul
);
define_server_key_bench_unary_fn!(smart_neg);
define_server_key_bench_unary_fn!(full_propagate);
define_server_key_bench_unary_fn!(full_propagate_parallelized);
define_server_key_bench_scalar_default_fn!(method_name: scalar_add_parallelized, display_name: add);
define_server_key_bench_scalar_default_fn!(method_name: scalar_sub_parallelized, display_name: sub);
define_server_key_bench_scalar_default_fn!(method_name: scalar_mul_parallelized, display_name: mul);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_left_shift_parallelized,
display_name: left_shift
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_right_shift_parallelized,
display_name: right_shift
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_eq_parallelized,
display_name: scalar_equal
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_ne_parallelized,
display_name: scalar_not_equal
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_le_parallelized,
display_name: scalar_less_or_equal
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_lt_parallelized,
display_name: scalar_less_than
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_ge_parallelized,
display_name: scalar_greater_or_equal
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_gt_parallelized,
display_name: scalar_greater_than
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_max_parallelized,
display_name: scalar_max
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_min_parallelized,
display_name: scalar_min
);
define_server_key_bench_fn!(unchecked_max);
define_server_key_bench_fn!(unchecked_min);
define_server_key_bench_fn!(unchecked_eq);
define_server_key_bench_fn!(unchecked_lt);
define_server_key_bench_fn!(unchecked_le);
define_server_key_bench_fn!(unchecked_gt);
define_server_key_bench_fn!(unchecked_ge);
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_add, display_name: add);
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_sub, display_name: sub);
define_server_key_bench_scalar_fn!(method_name: unchecked_small_scalar_mul, display_name: mul);
define_server_key_bench_fn!(unchecked_max_parallelized);
define_server_key_bench_fn!(unchecked_min_parallelized);
define_server_key_bench_fn!(unchecked_eq_parallelized);
define_server_key_bench_fn!(unchecked_lt_parallelized);
define_server_key_bench_fn!(unchecked_le_parallelized);
define_server_key_bench_fn!(unchecked_gt_parallelized);
define_server_key_bench_fn!(unchecked_ge_parallelized);
define_server_key_bench_unary_fn!(method_name: smart_neg, display_name: negation);
define_server_key_bench_unary_fn!(method_name: smart_neg_parallelized, display_name: negation);
define_server_key_bench_unary_default_fn!(method_name: neg_parallelized, display_name: negation);
define_server_key_bench_fn!(smart_max);
define_server_key_bench_fn!(smart_min);
define_server_key_bench_fn!(smart_eq);
define_server_key_bench_fn!(smart_lt);
define_server_key_bench_fn!(smart_le);
define_server_key_bench_fn!(smart_gt);
define_server_key_bench_fn!(smart_ge);
define_server_key_bench_unary_fn!(method_name: full_propagate, display_name: carry_propagation);
define_server_key_bench_unary_fn!(
method_name: full_propagate_parallelized,
display_name: carry_propagation
);
define_server_key_bench_fn!(method_name: unchecked_max, display_name: max);
define_server_key_bench_fn!(method_name: unchecked_min, display_name: min);
define_server_key_bench_fn!(method_name: unchecked_eq, display_name: equal);
define_server_key_bench_fn!(method_name: unchecked_lt, display_name: less_than);
define_server_key_bench_fn!(method_name: unchecked_le, display_name: less_or_equal);
define_server_key_bench_fn!(method_name: unchecked_gt, display_name: greater_than);
define_server_key_bench_fn!(method_name: unchecked_ge, display_name: greater_or_equal);
define_server_key_bench_fn!(method_name: unchecked_max_parallelized, display_name: max);
define_server_key_bench_fn!(method_name: unchecked_min_parallelized, display_name: min);
define_server_key_bench_fn!(method_name: unchecked_eq_parallelized, display_name: equal);
define_server_key_bench_fn!(
method_name: unchecked_lt_parallelized,
display_name: less_than
);
define_server_key_bench_fn!(
method_name: unchecked_le_parallelized,
display_name: less_or_equal
);
define_server_key_bench_fn!(
method_name: unchecked_gt_parallelized,
display_name: greater_than
);
define_server_key_bench_fn!(
method_name: unchecked_ge_parallelized,
display_name: greater_or_equal
);
define_server_key_bench_fn!(method_name: smart_max, display_name: max);
define_server_key_bench_fn!(method_name: smart_min, display_name: min);
define_server_key_bench_fn!(method_name: smart_eq, display_name: equal);
define_server_key_bench_fn!(method_name: smart_lt, display_name: less_than);
define_server_key_bench_fn!(method_name: smart_le, display_name: less_or_equal);
define_server_key_bench_fn!(method_name: smart_gt, display_name: greater_than);
define_server_key_bench_fn!(method_name: smart_ge, display_name: greater_or_equal);
define_server_key_bench_fn!(method_name: smart_max_parallelized, display_name: max);
define_server_key_bench_fn!(method_name: smart_min_parallelized, display_name: min);
define_server_key_bench_fn!(method_name: smart_eq_parallelized, display_name: equal);
define_server_key_bench_fn!(method_name: smart_lt_parallelized, display_name: less_than);
define_server_key_bench_fn!(
method_name: smart_le_parallelized,
display_name: less_or_equal
);
define_server_key_bench_fn!(
method_name: smart_gt_parallelized,
display_name: greater_than
);
define_server_key_bench_fn!(
method_name: smart_ge_parallelized,
display_name: greater_or_equal
);
define_server_key_bench_default_fn!(method_name: max_parallelized, display_name: max);
define_server_key_bench_default_fn!(method_name: min_parallelized, display_name: min);
define_server_key_bench_default_fn!(method_name: eq_parallelized, display_name: equal);
define_server_key_bench_default_fn!(method_name: ne_parallelized, display_name: not_equal);
define_server_key_bench_default_fn!(method_name: lt_parallelized, display_name: less_than);
define_server_key_bench_default_fn!(method_name: le_parallelized, display_name: less_or_equal);
define_server_key_bench_default_fn!(method_name: gt_parallelized, display_name: greater_than);
define_server_key_bench_default_fn!(method_name: ge_parallelized, display_name: greater_or_equal);
define_server_key_bench_default_fn!(
method_name: left_shift_parallelized,
display_name: left_shift
);
define_server_key_bench_default_fn!(
method_name: right_shift_parallelized,
display_name: right_shift
);
define_server_key_bench_default_fn!(
method_name: rotate_left_parallelized,
display_name: rotate_left
);
define_server_key_bench_default_fn!(
method_name: rotate_right_parallelized,
display_name: rotate_right
);
define_server_key_bench_fn!(smart_max_parallelized);
define_server_key_bench_fn!(smart_min_parallelized);
define_server_key_bench_fn!(smart_eq_parallelized);
define_server_key_bench_fn!(smart_lt_parallelized);
define_server_key_bench_fn!(smart_le_parallelized);
define_server_key_bench_fn!(smart_gt_parallelized);
define_server_key_bench_fn!(smart_ge_parallelized);
criterion_group!(
smart_arithmetic_operation,
@@ -753,23 +343,6 @@ criterion_group!(
smart_ge_parallelized,
);
criterion_group!(
arithmetic_parallelized_operation,
add_parallelized,
sub_parallelized,
mul_parallelized,
bitand_parallelized,
bitor_parallelized,
bitxor_parallelized,
max_parallelized,
min_parallelized,
eq_parallelized,
lt_parallelized,
le_parallelized,
gt_parallelized,
ge_parallelized,
);
criterion_group!(
smart_scalar_arithmetic_operation,
smart_scalar_add,
@@ -784,15 +357,6 @@ criterion_group!(
smart_scalar_mul_parallelized,
);
criterion_group!(
scalar_arithmetic_parallel_operation,
scalar_add_parallelized,
scalar_sub_parallelized,
scalar_mul_parallelized,
scalar_left_shift_parallelized,
scalar_right_shift_parallelized,
);
criterion_group!(
unchecked_arithmetic_operation,
unchecked_add,
@@ -822,58 +386,16 @@ criterion_group!(
unchecked_le_parallelized,
unchecked_gt_parallelized,
unchecked_ge_parallelized,
unchecked_bitand_parallelized,
unchecked_bitor_parallelized,
unchecked_bitxor_parallelized,
);
criterion_group!(misc, full_propagate, full_propagate_parallelized);
// User-oriented benchmark group.
// This gather all the operations that a high-level user could use.
criterion_group!(
fast_integer_benchmarks,
bitand_parallelized,
bitor_parallelized,
bitxor_parallelized,
add_parallelized,
sub_parallelized,
mul_parallelized,
neg_parallelized,
min_parallelized,
max_parallelized,
eq_parallelized,
ne_parallelized,
lt_parallelized,
le_parallelized,
gt_parallelized,
ge_parallelized,
left_shift_parallelized,
right_shift_parallelized,
rotate_left_parallelized,
rotate_right_parallelized,
scalar_add_parallelized,
scalar_sub_parallelized,
scalar_mul_parallelized,
scalar_left_shift_parallelized,
scalar_right_shift_parallelized,
scalar_eq_parallelized,
scalar_ne_parallelized,
scalar_lt_parallelized,
scalar_le_parallelized,
scalar_gt_parallelized,
scalar_ge_parallelized,
scalar_min_parallelized,
scalar_max_parallelized,
);
criterion_main!(
fast_integer_benchmarks,
// smart_arithmetic_operation,
// smart_arithmetic_parallelized_operation,
// smart_scalar_arithmetic_operation,
// smart_scalar_arithmetic_parallel_operation,
// unchecked_arithmetic_operation,
// unchecked_scalar_arithmetic_operation,
// misc,
smart_arithmetic_operation,
smart_arithmetic_parallelized_operation,
smart_scalar_arithmetic_operation,
smart_scalar_arithmetic_parallel_operation,
unchecked_arithmetic_operation,
unchecked_scalar_arithmetic_operation,
misc,
);

View File

@@ -3,7 +3,7 @@ use criterion::*;
use tfhe::core_crypto::commons::generators::DeterministicSeeder;
use tfhe::core_crypto::prelude::{
allocate_and_generate_new_binary_glwe_secret_key,
par_allocate_and_generate_new_lwe_bootstrap_key, ActivatedRandomGenerator, CiphertextModulus,
par_allocate_and_generate_new_lwe_bootstrap_key, ActivatedRandomGenerator,
EncryptionRandomGenerator, SecretRandomGenerator,
};
use tfhe::core_crypto::seeders::new_seeder;
@@ -34,7 +34,6 @@ fn criterion_bench(c: &mut Criterion) {
parameters.pbs_base_log,
parameters.pbs_level,
parameters.glwe_modular_std_dev,
CiphertextModulus::new_native(),
&mut encryption_generator,
);
});

View File

@@ -1,11 +1,7 @@
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use criterion::{criterion_group, criterion_main, Criterion};
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::*;
use tfhe::shortint::{Ciphertext, ClassicPBSParameters, ServerKey, ShortintParameterSet};
use tfhe::shortint::{CiphertextBig, Parameters, ServerKey};
use rand::Rng;
use tfhe::shortint::keycache::KEY_CACHE;
@@ -13,14 +9,14 @@ use tfhe::shortint::keycache::KEY_CACHE;
use tfhe::shortint::keycache::KEY_CACHE_WOPBS;
use tfhe::shortint::parameters::parameters_wopbs::WOPBS_PARAM_MESSAGE_4_NORM2_6;
const SERVER_KEY_BENCH_PARAMS: [ClassicPBSParameters; 4] = [
const SERVER_KEY_BENCH_PARAMS: [Parameters; 4] = [
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
];
const SERVER_KEY_BENCH_PARAMS_EXTENDED: [ClassicPBSParameters; 15] = [
const SERVER_KEY_BENCH_PARAMS_EXTENDED: [Parameters; 15] = [
PARAM_MESSAGE_1_CARRY_0,
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_0,
@@ -41,11 +37,10 @@ const SERVER_KEY_BENCH_PARAMS_EXTENDED: [ClassicPBSParameters; 15] = [
fn bench_server_key_unary_function<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
unary_op: F,
params: &[ClassicPBSParameters],
params: &[Parameters],
) where
F: Fn(&ServerKey, &mut Ciphertext),
F: Fn(&ServerKey, &mut CiphertextBig),
{
let mut bench_group = c.benchmark_group(bench_name);
@@ -55,7 +50,7 @@ fn bench_server_key_unary_function<F>(
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus().0 as u64;
let modulus = cks.parameters.message_modulus.0 as u64;
let clear_text = rng.gen::<u64>() % modulus;
@@ -67,16 +62,6 @@ fn bench_server_key_unary_function<F>(
unary_op(sks, &mut ct);
})
});
write_to_json(
&bench_id,
*param,
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
bench_group.finish()
@@ -85,11 +70,10 @@ fn bench_server_key_unary_function<F>(
fn bench_server_key_binary_function<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[ClassicPBSParameters],
params: &[Parameters],
) where
F: Fn(&ServerKey, &mut Ciphertext, &mut Ciphertext),
F: Fn(&ServerKey, &mut CiphertextBig, &mut CiphertextBig),
{
let mut bench_group = c.benchmark_group(bench_name);
@@ -99,7 +83,7 @@ fn bench_server_key_binary_function<F>(
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus().0 as u64;
let modulus = cks.parameters.message_modulus.0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
let clear_1 = rng.gen::<u64>() % modulus;
@@ -113,16 +97,6 @@ fn bench_server_key_binary_function<F>(
binary_op(sks, &mut ct_0, &mut ct_1);
})
});
write_to_json(
&bench_id,
*param,
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
bench_group.finish()
@@ -131,11 +105,10 @@ fn bench_server_key_binary_function<F>(
fn bench_server_key_binary_scalar_function<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[ClassicPBSParameters],
params: &[Parameters],
) where
F: Fn(&ServerKey, &mut Ciphertext, u8),
F: Fn(&ServerKey, &mut CiphertextBig, u8),
{
let mut bench_group = c.benchmark_group(bench_name);
@@ -145,7 +118,7 @@ fn bench_server_key_binary_scalar_function<F>(
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus().0 as u64;
let modulus = cks.parameters.message_modulus.0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
let clear_1 = rng.gen::<u64>() % modulus;
@@ -158,16 +131,6 @@ fn bench_server_key_binary_scalar_function<F>(
binary_op(sks, &mut ct_0, clear_1 as u8);
})
});
write_to_json(
&bench_id,
*param,
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
bench_group.finish()
@@ -176,11 +139,10 @@ fn bench_server_key_binary_scalar_function<F>(
fn bench_server_key_binary_scalar_division_function<F>(
c: &mut Criterion,
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[ClassicPBSParameters],
params: &[Parameters],
) where
F: Fn(&ServerKey, &mut Ciphertext, u8),
F: Fn(&ServerKey, &mut CiphertextBig, u8),
{
let mut bench_group = c.benchmark_group(bench_name);
@@ -190,7 +152,7 @@ fn bench_server_key_binary_scalar_division_function<F>(
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus().0 as u64;
let modulus = cks.parameters.message_modulus.0 as u64;
assert_ne!(modulus, 1);
let clear_0 = rng.gen::<u64>() % modulus;
@@ -207,16 +169,6 @@ fn bench_server_key_binary_scalar_division_function<F>(
binary_op(sks, &mut ct_0, clear_1 as u8);
})
});
write_to_json(
&bench_id,
*param,
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
bench_group.finish()
@@ -231,7 +183,7 @@ fn carry_extract(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus().0 as u64;
let modulus = cks.parameters.message_modulus.0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
@@ -243,16 +195,6 @@ fn carry_extract(c: &mut Criterion) {
let _ = sks.carry_extract(&ct_0);
})
});
write_to_json(
&bench_id,
param,
param.name(),
"carry_extract",
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
bench_group.finish()
@@ -267,45 +209,32 @@ fn programmable_bootstrapping(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus().0 as u64;
let modulus = cks.parameters.message_modulus.0 as u64;
let acc = sks.generate_lookup_table(|x| x);
let acc = sks.generate_accumulator(|x| x);
let clear_0 = rng.gen::<u64>() % modulus;
let ctxt = cks.encrypt(clear_0);
let bench_id = format!("ServerKey::programmable_bootstrap::{}", param.name());
let id = format!("ServerKey::programmable_bootstrap::{}", param.name());
bench_group.bench_function(&bench_id, |b| {
bench_group.bench_function(&id, |b| {
b.iter(|| {
let _ = sks.apply_lookup_table(&ctxt, &acc);
})
});
write_to_json(
&bench_id,
param,
param.name(),
"pbs",
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
bench_group.finish();
}
// TODO: remove?
fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
fn bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("programmable_bootstrap");
let param = WOPBS_PARAM_MESSAGE_4_NORM2_6;
let param_set: ShortintParameterSet = param.try_into().unwrap();
let pbs_params = param_set.pbs_parameters().unwrap();
let keys = KEY_CACHE_WOPBS.get_from_param((pbs_params, param));
let keys = KEY_CACHE_WOPBS.get_from_param((param, param));
let (cks, _, wopbs_key) = (keys.client_key(), keys.server_key(), keys.wopbs_key());
let mut rng = rand::thread_rng();
@@ -326,12 +255,11 @@ 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) => {
($server_key_method:ident, $params:expr) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_unary_function(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs| {
let _ = server_key.$server_key_method(lhs);},
$params)
@@ -340,12 +268,11 @@ macro_rules! define_server_key_unary_bench_fn (
);
macro_rules! define_server_key_bench_fn (
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
($server_key_method:ident, $params:expr) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_function(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
let _ = server_key.$server_key_method(lhs, rhs);},
$params)
@@ -354,12 +281,11 @@ macro_rules! define_server_key_bench_fn (
);
macro_rules! define_server_key_scalar_bench_fn (
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
($server_key_method:ident, $params:expr) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_scalar_function(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
let _ = server_key.$server_key_method(lhs, rhs);},
$params)
@@ -368,12 +294,11 @@ macro_rules! define_server_key_scalar_bench_fn (
);
macro_rules! define_server_key_scalar_div_bench_fn (
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
($server_key_method:ident, $params:expr) => {
fn $server_key_method(c: &mut Criterion) {
bench_server_key_binary_scalar_division_function(
c,
concat!("ServerKey::", stringify!($server_key_method)),
stringify!($name),
|server_key, lhs, rhs| {
let _ = server_key.$server_key_method(lhs, rhs);},
$params)
@@ -381,255 +306,31 @@ 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
);
define_server_key_unary_bench_fn!(unchecked_neg, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(
method_name: unchecked_add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
);
define_server_key_bench_fn!(
method_name: unchecked_sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
);
define_server_key_bench_fn!(
method_name: unchecked_mul_lsb,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
);
define_server_key_bench_fn!(
method_name: unchecked_mul_msb,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: unchecked_div,
display_name: div,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
);
define_server_key_bench_fn!(
method_name: smart_bitand,
display_name: bitand,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: smart_bitor,
display_name: bitor,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: smart_bitxor,
display_name: bitxor,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: smart_add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: smart_sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: smart_mul_lsb,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: bitand,
display_name: bitand,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: bitor,
display_name: bitor,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: bitxor,
display_name: bitxor,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: mul,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: div,
display_name: div,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: greater,
display_name: greater,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: greater_or_equal,
display_name: greater_or_equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: less,
display_name: less,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: less_or_equal,
display_name: less_or_equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: equal,
display_name: equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: not_equal,
display_name: not_equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_unary_bench_fn!(
method_name: neg,
display_name: negation,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: unchecked_greater,
display_name: greater_than,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: unchecked_less,
display_name: less_than,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(
method_name: unchecked_equal,
display_name: equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_bench_fn!(unchecked_add, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
define_server_key_bench_fn!(unchecked_sub, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
define_server_key_bench_fn!(unchecked_mul_lsb, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
define_server_key_bench_fn!(unchecked_mul_msb, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(unchecked_div, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
define_server_key_bench_fn!(smart_bitand, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(smart_bitor, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(smart_bitxor, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(smart_add, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(smart_sub, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(smart_mul_lsb, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(unchecked_greater, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(unchecked_less, &SERVER_KEY_BENCH_PARAMS);
define_server_key_bench_fn!(unchecked_equal, &SERVER_KEY_BENCH_PARAMS);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_mul,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_left_shift,
display_name: left_shift,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: unchecked_scalar_right_shift,
display_name: right_shift,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(unchecked_scalar_add, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
define_server_key_scalar_bench_fn!(unchecked_scalar_sub, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
define_server_key_scalar_bench_fn!(unchecked_scalar_mul, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
define_server_key_scalar_bench_fn!(unchecked_scalar_left_shift, &SERVER_KEY_BENCH_PARAMS);
define_server_key_scalar_bench_fn!(unchecked_scalar_right_shift, &SERVER_KEY_BENCH_PARAMS);
define_server_key_scalar_div_bench_fn!(
method_name: unchecked_scalar_div,
display_name: div,
&SERVER_KEY_BENCH_PARAMS_EXTENDED
);
define_server_key_scalar_div_bench_fn!(
method_name: unchecked_scalar_mod,
display_name: modulo,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_add,
display_name: add,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_sub,
display_name: sub,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_mul,
display_name: mul,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_left_shift,
display_name: left_shift,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_right_shift,
display_name: right_shift,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_div_bench_fn!(
method_name: scalar_div,
display_name: div,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_div_bench_fn!(
method_name: scalar_mod,
display_name: modulo,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_greater,
display_name: greater,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_greater_or_equal,
display_name: greater_or_equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_less,
display_name: less,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_bench_fn!(
method_name: scalar_less_or_equal,
display_name: less_or_equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_div_bench_fn!(
method_name: scalar_equal,
display_name: equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_div_bench_fn!(
method_name: scalar_not_equal,
display_name: not_equal,
&SERVER_KEY_BENCH_PARAMS
);
define_server_key_scalar_div_bench_fn!(unchecked_scalar_div, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
define_server_key_scalar_div_bench_fn!(unchecked_scalar_mod, &SERVER_KEY_BENCH_PARAMS);
criterion_group!(
arithmetic_operation,
@@ -653,7 +354,7 @@ criterion_group!(
// multivalue_programmable_bootstrapping
//bench_two_block_pbs
//wopbs_v0_norm2_2,
//bench_wopbs_param_message_8_norm2_5,
bench_wopbs_param_message_8_norm2_5,
programmable_bootstrapping
);
@@ -668,44 +369,4 @@ criterion_group!(
unchecked_scalar_right_shift,
);
criterion_group!(
default_ops,
neg,
bitand,
bitor,
bitxor,
add,
sub,
div,
mul,
greater,
greater_or_equal,
less,
less_or_equal,
equal,
not_equal
);
criterion_group!(
default_scalar_ops,
scalar_add,
scalar_sub,
scalar_div,
scalar_mul,
scalar_mod,
scalar_left_shift,
scalar_right_shift,
scalar_greater,
scalar_greater_or_equal,
scalar_less,
scalar_less_or_equal,
scalar_equal,
scalar_not_equal
);
criterion_main!(
// arithmetic_operation,
// arithmetic_scalar_operation,
default_ops,
default_scalar_ops,
);
criterion_main!(arithmetic_operation, arithmetic_scalar_operation);

View File

@@ -1,188 +0,0 @@
use serde::Serialize;
use std::fs;
use std::path::PathBuf;
#[cfg(feature = "boolean")]
use tfhe::boolean::parameters::BooleanParameters;
use tfhe::core_crypto::prelude::*;
#[cfg(feature = "shortint")]
use tfhe::shortint::ClassicPBSParameters;
#[derive(Clone, Copy, Default, Serialize)]
pub struct CryptoParametersRecord {
pub lwe_dimension: Option<LweDimension>,
pub glwe_dimension: Option<GlweDimension>,
pub polynomial_size: Option<PolynomialSize>,
pub lwe_modular_std_dev: Option<StandardDev>,
pub glwe_modular_std_dev: Option<StandardDev>,
pub pbs_base_log: Option<DecompositionBaseLog>,
pub pbs_level: Option<DecompositionLevelCount>,
pub ks_base_log: Option<DecompositionBaseLog>,
pub ks_level: Option<DecompositionLevelCount>,
pub pfks_level: Option<DecompositionLevelCount>,
pub pfks_base_log: Option<DecompositionBaseLog>,
pub pfks_modular_std_dev: Option<StandardDev>,
pub cbs_level: Option<DecompositionLevelCount>,
pub cbs_base_log: Option<DecompositionBaseLog>,
pub message_modulus: Option<usize>,
pub carry_modulus: Option<usize>,
}
#[cfg(feature = "boolean")]
impl From<BooleanParameters> for CryptoParametersRecord {
fn from(params: BooleanParameters) -> Self {
CryptoParametersRecord {
lwe_dimension: Some(params.lwe_dimension),
glwe_dimension: Some(params.glwe_dimension),
polynomial_size: Some(params.polynomial_size),
lwe_modular_std_dev: Some(params.lwe_modular_std_dev),
glwe_modular_std_dev: Some(params.glwe_modular_std_dev),
pbs_base_log: Some(params.pbs_base_log),
pbs_level: Some(params.pbs_level),
ks_base_log: Some(params.ks_base_log),
ks_level: Some(params.ks_level),
pfks_level: None,
pfks_base_log: None,
pfks_modular_std_dev: None,
cbs_level: None,
cbs_base_log: None,
message_modulus: None,
carry_modulus: None,
}
}
}
#[cfg(feature = "shortint")]
impl From<ClassicPBSParameters> for CryptoParametersRecord {
fn from(params: ClassicPBSParameters) -> Self {
CryptoParametersRecord {
lwe_dimension: Some(params.lwe_dimension),
glwe_dimension: Some(params.glwe_dimension),
polynomial_size: Some(params.polynomial_size),
lwe_modular_std_dev: Some(params.lwe_modular_std_dev),
glwe_modular_std_dev: Some(params.glwe_modular_std_dev),
pbs_base_log: Some(params.pbs_base_log),
pbs_level: Some(params.pbs_level),
ks_base_log: Some(params.ks_base_log),
ks_level: Some(params.ks_level),
pfks_level: None,
pfks_base_log: None,
pfks_modular_std_dev: None,
cbs_level: None,
cbs_base_log: None,
message_modulus: Some(params.message_modulus.0),
carry_modulus: Some(params.carry_modulus.0),
}
}
}
#[derive(Serialize)]
enum PolynomialMultiplication {
Fft,
// Ntt,
}
#[derive(Serialize)]
enum IntegerRepresentation {
Radix,
// Crt,
// Hybrid,
}
#[derive(Serialize)]
enum ExecutionType {
Sequential,
Parallel,
}
#[derive(Serialize)]
enum KeySetType {
Single,
// Multi,
}
#[derive(Serialize)]
enum OperandType {
CipherText,
PlainText,
}
#[derive(Clone, Serialize)]
pub enum OperatorType {
Atomic,
// AtomicPattern,
}
#[derive(Serialize)]
struct BenchmarkParametersRecord {
display_name: String,
crypto_parameters_alias: String,
crypto_parameters: CryptoParametersRecord,
message_modulus: Option<usize>,
carry_modulus: Option<usize>,
ciphertext_modulus: usize,
bit_size: u32,
polynomial_multiplication: PolynomialMultiplication,
precision: u32,
error_probability: f64,
integer_representation: IntegerRepresentation,
decomposition_basis: Vec<u32>,
pbs_algorithm: Option<String>,
execution_type: ExecutionType,
key_set_type: KeySetType,
operand_type: OperandType,
operator_type: OperatorType,
}
/// Writes benchmarks parameters to disk in JSON format.
pub fn write_to_json<T: Into<CryptoParametersRecord>>(
bench_id: &str,
params: T,
params_alias: impl Into<String>,
display_name: impl Into<String>,
operator_type: &OperatorType,
bit_size: u32,
decomposition_basis: Vec<u32>,
) {
let params = params.into();
let execution_type = match bench_id.contains("parallelized") {
true => ExecutionType::Parallel,
false => ExecutionType::Sequential,
};
let operand_type = match bench_id.contains("scalar") {
true => OperandType::PlainText,
false => OperandType::CipherText,
};
let record = BenchmarkParametersRecord {
display_name: display_name.into(),
crypto_parameters_alias: params_alias.into(),
crypto_parameters: params.to_owned(),
message_modulus: params.message_modulus,
carry_modulus: params.carry_modulus,
ciphertext_modulus: 64,
bit_size,
polynomial_multiplication: PolynomialMultiplication::Fft,
precision: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
error_probability: 2f64.powf(-41.0),
integer_representation: IntegerRepresentation::Radix,
decomposition_basis,
pbs_algorithm: None, // To be added in future version
execution_type,
key_set_type: KeySetType::Single,
operand_type,
operator_type: operator_type.to_owned(),
};
let mut params_directory = ["benchmarks_parameters", bench_id]
.iter()
.collect::<PathBuf>();
fs::create_dir_all(&params_directory).unwrap();
params_directory.push("parameters.json");
fs::write(params_directory, serde_json::to_string(&record).unwrap()).unwrap();
}
// Empty main to please clippy.
#[allow(dead_code)]
pub fn main() {}

View File

@@ -1,12 +1,10 @@
// tfhe/build.rs
#[cfg(feature = "__c_api")]
fn gen_c_api() {
use std::env;
use std::path::PathBuf;
if std::env::var("_CBINDGEN_IS_RUNNING").is_ok() {
return;
}
/// Find the location of the `target/` directory. Note that this may be
/// overridden by `cmake`, so we also need to check the `CARGO_TARGET_DIR`
/// variable.
@@ -26,35 +24,7 @@ fn gen_c_api() {
.display()
.to_string();
let parse_expand_features_vec = vec![
#[cfg(feature = "__c_api")]
"__c_api",
#[cfg(feature = "boolean-c-api")]
"boolean-c-api",
#[cfg(feature = "shortint-c-api")]
"shortint-c-api",
#[cfg(feature = "high-level-c-api")]
"high-level-c-api",
#[cfg(feature = "boolean")]
"boolean",
#[cfg(feature = "shortint")]
"shortint",
#[cfg(feature = "integer")]
"integer",
];
let parse_expand_vec = if parse_expand_features_vec.is_empty() {
vec![]
} else {
vec![package_name.as_str()]
};
cbindgen::Builder::new()
.with_crate(crate_dir.clone())
.with_config(cbindgen::Config::from_root_or_default(crate_dir))
.with_parse_expand(&parse_expand_vec)
.with_parse_expand_features(&parse_expand_features_vec)
.generate()
cbindgen::generate(crate_dir)
.unwrap()
.write_to_file(output_file);
}

View File

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

View File

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

View File

@@ -1,123 +0,0 @@
#include <tfhe.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
int uint128_client_key(const ClientKey *client_key) {
int ok;
FheUint128 *lhs = NULL;
FheUint128 *rhs = NULL;
FheUint128 *result = NULL;
U128 lhs_clear = {10, 20};
U128 rhs_clear = {1, 2};
U128 result_clear = {0};
ok = fhe_uint128_try_encrypt_with_client_key_u128(lhs_clear, client_key, &lhs);
assert(ok == 0);
ok = fhe_uint128_try_encrypt_with_client_key_u128(rhs_clear, client_key, &rhs);
assert(ok == 0);
ok = fhe_uint128_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint128_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 9);
assert(result_clear.w1 == 18);
fhe_uint128_destroy(lhs);
fhe_uint128_destroy(rhs);
fhe_uint128_destroy(result);
return ok;
}
int uint128_encrypt_trivial(const ClientKey *client_key) {
int ok;
FheUint128 *lhs = NULL;
FheUint128 *rhs = NULL;
FheUint128 *result = NULL;
U128 lhs_clear = {10, 20};
U128 rhs_clear = {1, 2};
U128 result_clear = {0};
ok = fhe_uint128_try_encrypt_trivial_u128(lhs_clear, &lhs);
assert(ok == 0);
ok = fhe_uint128_try_encrypt_trivial_u128(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_uint128_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint128_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 9);
assert(result_clear.w1 == 18);
fhe_uint128_destroy(lhs);
fhe_uint128_destroy(rhs);
fhe_uint128_destroy(result);
return ok;
}
int uint128_public_key(const ClientKey *client_key, const PublicKey *public_key) {
int ok;
FheUint128 *lhs = NULL;
FheUint128 *rhs = NULL;
FheUint128 *result = NULL;
U128 lhs_clear = {10, 20};
U128 rhs_clear = {1, 2};
U128 result_clear = {0};
ok = fhe_uint128_try_encrypt_with_public_key_u128(lhs_clear, public_key, &lhs);
assert(ok == 0);
ok = fhe_uint128_try_encrypt_with_public_key_u128(rhs_clear, public_key, &rhs);
assert(ok == 0);
ok = fhe_uint128_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint128_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 11);
assert(result_clear.w1 == 22);
fhe_uint128_destroy(lhs);
fhe_uint128_destroy(rhs);
fhe_uint128_destroy(result);
return ok;
}
int main(void) {
int ok = 0;
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_integers_small(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
ServerKey *server_key = NULL;
PublicKey *public_key = NULL;
generate_keys(config, &client_key, &server_key);
public_key_new(client_key, &public_key);
set_server_key(server_key);
uint128_client_key(client_key);
uint128_encrypt_trivial(client_key);
uint128_public_key(client_key, public_key);
client_key_destroy(client_key);
public_key_destroy(public_key);
server_key_destroy(server_key);
return ok;
}

View File

@@ -1,139 +0,0 @@
#include <tfhe.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
int uint256_client_key(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
FheUint64 *cast_result = NULL;
U256 lhs_clear = {1 , 2, 3, 4};
U256 rhs_clear = {5, 6, 7, 8};
U256 result_clear = { 0 };
ok = fhe_uint256_try_encrypt_with_client_key_u256(lhs_clear, client_key, &lhs);
assert(ok == 0);
ok = fhe_uint256_try_encrypt_with_client_key_u256(rhs_clear, client_key, &rhs);
assert(ok == 0);
ok = fhe_uint256_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 6);
assert(result_clear.w1 == 8);
assert(result_clear.w2 == 10);
assert(result_clear.w3 == 12);
// try some casting
ok = fhe_uint256_cast_into_fhe_uint64(result, &cast_result);
assert(ok == 0);
uint64_t u64_clear;
ok = fhe_uint64_decrypt(cast_result, client_key, &u64_clear);
assert(ok == 0);
assert(u64_clear == 6);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
fhe_uint64_destroy(cast_result);
return ok;
}
int uint256_encrypt_trivial(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
U256 lhs_clear = {1 , 2, 3, 4};
U256 rhs_clear = {5, 6, 7, 8};
U256 result_clear = { 0 };
ok = fhe_uint256_try_encrypt_trivial_u256(lhs_clear, &lhs);
assert(ok == 0);
ok = fhe_uint256_try_encrypt_trivial_u256(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_uint256_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 6);
assert(result_clear.w1 == 8);
assert(result_clear.w2 == 10);
assert(result_clear.w3 == 12);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
return ok;
}
int uint256_public_key(const ClientKey *client_key, const PublicKey *public_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
U256 lhs_clear = {5, 6, 7, 8};
U256 rhs_clear = {1 , 2, 3, 4};
U256 result_clear = { 0 };
ok = fhe_uint256_try_encrypt_with_public_key_u256(lhs_clear, public_key, &lhs);
assert(ok == 0);
ok = fhe_uint256_try_encrypt_with_public_key_u256(rhs_clear, public_key, &rhs);
assert(ok == 0);
ok = fhe_uint256_sub(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
assert(result_clear.w0 == 4);
assert(result_clear.w1 == 4);
assert(result_clear.w2 == 4);
assert(result_clear.w3 == 4);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
return ok;
}
int main(void) {
int ok = 0;
ConfigBuilder *builder;
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_integers_small(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
ServerKey *server_key = NULL;
PublicKey *public_key = NULL;
generate_keys(config, &client_key, &server_key);
public_key_new(client_key, &public_key);
set_server_key(server_key);
uint256_client_key(client_key);
uint256_encrypt_trivial(client_key);
uint256_public_key(client_key, public_key);
client_key_destroy(client_key);
public_key_destroy(public_key);
server_key_destroy(server_key);
return ok;
}

View File

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

View File

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

View File

@@ -1,213 +0,0 @@
#include <tfhe.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
int uint8_client_key(const ClientKey *client_key) {
int ok;
FheUint8 *lhs = NULL;
FheUint8 *rhs = NULL;
FheUint8 *result = NULL;
uint8_t lhs_clear = 123;
uint8_t rhs_clear = 14;
ok = fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, client_key, &lhs);
assert(ok == 0);
ok = fhe_uint8_try_encrypt_with_client_key_u8(rhs_clear, client_key, &rhs);
assert(ok == 0);
ok = fhe_uint8_add(lhs, rhs, &result);
assert(ok == 0);
uint8_t clear;
ok = fhe_uint8_decrypt(result, client_key, &clear);
assert(ok == 0);
assert(clear == (lhs_clear + rhs_clear));
fhe_uint8_destroy(lhs);
fhe_uint8_destroy(rhs);
fhe_uint8_destroy(result);
return ok;
}
int uint8_public_key(const ClientKey *client_key, const PublicKey *public_key) {
int ok;
FheUint8 *lhs = NULL;
FheUint8 *rhs = NULL;
FheUint8 *result = NULL;
uint8_t lhs_clear = 123;
uint8_t rhs_clear = 14;
ok = fhe_uint8_try_encrypt_with_public_key_u8(lhs_clear, public_key, &lhs);
assert(ok == 0);
ok = fhe_uint8_try_encrypt_with_public_key_u8(rhs_clear, public_key, &rhs);
assert(ok == 0);
ok = fhe_uint8_sub(lhs, rhs, &result);
assert(ok == 0);
uint8_t clear;
ok = fhe_uint8_decrypt(result, client_key, &clear);
assert(ok == 0);
assert(clear == (lhs_clear - rhs_clear));
fhe_uint8_destroy(lhs);
fhe_uint8_destroy(rhs);
fhe_uint8_destroy(result);
return ok;
}
int uint8_serialization(const ClientKey *client_key) {
int ok;
FheUint8 *lhs = NULL;
FheUint8 *deserialized_lhs = NULL;
FheUint8 *result = NULL;
Buffer value_buffer = {.pointer = NULL, .length = 0};
Buffer cks_buffer = {.pointer = NULL, .length = 0};
BufferView deser_view = {.pointer = NULL, .length = 0};
ClientKey *deserialized_client_key = NULL;
uint8_t lhs_clear = 123;
ok = client_key_serialize(client_key, &cks_buffer);
assert(ok == 0);
deser_view.pointer = cks_buffer.pointer;
deser_view.length = cks_buffer.length;
ok = client_key_deserialize(deser_view, &deserialized_client_key);
assert(ok == 0);
ok = fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, deserialized_client_key, &lhs);
assert(ok == 0);
ok = fhe_uint8_serialize(lhs, &value_buffer);
assert(ok == 0);
deser_view.pointer = value_buffer.pointer;
deser_view.length = value_buffer.length;
ok = fhe_uint8_deserialize(deser_view, &deserialized_lhs);
assert(ok == 0);
uint8_t clear;
ok = fhe_uint8_decrypt(deserialized_lhs, deserialized_client_key, &clear);
assert(ok == 0);
assert(clear == lhs_clear);
if (value_buffer.pointer != NULL) {
destroy_buffer(&value_buffer);
}
fhe_uint8_destroy(lhs);
fhe_uint8_destroy(deserialized_lhs);
fhe_uint8_destroy(result);
return ok;
}
int uint8_compressed(const ClientKey *client_key) {
int ok;
FheUint8 *lhs = NULL;
FheUint8 *result = NULL;
CompressedFheUint8 *clhs = NULL;
uint8_t lhs_clear = 123;
ok = compressed_fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, client_key, &clhs);
assert(ok == 0);
ok = compressed_fhe_uint8_decompress(clhs, &lhs);
assert(ok == 0);
uint8_t clear;
ok = fhe_uint8_decrypt(lhs, client_key, &clear);
assert(ok == 0);
assert(clear == lhs_clear);
fhe_uint8_destroy(lhs);
compressed_fhe_uint8_destroy(clhs);
fhe_uint8_destroy(result);
return ok;
}
int main(void) {
int ok = 0;
{
ConfigBuilder *builder;
Config *config;
ok = config_builder_all_disabled(&builder);
assert(ok == 0);
ok = config_builder_enable_default_integers(&builder);
assert(ok == 0);
ok = config_builder_build(builder, &config);
assert(ok == 0);
ClientKey *client_key = NULL;
ServerKey *server_key = NULL;
PublicKey *public_key = NULL;
ok = generate_keys(config, &client_key, &server_key);
assert(ok == 0);
ok = public_key_new(client_key, &public_key);
assert(ok == 0);
ok = uint8_serialization(client_key);
assert(ok == 0);
ok = uint8_compressed(client_key);
assert(ok == 0);
ok = set_server_key(server_key);
assert(ok == 0);
ok = uint8_client_key(client_key);
assert(ok == 0);
ok = uint8_public_key(client_key, public_key);
assert(ok == 0);
client_key_destroy(client_key);
public_key_destroy(public_key);
server_key_destroy(server_key);
}
{
ConfigBuilder *builder;
Config *config;
ok = config_builder_all_disabled(&builder);
assert(ok == 0);
ok = config_builder_enable_default_integers_small(&builder);
assert(ok == 0);
ok = config_builder_build(builder, &config);
assert(ok == 0);
ClientKey *client_key = NULL;
ServerKey *server_key = NULL;
PublicKey *public_key = NULL;
ok = generate_keys(config, &client_key, &server_key);
assert(ok == 0);
ok = public_key_new(client_key, &public_key);
assert(ok == 0);
ok = set_server_key(server_key);
assert(ok == 0);
ok = uint8_client_key(client_key);
assert(ok == 0);
ok = uint8_public_key(client_key, public_key);
assert(ok == 0);
client_key_destroy(client_key);
public_key_destroy(public_key);
server_key_destroy(server_key);
}
return ok;
}

View File

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

View File

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

View File

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

View File

@@ -47,7 +47,7 @@ void test_shortint_unary_op(const ShortintClientKey *cks, const ShortintServerKe
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, in, &ct_in);
int encrypt_left_ok = shortint_client_key_encrypt_small(cks_in_use, in, &ct_in);
assert(encrypt_left_ok == 0);
}
@@ -61,8 +61,8 @@ void test_shortint_unary_op(const ShortintClientKey *cks, const ShortintServerKe
assert(decrypted_result == expected);
shortint_destroy_ciphertext(ct_in);
shortint_destroy_ciphertext(ct_result);
destroy_shortint_ciphertext(ct_in);
destroy_shortint_ciphertext(ct_result);
}
}
}
@@ -95,7 +95,7 @@ void test_shortint_unary_op_assign(const ShortintClientKey *cks, const ShortintS
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, in, &ct_in_and_result);
int encrypt_left_ok = shortint_client_key_encrypt_small(cks_in_use, in, &ct_in_and_result);
assert(encrypt_left_ok == 0);
}
@@ -109,7 +109,7 @@ void test_shortint_unary_op_assign(const ShortintClientKey *cks, const ShortintS
assert(decrypted_result == expected);
shortint_destroy_ciphertext(ct_in_and_result);
destroy_shortint_ciphertext(ct_in_and_result);
}
}
}
@@ -148,10 +148,10 @@ void test_shortint_binary_op(const ShortintClientKey *cks, const ShortintServerK
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left);
int encrypt_left_ok = shortint_client_key_encrypt_small(cks_in_use, left, &ct_left);
assert(encrypt_left_ok == 0);
int encrypt_right_ok = shortint_client_key_encrypt(cks_in_use, right, &ct_right);
int encrypt_right_ok = shortint_client_key_encrypt_small(cks_in_use, right, &ct_right);
assert(encrypt_right_ok == 0);
}
@@ -165,9 +165,9 @@ void test_shortint_binary_op(const ShortintClientKey *cks, const ShortintServerK
assert(decrypted_result == expected);
shortint_destroy_ciphertext(ct_left);
shortint_destroy_ciphertext(ct_right);
shortint_destroy_ciphertext(ct_result);
destroy_shortint_ciphertext(ct_left);
destroy_shortint_ciphertext(ct_right);
destroy_shortint_ciphertext(ct_result);
}
}
}
@@ -209,10 +209,10 @@ void test_shortint_binary_op_assign(const ShortintClientKey *cks, const Shortint
sks_in_use = sks_small;
int encrypt_left_ok =
shortint_client_key_encrypt(cks_in_use, left, &ct_left_and_result);
shortint_client_key_encrypt_small(cks_in_use, left, &ct_left_and_result);
assert(encrypt_left_ok == 0);
int encrypt_right_ok = shortint_client_key_encrypt(cks_in_use, right, &ct_right);
int encrypt_right_ok = shortint_client_key_encrypt_small(cks_in_use, right, &ct_right);
assert(encrypt_right_ok == 0);
}
@@ -227,8 +227,8 @@ void test_shortint_binary_op_assign(const ShortintClientKey *cks, const Shortint
assert(decrypted_result == expected);
shortint_destroy_ciphertext(ct_left_and_result);
shortint_destroy_ciphertext(ct_right);
destroy_shortint_ciphertext(ct_left_and_result);
destroy_shortint_ciphertext(ct_right);
}
}
}
@@ -280,7 +280,7 @@ void test_shortint_binary_scalar_op(
cks_in_use = cks_small;
sks_in_use = sks_small;
int encrypt_left_ok = shortint_client_key_encrypt(cks_in_use, left, &ct_left);
int encrypt_left_ok = shortint_client_key_encrypt_small(cks_in_use, left, &ct_left);
assert(encrypt_left_ok == 0);
}
@@ -294,8 +294,8 @@ void test_shortint_binary_scalar_op(
assert(decrypted_result == expected);
shortint_destroy_ciphertext(ct_left);
shortint_destroy_ciphertext(ct_result);
destroy_shortint_ciphertext(ct_left);
destroy_shortint_ciphertext(ct_result);
}
}
}
@@ -347,7 +347,7 @@ void test_shortint_binary_scalar_op_assign(
sks_in_use = sks_small;
int encrypt_left_ok =
shortint_client_key_encrypt(cks_in_use, left, &ct_left_and_result);
shortint_client_key_encrypt_small(cks_in_use, left, &ct_left_and_result);
assert(encrypt_left_ok == 0);
}
@@ -362,7 +362,7 @@ void test_shortint_binary_scalar_op_assign(
assert(decrypted_result == expected);
shortint_destroy_ciphertext(ct_left_and_result);
destroy_shortint_ciphertext(ct_left_and_result);
}
}
}
@@ -419,10 +419,10 @@ void test_server_key(void) {
ShortintCompressedServerKey *deser_csks = NULL;
Buffer sks_ser_buffer = {.pointer = NULL, .length = 0};
ShortintServerKey *deser_sks = NULL;
ShortintParameters *params = NULL;
ShortintClientKey *cks_small = NULL;
ShortintServerKey *sks_small = NULL;
ShortintPBSParameters params = { 0 };
ShortintPBSParameters params_small = { 0 };
ShortintParameters *params_small = NULL;
const uint32_t message_bits = 2;
const uint32_t carry_bits = 2;
@@ -430,7 +430,7 @@ void test_server_key(void) {
int get_params_ok = shortint_get_parameters(message_bits, carry_bits, &params);
assert(get_params_ok == 0);
int get_params_small_ok = shortint_get_parameters_small(message_bits, carry_bits, &params_small);
int get_params_small_ok = shortint_get_parameters(message_bits, carry_bits, &params_small);
assert(get_params_small_ok == 0);
int gen_cks_ok = shortint_gen_client_key(params, &cks);
@@ -728,14 +728,16 @@ void test_server_key(void) {
deser_cks, deser_sks, cks_small, sks_small, message_bits, carry_bits, scalar_mod,
shortint_server_key_unchecked_scalar_mod_assign, forbidden_scalar_mod_values, 1);
shortint_destroy_client_key(cks);
shortint_destroy_client_key(cks_small);
shortint_destroy_compressed_server_key(csks);
shortint_destroy_server_key(sks);
shortint_destroy_server_key(sks_small);
shortint_destroy_client_key(deser_cks);
shortint_destroy_compressed_server_key(deser_csks);
shortint_destroy_server_key(deser_sks);
destroy_shortint_client_key(cks);
destroy_shortint_client_key(cks_small);
destroy_shortint_compressed_server_key(csks);
destroy_shortint_server_key(sks);
destroy_shortint_server_key(sks_small);
destroy_shortint_client_key(deser_cks);
destroy_shortint_compressed_server_key(deser_csks);
destroy_shortint_server_key(deser_sks);
destroy_shortint_parameters(params);
destroy_shortint_parameters(params_small);
destroy_buffer(&cks_ser_buffer);
destroy_buffer(&csks_ser_buffer);
destroy_buffer(&sks_ser_buffer);

View File

@@ -107,6 +107,7 @@ allow_static_const = true
allow_constexpr = false
sort_by = "Name"
[macro_expansion]
bitflags = false

View File

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

View File

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

View File

@@ -1,19 +1,19 @@
# Tutorial
This library is meant to be used both on the **server side** and the **client side**. The typical use case should follow the subsequent steps:
This library is meant to be used both on the **server side** and on the **client side**. The typical use case should follow the subsequent steps:
1. On the **client side**, generate the `client` and `server keys`.
2. Send the `server key` to the **server**.
3. Then any number of times:
* On the **client side**, _encrypt_ the input data with the `client key`.
* On the **client side**, _encryption_ of the input data with the `client key`.
* Transmit the encrypted input to the **server**.
* On the **server side**, perform _homomorphic computation_ with the `server key`.
* On the **server side**, _homomorphic computation_ with the `server key`.
* Transmit the encrypted output to the **client**.
* On the **client side**, _decrypt_ the output data with the `client key`.
* On the **client side**, _decryption_ of the output data with the `client key`.
## Setup
In the first step, the client creates two keys, the `client key` and the `server key`, with the `concrete_boolean::gen_keys` function:
In the first step, the client creates two keys: the `client key` and the `server key`, with the `concrete_boolean::gen_keys` function:
```rust
use tfhe::boolean::prelude::*;
@@ -26,10 +26,10 @@ fn main() {
}
```
* The `client_key` is of type `ClientKey`. It is **secret** and must **never** be transmitted. This key will only be used to encrypt and decrypt data.
* The `server_key` is of type `ServerKey`. It is a **public key** and can be shared with any party. This key has to be sent to the server because it is required for homomorphic computation.
* The `client_key` is of type `ClientKey`. It is **secret**, and must **never** be transmitted. This key will only be used to encrypt and decrypt data.
* The `server_key` is of type `ServerKey`. It is a **public key**, and can be shared with any party. This key has to be sent to the server because it is required for homomorphic computation.
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits. This way you can use any compatible serializer to store/send the data. To store the `server_key` in a binary file, you can use the `bincode` library:
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits. This way you can use any compatible serializer to store/send the data. For instance, to store the `server_key` in a binary file, you can use the `bincode` library:
```rust
use std::fs::File;
@@ -72,9 +72,9 @@ fn main() {
}
```
## Encrypting inputs
## Encrypting Inputs
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be employed:
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client simply needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be employed:
```rust
use tfhe::boolean::prelude::*;
@@ -99,7 +99,7 @@ fn main() {
}
```
## Encrypting inputs using a public key
## Encrypting Inputs using a public key
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client simply needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be utilized:

View File

@@ -1,12 +1,12 @@
# What is TFHE-rs?
📁 [Github](https://github.com/zama-ai/tfhe-rs) | 💛 [Community support](https://zama.ai/community) | 🟨 [Zama Bounty Program](https://github.com/zama-ai/bounty-program)
<mark style="background-color:yellow;">⭐️</mark> [<mark style="background-color:yellow;">Star the repo on Github</mark>](https://github.com/zama-ai/tfhe-rs) <mark style="background-color:yellow;">| 🗣</mark> [<mark style="background-color:yellow;">Community support forum</mark> ](https://community.zama.ai)<mark style="background-color:yellow;">| 📁</mark> [<mark style="background-color:yellow;">Contribute to the project</mark>](https://docs.zama.ai/tfhe-rs/developers/contributing)
![](\_static/tfhe-rs-doc-home.png)
TFHE-rs is a pure Rust implementation of TFHE for Boolean and integer arithmetics over encrypted data. It includes a Rust and C API, as well as a client-side WASM API.
TFHE-rs is a pure Rust implementation of TFHE for boolean and small integer arithmetics over encrypted data. It includes a Rust and C API, as well as a client-side WASM API.
TFHE-rs is meant for developers and researchers who want full control over what they can do with TFHE, while not worrying about the low level implementation.
TFHE-rs is meant for developers and researchers who want full control over what they can do with TFHE, while not having to worry about the low level implementation.
The goal is to have a stable, simple, high-performance, and production-ready library for all the advanced features of TFHE.

View File

@@ -3,17 +3,13 @@
* [What is TFHE-rs?](README.md)
## Getting Started
* [Installation](getting\_started/installation.md)
* [Quick Start](getting\_started/quick\_start.md)
* [Supported Operations](getting\_started/operations.md)
* [Benchmarks](getting\_started/benchmarks.md)
* [Security and Cryptography](getting\_started/security\_and\_cryptography.md)
## High Level API
* [Tutorial](high_level_api/tutorial.md)
* [Operations](high_level_api/operations.md)
* [Serialization/Deserialization](high_level_api/serialization.md)
## Boolean
* [Tutorial](Boolean/tutorial.md)
* [Operations](Boolean/operations.md)
@@ -27,20 +23,10 @@
* [Serialization/Deserialization](shortint/serialization.md)
## Integer
* [Tutorial](integer/tutorial.md)
* [Operations](integer/operations.md)
* [Cryptographic Parameters](integer/parameters.md)
* [Serialization/Deserialization](integer/serialization.md)
## Tutorials for real-life applications
* [Dark Market](tutorial/dark_market.md)
* [SHA256](tutorial/sha256_bool.md)
* [Homomorphic Regular Expressions](tutorial/regex/tutorial.md)
* [Summary](integer/SUMMARY.md)
## C API
* [High-Level API](c_api/high-level-api.md)
* [Shortint API](c_api/shortint-api.md)
* [Tutorial](c_api/tutorial.md)
## JS on WASM API
* [Tutorial](js_on_wasm_api/tutorial.md)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -1,143 +0,0 @@
# High-Level API
\#Using the High-level C API
This library exposes a C binding to the high-level TFHE-rs primitives to implement _Fully Homomorphic Encryption_ (FHE) programs.
## First steps using TFHE-rs C API
### Setting-up TFHE-rs C API for use in a C program.
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo +nightly build --release --features=x86_64-unix,high-level-c-api -p tfhe
```
or on a Unix aarch64 machine using the following command:
```shell
RUSTFLAGS="-C target-cpu=native" cargo build +nightly --release --features=aarch64-unix,high-level-c-api -p tfhe
```
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO\_ROOT}/target/release/"
The build system needs to be set up so that the C or C++ program links against TFHE-rs C API binaries.
Here is a minimal CMakeLists.txt to do just that:
```cmake
project(my-project)
cmake_minimum_required(VERSION 3.16)
set(TFHE_C_API "/path/to/tfhe-rs/binaries/and/header")
include_directories(${TFHE_C_API})
add_library(tfhe STATIC IMPORTED)
set_target_properties(tfhe PROPERTIES IMPORTED_LOCATION ${TFHE_C_API}/libtfhe.a)
if(APPLE)
find_library(SECURITY_FRAMEWORK Security)
if (NOT SECURITY_FRAMEWORK)
message(FATAL_ERROR "Security framework not found")
endif()
endif()
set(EXECUTABLE_NAME my-executable)
add_executable(${EXECUTABLE_NAME} main.c)
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC tfhe m pthread dl)
if(APPLE)
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC ${SECURITY_FRAMEWORK})
endif()
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
```
### Commented code of a uint128 subtraction using `TFHE-rs C API`.
{% hint style="warning" %}
WARNING: The following example does not have proper memory management in the error case to make it easier to fit the code on this page.
{% endhint %}
To run the example below, the above CMakeLists.txt and main.c files need to be in the same directory. The commands to run are:
```shell
# /!\ Be sure to update CMakeLists.txt to give the absolute path to the compiled tfhe library
$ ls
CMakeLists.txt main.c
$ mkdir build && cd build
$ cmake .. -DCMAKE_BUILD_TYPE=RELEASE
...
$ make
...
$ ./my-executable
Result: 2
$
```
```c
#include <tfhe.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
int main(void)
{
int ok = 0;
// Prepare the config builder for the high level API and choose which types to enable
ConfigBuilder *builder;
Config *config;
// Put the builder in a default state without any types enabled
config_builder_all_disabled(&builder);
// Enable the uint128 type using the small LWE key for encryption
config_builder_enable_default_uint128_small(&builder);
// Populate the config
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
ServerKey *server_key = NULL;
// Generate the keys using the config
generate_keys(config, &client_key, &server_key);
// Set the server key for the current thread
set_server_key(server_key);
FheUint128 *lhs = NULL;
FheUint128 *rhs = NULL;
FheUint128 *result = NULL;
// Encrypt a u128 using 64 bits words, we encrypt 20 << 64 | 10
ok = fhe_uint128_try_encrypt_with_client_key_u128(10, 20, client_key, &lhs);
assert(ok == 0);
// Encrypt a u128 using words, we encrypt 2 << 64 | 1
ok = fhe_uint128_try_encrypt_with_client_key_u128(1, 2, client_key, &rhs);
assert(ok == 0);
// Compute the subtraction
ok = fhe_uint128_sub(lhs, rhs, &result);
assert(ok == 0);
uint64_t w0, w1;
// Decrypt
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
assert(ok == 0);
// Here the subtraction allows us to compare each word
assert(w0 == 9);
assert(w1 == 18);
// Destroy the ciphertexts
fhe_uint128_destroy(lhs);
fhe_uint128_destroy(rhs);
fhe_uint128_destroy(result);
// Destroy the keys
client_key_destroy(client_key);
server_key_destroy(server_key);
return EXIT_SUCCESS;
}
```

View File

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

View File

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

View File

@@ -2,41 +2,42 @@
## Using the `core_crypto` primitives
Welcome to this tutorial about TFHE-rs `core_crypto` module.
Welcome to this tutorial about TFHE-rs `core_crypto` module!
### Setting up TFHE-rs to use the `core_crypto` module
### Setting-up TFHE-rs to use the `core_crypto` module
To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
```toml
tfhe = { version = "0.3.0", features = [ "x86_64-unix" ] }
tfhe = { version = "0.2.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.
Here, this enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is actived as a fallback if no hardware number generator is available, like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available. To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically\_secure\_pseudorandom\_number\_generator) used in TFHE-rs.
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. Note that `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs.
In short: For x86\_64-based machines running Unix-like OSes:
In short:
For x86_64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
tfhe = { version = "0.2.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.2.0", features = ["aarch64-unix"] }
```
For x86\_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
For x86_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
```toml
tfhe = { version = "0.3.0", features = ["x86_64"] }
tfhe = { version = "0.2.0", features = ["x86_64"] }
```
### Commented code to double a 2-bits message in a leveled fashion and using a PBS with the `core_crypto` module.
### Commented code to double a 2 bits message in a leveled fashion and using a PBS with the `core_crypto` module.
As a complete example showing the usage of some common primitives of the `core_crypto` APIs, the following Rust code homomorphically computes 2 \* 3 using two different methods. First using a cleartext multiplication and then using a PBS.
As a complete example showing the usage of some common primitives of the `core_crypto` APIs, the following Rust code homomorphically computes 2 * 3 using two different methods. First using a cleartext multiplication and second using a PBS.
```rust
use tfhe::core_crypto::prelude::*;
@@ -89,7 +90,6 @@ pub fn main() {
pbs_base_log,
pbs_level,
glwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
@@ -169,7 +169,6 @@ pub fn main() {
polynomial_size: PolynomialSize,
glwe_size: GlweSize,
message_modulus: usize,
ciphertext_modulus: CiphertextModulus<u64>,
delta: u64,
f: F,
) -> GlweCiphertextOwned<u64>
@@ -205,11 +204,7 @@ pub fn main() {
let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
let accumulator =
allocate_and_trivially_encrypt_new_glwe_ciphertext(
glwe_size,
&accumulator_plaintext,
ciphertext_modulus,
);
allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &accumulator_plaintext);
accumulator
}
@@ -219,7 +214,6 @@ pub fn main() {
polynomial_size,
glwe_dimension.to_glwe_size(),
message_modulus as usize,
ciphertext_modulus,
delta,
|x: u64| 2 * x,
);

View File

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

View File

@@ -1,16 +1,16 @@
# Benchmarks
Due to their nature, homomorphic operations are naturally slower than their clear equivalent. Some timings are exposed for basic operations. For completeness, benchmarks for other libraries are also given.
Due to their nature, homomorphic operations are obviously slower than their clear equivalent. In what follows, some timings are exposed for basic operations. For completeness, some benchmarks of other libraries are also given.
All 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.
All the benchmarks had been launched on an AWS m6i.metal with the following specifications: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
## Boolean
This measures the execution time of a single binary Boolean gate.
This measures the execution time of a single binary boolean gate.
### tfhe-rs::boolean.
### tfhe.rs::boolean.
| Parameter set | Concrete FFT | Concrete FFT + avx512 |
| Parameter set | concrete-fft | concrete-fft + avx512 |
| --------------------- | ------------ | --------------------- |
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
@@ -28,41 +28,17 @@ This measures the execution time of a single binary Boolean gate.
| STD\_128 | 172ms | 78ms |
| MEDIUM | 113ms | 50.2ms |
## Shortint
This measures the execution time for some operations and some parameter sets of tfhe-rs::shortint.
This uses the Concrete FFT + avx512 configuration.
This measures the execution time for some operations and some parameter sets of shortint.
### tfhe.rs::shortint.
This uses the concrete-fft + avx512 configuration.
| Parameter set | unchecked\_add | unchecked\_mul\_lsb | keyswitch\_programmable\_bootstrap |
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
| PARAM\_MESSAGE\_1\_CARRY\_1 | 338 ns | 8.3 ms | 8.1 ms |
| PARAM\_MESSAGE\_2\_CARRY\_2 | 406 ns | 18.4 ms | 18.4 ms |
| PARAM\_MESSAGE\_3\_CARRY\_3 | 3.06 µs | 134 ms | 129.4 ms |
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 828.1 ms |
Next, the timings for the operation flavor `default` are given. This flavor ensures predictable timings of an operation all along the circuit by clearing the carry space after each operation.
| Parameter set | add | mul\_lsb | keyswitch\_programmable\_bootstrap |
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
| PARAM\_MESSAGE\_1\_CARRY\_1 | 7.90 ms | 8.00 ms | 8.10 ms |
| PARAM\_MESSAGE\_2\_CARRY\_2 | 18.4 ms | 18.1 ms | 18.4 ms |
| PARAM\_MESSAGE\_3\_CARRY\_3 | 131.5 ms | 129.5 ms | 129.4 ms |
| PARAM\_MESSAGE\_4\_CARRY\_4 | 852.5 ms | 839.7 ms | 828.1 ms |
## Integer
This measures the execution time for some operation sets of tfhe-rs::integer.
All timings are related to parallelized Radix-based integer operations, where each block is encrypted using PARAM\_MESSAGE\_2\_CARRY\_2.
To ensure predictable timings, the operation flavor is the `default` one: a carry propagation is computed after each operation. Operation cost could be reduced by using `unchecked`, `checked`, or `smart`.
| Plaintext size | add | mul | greater\_than (gt) | min |
| -------------------| ---------------| --------------------| ---------------------| -------------|
| 8 bits | 129.0 ms | 227 ms | 111.9 ms | 186.8 ms |
| 16 bits | 195 ms | 369 ms | 145.3 ms | 233.1 ms |
| 32 bits | 238 ms | 519 ms | 192.0 ms | 282.9 ms |
| 40 bits | 283 ms | 754 ms | 228.4 ms | 318.6 ms |
| 64 bits | 297 ms | 1.18 s | 249.0 ms | 336.5 ms |
| 128 bits | 424 ms | 3.13 s | 294.7 ms | 398.6 ms |
| 256 bits | 500 ms | 11 s | 361.8 ms | 509.1 ms |
| PARAM\_MESSAGE\_3\_CARRY\_3 | 3.06 µs | 134 ms | 134 ms |
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 945 ms |

View File

@@ -5,13 +5,9 @@
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.2.0", features = [ "boolean", "shortint", "x86_64-unix" ] }
```
{% hint style="info" %}
When running code that uses `tfhe-rs`, it is highly recommended to run in release mode with cargo's `--release` flag to have the best performances possible, eg: `cargo run --release`.
{% endhint %}
## Choosing your features
`TFHE-rs` exposes different `cargo features` to customize the types and features used.
@@ -20,17 +16,16 @@ When running code that uses `tfhe-rs`, it is highly recommended to run in releas
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 unsigned integers |
### Serialization.
The different data types and keys exposed by the crate can be serialized / deserialized.
More information can be found [here](../Boolean/serialization.md) for Boolean and [here](../shortint/serialization.md) for shortint.
More information can be found [here](../Boolean/serialization.md) for boolean and [here](../shortint/serialization.md) for shortint.
## Supported platforms
@@ -48,7 +43,7 @@ Users who have ARM devices can use `TFHE-rs` by compiling using the `nightly` to
### Using TFHE-rs with nightly toolchain.
Install the needed Rust toolchain:
First, install the needed Rust toolchain:
```shell
rustup toolchain install nightly
@@ -58,6 +53,8 @@ Then, you can either:
* Manually specify the toolchain to use in each of the cargo commands:
For example:
```shell
cargo +nightly build
cargo +nightly test

View File

@@ -16,7 +16,7 @@ The list of supported operations by the homomorphic Booleans is:
A walk-through using homomorphic Booleans can be found [here](../Boolean/tutorial.md).
## Shortint
## ShortInt
In TFHE-rs, shortint represents short unsigned integers encoded over a maximum of 8 bits. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
@@ -38,27 +38,7 @@ The list of supported operations is:
| Exact Function Evaluation | Unary/Binary |
{% hint style="info" %}
The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. The division is tweaked so that dividing by 0 returns 0.
\* The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns 0.
{% endhint %}
A walk-through example can be found [here](../shortint/tutorial.md), and more examples and explanations can be found [here](../shortint/operations.md).
## Integer
In TFHE-rs, integers represent unsigned integers up to 256 bits. They are encoded using Radix representations by default (more details [here](../integer/operations.md)).
The list of supported operations is:
| Operation name | Type |
| ------------------------------ | ------ |
| Negation | Unary |
| Addition | Binary |
| Subtraction | Binary |
| Multiplication | Binary |
| Bitwise OR, AND, XOR | Binary |
| Equality | Binary |
| Left/Right Shift | Binary |
| Comparisons `<`,`<=`,`>`, `>=` | Binary |
| Min, Max | Binary |
A walk-through example can be found [here](../integer/tutorial.md).
A walk-through example can be found [here](../shortint/tutorial.md), and more examples and explanations can be found [here](../shortint/operations.md).[ ](../shortint/operations.md)

View File

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

View File

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

View File

@@ -1,458 +0,0 @@
# Operations
The structure and operations related to all types (ì.e., Booleans, shortint and integer) are described in this section.
## Booleans
Native homomorphic Booleans support common Boolean operations.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
## ShortInt
Native small homomorphic integer types (e.g., FheUint3 or FheUint4) easily compute various operations. In general, computing over encrypted data is as easy as computing over clear data, since the same operation symbol is used. The addition between two ciphertexts is done using the symbol `+` between two FheUint. Many operations can be computed between a clear value (i.e. a scalar) and a ciphertext.
In Rust native types, any operation is modular. In Rust, `u8`, computations are done modulus 2^8. The similar idea is applied for FheUintX, where operations are done modulus 2^X. In the type FheUint3, operations are done modulo 8.
### Arithmetic operations.
Small homomorphic integer types support all common arithmetic operations, meaning `+`, `-`, `x`, `/`, `mod`.
The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns 0.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------- | ------ | ------ |
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
| [Div](https://doc.rust-lang.org/std/ops/trait.Div.html) | `/` | Binary |
| [Rem](https://doc.rust-lang.org/std/ops/trait.Rem.html) | `%` | Binary |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let clear_c = 2;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
let mut c = FheUint3::try_encrypt(clear_c, &keys)?;
a = a * &b; // Clear equivalent computations: 7 * 3 mod 8 = 5
b = &b + &c; // Clear equivalent computations: 3 + 2 mod 8 = 5
b = b - 5; // Clear equivalent computations: 5 - 5 mod 8 = 0
let dec_a = a.decrypt(&keys);
let dec_b = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, (clear_a * clear_b) % 8);
assert_eq!(dec_b, ((clear_b + clear_c) - 5) % 8);
Ok(())
}
```
### Bitwise operations.
Small homomorphic integer types support some bitwise operations.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------------- | ------ | ------ |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
a = a ^ &b;
b = b ^ &a;
a = a ^ &b;
let dec_a = a.decrypt(&keys);
let dec_b = b.decrypt(&keys);
// We homomorphically swapped values using bitwise operations
assert_eq!(dec_a, clear_b);
assert_eq!(dec_b, clear_a);
Ok(())
}
```
### Comparisons.
Small homomorphic integer types support comparison operations.
Due to some Rust limitations, it is not possible to overload the comparison symbols because of the inner definition of the operations. Rust expects to have a Boolean as an output, whereas a ciphertext encrypted result is returned when using homomorphic types.
You will need to use the different methods instead of using symbols for the comparisons. These methods follow the same naming conventions as the two standard Rust traits:
* [PartialOrd](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html)
* [PartialEq](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint3().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 7;
let clear_b = 3;
let mut a = FheUint3::try_encrypt(clear_a, &keys)?;
let mut b = FheUint3::try_encrypt(clear_b, &keys)?;
assert_eq!(a.gt(&b).decrypt(&keys) != 0, true);
assert_eq!(b.le(&a).decrypt(&keys) != 0, true);
Ok(())
}
```
### Univariate function evaluations.
The shortint type also supports the computation of univariate functions, which deep down uses TFHE's _programmable bootstrapping_.
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint4};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint4().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let pow_5 = |value: u64| {
value.pow(5) % FheUint4::MODULUS as u64
};
let clear_a = 12;
let a = FheUint4::try_encrypt(12, &keys)?;
let c = a.map(pow_5);
let decrypted = c.decrypt(&keys);
assert_eq!(decrypted, pow_5(clear_a) as u8);
Ok(())
}
```
### Bivariate function evaluations.
Using the shortint type allows you to evaluate bivariate functions (i.e., functions that takes two ciphertexts as input).
A simple code example:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint2};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint2().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
let clear_a = 1;
let clear_b = 3;
let a = FheUint2::try_encrypt(clear_a, &keys)?;
let b = FheUint2::try_encrypt(clear_b, &keys)?;
let c = a.bivariate_function(&b, std::cmp::max);
let decrypted = c.decrypt(&keys);
assert_eq!(decrypted, std::cmp::max(clear_a, clear_b) as u8);
Ok(())
}
```
## Integer
In TFHE-rs, integers are used to encrypt any messages larger than 4 bits. All supported operations are listed below.
### Arithmetic operations.
Homomorphic integer types support arithmetic operations.
The list of supported operations is:
| name | symbol | type |
| ------------------------------------------------------- | ------ | ------ |
| [Add](https://doc.rust-lang.org/std/ops/trait.Add.html) | `+` | Binary |
| [Sub](https://doc.rust-lang.org/std/ops/trait.Sub.html) | `-` | Binary |
| [Mul](https://doc.rust-lang.org/std/ops/trait.Mul.html) | `*` | Binary |
| [Neg](https://doc.rust-lang.org/std/ops/trait.Neg.html) | `!` | Unary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_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 |
| ------------------------------------------------------------- | ------ | ------ |
| [BitAnd](https://doc.rust-lang.org/std/ops/trait.BitAnd.html) | `&` | Binary |
| [BitOr](https://doc.rust-lang.org/std/ops/trait.BitOr.html) | `\|` | Binary |
| [BitXor](https://doc.rust-lang.org/std/ops/trait.BitXor.html) | `^` | Binary |
| [Shr](https://doc.rust-lang.org/std/ops/trait.Shr.html) | `>>` | Binary |
| [Shl](https://doc.rust-lang.org/std/ops/trait.Shl.html) | `<<` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_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 |
| --------------------- | ------ | ------ |
| Greater than | `gt` | Binary |
| Greater or equal than | `ge` | Binary |
| Lower than | `lt` | Binary |
| Lower or equal than | `le` | Binary |
| Equal | `eq` | Binary |
A simple example on how to use these operations:
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_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
of `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(())
}
```

View File

@@ -1,73 +0,0 @@
# Serialization/Deserialization
As explained in the Introduction, most types are meant to be shared with the server that performs the computations.
The easiest way to send these data to a server is to use the `serialization` and `deserialization` features. `tfhe` uses the [serde](https://crates.io/crates/serde) framework. Serde's `Serialize` and `Deserialize` functions are implemented on TFHE's types.
To serialize our data, a [data format](https://serde.rs/#data-formats) should be picked. Here, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is a binary format.
```toml
# Cargo.toml
[dependencies]
# ...
tfhe = { version = "0.3.0", features = ["integer","x86_64-unix"]}
bincode = "1.3.3"
```
```rust
// main.rs
use bincode;
use std::io::Cursor;
use tfhe::{ConfigBuilder, ServerKey, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>>{
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let ( client_key, server_key) = generate_keys(config);
let msg1 = 1;
let msg2 = 0;
let value_1 = FheUint8::encrypt(msg1, &client_key);
let value_2 = FheUint8::encrypt(msg2, &client_key);
// Prepare to send data to the server
// The ClientKey is _not_ sent
let mut serialized_data = Vec::new();
bincode::serialize_into(&mut serialized_data, &server_key)?;
bincode::serialize_into(&mut serialized_data, &value_1)?;
bincode::serialize_into(&mut serialized_data, &value_2)?;
// Simulate sending serialized data to a server and getting
// back the serialized result
let serialized_result = server_function(&serialized_data)?;
let result: FheUint8 = bincode::deserialize(&serialized_result)?;
let output: u8 = result.decrypt(&client_key);
assert_eq!(output, msg1 + msg2);
Ok(())
}
fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut serialized_data = Cursor::new(serialized_data);
let server_key: ServerKey = bincode::deserialize_from(&mut serialized_data)?;
let ct_1: FheUint8 = bincode::deserialize_from(&mut serialized_data)?;
let ct_2: FheUint8 = bincode::deserialize_from(&mut serialized_data)?;
set_server_key(server_key);
let result = ct_1 + ct_2;
let serialized_result = bincode::serialize(&result)?;
Ok(serialized_result)
}
```

View File

@@ -1,679 +0,0 @@
# Tutorial
## Quick Start
The basic steps for using the high-level API of TFHE-rs are:
1. Importing TFHE-rs prelude;
2. Client-side: Configuring and creating keys;
3. Client-side: Encrypting data;
4. Server-side: Setting the server key;
5. Server-side: Computing over encrypted data;
6. Client-side: Decrypting data.
Here is the full example (mixing client and server parts):
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
// Client-side
let (client_key, server_key) = generate_keys(config);
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
//Server-side
set_server_key(server_key);
let result = a + b;
//Client-side
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
}
```
Default configuration for x86 Unix machines:
```toml
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
### Imports.
`tfhe` uses `traits` to have a consistent API for creating FHE types and enable users to write generic functions. To be able to use associated functions and methods of a trait, the trait has to be in scope.
To make it easier, the `prelude` 'pattern' is used. All `tfhe` important traits are in a `prelude` module that you **glob import**. With this, there is no need to remember or know the traits to import.
```rust
use tfhe::prelude::*;
```
### 1. Configuring and creating keys.
The first step is the creation of the configuration. The configuration is used to declare which type you will use or not use, as well as enabling you to use custom crypto-parameters for these types for more advanced usage / testing.
Creating a configuration is done using the ConfigBuilder type.
In this example, 8-bit unsigned integers with default parameters are used. The `integers`
feature must also be enabled, as per the table on the [Getting Started page](../getting_started/installation.md).
The config is done by first creating a builder with all types deactivated. Then, the `uint8` type with default parameters is activated.
```rust
use tfhe::{ConfigBuilder, generate_keys};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
}
```
The `generate_keys` command returns a client key and a server key.
The `client_key` is meant to stay private and not leave the client whereas the `server_key` can be made public and sent to a server for it to enable FHE computations.
### 2. Setting the server key.
The next step is to call `set_server_key`
This function will **move** the server key to an internal state of the crate and manage the details to give a simpler interface.
```rust
use tfhe::{ConfigBuilder, generate_keys, set_server_key};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
}
```
### 3. Encrypting data.
Encrypting data is done via the `encrypt` associated function of the \[FheEncrypt] trait.
Types exposed by this crate implement at least one of \[FheEncrypt] or \[FheTryEncrypt] to allow enryption.
```Rust
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
```
### 4. Computation and decryption.
Computations should be as easy as normal Rust to write, thanks to operator overloading.
```Rust
let result = a + b;
```
The decryption is done by using the `decrypt` method, which comes from the \[FheDecrypt] trait.
```Rust
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
```
## A first complete example: FheLatinString (Integer)
The goal of this tutorial is to build a data type that represents a Latin string in FHE while implementing the `to_lower` and `to_upper` functions.
The allowed characters in a Latin string are:
* Uppercase letters: `A B C D E F G H I J K L M N O P Q R S T U V W X Y Z`
* Lowercase letters: `a b c d e f g h i j k l m n o p q r s t u v w x y z`
For the code point of the letters,`ascii` codes are used:
* The uppercase letters are in the range \[65, 90]
* The lowercase letters are in the range \[97, 122]
`lower_case` = `upper_case` + 32 <=> `upper_case` = `lower_case` - 32
For this type, the `FheUint8` type is used.
### Types and methods.
This type will hold the encrypted characters as a `Vec<FheUint8>`, as well as the encrypted constant `32` to implement the functions that change the case.
In the `FheLatinString::encrypt` function, some data validation is done:
* The input string can only contain ascii letters (no digit, no special characters).
* The input string cannot mix lower and upper case letters.
These two points are to work around a limitation of FHE. It is not possible to create branches, meaning the function cannot use conditional statements. Checking if the 'char' is an uppercase letter to modify it to a lowercase one cannot be done, like in the example below.
```rust
fn to_lower(string: &String) -> String {
let mut result = String::with_capacity(string.len());
for char in string.chars() {
if char.is_uppercase() {
result.extend(char.to_lowercase().to_string().chars())
}
}
result
}
```
With these preconditions checked, implementing `to_lower` and `to_upper` is rather simple.
To use the `FheUint8` type, the `integer` feature must be activated:
```toml
# Cargo.toml
[dependencies]
# Default configuration for x86 Unix machines:
tfhe = { version = "0.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");
}
```
## A more complex example: Parity Bit (Boolean)
This example is dedicated to the building of a small function that homomorphically computes a parity bit.
First, a non-generic function is written. Then, generics are used to handle the case where the function inputs are both `FheBool`s and clear `bool`s.
The parity bit function takes as input two parameters:
* A slice of Boolean
* A mode (`Odd` or `Even`)
This function returns a Boolean that will be either `true` or `false` so that the sum of Booleans (in the input and the returned one) is either an `Odd` or `Even` number, depending on the requested mode.
***
### Non-generic version.
To use Booleans, the `booleans` feature in our Cargo.toml must be enabled:
```toml
# Cargo.toml
# Default configuration for x86 Unix machines:
tfhe = { version = "0.3.0", features = ["boolean", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
#### function definition
First, the verification function is defined.
The way to find the parity bit is to initialize it to `false, then` `XOR` it with all the bits, one after the other, adding negation depending on the requested mode.
A validation function is also defined to sum together the number of the bit set within the input with the computed parity bit and check that the sum is an even or odd number, depending on the mode.
```rust
use tfhe::FheBool;
use tfhe::prelude::*;
#[derive(Copy, Clone, Debug)]
enum ParityMode {
// The sum bits of message + parity bit must an odd number
Odd,
// The sum bits of message + parity bit must an even number
Even,
}
fn compute_parity_bit(fhe_bits: &[FheBool], mode: ParityMode) -> FheBool {
let mut parity_bit = fhe_bits[0].clone();
for fhe_bit in &fhe_bits[1..] {
parity_bit = fhe_bit ^ parity_bit
}
match mode {
ParityMode::Odd => !parity_bit,
ParityMode::Even => parity_bit,
}
}
fn is_even(n: u8) -> bool {
(n & 1) == 0
}
fn is_odd(n: u8) -> bool {
!is_even(n)
}
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
let num_bit_set = bits
.iter()
.map(|bit| *bit as u8)
.fold(parity_bit as u8, |acc, bit| acc + bit);
match mode {
ParityMode::Even => is_even(num_bit_set),
ParityMode::Odd => is_odd(num_bit_set),
}
}
```
#### final code
After the mandatory configuration steps, the function is called:
```rust
use tfhe::{FheBool, ConfigBuilder, generate_keys, set_server_key};
use tfhe::prelude::*;
#[derive(Copy, Clone, Debug)]
enum ParityMode {
// The sum bits of message + parity bit must an odd number
Odd,
// The sum bits of message + parity bit must an even number
Even,
}
fn compute_parity_bit(fhe_bits: &[FheBool], mode: ParityMode) -> FheBool {
let mut parity_bit = fhe_bits[0].clone();
for fhe_bit in &fhe_bits[1..] {
parity_bit = fhe_bit ^ parity_bit
}
match mode {
ParityMode::Odd => !parity_bit,
ParityMode::Even => parity_bit,
}
}
fn is_even(n: u8) -> bool {
(n & 1) == 0
}
fn is_odd(n: u8) -> bool {
!is_even(n)
}
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
let num_bit_set = bits
.iter()
.map(|bit| *bit as u8)
.fold(parity_bit as u8, |acc, bit| acc + bit);
match mode {
ParityMode::Even => is_even(num_bit_set),
ParityMode::Odd => is_odd(num_bit_set),
}
}
fn main() {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let (client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let clear_bits = [0, 1, 0, 0, 0, 1, 1].map(|b| (b != 0) as bool);
let fhe_bits = clear_bits
.iter()
.map(|bit| FheBool::encrypt(*bit, &client_key))
.collect::<Vec<FheBool>>();
let mode = ParityMode::Odd;
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
assert!(is_parity_bit_valid);
let mode = ParityMode::Even;
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
assert!(is_parity_bit_valid);
}
```
***
### Generic version.
To make the `compute_parity_bit` function compatible with both `FheBool` and `bool`, generics have to be used.
Writing a generic function that accepts `FHE` types as well as clear types can help test the function to see if it is correct. If the function is generic, it can run with clear data, allowing the use of print-debugging or a debugger to spot errors.
Writing generic functions that use operator overloading for our FHE types can be trickier than normal, since `FHE` types are not copy. So using the reference `&` is mandatory, even though this is not the case when using native types, which are all `Copy`.
This will make the generic bounds trickier at first.
#### writing the correct trait bounds
The function has the following signature:
```Rust
fn check_parity_bit_validity(
fhe_bits: &[FheBool],
mode: ParityMode,
) -> bool
```
To make it generic, the first step is:
```Rust
fn compute_parity_bit<BoolType>(
fhe_bits: &[BoolType],
mode: ParityMode,
) -> BoolType
```
Next, the generic bounds have to be defined with the `where` clause.
In the function, the following operators are used:
* `!` (trait: `Not`)
* `^` (trait: `BitXor`)
By adding them to `where`, this gives:
```Rust
where
BoolType: Clone + Not<Output = BoolType>,
BoolType: BitXor<BoolType, Output=BoolType>,
```
However, the compiler will complain:
```text
---- src/user_doc_tests.rs - user_doc_tests (line 199) stdout ----
error[E0369]: no implementation for `&BoolType ^ BoolType`
--> src/user_doc_tests.rs:218:30
|
21 | parity_bit = fhe_bit ^ parity_bit
| ------- ^ ---------- BoolType
| |
| &BoolType
|
help: consider extending the `where` bound, but there might be an alternative better way to express this requirement
|
17 | BoolType: BitXor<BoolType, Output=BoolType>, &BoolType: BitXor<BoolType>
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
```
`fhe_bit` is a reference to a `BoolType` (`&BoolType`) since it is borrowed from the `fhe_bits` slice when iterating over its elements. The first try is to change the `BitXor` bounds to what the Compiler suggests by requiring `&BoolType` to implement `BitXor` and not `BoolType`.
```Rust
where
BoolType: Clone + Not<Output = BoolType>,
&BoolType: BitXor<BoolType, Output=BoolType>,
```
The Compiler is still not happy:
```text
---- src/user_doc_tests.rs - user_doc_tests (line 236) stdout ----
error[E0637]: `&` without an explicit lifetime name cannot be used here
--> src/user_doc_tests.rs:251:5
|
17 | &BoolType: BitXor<BoolType, Output=BoolType>,
| ^ explicit lifetime name needed here
error[E0310]: the parameter type `BoolType` may not live long enough
--> src/user_doc_tests.rs:251:16
|
17 | &BoolType: BitXor<BoolType, Output=BoolType>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the reference type `&'static BoolType` does not outlive the data it points at
|
help: consider adding an explicit lifetime bound...
|
15 | BoolType: Clone + Not<Output = BoolType> + 'static,
|
```
The way to fix this is to use `Higher-Rank Trait Bounds`:
```Rust
where
BoolType: Clone + Not<Output = BoolType>,
for<'a> &'a BoolType: BitXor<BoolType, Output = BoolType>,
```
The final code will look like this:
```rust
use std::ops::{Not, BitXor};
#[derive(Copy, Clone, Debug)]
enum ParityMode {
// The sum bits of message + parity bit must an odd number
Odd,
// The sum bits of message + parity bit must an even number
Even,
}
fn compute_parity_bit<BoolType>(fhe_bits: &[BoolType], mode: ParityMode) -> BoolType
where
BoolType: Clone + Not<Output = BoolType>,
for<'a> &'a BoolType: BitXor<BoolType, Output = BoolType>,
{
let mut parity_bit = fhe_bits[0].clone();
for fhe_bit in &fhe_bits[1..] {
parity_bit = fhe_bit ^ parity_bit
}
match mode {
ParityMode::Odd => !parity_bit,
ParityMode::Even => parity_bit,
}
}
```
#### final code
Here is a complete example that uses this function for both clear and FHE values:
```rust
use tfhe::{FheBool, ConfigBuilder, generate_keys, set_server_key};
use tfhe::prelude::*;
use std::ops::{Not, BitXor};
#[derive(Copy, Clone, Debug)]
enum ParityMode {
// The sum bits of message + parity bit must an odd number
Odd,
// The sum bits of message + parity bit must an even number
Even,
}
fn compute_parity_bit<BoolType>(fhe_bits: &[BoolType], mode: ParityMode) -> BoolType
where
BoolType: Clone + Not<Output=BoolType>,
for<'a> &'a BoolType: BitXor<BoolType, Output=BoolType>,
{
let mut parity_bit = fhe_bits[0].clone();
for fhe_bit in &fhe_bits[1..] {
parity_bit = fhe_bit ^ parity_bit
}
match mode {
ParityMode::Odd => !parity_bit,
ParityMode::Even => parity_bit,
}
}
fn is_even(n: u8) -> bool {
(n & 1) == 0
}
fn is_odd(n: u8) -> bool {
!is_even(n)
}
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
let num_bit_set = bits
.iter()
.map(|bit| *bit as u8)
.fold(parity_bit as u8, |acc, bit| acc + bit);
match mode {
ParityMode::Even => is_even(num_bit_set),
ParityMode::Odd => is_odd(num_bit_set),
}
}
fn main() {
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
let ( client_key, server_key) = generate_keys(config);
set_server_key(server_key);
let clear_bits = [0, 1, 0, 0, 0, 1, 1].map(|b| (b != 0) as bool);
let fhe_bits = clear_bits
.iter()
.map(|bit| FheBool::encrypt(*bit, &client_key))
.collect::<Vec<FheBool>>();
let mode = ParityMode::Odd;
let clear_parity_bit = compute_parity_bit(&clear_bits, mode);
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
assert!(is_parity_bit_valid);
assert_eq!(decrypted_parity_bit, clear_parity_bit);
let mode = ParityMode::Even;
let clear_parity_bit = compute_parity_bit(&clear_bits, mode);
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
assert!(is_parity_bit_valid);
assert_eq!(decrypted_parity_bit, clear_parity_bit);
}
```

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
# Installation
## Cargo.toml
To use `integer`, you will need to add TFHE-rs to the list of dependencies your project, by updating your `Cargo.toml` file.
```toml
tfhe = { version = "0.2.0", features = ["integer", "x86_64-unix"] }
```
TODO doc

View File

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

View File

@@ -0,0 +1,86 @@
# How Integers are represented
In `integer`, the encrypted data is split amongst many ciphertexts
encrypted using the `shortint` library.
This crate implements two ways to represent an integer:
- the Radix representation
- the CRT (Chinese Reminder Theorem) representation
## Radix based Integers
The first possibility to represent a large integer is to use a radix-based decomposition on the
plaintexts. Let $$B \in \mathbb{N}$$ be a basis such that the size of $$B$$ is smaller (or equal)
to four bits.
Then, an integer $$m \in \mathbb{N}$$ can be written as $$m = m_0 + m_1*B + m_2*B^2 + ... $$, where
each $$m_i$$ is strictly smaller than $$B$$. Each $$m_i$$ is then independently encrypted. In
the end, an Integer ciphertext is defined as a set of Shortint ciphertexts.
In practice, the definition of an Integer requires the basis and the number of blocks. This is
done at the key creation step.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
}
```
In this example, the keys are dedicated to Integers decomposed as four blocks using the basis
$$B=2^2$$. Otherwise said, they allow to work on Integers modulus $$(2^2)^4 = 2^8$$.
In this representation, the correctness of operations requires to propagate the carries
between the ciphertext. This operation is costly since it relies on the computation of many
programmable bootstrapping over Shortints.
## CRT based Integers
The second approach to represent large integers is based on the Chinese Remainder Theorem.
In this cases, the basis $$B$$ is composed of several integers $$b_i$$, such that there are
pairwise coprime, and each b_i has a size smaller than four bits. Then, the Integer will be
defined modulus $$\prod b_i$$. For an integer $$m$$, its CRT decomposition is simply defined as
$$m % b_0, m % b_1, ...$$. Each part is then encrypted as a Shortint ciphertext. In
the end, an Integer ciphertext is defined as a set of Shortint ciphertexts.
An example of such a basis
could be $$B = [2, 3, 5]$$. This means that the Integer is defined modulus $$2*3*5 = 30$$.
This representation has many advantages: no carry propagation is required, so that only cleaning
the carry buffer of each ciphertexts is enough. This implies that operations can easily be
parallelized. Moreover, it allows to efficiently compute PBS in the case where the function is
CRT compliant.
A variant of the CRT is proposed, where each block might be associated to a different key couple.
In the end, a keychain is required to the computations, but performance might be improved.
# Types of operations
Much like `shortint`, the operations available via a `ServerKey` may come in different variants:
- operations that take their inputs as encrypted values.
- scalar operations take at least one non-encrypted value as input.
For example, the addition has both variants:
- `ServerKey::unchecked_add` which takes two encrypted values and adds them.
- `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the
so-called scalar) and adds them.
Each operation may come in different 'flavors':
- `unchecked`: Always does the operation, without checking if the result may exceed the capacity of
the plaintext space.
- `checked`: Checks are done before computing the operation, returning an error if operation
cannot be done safely.
- `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation
will propagate the carry buffer to make the operation possible.
Not all operations have these 3 flavors, as some of them are implemented in a way that the operation
is always possible without ever exceeding the plaintext space capacity.

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
# TFHE-rs Integer
## Introduction
`integer` is a module of TFHE-rs based on its `shortint` module, this crate provides
large precision integers by using multiple `shortint` ciphertexts.
The intended target audience for this library is people who are somewhat familiar with cryptography.

View File

@@ -1,248 +0,0 @@
# Operations
The structure and operations related to the integers are described in this section.
## How an integer is represented
In `integer`, the encrypted data is split amongst many ciphertexts encrypted with the `shortint` library. Below is a scheme representing an integer composed by k shortint ciphertexts.
![](../\_static/integer-ciphertext.png)
This crate implements two ways to represent an integer:
* the Radix representation
* the CRT (Chinese Reminder Theorem) representation
### Radix-based integers.
The first possibility to represent a large integer is to use a Radix-based decomposition on the plaintexts. Let $$B \in \mathbb{N}$$ be a basis such that the size of $$B$$ is smaller than (or equal to) 4 bits. Then, an integer $$m \in \mathbb{N}$$ can be written as $$m = m_0 + m_1*B + m_2*B^2 + ...$$, where each $$m_i$$ is strictly smaller than $$B$$. Each $$m_i$$ is then independently encrypted. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
The definition of an integer requires a basis and a number of blocks. This is done at key generation. Below, the keys are dedicated to unsigned integers encrypting messages over 8 bits, using a basis over 2 bits (i.e., $$B=2^2$$) and 4 blocks.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
}
```
In this representation, the correctness of operations requires to propagate the carries between the ciphertext. This operation is costly since it relies on the computation of many programmable bootstrapping operations over shortints.
### CRT-based integers.
The second approach to represent large integers is based on the Chinese Remainder Theorem. In this case, the basis $$B$$ is composed of several integers $$b_i$$, such that there are pairwise coprime, and each $$b\_i$$ has a size smaller than 4 bits. The CRT-based integer are defined modulus $$\prod b_i$$. For an integer $$m$$, its CRT decomposition is simply defined as $$m % b_0, m % b_1, ...$$. Each part is then encrypted as a shortint ciphertext. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
In the following example, the chosen basis is $$B = [2, 3, 5]$$. The integer is defined modulus $$2*3*5 = 30$$. There is no need to pre-size the number of blocks since it is determined from the number of values composing the basis. Here, the integer is split over three blocks.
```rust
use tfhe::integer::CrtClientKey;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let basis = vec![2, 3, 5];
let cks = CrtClientKey::new(PARAM_MESSAGE_2_CARRY_2, basis);
}
```
This representation has many advantages: no carry propagation is required, cleaning the carry buffer of each ciphertext block is enough. This implies that operations can easily be
parallelized. It also allows the efficient computation of PBS in the case where the function is CRT-compliant.
A variant of the CRT is proposed, where each block might be associated to a different key couple. In the end, a keychain to the computations is required, but performance might be improved.
## List of available operations
The list of operations available in `integer` depends on the type of representation:
| Operation name | Radix-based | CRT-based |
| ------------------------------ | -------------------- | -------------------------- |
| Negation | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Addition | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Scalar Addition | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Subtraction | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Scalar Subtraction | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Multiplication | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Scalar Multiplication | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Bitwise OR, AND, XOR | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Equality | :heavy\_check\_mark: | :heavy\_check\_mark: |
| Left/Right Shift | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
| Comparisons `<`,`<=`,`>`, `>=` | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
| Min, Max | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
## Types of operations
Much like `shortint`, the operations available via a `ServerKey` may come in different variants:
* operations that take their inputs as encrypted values.
* scalar operations take at least one non-encrypted value as input.
For example, the addition has both variants:
* `ServerKey::unchecked_add`, which takes two encrypted values and adds them.
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (the so-called scalar) and adds them.
Each operation may come in different 'flavors':
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space.
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely.
* `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation will propagate the carry buffer to make the operation possible.
* `default`: Always compute the operation and always clear the carry. Could be **slower** than smart, but ensure that the timings are consistent from one call to another.
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
## How to use each operation type
Let's try to do a circuit evaluation using the different flavors of already introduced operations. For a very small circuit, the `unchecked` flavor may be enough to do the computation correctly. Otherwise, `checked` and `smart` are the best options.
As an example, let's do a scalar multiplication, a subtraction, and an addition.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12u64;
let msg2 = 11u64;
let msg3 = 9u64;
let scalar = 3u64;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus().0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
let ct_3 = client_key.encrypt(msg2);
server_key.unchecked_small_scalar_mul_assign(&mut ct_1, scalar);
server_key.unchecked_sub_assign(&mut ct_1, &ct_2);
server_key.unchecked_add_assign(&mut ct_1, &ct_3);
// We use the client key to decrypt the output of the circuit:
let output: u64 = client_key.decrypt(&ct_1);
// The carry buffer has been overflowed, the result is not correct
assert_ne!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
}
```
During this computation the carry buffer has been overflowed, and the output may be incorrect as all the operations were `unchecked`.
If the same circuit is done but using the `checked` flavor, a panic will occur:
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 2;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12u64;
let msg2 = 11u64;
let msg3 = 9u64;
let scalar = 3u64;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus().0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
let ct_3 = client_key.encrypt(msg3);
let result = server_key.checked_small_scalar_mul_assign(&mut ct_1, scalar);
assert!(result.is_ok());
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
assert!(result.is_err());
// We use the client key to decrypt the output of the circuit:
// Only the scalar multiplication could be done
let output: u64 = client_key.decrypt(&ct_1);
assert_eq!(output, (msg1 * scalar) % modulus as u64);
}
```
The `checked` flavor permits the manual management of the overflow of the carry buffer by raising an error if correctness is not guaranteed.
Using the `smart` flavor will output the correct result all the time. However, the computation may be slower as the carry buffer may be propagated during the computations.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12u64;
let msg2 = 11u64;
let msg3 = 9u64;
let scalar = 3u64;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus().0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let mut ct_2 = client_key.encrypt(msg2);
let mut ct_3 = client_key.encrypt(msg3);
server_key.smart_scalar_mul_assign(&mut ct_1, scalar);
server_key.smart_sub_assign(&mut ct_1, &mut ct_2);
server_key.smart_add_assign(&mut ct_1, &mut ct_3);
// We use the client key to decrypt the output of the circuit:
let output: u64 = client_key.decrypt(&ct_1);
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
}
```
The main advantage of the default flavor is to ensure predictable timings, as long as only this kind of operation is used. Only the parallelized version of the operations is provided.
{% hint style="warning" %}
Using `default` could **slow down** computations.
{% endhint %}
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12u64;
let msg2 = 11u64;
let msg3 = 9u64;
let scalar = 3u64;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus().0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let mut ct_2 = client_key.encrypt(msg2);
let mut ct_3 = client_key.encrypt(msg3);
server_key.scalar_mul_assign_parallelized(&mut ct_1, scalar);
server_key.sub_assign_parallelized(&mut ct_1, &mut ct_2);
server_key.add_assign_parallelized(&mut ct_1, &mut ct_3);
// We use the client key to decrypt the output of the circuit:
let output: u64 = client_key.decrypt(&ct_1);
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
}
```

View File

@@ -1,3 +0,0 @@
# Cryptographic Parameters
`integer` does not come with its own set of parameters. Instead, it relies on parameters from `shortint`. Currently, parameter sets having the same space dedicated to the message and the carry (i.e. `PARAM_MESSAGE_{X}_CARRY_{X}` with `X` in \[1,4]) are recommended. See [here](../shortint/parameters.md) for more details about cryptographic parameters, and [here](operations.md) to see how to properly instantiate integers depending on the chosen representation.

View File

@@ -1,121 +0,0 @@
# Tutorial
The steps to homomorphically evaluate an integer circuit are described here.
## Key Types
`integer` provides 3 basic key types:
* `ClientKey`
* `ServerKey`
* `PublicKey`
The `ClientKey` is the key that encrypts and decrypts messages, thus this key is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
The `ServerKey` is the key that is used to actually do the FHE computations. It contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, so it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
To reflect that, computation/operation methods are tied to the `ServerKey` type.
The `PublicKey` is a key used to encrypt messages. It can be publicly shared to allow users to encrypt data such that only the `ClientKey` holder will be able to decrypt. Encrypting with the `PublicKey` does not alter the homomorphic capabilities associated to the `ServerKey`.
## 1. Key Generation
To generate the keys, a user needs two parameters:
* A set of `shortint` cryptographic parameters.
* The number of ciphertexts used to encrypt an integer (we call them "shortint blocks").
We are now going to build a pair of keys that can encrypt an **8-bit** integer by using **4** shortint blocks that store **2** bits of message each.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
}
```
## 2. Encrypting values
Once we have our keys, we can encrypt values:
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 128u64;
let msg2 = 13u64;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
}
```
## 3. Encrypting values with the public key
Once the client key is generated, the public key can be derived and used to encrypt data.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::integer::PublicKey;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, _) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
//We generate the public key from the secret client key:
let public_key = PublicKey::new(&client_key);
//encryption
let msg1 = 128u64;
let msg2 = 13u64;
// We use the public key to encrypt two messages:
let ct_1 = public_key.encrypt_radix(msg1, num_block);
let ct_2 = public_key.encrypt_radix(msg2, num_block);
}
```
## 4. Computing and decrypting
With our `server_key`, and encrypted values, we can now do an addition and then decrypt the result.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 128;
let msg2 = 13;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus().0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
// We use the server public key to execute an integer circuit:
let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);
// We use the client key to decrypt the output of the circuit:
let output: u64 = client_key.decrypt(&ct_3);
assert_eq!(output, (msg1 + msg2) % modulus);
}
```

View File

@@ -0,0 +1,120 @@
# Circuit evaluation
Let's try to do a circuit evaluation using the different flavours of operations we already introduced.
For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly.
Otherwise, the `checked` and `smart` are the best options.
As an example, let's do a scalar multiplication, a subtraction and an addition.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12;
let msg2 = 11;
let msg3 = 9;
let scalar = 3;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
let ct_3 = client_key.encrypt(msg2);
server_key.unchecked_small_scalar_mul_assign(&mut ct_1, scalar);
server_key.unchecked_sub_assign(&mut ct_1, &ct_2);
server_key.unchecked_add_assign(&mut ct_1, &ct_3);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_1);
// The carry buffer has been overflowed, the result is not correct
assert_ne!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
}
```
During this computation the carry buffer has been overflowed and as all the operations were `unchecked` the output
may be incorrect.
If we redo this same circuit but using the `checked` flavour, a panic will occur.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 2;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12;
let msg2 = 11;
let msg3 = 9;
let scalar = 3;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
let ct_3 = client_key.encrypt(msg3);
let result = server_key.checked_small_scalar_mul_assign(&mut ct_1, scalar);
assert!(result.is_ok());
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
assert!(result.is_err());
// We use the client key to decrypt the output of the circuit:
// Only the scalar multiplication could be done
let output = client_key.decrypt(&ct_1);
assert_eq!(output, (msg1 * scalar) % modulus as u64);
}
```
Therefore the `checked` flavour permits to manually manage the overflow of the carry buffer
by raising an error if the correctness is not guaranteed.
Lastly, using the `smart` flavour will output the correct result all the time. However, the computation may be slower
as the carry buffer may be propagated during the computations.
```rust
use tfhe::integer::gen_keys_radix;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12;
let msg2 = 11;
let msg3 = 9;
let scalar = 3;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let mut ct_2 = client_key.encrypt(msg2);
let mut ct_3 = client_key.encrypt(msg3);
server_key.smart_scalar_mul_assign(&mut ct_1, scalar);
server_key.smart_sub_assign(&mut ct_1, &mut ct_2);
server_key.smart_add_assign(&mut ct_1, &mut ct_3);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_1);
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
}
```

View File

@@ -1,10 +1,14 @@
# Serialization/Deserialization
# Serialization / Deserialization
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that does the computations.
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared
with the server that does the computations.
The easiest way to send these data to a server is to use the serialization and deserialization features. TFHE-rs uses the serde framework, so serde's Serialize and Deserialize are implemented.
The easiest way to send these data to a server is to use the serialization and deserialization features.
concrete-integer uses the serde framework, serde's Serialize and Deserialize are implemented.
To be able to serialize our data, we need to pick a [data format], for our use case,
[bincode] is a good choice, mainly because it is binary format.
To be able to serialize our data, a [data format](https://serde.rs/#data-formats) needs to be picked. Here, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
```toml
# Cargo.toml
@@ -14,6 +18,7 @@ To be able to serialize our data, a [data format](https://serde.rs/#data-formats
bincode = "1.3.3"
```
```rust
// main.rs
@@ -27,13 +32,13 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 201;
let msg2 = 12;
// message_modulus^vec_length
let modulus = client_key.parameters().message_modulus().0.pow(num_block as u32) as u64;
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
@@ -48,7 +53,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let serialized_result = server_function(&serialized_data)?;
let result: RadixCiphertext = bincode::deserialize(&serialized_result)?;
let output: u64 = client_key.decrypt(&result);
let output = client_key.decrypt(&result);
assert_eq!(output, (msg1 + msg2) % modulus);
Ok(())
}
@@ -67,3 +72,7 @@ fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error
Ok(serialized_result)
}
```
[serde]: https://crates.io/crates/serde
[data format]: https://serde.rs/#data-formats
[bincode]: https://crates.io/crates/bincode

View File

@@ -2,21 +2,24 @@
## Using the JS on WASM API
Welcome to this TFHE-rs JS on WASM API tutorial.
Welcome to this TFHE-rs JS on WASM API tutorial!
TFHE-rs uses WASM to expose a JS binding to the client-side primitives, like key generation and encryption, of the Boolean and shortint modules.
There are several limitations at this time. Due to a lack of threading support in WASM, key generation can be too slow to be practical for bigger parameter sets.
There are several limitations at this time. Due to a lack of threading support in WASM, key
generation can be too slow to be practical for bigger parameter sets.
Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory space of WASM. This means that some parameter sets are virtually unusable.
Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory space of WASM.
This means that some parameters sets are virtually unusable.
## First steps using TFHE-rs JS on WASM API
### Setting-up TFHE-rs JS on WASM API for use in nodejs programs.
To build the JS on WASM bindings for TFHE-rs, you need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.67) [`rust toolchain`](https://rustup.rs/).
To build the JS on WASM bindings for TFHE-rs, you will first need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.65) [`rust toolchain`](https://rustup.rs/).
In a shell, then run the following to clone the TFHE-rs repo (one may want to checkout a specific tag, here the default branch is used for the build):
Then, in a shell run the following to clone the TFHE-rs repo (one may want to checkout a
specific tag, here the default branch is used for the build):
```shell
$ git clone https://github.com/zama-ai/tfhe-rs.git
@@ -31,11 +34,12 @@ $ rustup run wasm-pack build --release --target=nodejs --features=boolean-client
[INFO]: :-) Your wasm pkg is ready to publish at ...
```
The command above targets nodejs. A binding for a web browser can be generated as well using `--target=web`. This use case will not be discussed in this tutorial.
The command above targets nodejs. A binding for a web browser can be generated as well using
`--target=web`. This use case will not be discussed in this tutorial.
Both Boolean and shortint features are enabled here, but it's possible to use one without the other.
Both Boolean and shortint features are enabled here but it's possible to use one without the other.
After the build, a new directory _**pkg**_ is present in the `tfhe` directory.
After the build, a new directory ***pkg*** is present in the `tfhe` directory.
```shell
$ ls pkg
@@ -45,8 +49,9 @@ $
### Commented code to generate keys for shortint and encrypt a ciphertext
{% hint style="info" %}
Be sure to update the path of the required clause in the example below for the TFHE package that was just built.
{% hint style=“warning” %}
Be sure to update the path of the required clause in the example below for the TFHE
package that was just built.
{% endhint %}
```javascript
@@ -96,7 +101,7 @@ function shortint_example() {
shortint_example();
```
The `example.js` script can then be run using [`node`](https://nodejs.org/), like so:
The `example.js` script can then be run using [`node`](https://nodejs.org/) like so:
```shell
$ node example.js

View File

@@ -1,8 +1,6 @@
# Operations
The structure and the operations related to the short integers are described in this section.
## How a shortint is represented
## How shortint is represented
In `shortint`, the encrypted data is stored in an LWE ciphertext.
@@ -10,38 +8,37 @@ Conceptually, the message stored in an LWE ciphertext is divided into a **carry
![](../\_static/ciphertext-representation.png)
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus. The exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus: the exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in incorrect outputs.
In order to ensure the correctness of the computation, we track the maximum value encrypted in a ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to safely resume the computations. In `shortint` the carry modulus is considered useful as a means to do more computations.
In order to ensure the correctness of the computation, we keep track of the maximum value encrypted in a ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to safely resume the computations. Therefore, in `shortint` the carry modulus is mainly considered as a means to do more computations.
## Types of operations
The operations available via a `ServerKey` may come in different variants:
* operations that take their inputs as encrypted values
* scalar operations that take at least one non-encrypted value as input
* operations that take their inputs as encrypted values.
* scalar operations that take at least one non-encrypted value as input.
For example, the addition has both variants:
* `ServerKey::unchecked_add`, which takes two encrypted values and adds them.
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (the so-called scalar) and adds them.
* `ServerKey::unchecked_add` which takes two encrypted values and adds them.
* `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the so-called scalar) and adds them.
Each operation may come in different 'flavors':
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely;
* `smart`: Always does the operation. If the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible;
* `default`: Always does the operation and always clears the carry. Could be **slower** than smart, but it ensures that the timings are consistent from one call to another.
* `smart`: Always does the operation - if the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible.
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
Not all operations have these 3 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
## How to use operation types
Let's try to do a circuit evaluation using the different flavors of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise,`checked` and `smart` are the best options.
Let's try to do a circuit evaluation using the different flavours of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise, the `checked` and `smart` are the best options.
Let's do a scalar multiplication, a subtraction, and a multiplication.
As an example, let's do a scalar multiplication, a subtraction, and a multiplication.
```rust
use tfhe::shortint::prelude::*;
@@ -55,7 +52,7 @@ fn main() {
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus().0;
let modulus = client_key.parameters.message_modulus.0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -73,7 +70,7 @@ fn main() {
During this computation, the carry buffer has been overflowed and, as all the operations were `unchecked`, the output may be incorrect.
If we redo this same circuit with the `checked` flavor, a panic will occur:
If we redo this same circuit with the `checked` flavour, a panic will occur.
```rust
use tfhe::shortint::prelude::*;
@@ -88,7 +85,7 @@ fn main() {
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus().0;
let modulus = client_key.parameters.message_modulus.0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -115,9 +112,9 @@ fn main() {
}
```
The `checked` flavor permits manual management of the overflow of the carry buffer by raising an error if correctness is not guaranteed.
Therefore, the `checked` flavour permits manual management of the overflow of the carry buffer by raising an error if the correctness is not guaranteed.
Using the `smart` flavor will output the correct result all the time. However, the computation may be slower as the carry buffer may be cleaned during the computations.
Lastly, using the `smart` flavour will output the correct result all the time. However, the computation may be slower as the carry buffer may be cleaned during the computations.
```rust
use tfhe::shortint::prelude::*;
@@ -131,7 +128,7 @@ fn main() {
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus().0;
let modulus = client_key.parameters.message_modulus.0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -147,51 +144,17 @@ fn main() {
}
```
The main advantage of the default flavor is to ensure predictable timings as long as only this kind of operation is used.
{% hint style="warning" %}
Using `default` could **slow-down** computations.
{% endhint %}
```rust
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let msg1 = 3;
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
let mut ct_2 = client_key.encrypt(msg2);
server_key.scalar_mul_assign(&mut ct_1, scalar);
server_key.sub_assign(&mut ct_1, &mut ct_2);
server_key.mul_lsb_assign(&mut ct_1, &mut ct_2);
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_1);
assert_eq!(output, ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64);
}
```
\#List of available operations
{% hint style="warning" %}
Certain operations can only be used if the parameter set chosen is compatible with the bivariate programmable bootstrapping, meaning the carry buffer is larger than or equal to the message buffer. These operations are marked with a star (\*).
Currently, certain operations can only be used if the parameter set chosen is compatible with the bivariate programmable bootstrapping, meaning the carry buffer is larger than or equal to the message buffer. These operations are marked with a star (\*).
{% endhint %}
The list of implemented operations for shortint is:
* addition between two ciphertexts
* addition between a ciphertext and an unencrypted scalar
* comparisons `<`, `<=`, `>`, `>=`, `==`, `!=` between a ciphertext and an unencrypted scalar
* comparisons `<`, `<=`, `>`, `>=`, `==` between a ciphertext and an unencrypted scalar
* division of a ciphertext by an unencrypted scalar
* LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
* multiplication of a ciphertext by an unencrypted scalar
@@ -200,13 +163,15 @@ The list of implemented operations for shortint is:
* subtraction of a ciphertext by an unencrypted scalar
* negation of a ciphertext
* bitwise and, or and xor (\*)
* comparisons `<`, `<=`, `>`, `>=`, `==`, `!=` between two ciphertexts (\*)
* comparisons `<`, `<=`, `>`, `>=`, `==` between two ciphertexts (\*)
* division between two ciphertexts (\*)
* MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (\*)
In what follows, some simple code examples are given.
### Public key encryption.
TFHE-rs supports both private and public key encryption methods. The only difference between both lies in the encryption step: in this case, the encryption method is called using `public_key` instead of `client_key`.
TFHE-rs supports both private and public key encryption methods. Note that the only difference between both lies into the encryption step: in this case, the encryption method is called using `public_key` instead of `client_key`.
Here is a small example on how to use public encryption:
@@ -216,7 +181,7 @@ use tfhe::shortint::prelude::*;
fn main() {
// Generate the client key and the server key:
let (cks, _) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let pks = PublicKey::new(&cks);
let pks = PublicKeyBig::new(&cks);
let msg = 2;
// Encryption of one message:
@@ -227,6 +192,8 @@ fn main() {
}
```
In what follows, all examples are related to private key encryption.
### Arithmetic operations.
Classical arithmetic operations are supported by shortint:
@@ -241,7 +208,7 @@ fn main() {
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus().0;
let modulus = client_key.parameters.message_modulus.0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -272,7 +239,7 @@ fn main() {
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus().0;
let modulus = client_key.parameters.message_modulus.0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -303,7 +270,7 @@ fn main() {
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus().0;
let modulus = client_key.parameters.message_modulus.0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -331,12 +298,13 @@ fn main() {
let msg1 = 3;
let modulus = client_key.parameters.message_modulus().0;
let modulus = client_key.parameters.message_modulus.0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let acc = server_key.generate_lookup_table(|n| n.count_ones().into());
//define the accumulator as the
let acc = server_key.generate_accumulator(|n| n.count_ones().into());
// add the two ciphertexts
let ct_res = server_key.apply_lookup_table(&ct_1, &acc);
@@ -350,7 +318,7 @@ fn main() {
#### bi-variate function evaluations
Using the shortint types offers the possibility to evaluate bi-variate functions, or functions that take two ciphertexts as input. This requires choosing a parameter set such that the carry buffer size is at least as large as the message (i.e., PARAM\_MESSAGE\_X\_CARRY\_Y with X <= Y).
Using the shortint types offers the possibility to evaluate bi-variate functions, i.e., functions that takes two ciphertexts as input. This requires choosing a parameter set such that the carry buffer size is at least as large as the message one i.e., PARAM\_MESSAGE\_X\_CARRY\_Y with X <= Y.
Here is a simple code example:
@@ -364,14 +332,14 @@ fn main() {
let msg1 = 3;
let msg2 = 2;
let modulus = client_key.parameters.message_modulus().0 as u64;
let modulus = client_key.parameters.message_modulus.0 as u64;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let mut ct_2 = client_key.encrypt(msg2);
// Compute the lookup table for the bivariate functions
let acc = server_key.generate_lookup_table_bivariate(|x,y| (x.count_ones()
// Compute the accumulator for the bivariate functions
let acc = server_key.generate_accumulator_bivariate(|x,y| (x.count_ones()
+ y.count_ones()) as u64 % modulus );
let ct_res = server_key.smart_apply_lookup_table_bivariate(&ct_1, &mut ct_2, &acc);

View File

@@ -1,6 +1,6 @@
# Cryptographic Parameters
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing using programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting\_started/security\_and\_cryptography.md) for more details about the encryption process).
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing a programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting\_started/security\_and\_cryptography.md) for more details about the encryption process).
## Parameters and message precision
@@ -10,9 +10,9 @@ The user is allowed to choose which set of parameters to use when creating the p
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
This example contains keys that are generated to have messages encoded over 2 bits (i.e., computations are done modulus $$2^2 = 4$$) with 2 bits of carry.
In what follows, there is an example where keys are generated to have messages encoded over 2 bits i.e., computations are done modulus $$2^2 = 4$$), with 2 bits of carry.
The `PARAM_MESSAGE_2_CARRY_2` parameter set is the default `shortint` parameter set that you can also use through the `tfhe::shortint::prelude::DEFAULT_PARAMETERS` constant.
Note that the `PARAM_MESSAGE_2_CARRY_2` parameter set is the default `shortint` parameter set that you can also use through the `tfhe::shortint::prelude::DEFAULT_PARAMETERS` constant.
```rust
use tfhe::shortint::prelude::*;
@@ -36,15 +36,15 @@ As shown [here](../getting\_started/benchmarks.md), the choice of the parameter
### Generic bi-variate functions.
The computations of bi-variate functions is based on a trick: _concatenating_ two ciphertexts into one. Where the carry buffer is not at least as large as the message one, this trick no longer works. Many bi-variate operations, such as comparisons, then cannot be correctly computed. The only exception concerns multiplication.
The computations of bi-variate functions is based on a trick, _concatenating_ two ciphertexts into one. In the case where the carry buffer is not at least as large as the message one, this trick no longer works. Then, many bi-variate operations, such as comparisons cannot be correctly computed. The only exception concerns the multiplication.
### Multiplication.
In the case of multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication\_algorithm#Quarter\_square\_multiplication). To correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM\_MESSAGE\_X\_CARRY\_Y with Y>=1). This method is slower than using the other one. Using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
In the case of the multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication\_algorithm#Quarter\_square\_multiplication). In order to correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM\_MESSAGE\_X\_CARRY\_Y with Y>=1). This method is, in general, slower than using the other one. Note that using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
## User-defined parameter sets
It is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `ClassicPBSParameters` structure fields.
Beyond the predefined parameter sets, it is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `Parameter` structure fields.
For instance:
@@ -53,7 +53,7 @@ use tfhe::shortint::prelude::*;
fn main() {
let param = unsafe {
ClassicPBSParameters::new(
Parameters::new(
LweDimension(656),
GlweDimension(2),
PolynomialSize(512),
@@ -63,10 +63,14 @@ fn main() {
DecompositionLevelCount(2),
DecompositionBaseLog(3),
DecompositionLevelCount(4),
StandardDev(0.00000000037411618952047216),
DecompositionBaseLog(15),
DecompositionLevelCount(1),
DecompositionLevelCount(0),
DecompositionBaseLog(0),
MessageModulus(4),
CarryModulus(1),
CiphertextModulus::new_native(),
EncryptionKeyChoice::Big,
)
};
}

View File

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

View File

@@ -1,29 +1,26 @@
# Tutorial
The steps to homomorphically evaluate a circuit are described below.
## Writing an homomorphic circuit using shortint
## Key generation
## Key Generation
`tfhe::shortint` provides 3 key types:
`tfhe::shortint` provides 2 key types:
* `ClientKey`
* `ServerKey`
* `PublicKey`
The `ClientKey` is the key that encrypts and decrypts messages (integer values up to 8 bits here). It is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
The `ClientKey` is the key that encrypts and decrypts messages (integer values up to 8 bits here), thus this key is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
The `ServerKey` is the key that is used to actually do the FHE computations. Most importantly, it contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, therefore it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
The `ServerKey` is the key that is used to actually do the FHE computations. It contains (among other things) a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, therefore it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
Computation/operation methods are tied to the `ServerKey` type.
The `PublicKey` is the key used to encrypt messages. It can be publicly shared to allow users to encrypt data such that only the `ClientKey` holder will be able to decrypt. Encrypting with the `PublicKey` does not alter the homomorphic capabilities associated to the `ServerKey`.
To reflect that, computation/operation methods are tied to the `ServerKey` type.
```rust
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(Parameters::default());
}
```
@@ -35,8 +32,8 @@ Once the keys have been generated, the client key is used to encrypt data:
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(Parameters::default());
let msg1 = 1;
let msg2 = 0;
@@ -55,9 +52,9 @@ Once the keys have been generated, the client key is used to encrypt data:
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, _) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
let public_key = PublicKey::new(&client_key);
// We generate a set of client/server keys, using the default parameters:
let (client_key, _) = gen_keys(Parameters::default());
let public_key = PublicKeyBig::new(&client_key);
let msg1 = 1;
let msg2 = 0;
@@ -70,19 +67,19 @@ fn main() {
## Computing and decrypting
With the `server_key`, addition is now possible over encrypted values. The resulting plaintext is recovered after the decryption with the secret client key.
With our `server_key` and encrypted values, we can now do an addition and then decrypt the result.
```rust
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
// We generate a set of client/server keys, using the default parameters:
let (client_key, server_key) = gen_keys(Parameters::default());
let msg1 = 1;
let msg2 = 0;
let modulus = client_key.parameters.message_modulus().0;
let modulus = client_key.parameters.message_modulus.0;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,3 @@
#[path = "../../benches/utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
@@ -16,8 +12,8 @@ fn write_result(file: &mut File, name: &str, value: usize) {
fn client_server_key_sizes(results_file: &Path) {
let boolean_params_vec = vec![
(DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS"),
(TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS"),
(DEFAULT_PARAMETERS, "default"),
(TFHE_LIB_PARAMETERS, "tfhe_lib"),
];
File::create(results_file).expect("create results file failed");
let mut file = OpenOptions::new()
@@ -25,33 +21,19 @@ fn client_server_key_sizes(results_file: &Path) {
.open(results_file)
.expect("cannot open results file");
let operator = OperatorType::Atomic;
println!("Generating boolean (ClientKey, ServerKey)");
for (i, (params, params_name)) in boolean_params_vec.iter().enumerate() {
for (i, (params, name)) in boolean_params_vec.iter().enumerate() {
println!(
"Generating [{} / {}] : {}",
i + 1,
boolean_params_vec.len(),
params_name.to_lowercase()
name
);
let cks = client_key::ClientKey::new(params);
let sks = server_key::ServerKey::new(&cks);
let ksk_size = sks.key_switching_key_size_bytes();
let test_name = format!("boolean_key_sizes_{params_name}_ksk");
write_result(&mut file, &test_name, ksk_size);
write_to_json(
&test_name,
*params,
*params_name,
"KSK",
&operator,
0,
vec![],
);
write_result(&mut file, &format!("boolean_{}_{}", name, "ksk"), ksk_size);
println!(
"Element in KSK: {}, size in bytes: {}",
sks.key_switching_key_size_elements(),
@@ -59,19 +41,7 @@ fn client_server_key_sizes(results_file: &Path) {
);
let bsk_size = sks.bootstrapping_key_size_bytes();
let test_name = format!("boolean_key_sizes_{params_name}_bsk");
write_result(&mut file, &test_name, bsk_size);
write_to_json(
&test_name,
*params,
*params_name,
"BSK",
&operator,
0,
vec![],
);
write_result(&mut file, &format!("boolean_{}_{}", name, "bsk"), bsk_size);
println!(
"Element in BSK: {}, size in bytes: {}",
sks.bootstrapping_key_size_elements(),
@@ -82,6 +52,8 @@ fn client_server_key_sizes(results_file: &Path) {
fn main() {
let work_dir = std::env::current_dir().unwrap();
println!("work_dir: {}", std::env::current_dir().unwrap().display());
// Change workdir so that the location of the keycache matches the one for tests
let mut new_work_dir = work_dir;
new_work_dir.push("tfhe");
std::env::set_current_dir(new_work_dir).unwrap();

View File

@@ -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;
/// 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);
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!();
}
}
}

View File

@@ -0,0 +1,72 @@
use tfhe::shortint::keycache::{NamedParam, KEY_CACHE, KEY_CACHE_WOPBS};
use tfhe::shortint::parameters::parameters_wopbs_message_carry::{
WOPBS_PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_3_CARRY_3,
WOPBS_PARAM_MESSAGE_4_CARRY_4,
};
use tfhe::shortint::parameters::{
Parameters, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_4_CARRY_4,
};
fn client_server_keys() {
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()
}
const WOPBS_PARAMS: [(Parameters, Parameters); 4] = [
(PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_1_CARRY_1),
(PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_2_CARRY_2),
(PARAM_MESSAGE_3_CARRY_3, WOPBS_PARAM_MESSAGE_3_CARRY_3),
(PARAM_MESSAGE_4_CARRY_4, WOPBS_PARAM_MESSAGE_4_CARRY_4),
];
println!("Generating woPBS keys");
for (i, (params_shortint, params_wopbs)) in WOPBS_PARAMS.iter().copied().enumerate() {
println!(
"Generating [{} / {}] : {}, {}",
i + 1,
WOPBS_PARAMS.len(),
params_shortint.name(),
params_wopbs.name(),
);
let start = std::time::Instant::now();
let _ = KEY_CACHE_WOPBS.get_from_param((params_shortint, params_wopbs));
let stop = start.elapsed().as_secs();
println!("Generation took {stop} seconds");
// Clear keys as we go to avoid filling the RAM
KEY_CACHE_WOPBS.clear_in_memory_cache()
}
}
fn main() {
let work_dir = std::env::current_dir().unwrap();
println!("work_dir: {}", std::env::current_dir().unwrap().display());
// Change workdir so that the location of the keycache matches the one for tests
let mut new_work_dir = work_dir;
new_work_dir.push("tfhe");
std::env::set_current_dir(new_work_dir).unwrap();
client_server_keys()
}

View File

@@ -1,22 +0,0 @@
use tfhe::integer::{gen_keys_radix, RadixCiphertext, RadixClientKey, ServerKey};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
pub type StringCiphertext = Vec<RadixCiphertext>;
pub fn encrypt_str(
client_key: &RadixClientKey,
s: &str,
) -> Result<StringCiphertext, Box<dyn std::error::Error>> {
if !s.is_ascii() {
return Err("content contains non-ascii characters".into());
}
Ok(s.as_bytes()
.iter()
.map(|byte| client_key.encrypt(*byte as u64))
.collect())
}
pub fn gen_keys() -> (RadixClientKey, ServerKey) {
let num_block = 4;
gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block)
}

View File

@@ -1,263 +0,0 @@
use crate::execution::{Executed, Execution, LazyExecution};
use crate::parser::{parse, RegExpr};
use std::rc::Rc;
use tfhe::integer::{RadixCiphertext, ServerKey};
pub fn has_match(
sk: &ServerKey,
content: &[RadixCiphertext],
pattern: &str,
) -> Result<RadixCiphertext, Box<dyn std::error::Error>> {
let re = parse(pattern)?;
let branches: Vec<LazyExecution> = (0..content.len())
.flat_map(|i| build_branches(content, &re, i))
.map(|(lazy_branch_res, _)| lazy_branch_res)
.collect();
let mut exec = Execution::new(sk.clone());
let res = if branches.len() <= 1 {
branches
.get(0)
.map_or(exec.ct_false(), |branch| branch(&mut exec))
.0
} else {
branches[1..]
.iter()
.fold(branches[0](&mut exec), |res, branch| {
let branch_res = branch(&mut exec);
exec.ct_or(res, branch_res)
})
.0
};
info!(
"{} ciphertext operations, {} cache hits",
exec.ct_operations_count(),
exec.cache_hits(),
);
Ok(res)
}
fn build_branches(
content: &[RadixCiphertext],
re: &RegExpr,
c_pos: usize,
) -> Vec<(LazyExecution, usize)> {
trace!("program pointer: regex={:?}, content pos={}", re, c_pos);
match re {
RegExpr::Sof => {
if c_pos == 0 {
return vec![(Rc::new(|exec| exec.ct_true()), c_pos)];
} else {
return vec![];
}
}
RegExpr::Eof => {
if c_pos == content.len() {
return vec![(Rc::new(|exec| exec.ct_true()), c_pos)];
} else {
return vec![];
}
}
_ => (),
};
if c_pos >= content.len() {
return vec![];
}
match re.clone() {
RegExpr::Char { c } => {
let c_char = (content[c_pos].clone(), Executed::ct_pos(c_pos));
vec![(
Rc::new(move |exec| exec.ct_eq(c_char.clone(), exec.ct_constant(c))),
c_pos + 1,
)]
}
RegExpr::AnyChar => vec![(Rc::new(|exec| exec.ct_true()), c_pos + 1)],
RegExpr::Not { not_re } => build_branches(content, &not_re, c_pos)
.into_iter()
.map(|(branch, c_pos)| {
(
Rc::new(move |exec: &mut Execution| {
let branch_res = branch(exec);
exec.ct_not(branch_res)
}) as LazyExecution,
c_pos,
)
})
.collect(),
RegExpr::Either { l_re, r_re } => {
let mut res = build_branches(content, &l_re, c_pos);
res.append(&mut build_branches(content, &r_re, c_pos));
res
}
RegExpr::Between { from, to } => {
let c_char = (content[c_pos].clone(), Executed::ct_pos(c_pos));
vec![(
Rc::new(move |exec| {
let ct_from = exec.ct_constant(from);
let ct_to = exec.ct_constant(to);
let ge_from = exec.ct_ge(c_char.clone(), ct_from);
let le_to = exec.ct_le(c_char.clone(), ct_to);
exec.ct_and(ge_from, le_to)
}),
c_pos + 1,
)]
}
RegExpr::Range { cs } => {
let c_char = (content[c_pos].clone(), Executed::ct_pos(c_pos));
vec![(
Rc::new(move |exec| {
cs[1..].iter().fold(
exec.ct_eq(c_char.clone(), exec.ct_constant(cs[0])),
|res, c| {
let ct_c_char_eq = exec.ct_eq(c_char.clone(), exec.ct_constant(*c));
exec.ct_or(res, ct_c_char_eq)
},
)
}),
c_pos + 1,
)]
}
RegExpr::Repeated {
repeat_re,
at_least,
at_most,
} => {
let at_least = at_least.unwrap_or(0);
let at_most = at_most.unwrap_or(content.len() - c_pos);
if at_least > at_most {
return vec![];
}
let mut res = vec![
if at_least == 0 {
vec![(
Rc::new(|exec: &mut Execution| exec.ct_true()) as LazyExecution,
c_pos,
)]
} else {
vec![]
},
build_branches(
content,
&(RegExpr::Seq {
re_xs: std::iter::repeat(*repeat_re.clone())
.take(std::cmp::max(1, at_least))
.collect(),
}),
c_pos,
),
];
for _ in (at_least + 1)..(at_most + 1) {
res.push(
res.last()
.unwrap()
.iter()
.flat_map(|(branch_prev, branch_c_pos)| {
build_branches(content, &repeat_re, *branch_c_pos)
.into_iter()
.map(move |(branch_x, branch_x_c_pos)| {
let branch_prev = branch_prev.clone();
(
Rc::new(move |exec: &mut Execution| {
let res_prev = branch_prev(exec);
let res_x = branch_x(exec);
exec.ct_and(res_prev, res_x)
}) as LazyExecution,
branch_x_c_pos,
)
})
})
.collect(),
);
}
res.into_iter().flatten().collect()
}
RegExpr::Optional { opt_re } => {
let mut res = build_branches(content, &opt_re, c_pos);
res.push((Rc::new(|exec| exec.ct_true()), c_pos));
res
}
RegExpr::Seq { re_xs } => re_xs[1..].iter().fold(
build_branches(content, &re_xs[0], c_pos),
|continuations, re_x| {
continuations
.into_iter()
.flat_map(|(branch_prev, branch_prev_c_pos)| {
build_branches(content, re_x, branch_prev_c_pos)
.into_iter()
.map(move |(branch_x, branch_x_c_pos)| {
let branch_prev = branch_prev.clone();
(
Rc::new(move |exec: &mut Execution| {
let res_prev = branch_prev(exec);
let res_x = branch_x(exec);
exec.ct_and(res_prev, res_x)
}) as LazyExecution,
branch_x_c_pos,
)
})
})
.collect()
},
),
_ => panic!("unmatched regex variant"),
}
}
#[cfg(test)]
mod tests {
use crate::engine::has_match;
use test_case::test_case;
use crate::ciphertext::{encrypt_str, gen_keys, StringCiphertext};
use lazy_static::lazy_static;
use tfhe::integer::{RadixClientKey, ServerKey};
lazy_static! {
pub static ref KEYS: (RadixClientKey, ServerKey) = gen_keys();
}
#[test_case("ab", "/ab/", 1)]
#[test_case("b", "/ab/", 0)]
#[test_case("ab", "/a?b/", 1)]
#[test_case("b", "/a?b/", 1)]
#[test_case("ab", "/^ab|cd$/", 1)]
#[test_case(" ab", "/^ab|cd$/", 0)]
#[test_case(" cd", "/^ab|cd$/", 0)]
#[test_case("cd", "/^ab|cd$/", 1)]
#[test_case("abcd", "/^ab|cd$/", 0)]
#[test_case("abcd", "/ab|cd$/", 1)]
#[test_case("abc", "/abc/", 1)]
#[test_case("123abc", "/abc/", 1)]
#[test_case("123abc456", "/abc/", 1)]
#[test_case("123abdc456", "/abc/", 0)]
#[test_case("abc456", "/abc/", 1)]
#[test_case("bc", "/a*bc/", 1)]
#[test_case("cdaabc", "/a*bc/", 1)]
#[test_case("cdbc", "/a+bc/", 0)]
#[test_case("bc", "/a+bc/", 0)]
#[test_case("Ab", "/ab/i", 1 ; "ab case insensitive")]
#[test_case("Ab", "/ab/", 0 ; "ab case sensitive")]
#[test_case("cD", "/ab|cd/i", 1)]
#[test_case("cD", "/cD/", 1)]
#[test_case("test a num 8", "/8/", 1)]
#[test_case("test a num 8", "/^8/", 0)]
#[test_case("4453", "/^[0-9]*$/", 1)]
#[test_case("4453", "/^[09]*$/", 0)]
#[test_case("09009", "/^[09]*$/", 1)]
#[test_case("de", "/^ab|cd|de$/", 1 ; "multiple or")]
#[test_case(" de", "/^ab|cd|de$/", 0 ; "multiple or nests below ^")]
fn test_has_match(content: &str, pattern: &str, exp: u64) {
let ct_content: StringCiphertext = encrypt_str(&KEYS.0, content).unwrap();
let ct_res = has_match(&KEYS.1, &ct_content, pattern).unwrap();
let got = KEYS.0.decrypt(&ct_res);
assert_eq!(exp, got);
}
}

View File

@@ -1,272 +0,0 @@
use std::collections::HashMap;
use std::rc::Rc;
use tfhe::integer::{RadixCiphertext, ServerKey};
use crate::parser::u8_to_char;
#[derive(Clone, PartialEq, Eq, Hash)]
pub(crate) enum Executed {
Constant { c: u8 },
CtPos { at: usize },
And { a: Box<Executed>, b: Box<Executed> },
Or { a: Box<Executed>, b: Box<Executed> },
Equal { a: Box<Executed>, b: Box<Executed> },
GreaterOrEqual { a: Box<Executed>, b: Box<Executed> },
LessOrEqual { a: Box<Executed>, b: Box<Executed> },
Not { a: Box<Executed> },
}
type ExecutedResult = (RadixCiphertext, Executed);
impl Executed {
pub(crate) fn ct_pos(at: usize) -> Self {
Executed::CtPos { at }
}
fn get_trivial_constant(&self) -> Option<u8> {
match self {
Self::Constant { c } => Some(*c),
_ => None,
}
}
}
const CT_FALSE: u8 = 0;
const CT_TRUE: u8 = 1;
pub(crate) struct Execution {
sk: ServerKey,
cache: HashMap<Executed, RadixCiphertext>,
ct_ops: usize,
cache_hits: usize,
}
pub(crate) type LazyExecution = Rc<dyn Fn(&mut Execution) -> ExecutedResult>;
impl Execution {
pub(crate) fn new(sk: ServerKey) -> Self {
Self {
sk,
cache: HashMap::new(),
ct_ops: 0,
cache_hits: 0,
}
}
pub(crate) fn ct_operations_count(&self) -> usize {
self.ct_ops
}
pub(crate) fn cache_hits(&self) -> usize {
self.cache_hits
}
pub(crate) fn ct_eq(&mut self, a: ExecutedResult, b: ExecutedResult) -> ExecutedResult {
let ctx = Executed::Equal {
a: Box::new(a.1.clone()),
b: Box::new(b.1.clone()),
};
self.with_cache(
ctx.clone(),
Rc::new(move |exec: &mut Execution| {
exec.ct_ops += 1;
let mut ct_a = a.0.clone();
let mut ct_b = b.0.clone();
(exec.sk.smart_eq(&mut ct_a, &mut ct_b), ctx.clone())
}),
)
}
pub(crate) fn ct_ge(&mut self, a: ExecutedResult, b: ExecutedResult) -> ExecutedResult {
let ctx = Executed::GreaterOrEqual {
a: Box::new(a.1.clone()),
b: Box::new(b.1.clone()),
};
self.with_cache(
ctx.clone(),
Rc::new(move |exec| {
exec.ct_ops += 1;
let mut ct_a = a.0.clone();
let mut ct_b = b.0.clone();
(exec.sk.smart_gt(&mut ct_a, &mut ct_b), ctx.clone())
}),
)
}
pub(crate) fn ct_le(&mut self, a: ExecutedResult, b: ExecutedResult) -> ExecutedResult {
let ctx = Executed::LessOrEqual {
a: Box::new(a.1.clone()),
b: Box::new(b.1.clone()),
};
self.with_cache(
ctx.clone(),
Rc::new(move |exec| {
exec.ct_ops += 1;
let mut ct_a = a.0.clone();
let mut ct_b = b.0.clone();
(exec.sk.smart_le(&mut ct_a, &mut ct_b), ctx.clone())
}),
)
}
pub(crate) fn ct_and(&mut self, a: ExecutedResult, b: ExecutedResult) -> ExecutedResult {
let ctx = Executed::And {
a: Box::new(a.1.clone()),
b: Box::new(b.1.clone()),
};
let c_a = a.1.get_trivial_constant();
let c_b = b.1.get_trivial_constant();
if c_a == Some(CT_TRUE) {
return (b.0, ctx);
}
if c_a == Some(CT_FALSE) {
return (a.0, ctx);
}
if c_b == Some(CT_TRUE) {
return (a.0, ctx);
}
if c_b == Some(CT_FALSE) {
return (b.0, ctx);
}
self.with_cache(
ctx.clone(),
Rc::new(move |exec| {
exec.ct_ops += 1;
let mut ct_a = a.0.clone();
let mut ct_b = b.0.clone();
(exec.sk.smart_bitand(&mut ct_a, &mut ct_b), ctx.clone())
}),
)
}
pub(crate) fn ct_or(&mut self, a: ExecutedResult, b: ExecutedResult) -> ExecutedResult {
let ctx = Executed::Or {
a: Box::new(a.1.clone()),
b: Box::new(b.1.clone()),
};
let c_a = a.1.get_trivial_constant();
let c_b = b.1.get_trivial_constant();
if c_a == Some(CT_TRUE) {
return (a.0, ctx);
}
if c_b == Some(CT_TRUE) {
return (b.0, ctx);
}
if c_a == Some(CT_FALSE) && c_b == Some(CT_FALSE) {
return (a.0, ctx);
}
self.with_cache(
ctx.clone(),
Rc::new(move |exec| {
exec.ct_ops += 1;
let mut ct_a = a.0.clone();
let mut ct_b = b.0.clone();
(exec.sk.smart_bitor(&mut ct_a, &mut ct_b), ctx.clone())
}),
)
}
pub(crate) fn ct_not(&mut self, a: ExecutedResult) -> ExecutedResult {
let ctx = Executed::Not {
a: Box::new(a.1.clone()),
};
self.with_cache(
ctx.clone(),
Rc::new(move |exec| {
exec.ct_ops += 1;
let mut ct_a = a.0.clone();
let mut ct_b = exec.ct_constant(1).0;
(exec.sk.smart_bitxor(&mut ct_a, &mut ct_b), ctx.clone())
}),
)
}
pub(crate) fn ct_false(&self) -> ExecutedResult {
self.ct_constant(CT_FALSE)
}
pub(crate) fn ct_true(&self) -> ExecutedResult {
self.ct_constant(CT_TRUE)
}
pub(crate) fn ct_constant(&self, c: u8) -> ExecutedResult {
(
self.sk.create_trivial_radix(c as u64, 4),
Executed::Constant { c },
)
}
fn with_cache(&mut self, ctx: Executed, f: LazyExecution) -> ExecutedResult {
if let Some(res) = self.cache.get(&ctx) {
trace!("cache hit: {:?}", &ctx);
self.cache_hits += 1;
return (res.clone(), ctx);
}
debug!("evaluation for: {:?}", &ctx);
let res = f(self);
self.cache.insert(ctx, res.0.clone());
res
}
}
impl std::fmt::Debug for Executed {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Constant { c } => match c {
0 => write!(f, "f"),
1 => write!(f, "t"),
_ => write!(f, "{}", u8_to_char(*c)),
},
Self::CtPos { at } => write!(f, "ct_{}", at),
Self::And { a, b } => {
write!(f, "(")?;
a.fmt(f)?;
write!(f, "/\\")?;
b.fmt(f)?;
write!(f, ")")
}
Self::Or { a, b } => {
write!(f, "(")?;
a.fmt(f)?;
write!(f, "\\/")?;
b.fmt(f)?;
write!(f, ")")
}
Self::Equal { a, b } => {
write!(f, "(")?;
a.fmt(f)?;
write!(f, "==")?;
b.fmt(f)?;
write!(f, ")")
}
Self::GreaterOrEqual { a, b } => {
write!(f, "(")?;
a.fmt(f)?;
write!(f, ">=")?;
b.fmt(f)?;
write!(f, ")")
}
Self::LessOrEqual { a, b } => {
write!(f, "(")?;
a.fmt(f)?;
write!(f, "<=")?;
b.fmt(f)?;
write!(f, ")")
}
Self::Not { a } => {
write!(f, "(!")?;
a.fmt(f)?;
write!(f, ")")
}
}
}
}

View File

@@ -1,30 +0,0 @@
#[macro_use]
extern crate log;
mod ciphertext;
mod engine;
mod execution;
mod parser;
use env_logger::Env;
use std::env;
fn main() {
let env = Env::default().filter_or("RUST_LOG", "info");
env_logger::init_from_env(env);
let args: Vec<String> = env::args().collect();
let content = &args[1];
let pattern = &args[2];
let (client_key, server_key) = ciphertext::gen_keys();
let ct_content = ciphertext::encrypt_str(&client_key, content).unwrap();
let ct_res = engine::has_match(&server_key, &ct_content, pattern).unwrap();
let res: u64 = client_key.decrypt(&ct_res);
if res == 0 {
println!("no match");
} else {
println!("match");
}
}

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