mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-11 07:38:08 -05:00
Compare commits
115 Commits
0.1.3
...
blockchain
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cd3d2d75a | ||
|
|
b3fc2378cc | ||
|
|
05de72602c | ||
|
|
bcc791a88d | ||
|
|
4d34f93621 | ||
|
|
61459fd3d0 | ||
|
|
589238267b | ||
|
|
d35c595d89 | ||
|
|
5642880a1e | ||
|
|
d8a0067aff | ||
|
|
983f94d64f | ||
|
|
5f14bf1cca | ||
|
|
5589572efc | ||
|
|
fc1f322f5a | ||
|
|
55ae47bed6 | ||
|
|
db96573595 | ||
|
|
d763131b41 | ||
|
|
d0650ad6ac | ||
|
|
43f47806d2 | ||
|
|
45505cb198 | ||
|
|
5190aee8ab | ||
|
|
6568d7f807 | ||
|
|
7559c22b7e | ||
|
|
cc4587d010 | ||
|
|
b4c321835c | ||
|
|
a87849789d | ||
|
|
bfc111a2db | ||
|
|
7cec0d40fb | ||
|
|
0bdb72466c | ||
|
|
6053ee5610 | ||
|
|
9233b4a7c3 | ||
|
|
60ff266bc1 | ||
|
|
5f284a7c55 | ||
|
|
27d54d19ba | ||
|
|
315f5b68ab | ||
|
|
2d73edc5a3 | ||
|
|
2de0ce4d36 | ||
|
|
45eb5ababd | ||
|
|
869eace723 | ||
|
|
97d536b810 | ||
|
|
2ba22da596 | ||
|
|
b57b6acd8a | ||
|
|
2790b08d39 | ||
|
|
7e73b8ea78 | ||
|
|
bc3fe8e0db | ||
|
|
07e567f279 | ||
|
|
9abe5c01fe | ||
|
|
c8af9c096b | ||
|
|
9db65bc3b7 | ||
|
|
0e4c37ed71 | ||
|
|
4564182894 | ||
|
|
1311b0d18b | ||
|
|
94c023d810 | ||
|
|
57060aa509 | ||
|
|
8eb5b26edb | ||
|
|
03e52562ed | ||
|
|
2384a00696 | ||
|
|
22699d2c2a | ||
|
|
deb4909f0d | ||
|
|
8094067759 | ||
|
|
c170602f3a | ||
|
|
533e37e6ee | ||
|
|
ce771791b6 | ||
|
|
e9e5e75954 | ||
|
|
402cd62fda | ||
|
|
9b7559b2dc | ||
|
|
c7e1cae9d9 | ||
|
|
e6a2e2e6e5 | ||
|
|
e94d84bed1 | ||
|
|
2225eb712a | ||
|
|
28370cd7c3 | ||
|
|
61052df95d | ||
|
|
e123524397 | ||
|
|
75348e273a | ||
|
|
e6d8130c65 | ||
|
|
c23f65dd8f | ||
|
|
7265dbc78b | ||
|
|
bd25256efb | ||
|
|
1a2ed8c3f7 | ||
|
|
6ba6ddddc5 | ||
|
|
a2b6cab69e | ||
|
|
4adef7844d | ||
|
|
d407bb0213 | ||
|
|
1fd1c3516e | ||
|
|
a0932f979c | ||
|
|
9ab75b646f | ||
|
|
bc54f357a5 | ||
|
|
5aa1b1ebf2 | ||
|
|
c92f2bd837 | ||
|
|
fe2263e67d | ||
|
|
31be933571 | ||
|
|
86b348f0c3 | ||
|
|
506fd88468 | ||
|
|
da1592f997 | ||
|
|
d38edb5096 | ||
|
|
6d6fcb9562 | ||
|
|
9a2212e305 | ||
|
|
b8d437cbde | ||
|
|
b89ca6fd87 | ||
|
|
d92bcb3ef4 | ||
|
|
34011798f5 | ||
|
|
3e192630d5 | ||
|
|
76ec565217 | ||
|
|
0c7159c040 | ||
|
|
8ea446a105 | ||
|
|
3c5ffca775 | ||
|
|
cc67dc9bb6 | ||
|
|
0891ea5551 | ||
|
|
45fb747c20 | ||
|
|
dc9c651d3b | ||
|
|
95646ca03a | ||
|
|
2d4b8e3aa3 | ||
|
|
352a2c69ab | ||
|
|
d7b7b84f5b | ||
|
|
ca16a80dfb |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
xtask = "run --manifest-path ./tasks/Cargo.toml --"
|
||||
11
.github/workflows/aws_tfhe_tests.yml
vendored
11
.github/workflows/aws_tfhe_tests.yml
vendored
@@ -22,6 +22,12 @@ on:
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
matrix_item:
|
||||
description: 'Build matrix item'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
shortint-tests:
|
||||
@@ -36,6 +42,7 @@ jobs:
|
||||
echo "ID: ${{ github.event.inputs.instance_id }}"
|
||||
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
|
||||
echo "Type: ${{ github.event.inputs.instance_type }}"
|
||||
echo "Request ID: ${{ github.event.inputs.request_id }}"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -61,6 +68,10 @@ jobs:
|
||||
run: |
|
||||
make test_user_doc
|
||||
|
||||
- name: Run boolean tests
|
||||
run: |
|
||||
make test_boolean
|
||||
|
||||
- name: Install AWS CLI
|
||||
run: |
|
||||
apt update
|
||||
|
||||
113
.github/workflows/aws_tfhe_tests_w_gpu.yml
vendored
113
.github/workflows/aws_tfhe_tests_w_gpu.yml
vendored
@@ -1,113 +0,0 @@
|
||||
# Compile and test project on an AWS instance
|
||||
name: AWS tests on GPU
|
||||
|
||||
# This workflow is meant to be run via Zama CI bot Slab.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: "AWS instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: "AWS instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: "AWS EC2 instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: "-C target-cpu=native"
|
||||
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
jobs:
|
||||
run-tests-linux:
|
||||
concurrency:
|
||||
group: ${{ github.ref }}_${{ github.event.inputs.instance_image_id }}_${{ github.event.inputs.instance_type }}
|
||||
cancel-in-progress: true
|
||||
name: Test code in EC2
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# explicit include-based build matrix, of known valid options
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
cuda: "11.8"
|
||||
old_cuda: "11.1"
|
||||
cuda_arch: "70"
|
||||
gcc: 8
|
||||
env:
|
||||
CUDA_PATH: /usr/local/cuda-${{ matrix.cuda }}
|
||||
OLD_CUDA_PATH: /usr/local/cuda-${{ matrix.old_cuda }}
|
||||
|
||||
steps:
|
||||
- name: EC2 instance configuration used
|
||||
run: |
|
||||
echo "IDs: ${{ github.event.inputs.instance_id }}"
|
||||
echo "AMI: ${{ github.event.inputs.instance_image_id }}"
|
||||
echo "Type: ${{ github.event.inputs.instance_type }}"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up home
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
- name: Export CUDA variables
|
||||
run: |
|
||||
echo "CUDA_PATH=$CUDA_PATH" >> "${GITHUB_ENV}"
|
||||
echo "$CUDA_PATH/bin" >> "${GITHUB_PATH}"
|
||||
echo "LD_LIBRARY_PATH=$CUDA_PATH/lib:$LD_LIBRARY_PATH" >> "${GITHUB_ENV}"
|
||||
# Specify the correct host compilers
|
||||
- name: Export gcc and g++ variables
|
||||
run: |
|
||||
echo "CC=/usr/bin/gcc-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
|
||||
echo "CXX=/usr/bin/g++-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
|
||||
echo "CUDAHOSTCXX=/usr/bin/g++-${{ matrix.gcc }}" >> "${GITHUB_ENV}"
|
||||
echo "CUDACXX=$CUDA_PATH/bin/nvcc" >> "${GITHUB_ENV}"
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
- name: Install latest stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Cuda clippy
|
||||
run: |
|
||||
make clippy_cuda
|
||||
|
||||
- name: Run core cuda tests
|
||||
run: |
|
||||
make test_core_crypto_cuda
|
||||
|
||||
- name: Test tfhe-rs/boolean with cpu
|
||||
run: |
|
||||
make test_boolean
|
||||
|
||||
- name: Test tfhe-rs/boolean with cuda backend with CUDA 11.8
|
||||
run: |
|
||||
make test_boolean_cuda
|
||||
|
||||
- name: Export variables for CUDA 11.1
|
||||
run: |
|
||||
echo "CUDA_PATH=$OLD_CUDA_PATH" >> "${GITHUB_ENV}"
|
||||
echo "LD_LIBRARY_PATH=$OLD_CUDA_PATH/lib:$LD_LIBRARY_PATH" >> "${GITHUB_ENV}"
|
||||
echo "CUDACXX=$OLD_CUDA_PATH/bin/nvcc" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Test tfhe-rs/boolean with cuda backend with CUDA 11.1
|
||||
run: |
|
||||
cargo clean
|
||||
make test_boolean_cuda
|
||||
|
||||
- name: Slack Notification
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
|
||||
env:
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
|
||||
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
|
||||
SLACK_MESSAGE: "(Slab ci-bot beta) AWS tests GPU finished with status ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
|
||||
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
120
.github/workflows/boolean_benchmark.yml
vendored
Normal file
120
.github/workflows/boolean_benchmark.yml
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
# Run boolean benchmarks on an AWS instance and return parsed results to Slab CI bot.
|
||||
name: Boolean benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: 'Instance ID'
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: 'Instance AMI ID'
|
||||
type: string
|
||||
instance_type:
|
||||
description: 'Instance product type'
|
||||
type: string
|
||||
runner_name:
|
||||
description: 'Action runner name'
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
matrix_item:
|
||||
description: 'Build matrix item'
|
||||
type: string
|
||||
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
|
||||
|
||||
jobs:
|
||||
run-boolean-benchmarks:
|
||||
name: Execute boolean benchmarks in EC2
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Instance configuration used
|
||||
run: |
|
||||
echo "IDs: ${{ inputs.instance_id }}"
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
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@v3
|
||||
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@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
RUSTFLAGS="-C target-cpu=native" cargo +nightly bench --bench boolean-bench --features=x86_64-unix,boolean,internal-keycache -p tfhe
|
||||
|
||||
- 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_benchmarks \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--project-version "${COMMIT_HASH}" \
|
||||
--branch ${{ github.ref_name }} \
|
||||
--commit-date "${COMMIT_DATE}" \
|
||||
--bench-date "${{ env.BENCH_DATE }}"
|
||||
|
||||
- name: Remove previous raw results
|
||||
run: |
|
||||
rm -rf target/criterion
|
||||
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
RUSTFLAGS="-C target-cpu=native" cargo +nightly bench --bench boolean-bench --features=x86_64-unix,boolean,internal-keycache,nightly-avx512 -p tfhe
|
||||
|
||||
- name: Parse AVX512 results
|
||||
run: |
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--name-suffix avx512 \
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ github.sha }}_boolean
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
|
||||
|
||||
- name: Send data to Slab
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Computing HMac on downloaded artifact"
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
|
||||
echo "Sending results to Slab..."
|
||||
curl -v -k \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: store_data" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @${{ env.RESULTS_FILENAME }} \
|
||||
${{ secrets.SLAB_URL }}
|
||||
27
.github/workflows/cargo_build.yml
vendored
27
.github/workflows/cargo_build.yml
vendored
@@ -28,42 +28,25 @@ jobs:
|
||||
run: |
|
||||
echo "rs-toolchain=$(make rs_toolchain)" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Check format
|
||||
- name: Run pcc checks
|
||||
run: |
|
||||
make check_fmt
|
||||
|
||||
- name: Build doc
|
||||
run: |
|
||||
make doc
|
||||
|
||||
- name: Clippy boolean
|
||||
run: |
|
||||
make clippy_boolean
|
||||
make pcc
|
||||
|
||||
- name: Build Release boolean
|
||||
run: |
|
||||
make build_boolean
|
||||
|
||||
- name: Clippy shortint
|
||||
run: |
|
||||
make clippy_shortint
|
||||
|
||||
- name: Build Release shortint
|
||||
run: |
|
||||
make build_shortint
|
||||
|
||||
- name: Clippy shortint and boolean
|
||||
run: |
|
||||
make clippy
|
||||
|
||||
- name: Build Release shortint and boolean
|
||||
run: |
|
||||
make build_boolean_and_shortint
|
||||
|
||||
- name: C API Clippy
|
||||
run: |
|
||||
make clippy_c_api
|
||||
|
||||
- name: Build Release c_api
|
||||
run: |
|
||||
make build_c_api
|
||||
|
||||
# The wasm build check is a bit annoying to set-up here and is done during the tests in
|
||||
# aws_tfhe_tests.yml
|
||||
|
||||
3
.github/workflows/check_commit.yml
vendored
3
.github/workflows/check_commit.yml
vendored
@@ -2,9 +2,6 @@
|
||||
name: Check commit and PR compliance
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
jobs:
|
||||
check-commit-pr:
|
||||
name: Check commit and PR
|
||||
|
||||
20
.github/workflows/m1_tests.yml
vendored
20
.github/workflows/m1_tests.yml
vendored
@@ -28,38 +28,22 @@ jobs:
|
||||
toolchain: stable
|
||||
default: true
|
||||
|
||||
- name: Build doc
|
||||
- name: Run pcc checks
|
||||
run: |
|
||||
make doc
|
||||
|
||||
- name: Clippy boolean
|
||||
run: |
|
||||
make clippy_boolean
|
||||
make pcc
|
||||
|
||||
- name: Build Release boolean
|
||||
run: |
|
||||
make build_boolean
|
||||
|
||||
- name: Clippy shortint
|
||||
run: |
|
||||
make clippy_shortint
|
||||
|
||||
- name: Build Release shortint
|
||||
run: |
|
||||
make build_shortint
|
||||
|
||||
- name: Clippy shortint and boolean
|
||||
run: |
|
||||
make clippy
|
||||
|
||||
- name: Build Release shortint and boolean
|
||||
run: |
|
||||
make build_boolean_and_shortint
|
||||
|
||||
- name: C API Clippy
|
||||
run: |
|
||||
make clippy_c_api
|
||||
|
||||
- name: Build Release c_api
|
||||
run: |
|
||||
make build_c_api
|
||||
|
||||
122
.github/workflows/shortint_benchmark.yml
vendored
Normal file
122
.github/workflows/shortint_benchmark.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# Run shortint benchmarks on an AWS instance and return parsed results to Slab CI bot.
|
||||
name: Shortint benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: 'Instance ID'
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: 'Instance AMI ID'
|
||||
type: string
|
||||
instance_type:
|
||||
description: 'Instance product type'
|
||||
type: string
|
||||
runner_name:
|
||||
description: 'Action runner name'
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
matrix_item:
|
||||
description: 'Build matrix item'
|
||||
type: string
|
||||
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
|
||||
|
||||
jobs:
|
||||
run-shortint-benchmarks:
|
||||
name: Execute shortint benchmarks in EC2
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Instance configuration used
|
||||
run: |
|
||||
echo "IDs: ${{ inputs.instance_id }}"
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
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@v3
|
||||
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@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
RUSTFLAGS="-C target-cpu=native" cargo +nightly bench --bench shortint-bench --features=x86_64-unix,shortint,internal-keycache -p tfhe
|
||||
|
||||
- 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_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: Remove previous raw results
|
||||
run: |
|
||||
rm -rf target/criterion
|
||||
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
RUSTFLAGS="-C target-cpu=native" cargo +nightly bench --bench shortint-bench --features=x86_64-unix,shortint,internal-keycache,nightly-avx512 -p tfhe
|
||||
|
||||
- name: Parse AVX512 results
|
||||
run: |
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--walk-subdirs \
|
||||
--name-suffix avx512 \
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ github.sha }}_shortint
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
|
||||
|
||||
- name: Send data to Slab
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Computing HMac on downloaded artifact"
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
|
||||
echo "Sending results to Slab..."
|
||||
curl -v -k \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: store_data" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @${{ env.RESULTS_FILENAME }} \
|
||||
${{ secrets.SLAB_URL }}
|
||||
36
.github/workflows/start_benchmarks.yml
vendored
Normal file
36
.github/workflows/start_benchmarks.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Start all benchmark jobs on Slab CI bot.
|
||||
name: Start all benchmarks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
start-benchmarks:
|
||||
strategy:
|
||||
matrix:
|
||||
command: [boolean_bench, shortint_bench]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
|
||||
|
||||
- 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 \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: start_aws" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @command.json \
|
||||
${{ secrets.SLAB_URL }}
|
||||
30
.github/workflows/sync_on_push.yml
vendored
Normal file
30
.github/workflows/sync_on_push.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Sync repos
|
||||
name: Sync repos
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-repo:
|
||||
if: ${{ github.repository == 'zama-ai/tfhe-rs' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Save repo
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: repo-archive
|
||||
path: '.'
|
||||
- name: git-sync
|
||||
uses: wei/git-sync@v3
|
||||
with:
|
||||
source_repo: "zama-ai/tfhe-rs"
|
||||
source_branch: "main"
|
||||
destination_repo: ${{ secrets.SYNC_DEST_REPO }}
|
||||
destination_branch: "main"
|
||||
@@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["tfhe"]
|
||||
members = ["tfhe", "tasks"]
|
||||
|
||||
[profile.bench]
|
||||
lto = "fat"
|
||||
|
||||
61
LICENSE
61
LICENSE
@@ -1,33 +1,28 @@
|
||||
BSD 3-Clause Clear License
|
||||
|
||||
Copyright © 2022 ZAMA.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE*.
|
||||
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
ZAMA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*In addition to the rights carried by this license, ZAMA grants to the user a non-exclusive,
|
||||
free and non-commercial license on all patents filed in its name relating to the open-source
|
||||
code (the "Patents") for the sole purpose of evaluation, development, research, prototyping
|
||||
and experimentation.
|
||||
BSD 3-Clause Clear License
|
||||
|
||||
Copyright © 2022 ZAMA.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
ZAMA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
71
Makefile
71
Makefile
@@ -5,6 +5,7 @@ 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)
|
||||
MIN_RUST_VERSION:=1.65
|
||||
# This is done to avoid forgetting it, we still precise the RUSTFLAGS in the commands to be able to
|
||||
# copy paste the command in the termianl and change them if required without forgetting the flags
|
||||
export RUSTFLAGS:=-C target-cpu=native
|
||||
@@ -21,21 +22,24 @@ rs_build_toolchain:
|
||||
install_rs_check_toolchain:
|
||||
@rustup toolchain list | grep -q "$(RS_CHECK_TOOLCHAIN)" || \
|
||||
rustup toolchain install --profile default "$(RS_CHECK_TOOLCHAIN)" || \
|
||||
echo "Unable to install $(RS_CHECK_TOOLCHAIN) toolchain, check your rustup installation. \
|
||||
Rustup can be downloaded at https://rustup.rs/"
|
||||
( echo "Unable to install $(RS_CHECK_TOOLCHAIN) toolchain, check your rustup installation. \
|
||||
Rustup can be downloaded at https://rustup.rs/" && exit 1 )
|
||||
|
||||
.PHONY: install_rs_build_toolchain # Install the toolchain used for builds
|
||||
install_rs_build_toolchain:
|
||||
@rustup toolchain list | grep -q "$(RS_BUILD_TOOLCHAIN)" || \
|
||||
@( rustup toolchain list | grep -q "$(RS_BUILD_TOOLCHAIN)" && \
|
||||
./scripts/check_cargo_min_ver.sh \
|
||||
--rust-toolchain "$(CARGO_RS_BUILD_TOOLCHAIN)" \
|
||||
--min-rust-version "$(MIN_RUST_VERSION)" ) || \
|
||||
rustup toolchain install --profile default "$(RS_BUILD_TOOLCHAIN)" || \
|
||||
echo "Unable to install $(RS_BUILD_TOOLCHAIN) toolchain, check your rustup installation. \
|
||||
Rustup can be downloaded at https://rustup.rs/"
|
||||
( echo "Unable to install $(RS_BUILD_TOOLCHAIN) toolchain, check your rustup installation. \
|
||||
Rustup can be downloaded at https://rustup.rs/" && exit 1 )
|
||||
|
||||
.PHONY: install_cargo_nextest # Install cargo nextest used for shortint tests
|
||||
install_cargo_nextest: install_rs_build_toolchain
|
||||
@cargo nextest --version > /dev/null 2>&1 || \
|
||||
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-nextest --locked || \
|
||||
echo "Unable to install cargo nextest, unknown error."
|
||||
( echo "Unable to install cargo nextest, unknown error." && exit 1 )
|
||||
|
||||
.PHONY: fmt # Format rust code
|
||||
fmt: install_rs_check_toolchain
|
||||
@@ -69,12 +73,27 @@ clippy_c_api: install_rs_check_toolchain
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_cuda # Run clippy lints enabling the boolean, shortint, cuda and c API features
|
||||
clippy_cuda: install_rs_check_toolchain
|
||||
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint and the js wasm API
|
||||
clippy_js_wasm_api: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
--features=$(TARGET_ARCH_FEATURE),cuda,boolean-c-api,shortint-c-api \
|
||||
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_tasks # Run clippy lints on helper tasks crate.
|
||||
clippy_tasks:
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
-p tasks -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_all_targets # Run clippy lints on all targets (benches, examples, etc.)
|
||||
clippy_all_targets:
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_all # Run all clippy targets
|
||||
clippy_all: clippy clippy_boolean clippy_shortint clippy_all_targets clippy_c_api \
|
||||
clippy_js_wasm_api clippy_tasks
|
||||
|
||||
.PHONY: gen_key_cache # Run the script to generate keys and cache them for shortint tests
|
||||
gen_key_cache: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --release \
|
||||
@@ -98,7 +117,7 @@ build_boolean_and_shortint: install_rs_build_toolchain
|
||||
|
||||
.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
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api -p tfhe
|
||||
|
||||
.PHONY: test_core_crypto # Run the tests of the core_crypto module
|
||||
@@ -106,21 +125,11 @@ test_core_crypto: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
--features=$(TARGET_ARCH_FEATURE) -p tfhe -- core_crypto::
|
||||
|
||||
.PHONY: test_core_crypto_cuda # Run the tests of the core_crypto module with cuda enabled
|
||||
test_core_crypto_cuda: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),cuda -p tfhe -- core_crypto::backends::cuda::
|
||||
|
||||
.PHONY: test_boolean # Run the tests of the boolean module
|
||||
test_boolean: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
|
||||
|
||||
.PHONY: test_boolean_cuda # Run the tests of the boolean module with cuda enabled
|
||||
test_boolean_cuda: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,cuda -p tfhe -- boolean::
|
||||
|
||||
.PHONY: test_c_api # Run the tests for the C API
|
||||
test_c_api: install_rs_build_toolchain
|
||||
./scripts/c_api_tests.sh $(CARGO_RS_BUILD_TOOLCHAIN)
|
||||
@@ -146,6 +155,26 @@ doc: install_rs_check_toolchain
|
||||
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" doc \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint --no-deps
|
||||
|
||||
.PHONY: format_doc_latex # Format the documentation latex equations to avoid broken rendering.
|
||||
format_doc_latex:
|
||||
cargo xtask format_latex_doc
|
||||
@"$(MAKE)" --no-print-directory fmt
|
||||
@printf "\n===============================\n\n"
|
||||
@printf "Please manually inspect changes made by format_latex_doc, rustfmt can break equations \
|
||||
if the line length is exceeded\n"
|
||||
@printf "\n===============================\n"
|
||||
|
||||
.PHONY: check_compile_tests # Build tests in debug without running them
|
||||
check_compile_tests:
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --no-run \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,boolean,internal-keycache -p tfhe
|
||||
|
||||
.PHONY: pcc # pcc stands for pre commit checks
|
||||
pcc: check_fmt doc clippy_all check_compile_tests
|
||||
|
||||
.PHONY: conformance # Automatically fix problems that can be fixed
|
||||
conformance: fmt
|
||||
|
||||
.PHONY: help # Generate list of targets with descriptions
|
||||
help:
|
||||
@grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1\t\2/' | expand -t30 | sort
|
||||
@grep '^\.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1\t\2/' | expand -t30 | sort
|
||||
|
||||
@@ -38,10 +38,10 @@ production-ready library for all the advanced features of TFHE.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
To use the latest version of `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.1.0", features = [ "boolean","shortint","x86_64-unix" ] }
|
||||
tfhe = { version = "*", features = [ "boolean","shortint","x86_64-unix" ] }
|
||||
```
|
||||
|
||||
Here is a full example evaluating a Boolean circuit:
|
||||
|
||||
163
ci/benchmark_parser.py
Normal file
163
ci/benchmark_parser.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
benchmark_parser
|
||||
----------------
|
||||
|
||||
Parse criterion benchmark results.
|
||||
"""
|
||||
import argparse
|
||||
import pathlib
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('results_dir',
|
||||
help='Location of criterion benchmark results directory')
|
||||
parser.add_argument('output_file', help='File storing parsed results')
|
||||
parser.add_argument('-d', '--database', dest='database',
|
||||
help='Name of the database used to store results')
|
||||
parser.add_argument('-w', '--hardware', dest='hardware',
|
||||
help='Hardware reference used to perform benchmark')
|
||||
parser.add_argument('-V', '--project-version', dest='project_version',
|
||||
help='Commit hash reference')
|
||||
parser.add_argument('-b', '--branch', dest='branch',
|
||||
help='Git branch name on which benchmark was performed')
|
||||
parser.add_argument('--commit-date', dest='commit_date',
|
||||
help='Timestamp of commit hash used in project_version')
|
||||
parser.add_argument('--bench-date', dest='bench_date',
|
||||
help='Timestamp when benchmark was run')
|
||||
parser.add_argument('--name-suffix', dest='name_suffix', default='',
|
||||
help='Suffix to append to each of the result test names')
|
||||
parser.add_argument('--append-results', dest='append_results', action='store_true',
|
||||
help='Append parsed results to an existing file')
|
||||
parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true',
|
||||
help='Check for results in subdirectories')
|
||||
|
||||
|
||||
def recursive_parse(directory, walk_subdirs=False, name_suffix=""):
|
||||
"""
|
||||
Parse all the benchmark results in a directory. It will attempt to parse all the files having a
|
||||
.json extension at the top-level of this directory.
|
||||
|
||||
:param directory: path to directory that contains raw results as :class:`pathlib.Path`
|
||||
:param walk_subdirs: traverse results subdirectories if parameters changes for benchmark case.
|
||||
:param name_suffix: a :class:`str` suffix to apply to each test name found
|
||||
|
||||
:return: :class:`list` of data points
|
||||
"""
|
||||
excluded_directories = ["child_generate", "fork", "parent_generate", "report"]
|
||||
result_values = list()
|
||||
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:
|
||||
subdir = subdir.joinpath("new")
|
||||
if not subdir.exists():
|
||||
continue
|
||||
elif subdir.name != "new":
|
||||
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({"value": value, "test": "_".join(test_name_parts)})
|
||||
|
||||
return result_values
|
||||
|
||||
|
||||
def parse_benchmark_file(directory):
|
||||
"""
|
||||
Parse file containing details of the parameters used for a benchmark.
|
||||
|
||||
:param directory: directory where a benchmark case results are located as :class:`pathlib.Path`
|
||||
|
||||
:return: name of the test as :class:`str`
|
||||
"""
|
||||
raw_results = _parse_file_to_json(directory, "benchmark.json")
|
||||
return raw_results["full_id"].replace(" ", "_")
|
||||
|
||||
|
||||
def parse_estimate_file(directory):
|
||||
"""
|
||||
Parse file containing timing results for a benchmark.
|
||||
|
||||
:param directory: directory where a benchmark case results are located as :class:`pathlib.Path`
|
||||
|
||||
:return: :class:`dict` of data points
|
||||
"""
|
||||
raw_results = _parse_file_to_json(directory, "estimates.json")
|
||||
return {
|
||||
stat_name: raw_results[stat_name]["point_estimate"]
|
||||
for stat_name in ("mean", "std_dev")
|
||||
}
|
||||
|
||||
|
||||
def _parse_file_to_json(directory, filename):
|
||||
result_file = directory.joinpath(filename)
|
||||
return json.loads(result_file.read_text())
|
||||
|
||||
|
||||
def dump_results(parsed_results, filename, input_args):
|
||||
"""
|
||||
Dump parsed results formatted as JSON to file.
|
||||
|
||||
:param parsed_results: :class:`list` of data points
|
||||
:param filename: filename for dump file as :class:`pathlib.Path`
|
||||
:param input_args: CLI input arguments
|
||||
"""
|
||||
if input_args.append_results:
|
||||
parsed_content = json.loads(filename.read_text())
|
||||
parsed_content["points"].extend(parsed_results)
|
||||
filename.write_text(json.dumps(parsed_content))
|
||||
else:
|
||||
filename.parent.mkdir(parents=True, exist_ok=True)
|
||||
series = {
|
||||
"database": input_args.database,
|
||||
"hardware": input_args.hardware,
|
||||
"project_version": input_args.project_version,
|
||||
"branch": input_args.branch,
|
||||
"insert_date": input_args.bench_date,
|
||||
"commit_date": input_args.commit_date,
|
||||
"points": parsed_results,
|
||||
}
|
||||
filename.write_text(json.dumps(series))
|
||||
|
||||
|
||||
def check_mandatory_args(input_args):
|
||||
"""
|
||||
Check for availability of required input arguments, the program will exit if one of them is
|
||||
not present. If `append_results` flag is set, all the required arguments will be ignored.
|
||||
|
||||
:param input_args: CLI input arguments
|
||||
"""
|
||||
if input_args.append_results:
|
||||
return
|
||||
|
||||
missing_args = list()
|
||||
for arg_name in vars(input_args):
|
||||
if arg_name in ["results_dir", "output_file", "name_suffix",
|
||||
"append_results", "walk_subdirs"]:
|
||||
continue
|
||||
if not getattr(input_args, arg_name):
|
||||
missing_args.append(arg_name)
|
||||
|
||||
if missing_args:
|
||||
for arg_name in missing_args:
|
||||
print(f"Missing required argument: --{arg_name.replace('_', '-')}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
check_mandatory_args(args)
|
||||
|
||||
print("Parsing benchmark results... ")
|
||||
results = recursive_parse(pathlib.Path(args.results_dir), args.walk_subdirs, args.name_suffix)
|
||||
print("Parsing results done")
|
||||
|
||||
output_file = pathlib.Path(args.output_file)
|
||||
print(f"Dump parsed results into '{output_file.resolve()}' ... ", end="")
|
||||
dump_results(results, output_file, args)
|
||||
|
||||
print("Done")
|
||||
27
ci/slab.toml
27
ci/slab.toml
@@ -1,21 +1,24 @@
|
||||
[profile.cpu-big]
|
||||
region = "eu-west-3"
|
||||
image_id = "ami-04deffe45b5b236fd"
|
||||
instance_type = "c5a.8xlarge"
|
||||
instance_type = "c6i.8xlarge"
|
||||
|
||||
[profile.gpu]
|
||||
region = "us-east-1"
|
||||
image_id = "ami-0ae662beb44082155"
|
||||
instance_type = "p3.2xlarge"
|
||||
subnet_id = "subnet-8123c9e7"
|
||||
security_group = "sg-0466d33ced960ba35"
|
||||
[profile.bench]
|
||||
region = "eu-west-3"
|
||||
image_id = "ami-04deffe45b5b236fd"
|
||||
instance_type = "m6i.metal"
|
||||
|
||||
[command.cpu_test]
|
||||
workflow = "aws_tfhe_tests.yml"
|
||||
profile = "cpu-big"
|
||||
check_run_name = "Shortint CPU AWS Tests"
|
||||
check_run_name = "CPU AWS Tests"
|
||||
|
||||
[command.gpu_test]
|
||||
workflow = "aws_tfhe_tests_w_gpu.yml"
|
||||
profile = "gpu"
|
||||
check_run_name = "AWS tests GPU (Slab)"
|
||||
[command.shortint_bench]
|
||||
workflow = "shortint_benchmark.yml"
|
||||
profile = "bench"
|
||||
check_run_name = "Shortint CPU AWS Benchmarks"
|
||||
|
||||
[command.boolean_bench]
|
||||
workflow = "boolean_benchmark.yml"
|
||||
profile = "bench"
|
||||
check_run_name = "Boolean CPU AWS Benchmarks"
|
||||
|
||||
60
scripts/check_cargo_min_ver.sh
Executable file
60
scripts/check_cargo_min_ver.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
function usage() {
|
||||
echo "$0: check minimum cargo version"
|
||||
echo
|
||||
echo "--help Print this message"
|
||||
echo "--rust-toolchain The toolchain to check the version for with leading"
|
||||
echo "--min-rust-version Check toolchain version is >= to this version, default is 1.65"
|
||||
echo
|
||||
}
|
||||
|
||||
RUST_TOOLCHAIN=""
|
||||
# We set the default rust version 1.65 which is the minimum version required for stable GATs
|
||||
MIN_RUST_VERSION="1.65"
|
||||
|
||||
while [ -n "$1" ]
|
||||
do
|
||||
case "$1" in
|
||||
"--help" | "-h" )
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
|
||||
"--rust-toolchain" )
|
||||
shift
|
||||
RUST_TOOLCHAIN="$1"
|
||||
;;
|
||||
|
||||
"--min-rust-version" )
|
||||
shift
|
||||
MIN_RUST_VERSION="$1"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown param : $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ "${RUST_TOOLCHAIN::1}" != "+" ]]; then
|
||||
RUST_TOOLCHAIN="+${RUST_TOOLCHAIN}"
|
||||
fi
|
||||
|
||||
ver_string="$(cargo ${RUST_TOOLCHAIN:+"${RUST_TOOLCHAIN}"} --version | \
|
||||
cut -d ' ' -f 2 | cut -d '-' -f 1)"
|
||||
ver_major="$(echo "${ver_string}" | cut -d '.' -f 1)"
|
||||
ver_minor="$(echo "${ver_string}" | cut -d '.' -f 2)"
|
||||
|
||||
min_ver_major="$(echo "${MIN_RUST_VERSION}" | cut -d '.' -f 1)"
|
||||
min_ver_minor="$(echo "${MIN_RUST_VERSION}" | cut -d '.' -f 2)"
|
||||
|
||||
if [[ "${ver_major}" -ge "${min_ver_major}" ]] && [[ "${ver_minor}" -ge "${min_ver_minor}" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 1
|
||||
@@ -50,6 +50,18 @@ cargo ${1:+"${1}"} nextest run \
|
||||
--test-threads "${n_threads}" \
|
||||
-E "${filter_expression}"
|
||||
|
||||
filter_expression_wopbs='test(/^shortint::wopbs::.*$/)'
|
||||
|
||||
# Run tests only no examples or benches for wopbs
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
--tests \
|
||||
--release \
|
||||
--package tfhe \
|
||||
--profile ci \
|
||||
--features="${ARCH_FEATURE}",shortint,internal-keycache \
|
||||
--test-threads "${n_threads}" \
|
||||
-E "${filter_expression_wopbs}"
|
||||
|
||||
cargo ${1:+"${1}"} test \
|
||||
--release \
|
||||
--package tfhe \
|
||||
|
||||
12
tasks/Cargo.toml
Normal file
12
tasks/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "tasks"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = "3.1"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
simplelog = "0.12"
|
||||
453
tasks/src/format_latex_doc.rs
Normal file
453
tasks/src/format_latex_doc.rs
Normal file
@@ -0,0 +1,453 @@
|
||||
use crate::utils::project_root;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::{fmt, fs};
|
||||
|
||||
fn recurse_find_rs_files(
|
||||
root_dir: std::path::PathBuf,
|
||||
rs_files: &mut Vec<std::path::PathBuf>,
|
||||
at_root: bool,
|
||||
) {
|
||||
for curr_entry in root_dir.read_dir().unwrap() {
|
||||
let curr_path = curr_entry.unwrap().path().canonicalize().unwrap();
|
||||
if curr_path.is_file() {
|
||||
if let Some(extension) = curr_path.extension() {
|
||||
if extension == "rs" {
|
||||
rs_files.push(curr_path);
|
||||
}
|
||||
}
|
||||
} else if curr_path.is_dir() {
|
||||
if at_root {
|
||||
// Hardcoded ignores for root .git and target
|
||||
match curr_path.file_name().unwrap().to_str().unwrap() {
|
||||
".git" => continue,
|
||||
"target" => continue,
|
||||
_ => recurse_find_rs_files(curr_path.to_path_buf(), rs_files, false),
|
||||
};
|
||||
} else {
|
||||
recurse_find_rs_files(curr_path.to_path_buf(), rs_files, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LatexEscapeToolError {
|
||||
details: String,
|
||||
}
|
||||
|
||||
impl LatexEscapeToolError {
|
||||
fn new(msg: &str) -> LatexEscapeToolError {
|
||||
LatexEscapeToolError {
|
||||
details: msg.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LatexEscapeToolError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.details)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LatexEscapeToolError {}
|
||||
|
||||
const DOC_TEST_START: &str = "///";
|
||||
const DOC_COMMENT_START: &str = "//!";
|
||||
const BACKSLASH_UTF8_LEN: usize = '\\'.len_utf8();
|
||||
|
||||
enum LineType {
|
||||
DocTest { code_block_limit: bool },
|
||||
DocComment { code_block_limit: bool },
|
||||
EmptyLine,
|
||||
Other,
|
||||
}
|
||||
|
||||
fn get_line_type_and_trimmed_line(line: &str) -> (LineType, &str) {
|
||||
let mut trimmed_line = line.trim_start();
|
||||
let line_type = if trimmed_line.starts_with(DOC_COMMENT_START) {
|
||||
trimmed_line = trimmed_line
|
||||
.strip_prefix(DOC_COMMENT_START)
|
||||
.unwrap()
|
||||
.trim_start();
|
||||
let has_code_block_limit = trimmed_line.starts_with("```");
|
||||
LineType::DocComment {
|
||||
code_block_limit: has_code_block_limit,
|
||||
}
|
||||
} else if trimmed_line.starts_with(DOC_TEST_START) {
|
||||
trimmed_line = trimmed_line
|
||||
.strip_prefix(DOC_TEST_START)
|
||||
.unwrap()
|
||||
.trim_start();
|
||||
let has_code_block_limit = trimmed_line.starts_with("```");
|
||||
LineType::DocTest {
|
||||
code_block_limit: has_code_block_limit,
|
||||
}
|
||||
} else if trimmed_line.is_empty() {
|
||||
LineType::EmptyLine
|
||||
} else {
|
||||
LineType::Other
|
||||
};
|
||||
(line_type, trimmed_line)
|
||||
}
|
||||
|
||||
struct CommentContent<'a> {
|
||||
is_in_code_block: bool,
|
||||
line_start: &'a str,
|
||||
line_content: &'a str,
|
||||
}
|
||||
|
||||
fn find_contiguous_doc_comment<'a>(
|
||||
lines: &[&'a str],
|
||||
start_line_idx: usize,
|
||||
) -> (Vec<CommentContent<'a>>, usize) {
|
||||
let mut doc_comment_end_line_idx = start_line_idx + 1;
|
||||
|
||||
let mut is_in_code_block = false;
|
||||
let mut contiguous_doc_comment = Vec::<CommentContent>::new();
|
||||
|
||||
for (line_idx, line) in lines.iter().enumerate().skip(start_line_idx) {
|
||||
let (line_type, line_content) = get_line_type_and_trimmed_line(line);
|
||||
|
||||
let line_start = &line[..line.len() - line_content.len()];
|
||||
// If there is an empty line we are still in the DocComment
|
||||
let line_type = if let LineType::EmptyLine = line_type {
|
||||
LineType::DocComment {
|
||||
code_block_limit: false,
|
||||
}
|
||||
} else {
|
||||
line_type
|
||||
};
|
||||
|
||||
match line_type {
|
||||
LineType::DocComment { code_block_limit } => {
|
||||
if code_block_limit {
|
||||
// We have found a code block limit, either starting or ending, toggle the
|
||||
// flag
|
||||
is_in_code_block = !is_in_code_block;
|
||||
};
|
||||
contiguous_doc_comment.push(CommentContent {
|
||||
is_in_code_block,
|
||||
line_start,
|
||||
line_content,
|
||||
});
|
||||
// For now the only thing we know is that the next line is potentially the end of
|
||||
// the comment block, required if a file is a giant comment block to have the proper
|
||||
// bound
|
||||
doc_comment_end_line_idx = line_idx + 1;
|
||||
}
|
||||
_ => {
|
||||
// We are sure that the current line is the end of the comment block
|
||||
doc_comment_end_line_idx = line_idx;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
(contiguous_doc_comment, doc_comment_end_line_idx)
|
||||
}
|
||||
|
||||
fn find_contiguous_doc_test<'a>(
|
||||
lines: &[&'a str],
|
||||
start_line_idx: usize,
|
||||
) -> (Vec<CommentContent<'a>>, usize) {
|
||||
let mut doc_test_end_line_idx = start_line_idx + 1;
|
||||
|
||||
let mut is_in_code_block = false;
|
||||
let mut contiguous_doc_test = Vec::<CommentContent>::new();
|
||||
|
||||
for (line_idx, line) in lines.iter().enumerate().skip(start_line_idx) {
|
||||
let (line_type, line_content) = get_line_type_and_trimmed_line(line);
|
||||
|
||||
let line_start = &line[..line.len() - line_content.len()];
|
||||
// If there is an empty line we are still in the DocTest
|
||||
let line_type = if let LineType::EmptyLine = line_type {
|
||||
LineType::DocTest {
|
||||
code_block_limit: false,
|
||||
}
|
||||
} else {
|
||||
line_type
|
||||
};
|
||||
|
||||
match line_type {
|
||||
LineType::DocTest { code_block_limit } => {
|
||||
if code_block_limit {
|
||||
// We have found a code block limit, either starting or ending, toggle the
|
||||
// flag
|
||||
is_in_code_block = !is_in_code_block;
|
||||
};
|
||||
contiguous_doc_test.push(CommentContent {
|
||||
is_in_code_block,
|
||||
line_start,
|
||||
line_content,
|
||||
});
|
||||
// For now the only thing we know is that the next line is potentially the end of
|
||||
// the comment block, required if a file is a giant comment block to have the proper
|
||||
// bound
|
||||
doc_test_end_line_idx = line_idx + 1;
|
||||
}
|
||||
_ => {
|
||||
// We are sure that the current line is the end of the comment block
|
||||
doc_test_end_line_idx = line_idx;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
(contiguous_doc_test, doc_test_end_line_idx)
|
||||
}
|
||||
|
||||
fn find_contiguous_part_in_doc_test_or_comment(
|
||||
part_is_code_block: bool,
|
||||
full_doc_comment_content: &Vec<CommentContent>,
|
||||
part_start_idx: usize,
|
||||
) -> (usize, usize) {
|
||||
let mut next_line_idx = part_start_idx + 1;
|
||||
loop {
|
||||
// We have exhausted the doc comment content, break
|
||||
if next_line_idx == full_doc_comment_content.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let CommentContent {
|
||||
is_in_code_block: next_line_is_in_code_block,
|
||||
line_start: _,
|
||||
line_content: _,
|
||||
} = full_doc_comment_content[next_line_idx];
|
||||
|
||||
// We check if the next line is in a different part, if so we break
|
||||
if next_line_is_in_code_block != part_is_code_block {
|
||||
break;
|
||||
}
|
||||
next_line_idx += 1;
|
||||
}
|
||||
// next_line_idx points to the end of the part and is therefore returned as the part_stop_idx
|
||||
(part_start_idx, next_line_idx)
|
||||
}
|
||||
|
||||
enum LatexEquationKind {
|
||||
Inline,
|
||||
Multiline,
|
||||
NotAnEquation,
|
||||
}
|
||||
|
||||
fn escape_underscores_rewrite_equations(
|
||||
comment_to_rewrite: &[CommentContent],
|
||||
rewritten_content: &mut String,
|
||||
) -> Result<(), LatexEscapeToolError> {
|
||||
let mut latex_equation_kind = LatexEquationKind::NotAnEquation;
|
||||
for CommentContent {
|
||||
is_in_code_block: _,
|
||||
line_start,
|
||||
line_content,
|
||||
} in comment_to_rewrite.iter()
|
||||
{
|
||||
rewritten_content.push_str(line_start);
|
||||
let mut previous_char = '\0';
|
||||
let mut chars = line_content.chars().peekable();
|
||||
while let Some(current_char) = chars.next() {
|
||||
match (previous_char, current_char) {
|
||||
('$', '$') => {
|
||||
match latex_equation_kind {
|
||||
LatexEquationKind::Inline => {
|
||||
// Problem we find an opening $$ after an opening $, return an error
|
||||
return Err(LatexEscapeToolError::new(
|
||||
"Found an opening '$' without a corresponding closing '$'",
|
||||
));
|
||||
}
|
||||
LatexEquationKind::Multiline => {
|
||||
// Closing $$, no more in a latex equation
|
||||
latex_equation_kind = LatexEquationKind::NotAnEquation
|
||||
}
|
||||
LatexEquationKind::NotAnEquation => {
|
||||
// Opening $$, in a multiline latex equation
|
||||
latex_equation_kind = LatexEquationKind::Multiline
|
||||
}
|
||||
};
|
||||
}
|
||||
(_, '$') => {
|
||||
let is_inline_marker = chars.peek() != Some(&'$');
|
||||
if is_inline_marker {
|
||||
match latex_equation_kind {
|
||||
LatexEquationKind::Multiline => {
|
||||
// Problem we find an opening $ after an opening $$, return an error
|
||||
return Err(LatexEscapeToolError::new(
|
||||
"Found an opening '$$' without a corresponding closing '$$'",
|
||||
));
|
||||
}
|
||||
LatexEquationKind::Inline => {
|
||||
// Closing $, no more in a latex equation
|
||||
latex_equation_kind = LatexEquationKind::NotAnEquation
|
||||
}
|
||||
LatexEquationKind::NotAnEquation => {
|
||||
// Opening $, in an inline latex equation
|
||||
latex_equation_kind = LatexEquationKind::Inline
|
||||
}
|
||||
};
|
||||
}
|
||||
// If the marker is not an inline marker but a multiline marker let the other
|
||||
// case manage it at the next iteration
|
||||
}
|
||||
// If the _ is not escaped and we are in an equation we need to escape it
|
||||
(prev, '_') if prev != '\\' => match latex_equation_kind {
|
||||
LatexEquationKind::NotAnEquation => (),
|
||||
_ => rewritten_content.push('\\'),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
rewritten_content.push(current_char);
|
||||
previous_char = current_char;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_doc_lines_until_impossible<'a>(
|
||||
lines: &[&'a str],
|
||||
rewritten_content: &'a mut String,
|
||||
comment_search_fn: fn(&[&'a str], usize) -> (Vec<CommentContent<'a>>, usize),
|
||||
start_line_idx: usize,
|
||||
) -> Result<usize, LatexEscapeToolError> {
|
||||
let (full_doc_content, doc_end_line_idx) = comment_search_fn(lines, start_line_idx);
|
||||
|
||||
// Now we find code blocks parts OR pure comments parts
|
||||
let mut current_line_in_doc_idx = 0;
|
||||
while current_line_in_doc_idx < full_doc_content.len() {
|
||||
let CommentContent {
|
||||
is_in_code_block,
|
||||
line_start: _,
|
||||
line_content: _,
|
||||
} = full_doc_content[current_line_in_doc_idx];
|
||||
|
||||
let (current_part_start_idx, current_part_stop_idx) =
|
||||
find_contiguous_part_in_doc_test_or_comment(
|
||||
is_in_code_block,
|
||||
&full_doc_content,
|
||||
current_line_in_doc_idx,
|
||||
);
|
||||
|
||||
let current_part_content = &full_doc_content[current_part_start_idx..current_part_stop_idx];
|
||||
|
||||
// The current part is a code block
|
||||
if is_in_code_block {
|
||||
for CommentContent {
|
||||
is_in_code_block: _,
|
||||
line_start,
|
||||
line_content,
|
||||
} in current_part_content.iter()
|
||||
{
|
||||
// We can just push the content unmodified
|
||||
rewritten_content.push_str(line_start);
|
||||
rewritten_content.push_str(line_content);
|
||||
}
|
||||
} else {
|
||||
// The part is a pure comment, we need to rewrite equations
|
||||
escape_underscores_rewrite_equations(current_part_content, rewritten_content)?;
|
||||
}
|
||||
current_line_in_doc_idx += current_part_content.len();
|
||||
}
|
||||
|
||||
Ok(doc_end_line_idx)
|
||||
}
|
||||
|
||||
fn process_non_doc_lines_until_impossible(
|
||||
lines: &Vec<&str>,
|
||||
rewritten_content: &mut String,
|
||||
mut line_idx: usize,
|
||||
) -> usize {
|
||||
while line_idx < lines.len() {
|
||||
let line = lines[line_idx];
|
||||
match get_line_type_and_trimmed_line(line) {
|
||||
(LineType::Other, _) => {
|
||||
rewritten_content.push_str(line);
|
||||
line_idx += 1;
|
||||
}
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
line_idx
|
||||
}
|
||||
|
||||
fn escape_underscore_in_latex_doc_in_file(
|
||||
file_path: &std::path::Path,
|
||||
) -> Result<(), LatexEscapeToolError> {
|
||||
let file_name = file_path.to_str().unwrap();
|
||||
let content = std::fs::read_to_string(file_name).unwrap();
|
||||
|
||||
let number_of_underscores = content.matches('_').count();
|
||||
let potential_additional_capacity_required = number_of_underscores * BACKSLASH_UTF8_LEN;
|
||||
|
||||
// Enough for the length of the original string + the length if we had to escape *all* `_`
|
||||
// which won't happen but avoids reallocations
|
||||
let mut rewritten_content =
|
||||
String::with_capacity(content.len() + potential_additional_capacity_required);
|
||||
|
||||
let content_by_lines: Vec<&str> = content.split_inclusive('\n').collect();
|
||||
let mut line_idx = 0_usize;
|
||||
|
||||
while line_idx < content_by_lines.len() {
|
||||
let line = content_by_lines[line_idx];
|
||||
let (line_type, _) = get_line_type_and_trimmed_line(line);
|
||||
line_idx = match line_type {
|
||||
LineType::DocComment {
|
||||
code_block_limit: _,
|
||||
} => process_doc_lines_until_impossible(
|
||||
&content_by_lines,
|
||||
&mut rewritten_content,
|
||||
find_contiguous_doc_comment,
|
||||
line_idx,
|
||||
)?,
|
||||
LineType::DocTest {
|
||||
code_block_limit: _,
|
||||
} => process_doc_lines_until_impossible(
|
||||
&content_by_lines,
|
||||
&mut rewritten_content,
|
||||
find_contiguous_doc_test,
|
||||
line_idx,
|
||||
)?,
|
||||
LineType::Other => process_non_doc_lines_until_impossible(
|
||||
&content_by_lines,
|
||||
&mut rewritten_content,
|
||||
line_idx,
|
||||
),
|
||||
LineType::EmptyLine => {
|
||||
rewritten_content.push_str(line);
|
||||
line_idx + 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fs::write(file_name, rewritten_content).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn escape_underscore_in_latex_doc() -> Result<(), Error> {
|
||||
let project_root = project_root();
|
||||
let mut src_files: Vec<std::path::PathBuf> = Vec::new();
|
||||
recurse_find_rs_files(project_root, &mut src_files, true);
|
||||
|
||||
println!("Found {} files to process.", src_files.len());
|
||||
|
||||
let mut files_with_problems: Vec<(std::path::PathBuf, LatexEscapeToolError)> = Vec::new();
|
||||
|
||||
println!("Processing...");
|
||||
for file in src_files.into_iter() {
|
||||
if let Err(err) = escape_underscore_in_latex_doc_in_file(&file) {
|
||||
files_with_problems.push((file, err));
|
||||
}
|
||||
}
|
||||
println!("Done!");
|
||||
|
||||
if !files_with_problems.is_empty() {
|
||||
for (file_with_problem, error) in files_with_problems.iter() {
|
||||
println!(
|
||||
"File: {}, has error: {}",
|
||||
file_with_problem.display(),
|
||||
error
|
||||
);
|
||||
}
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Issues while processing files, check log.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
88
tasks/src/main.rs
Normal file
88
tasks/src/main.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
use clap::{Arg, Command};
|
||||
use log::LevelFilter;
|
||||
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
mod format_latex_doc;
|
||||
mod utils;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// CONSTANTS
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
lazy_static! {
|
||||
static ref DRY_RUN: AtomicBool = AtomicBool::new(false);
|
||||
static ref ROOT_DIR: PathBuf = utils::project_root();
|
||||
static ref ENV_TARGET_NATIVE: utils::Environment = {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("RUSTFLAGS", "-Ctarget-cpu=native");
|
||||
env
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// MACROS
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cmd {
|
||||
(<$env: ident> $cmd: expr) => {
|
||||
$crate::utils::execute($cmd, Some(&*$env), Some(&*$crate::ROOT_DIR))
|
||||
};
|
||||
($cmd: expr) => {
|
||||
$crate::utils::execute($cmd, None, Some(&*$crate::ROOT_DIR))
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// MAIN
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
// We parse the input args
|
||||
let matches = Command::new("tasks")
|
||||
.about("Rust scripts runner")
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.help("Prints debug messages"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("dry-run")
|
||||
.long("dry-run")
|
||||
.help("Do not execute the commands"),
|
||||
)
|
||||
.subcommand(Command::new("format_latex_doc").about("Escape underscores in latex equations"))
|
||||
.arg_required_else_help(true)
|
||||
.get_matches();
|
||||
|
||||
// We initialize the logger with proper verbosity
|
||||
let verb = if matches.contains_id("verbose") {
|
||||
LevelFilter::Debug
|
||||
} else {
|
||||
LevelFilter::Info
|
||||
};
|
||||
CombinedLogger::init(vec![TermLogger::new(
|
||||
verb,
|
||||
Config::default(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
// We set the dry-run mode if present
|
||||
if matches.contains_id("dry-run") {
|
||||
DRY_RUN.store(true, Relaxed);
|
||||
}
|
||||
|
||||
if matches.subcommand_matches("format_latex_doc").is_some() {
|
||||
format_latex_doc::escape_underscore_in_latex_doc()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
50
tasks/src/utils.rs
Normal file
50
tasks/src/utils.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use log::{debug, info};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
pub type Environment = HashMap<&'static str, &'static str>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn execute(cmd: &str, env: Option<&Environment>, cwd: Option<&PathBuf>) -> Result<(), Error> {
|
||||
info!("Executing {}", cmd);
|
||||
debug!("Env {:?}", env);
|
||||
debug!("Cwd {:?}", cwd);
|
||||
if crate::DRY_RUN.load(Relaxed) {
|
||||
info!("Skipping execution because of --dry-run mode");
|
||||
return Ok(());
|
||||
}
|
||||
let mut command = Command::new("sh");
|
||||
command
|
||||
.arg("-c")
|
||||
.arg(cmd)
|
||||
.stderr(Stdio::inherit())
|
||||
.stdout(Stdio::inherit());
|
||||
if let Some(env) = env {
|
||||
for (key, val) in env.iter() {
|
||||
command.env(key, val);
|
||||
}
|
||||
}
|
||||
if let Some(cwd) = cwd {
|
||||
command.current_dir(cwd);
|
||||
}
|
||||
let output = command.output()?;
|
||||
if !output.status.success() {
|
||||
Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
"Command exited with nonzero status.",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_root() -> PathBuf {
|
||||
Path::new(&env!("CARGO_MANIFEST_DIR"))
|
||||
.ancestors()
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.to_path_buf()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tfhe"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
|
||||
@@ -8,14 +8,15 @@ homepage = "https://zama.ai/"
|
||||
documentation = "https://docs.zama.ai/tfhe-rs"
|
||||
repository = "https://github.com/zama-ai/tfhe-rs"
|
||||
license = "BSD-3-Clause-Clear"
|
||||
description = "Concrete is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
|
||||
description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
|
||||
build = "build.rs"
|
||||
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7"
|
||||
rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
kolmogorov_smirnov = "1.1.0"
|
||||
paste = "1.0.7"
|
||||
lazy_static = { version = "1.4.0" }
|
||||
@@ -23,24 +24,28 @@ criterion = "0.3.5"
|
||||
doc-comment = "0.3.3"
|
||||
# Used in user documentation
|
||||
bincode = "1.3.3"
|
||||
fs2 = { version = "0.4.3"}
|
||||
fs2 = { version = "0.4.3" }
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = { version = "0.24.3", optional = true }
|
||||
|
||||
[dependencies]
|
||||
concrete-csprng = { version = "0.2.1" }
|
||||
concrete-cuda = { version = "0.1.1", optional = true }
|
||||
concrete-csprng = { version = "0.2.1", features = [
|
||||
"generator_soft",
|
||||
"parallel",
|
||||
] }
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
rayon = { version = "1.5.0", optional = true }
|
||||
bincode = { version = "1.3.3", optional = true }
|
||||
concrete-fft = { version = "0.1", optional = true }
|
||||
aligned-vec = "0.5"
|
||||
dyn-stack = { version = "0.8", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
rayon = { version = "1.5.0" }
|
||||
bincode = { version = "1.3.3" }
|
||||
concrete-fft = { version = "0.1", features = ["serde"] }
|
||||
aligned-vec = { version = "0.5", features = ["serde"] }
|
||||
dyn-stack = { version = "0.8" }
|
||||
once_cell = "1.13"
|
||||
paste = "1.0.7"
|
||||
fs2 = { version = "0.4.3", optional = true }
|
||||
# While we wait for repeat_n in rust standard library
|
||||
itertools = "0.10.5"
|
||||
|
||||
# wasm deps
|
||||
wasm-bindgen = { version = "0.2.63", features = [
|
||||
@@ -52,11 +57,11 @@ serde-wasm-bindgen = { version = "0.4", optional = true }
|
||||
getrandom = { version = "0.2.8", optional = true }
|
||||
|
||||
[features]
|
||||
boolean = ["minimal_core_crypto_features"]
|
||||
shortint = ["minimal_core_crypto_features"]
|
||||
boolean = []
|
||||
shortint = []
|
||||
internal-keycache = ["lazy_static", "fs2"]
|
||||
|
||||
__c_api = ["cbindgen", "minimal_core_crypto_features"]
|
||||
__c_api = ["cbindgen"]
|
||||
boolean-c-api = ["boolean", "__c_api"]
|
||||
shortint-c-api = ["shortint", "__c_api"]
|
||||
|
||||
@@ -71,78 +76,30 @@ __wasm_api = [
|
||||
boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
|
||||
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
|
||||
|
||||
cuda = ["backend_cuda"]
|
||||
nightly-avx512 = ["backend_fft_nightly_avx512"]
|
||||
|
||||
# A pure-rust CPU backend.
|
||||
backend_default = ["concrete-csprng/generator_soft"]
|
||||
|
||||
# An accelerated backend, using the `concrete-fft` library.
|
||||
backend_fft = ["concrete-fft", "dyn-stack"]
|
||||
backend_fft_serialization = [
|
||||
"bincode",
|
||||
"concrete-fft/serde",
|
||||
"aligned-vec/serde",
|
||||
"__commons_serialization",
|
||||
]
|
||||
backend_fft_nightly_avx512 = ["concrete-fft/nightly"]
|
||||
|
||||
# Enables the parallel engine in default backend.
|
||||
backend_default_parallel = ["__commons_parallel"]
|
||||
nightly-avx512 = ["concrete-fft/nightly"]
|
||||
|
||||
# Enable the x86_64 specific accelerated implementation of the random generator for the default
|
||||
# backend
|
||||
backend_default_generator_x86_64_aesni = [
|
||||
"concrete-csprng/generator_x86_64_aesni",
|
||||
]
|
||||
generator_x86_64_aesni = ["concrete-csprng/generator_x86_64_aesni"]
|
||||
|
||||
# Enable the aarch64 specific accelerated implementation of the random generator for the default
|
||||
# backend
|
||||
backend_default_generator_aarch64_aes = [
|
||||
"concrete-csprng/generator_aarch64_aes",
|
||||
]
|
||||
|
||||
# Enable the serialization engine in the default backend.
|
||||
backend_default_serialization = ["bincode", "__commons_serialization"]
|
||||
|
||||
# A GPU backend, relying on Cuda acceleration
|
||||
backend_cuda = ["concrete-cuda"]
|
||||
generator_aarch64_aes = ["concrete-csprng/generator_aarch64_aes"]
|
||||
|
||||
# Private features
|
||||
__profiling = []
|
||||
__private_docs = []
|
||||
__commons_parallel = ["rayon", "concrete-csprng/parallel"]
|
||||
__commons_serialization = ["serde", "serde/derive"]
|
||||
|
||||
seeder_unix = ["concrete-csprng/seeder_unix"]
|
||||
seeder_x86_64_rdseed = ["concrete-csprng/seeder_x86_64_rdseed"]
|
||||
|
||||
minimal_core_crypto_features = [
|
||||
"backend_default",
|
||||
"backend_default_parallel",
|
||||
"backend_default_serialization",
|
||||
"backend_fft",
|
||||
"backend_fft_serialization",
|
||||
]
|
||||
|
||||
# These target_arch features enable a set of public features for concrete-core if users want a known
|
||||
# good/working configuration for concrete-core.
|
||||
# For a target_arch that does not yet have such a feature, one can still enable features manually or
|
||||
# create a feature for said target_arch to make its use simpler.
|
||||
x86_64 = [
|
||||
"minimal_core_crypto_features",
|
||||
"backend_default_generator_x86_64_aesni",
|
||||
"seeder_x86_64_rdseed",
|
||||
]
|
||||
x86_64 = ["generator_x86_64_aesni", "seeder_x86_64_rdseed"]
|
||||
x86_64-unix = ["x86_64", "seeder_unix"]
|
||||
|
||||
# CUDA builds are Unix only at the moment
|
||||
x86_64-unix-cuda = ["x86_64-unix", "cuda"]
|
||||
|
||||
aarch64 = [
|
||||
"minimal_core_crypto_features",
|
||||
"backend_default_generator_aarch64_aes",
|
||||
]
|
||||
aarch64 = ["generator_aarch64_aes"]
|
||||
aarch64-unix = ["aarch64", "seeder_unix"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -15,7 +15,8 @@ materials provided with the distribution.
|
||||
|
||||
3. Neither the name of ZAMA nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE*.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
@@ -25,8 +26,3 @@ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CA
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*In addition to the rights carried by this license, ZAMA grants to the user a non-exclusive,
|
||||
free and non-commercial license on all patents filed in its name relating to the open-source
|
||||
code (the "Patents") for the sole purpose of evaluation, development, research, prototyping
|
||||
and experimentation.
|
||||
|
||||
@@ -44,17 +44,10 @@ fn bench_gates(c: &mut Criterion, params: BooleanParameters, parameter_name: &st
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.mux(&ct1, &ct2, &ct3))));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
fn bench_default_parameters(c: &mut Criterion) {
|
||||
bench_gates(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
fn bench_default_parameters(_: &mut Criterion) {
|
||||
let _ = DEFAULT_PARAMETERS; // to avoid unused import warnings
|
||||
println!("DEFAULT_PARAMETERS not benched as they are not compatible with the cuda feature.");
|
||||
}
|
||||
|
||||
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
|
||||
bench_gates(c, TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS");
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# Operations and Examples
|
||||
# Operations
|
||||
|
||||
In thfe::boolean, the available operations are mainly related to their equivalent Boolean gates,
|
||||
i.e., AND, OR,... In what follows, an example of a unary gate (NOT) and one about a binary gate
|
||||
(XOR). The last one is about the ternary MUX gate are detailed, which gives the possibility to
|
||||
homomorphically compute conditional statements of the form ``If..Then..Else``.
|
||||
In thfe::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
|
||||
|
||||
@@ -26,7 +23,6 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Binary gates
|
||||
|
||||
```rust
|
||||
@@ -49,10 +45,10 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## The MUX ternary gate
|
||||
Let ``ct_1, ct_2, ct_3`` be three Boolean
|
||||
ciphertexts. Then, the MUX gate (abbreviation of MUtipleXer) is equivalent to the operation:
|
||||
|
||||
Let `ct_1, ct_2, ct_3` be three Boolean ciphertexts. Then, the MUX gate (abbreviation of MUtipleXer) is equivalent to the operation:
|
||||
|
||||
```r
|
||||
if ct_1 {
|
||||
return ct_2
|
||||
@@ -61,7 +57,7 @@ if ct_1 {
|
||||
}
|
||||
```
|
||||
|
||||
This example show how to use the MUX ternary gate.
|
||||
This example shows how to use the MUX ternary gate:
|
||||
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
@@ -1,35 +1,27 @@
|
||||
# Cryptographic parameters
|
||||
# Cryptographic Parameters
|
||||
|
||||
## Default parameters
|
||||
|
||||
The TFHE cryptographic scheme relies on a variant of [Regev cryptosystem](https://cims.nyu.edu/~regev/papers/lwesurvey.pdf), and is based on a problem so hard to solve, that is even post-quantum resistant.
|
||||
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.
|
||||
|
||||
In practice, you need to tune some cryptographic parameters, in order to ensure 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 provide two sets of parameters**, which ensure correct computations for a certain probability with the standard security of 128 bits. There exists an error probability due the probabilistic nature of the encryption, which requires adding randomness (called noise) following a Gaussian distribution. If this noise is too large, the decryption will not give a correct result. There is a trade-off between efficiency and correctness: generally, using a less efficient parameter set (in terms of computation time) leads to a smaller risk of having an error during homomorphic evaluation.
|
||||
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 into this probability error.
|
||||
The default parameter set ensures a probability error of at most $$2^{-40}$$ when computing a
|
||||
programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error
|
||||
probability claimed into the original [TFHE paper](https://eprint.iacr.org/2018/421),
|
||||
namely $$2^{-165}$$, but up to date regarding security requirements.
|
||||
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:
|
||||
|
||||
| Parameter set | Error probability |
|
||||
|:-------------------:|:-----------------:|
|
||||
| DEFAULT_PARAMETERS | $$ 2^{-40} $$ |
|
||||
| TFHE_LIB_PARAMETERS | $$ 2^{-165} $$ |
|
||||
|
||||
| Parameter set | Error probability |
|
||||
| :-------------------: | :---------------: |
|
||||
| DEFAULT\_PARAMETERS | $$2^{-40}$$ |
|
||||
| TFHE\_LIB\_PARAMETERS | $$2^{-165}$$ |
|
||||
|
||||
## User-defined parameters
|
||||
|
||||
|
||||
Note that if you desire, you can also create your own set of parameters.
|
||||
This is an `unsafe` operation as failing to properly fix the parameters will potentially result with an incorrect and/or insecure computation:
|
||||
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::*;
|
||||
|
||||
fn main() {
|
||||
@@ -50,5 +42,3 @@ fn main() {
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
# Tutorial: a first boolean circuit
|
||||
# Tutorial
|
||||
|
||||
This library is meant to be used both on the **server side** and on the **client side**.
|
||||
The usual use case would follow those steps:
|
||||
This library is meant to be used both on the **server side** and 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**, *encryption* of the input data with the `client key`.
|
||||
+ Transmit the encrypted input to the **server**.
|
||||
+ On the **server side**, *homomorphic computation* with the `server key`.
|
||||
+ Transmit the encrypted output to the **client**.
|
||||
+ On the **client side**, *decryption* of the output data with `client key`.
|
||||
* On the **client side**, _encryption_ of the input data with the `client key`.
|
||||
* Transmit the encrypted input to the **server**.
|
||||
* On the **server side**, _homomorphic computation_ with the `server key`.
|
||||
* Transmit the encrypted output to the **client**.
|
||||
* On the **client side**, _decryption_ of the output data with the `client key`.
|
||||
|
||||
## 1. Setup
|
||||
## Setup
|
||||
|
||||
In the first step, the client creates two keys: the `client key` and the `server key`, with the `concrete_boolean::gen_keys` function:
|
||||
|
||||
In the first step, the client creates two keys: the `client key` and the `server key`,
|
||||
with the
|
||||
`concrete_boolean::gen_keys` function:
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
@@ -28,17 +26,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
In more details:
|
||||
* The `client_key` is of type `ClientKey`. It is **secret**, and must **never** be transmitted. This key will only be used to encrypt and decrypt data.
|
||||
* The `server_key` is of type `ServerKey`. It is a **public key**, and can be shared with any party. This key has to be sent to the server because it is required for homomorphic computation.
|
||||
|
||||
+ The `client_key` is of type `ClientKey`. It is **secret**, and must **never** be transmitted.
|
||||
This key will only be used to encrypt and decrypt data.
|
||||
+ The `server_key` is of type `ServerKey`. It is a **public key**, and can be shared with any
|
||||
party.
|
||||
This key has to be sent to the server because it is required for the homomorphic computation.
|
||||
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits. This way you can use any compatible serializer to store/send the data. For instance, to store the `server_key` in a binary file, you can use the `bincode` library:
|
||||
|
||||
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits.
|
||||
This way you can use any compatible serializer to store/send the data. For instance, to store
|
||||
the `server_key` in a binary file, you can use the `bincode` library:
|
||||
```rust
|
||||
use std::fs::File;
|
||||
use std::io::{Write, Read};
|
||||
@@ -80,15 +72,10 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Encrypting Inputs
|
||||
## Encrypting Inputs
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client 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:
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some
|
||||
homomorphic computations.
|
||||
The client simply needs to encrypt some data and send it to the server.
|
||||
Again, the `Ciphertext` type implements the `Serialize` and
|
||||
the `Deserialize` traits, so that any serializer and communication tool suiting your use case
|
||||
can be
|
||||
used:
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
@@ -112,15 +99,10 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## 2bis. Encrypting Inputs using public key
|
||||
## Encrypting Inputs using a public key
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client simply needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be utilized:
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some
|
||||
homomorphic computations.
|
||||
The client simply needs to encrypt some data and send it to the server.
|
||||
Again, the `Ciphertext` type implements the `Serialize` and
|
||||
the `Deserialize` traits, so that any serializer and communication tool suiting your use case
|
||||
can be
|
||||
used:
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
@@ -145,11 +127,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Executing a Boolean circuit
|
||||
|
||||
## Executing a Boolean Circuit
|
||||
|
||||
Once the encrypted inputs are on the **server side**, the `server_key` can be used to
|
||||
homomorphically execute the desired boolean circuit:
|
||||
Once the encrypted inputs are on the **server side**, the `server_key` can be used to homomorphically execute the desired Boolean circuit:
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
@@ -191,8 +171,7 @@ fn main() {
|
||||
|
||||
## Decrypting the output
|
||||
|
||||
Once the encrypted output is on the client side, the `client_key` can be used to
|
||||
decrypt it:
|
||||
Once the encrypted output is on the client side, the `client_key` can be used to decrypt it:
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
@@ -218,29 +197,3 @@ fn main() {
|
||||
assert_eq!(output, true);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# What is TFHE-rs?
|
||||
|
||||
<mark style="background-color:yellow;">⭐️</mark> [<mark style="background-color:yellow;">Star the repo on Github</mark>](https://github.com/zama-ai/tfhe-rs) <mark style="background-color:yellow;">| 🗣</mark> [<mark style="background-color:yellow;">Community support forum</mark> ](https://community.zama.ai)<mark style="background-color:yellow;">| 📁</mark> [<mark style="background-color:yellow;">Contribute to the project</mark>](https://docs.zama.ai/tfhe-rs/developers/contributing)<mark style="background-color:yellow;"></mark>
|
||||
<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)
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||
@@ -10,14 +10,13 @@ TFHE-rs is meant for developers and researchers who want full control over what
|
||||
|
||||
The goal is to have a stable, simple, high-performance, and production-ready library for all the advanced features of TFHE.
|
||||
|
||||
### Key Cryptographic concepts
|
||||
## Key cryptographic concepts
|
||||
|
||||
TFHE-rs library implements Zama’s variant of Fully Homomorphic Encryption over the Torus (TFHE). TFHE is based on Learning With Errors (LWE), a well studied cryptographic primitive believed to be secure even against quantum computers.
|
||||
The TFHE-rs library implements Zama’s variant of Fully Homomorphic Encryption over the Torus (TFHE). TFHE is based on Learning With Errors (LWE), a well-studied cryptographic primitive believed to be secure even against quantum computers.
|
||||
|
||||
In cryptography, a raw value is called a message (also sometimes called a cleartext), an encoded message is called a plaintext and an encrypted plaintext is called a ciphertext.
|
||||
In cryptography, a raw value is called a message (also sometimes called a cleartext), while an encoded message is called a plaintext and an encrypted plaintext is called a ciphertext.
|
||||
|
||||
The idea of homomorphic encryption is that you can compute on ciphertexts while not knowing messages encrypted in them. A scheme is said to be _fully homomorphic_, meaning any program can be evaluated with it, if at least two of the following operations are supported \($$x$$is a plaintext and $$E[x]$$ is the
|
||||
corresponding ciphertext\):
|
||||
The idea of homomorphic encryption is that you can compute on ciphertexts while not knowing messages encrypted within them. A scheme is said to be _fully homomorphic_, meaning any program can be evaluated with it, if at least two of the following operations are supported ($$x$$is a plaintext and $$E[x]$$ is the corresponding ciphertext):
|
||||
|
||||
* homomorphic univariate function evaluation: $$f(E[x]) = E[f(x)]$$
|
||||
* homomorphic addition: $$E[x] + E[y] = E[x + y]$$
|
||||
@@ -28,9 +27,8 @@ Zama's variant of TFHE is fully homomorphic and deals with fixed-precision numbe
|
||||
Using FHE in a Rust program with TFHE-rs consists in:
|
||||
|
||||
* generating a client key and a server key using secure parameters:
|
||||
* client key encrypts/decrypts data and must be kept secret
|
||||
* server key is used to perform operations on encrypted data and could be
|
||||
public (also called evaluation key)
|
||||
* a client key encrypts/decrypts data and must be kept secret
|
||||
* a server key is used to perform operations on encrypted data and could be public (also called an evaluation key)
|
||||
* encrypting plaintexts using the client key to produce ciphertexts
|
||||
* operating homomorphically on ciphertexts with the server key
|
||||
* decrypting the resulting ciphertexts into plaintexts using the client key
|
||||
|
||||
BIN
tfhe/docs/_static/ciphertext-representation.png
vendored
Normal file
BIN
tfhe/docs/_static/ciphertext-representation.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
16
tfhe/docs/_static/ciphertext-representation.svg
vendored
16
tfhe/docs/_static/ciphertext-representation.svg
vendored
@@ -1,16 +0,0 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 424 173" width="424" height="173">
|
||||
<!-- svg-source:excalidraw -->
|
||||
|
||||
<defs>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Virgil";
|
||||
src: url("https://excalidraw.com/Virgil.woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Cascadia";
|
||||
src: url("https://excalidraw.com/Cascadia.woff2");
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g stroke-linecap="round" transform="translate(26 44) rotate(0 194 38.60598503740641)"><path d="M-0.72 -1.78 C148.22 -1.79, 298.89 -0.4, 389.35 -0.66 M0.64 -0.29 C96.57 -1.4, 195.57 -0.99, 387.19 -1.07 M388.14 2.03 C392.43 23.61, 391.39 49, 385.98 75.21 M389.42 -0.67 C389.81 14.73, 390.33 33.43, 388.78 76.49 M387.8 79.39 C277.29 77.61, 166.8 75.17, -1.18 76.59 M387.01 76.05 C246.87 81.15, 103.96 81.92, 0.98 78.37 M-3.84 75.76 C-2.18 57.07, 4.75 34.74, -0.59 -2.81 M1 77.36 C-0.79 48.54, -1.41 22.74, 1.4 1.83" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(43.27283572239912 63.29950568870447) rotate(0 35.31670822942641 19.35162094763092)"><path d="M0.11 -2.85 C28.93 -4.68, 48.62 2.97, 73.79 0.71 M0.92 1.49 C21.03 0.46, 41.01 -1.23, 70.4 0.64 M70.88 3.15 C68.43 11.93, 69.14 26.71, 68.06 39.17 M69.56 -1.68 C71.59 8.67, 68.48 17.57, 70.35 40.23 M68.74 39.32 C48.28 40.85, 30.28 35.73, -0.92 40.56 M69.59 37.89 C48.04 38.14, 23.44 38.86, -0.31 39.89 M2.24 36.95 C-1.92 26.09, -1.01 13.79, -3.25 -2.11 M0.55 39.03 C1.24 29.93, -1.56 22.42, 0.25 1.64" stroke="#0b7285" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(244.47880299251847 58.546134663341206) rotate(0 77.40648379052368 25.15710723192018)"><path d="M0.5 3.27 C41.25 1.83, 85.12 4.03, 158.28 2.64 M1.74 -0.75 C60.33 -2.64, 122.13 -1.19, 153.85 -0.7 M155.49 -2.28 C158.86 11.5, 154.4 20.06, 153.49 51.17 M153.03 -0.55 C152.94 16.04, 156.21 35.59, 154.41 49.32 M157.56 51.63 C96.68 46.86, 29.51 52.23, -2.5 49.76 M155.17 52.19 C121.59 51.66, 87.99 54.57, -0.92 50.35 M-1.24 51.37 C-0.7 37.8, 0.93 29.69, -2.96 2.02 M-1.48 52.03 C0.44 36.29, -0.71 20.9, 1.27 0.34" stroke="#a61e4d" stroke-width="1" fill="none"></path></g><g transform="translate(249.47880299251847 71.70324189526139) rotate(0 72.40648379052368 12)"><text x="72.40648379052374" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.30839567747301px" fill="#a61e4d" text-anchor="middle" style="white-space: pre;" direction="ltr">noise</text></g><g stroke-linecap="round" transform="translate(35.610972568578745 52.67581047381532) rotate(0 89.501246882793 30.962593516209466)"><path d="M3.48 2.12 C42.75 -4.16, 86.77 -1.56, 177.5 3.84 M-1.92 1.02 C66.55 2.87, 130.16 1.88, 179.45 -1.63 M179.35 -3.41 C181.95 11.53, 178.24 25.72, 176.46 63.54 M180.52 0.51 C178.89 22.76, 177.57 49.34, 177.3 60.97 M175.68 59.6 C140.66 62.47, 97.79 56.72, 3.94 58.21 M177.14 61.73 C134.25 62.89, 91.18 61.47, 1.89 60.4 M-0.84 60.36 C-0.67 48.08, 3.19 30.78, -2.25 -3.82 M0.14 61.15 C-2.52 41.13, 0.16 19.4, -0.06 -0.75" stroke="#087f5b" stroke-width="1" fill="none"></path></g><g transform="translate(48.27283572239912 70.65112663633539) rotate(0 30.31670822942641 12)"><text x="30.316708229426418" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.248703637731044px" fill="#087f5b" text-anchor="middle" style="white-space: pre;" direction="ltr">carry</text></g><g stroke-linecap="round" transform="translate(123.17705735660911 63.319201995012975) rotate(0 43.5411471321695 21.286783042394035)"><path d="M3.78 -1.95 C21.85 2.27, 33.36 -1.31, 85.7 0.15 M0.95 -1.99 C30.47 0.73, 63.7 1.15, 87.61 0.81 M83.5 1.56 C89.47 15.22, 83.3 24.69, 84.55 46.54 M85.31 -0.87 C84.54 12.96, 85.46 26.61, 85.69 43.87 M85.54 45.92 C71.62 40.51, 50.01 40.74, -2.16 41.74 M85.45 44.45 C55.35 43.91, 24.36 42.43, 0.99 42.77 M-3.26 44.49 C1.26 31.11, -1.14 19.41, -1.89 2.33 M-1.27 43.89 C-1.22 30.47, 1.35 21.91, -0.8 -1.4" stroke="#0b7285" stroke-width="1" fill="none"></path></g><g transform="translate(128.1770573566091 72.60598503740698) rotate(0 38.5411471321695 12)"><text x="38.54114713216951" y="17" font-family="Virgil, Segoe UI Emoji" font-size="19.27057356608477px" fill="#087f5b" text-anchor="middle" style="white-space: pre;" direction="ltr">message</text></g><g transform="translate(371 138) rotate(0 20 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">LSB</text></g><g transform="translate(10 135) rotate(0 21.5 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">MSB</text></g><g transform="translate(162 10) rotate(0 51 12.5)"><text x="0" y="18" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">Ciphertext</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB |
@@ -1,25 +1,32 @@
|
||||
# Tutorial: using the C API
|
||||
# Tutorial
|
||||
|
||||
## Using the C API
|
||||
|
||||
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
|
||||
## First steps using `TFHE-rs` C API
|
||||
|
||||
## Setting-up `TFHE-rs` C API for use in a C program.
|
||||
### Setting-up `TFHE-rs` C API for use in a C program.
|
||||
|
||||
`TFHE-rs` C API can be built on a Unix x86_64 machine using the following command:
|
||||
`TFHE-rs` C API can be built on a Unix x86\_64 machine using the following command:
|
||||
|
||||
```shell
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features=x86_64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
```
|
||||
|
||||
All features are opt-in, but for simplicity here, the C API is enabled for booleans and shortints.
|
||||
or on a Unix aarch64 machine using the following command
|
||||
|
||||
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO_ROOT}/target/release/"
|
||||
```shell
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features=aarch64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
```
|
||||
|
||||
The build system needs to be set-up so that the C or C++ program links against `TFHE-rs` C API
|
||||
binaries.
|
||||
All features are opt-in, but for simplicity here, the C API is enabled for boolean and shortint.
|
||||
|
||||
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 allowing to do just that:
|
||||
|
||||
@@ -51,17 +58,14 @@ endif()
|
||||
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
|
||||
```
|
||||
|
||||
## Commented code of a PBS doubling a 2 bits encrypted message using `TFHE-rs C API`
|
||||
### Commented code of a PBS doubling a 2 bits encrypted message using `TFHE-rs C API`.
|
||||
|
||||
The steps required to perform the mutiplication by 2 of a 2 bits ciphertext
|
||||
using a PBS are detailed.
|
||||
This is NOT the most efficient way of doing this operation,
|
||||
but it allows to show the management required to run a PBS manually using the C API.
|
||||
The steps required to perform the mutiplication by 2 of a 2 bits ciphertext using a PBS are detailed. This is NOT the most efficient way of doing this operation, but it can help to show the management required to run a PBS manually using the C API.
|
||||
|
||||
WARNING: The following example does not have proper memory management in the error case to make it easier to fit the code on this page.
|
||||
|
||||
To run the example below, the above CMakeLists.txt and main.c files need to be in the same
|
||||
directory. The commands to run are:
|
||||
To run the example below, the above CMakeLists.txt and main.c files need to be in the same directory. The commands to run are:
|
||||
|
||||
```shell
|
||||
# /!\ Be sure to update CMakeLists.txt to give the absolute path to the compiled tfhe library
|
||||
$ ls
|
||||
@@ -169,6 +173,6 @@ int main(void)
|
||||
}
|
||||
```
|
||||
|
||||
# Audience
|
||||
## Audience
|
||||
|
||||
Programmers wishing to use `TFHE-rs` but unable to use Rust (for various reasons) can use these bindings in their language of choice as long as it can interface with C code to bring `TFHE-rs` functionalities to said language.
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contribute
|
||||
# Contributing
|
||||
|
||||
There are two ways to contribute to **TFHE-rs**:
|
||||
|
||||
* 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!
|
||||
* 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!
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
# Benchmarks
|
||||
|
||||
Due to their nature, homomorphic operations are obviously slower than their clear equivalent. In what follows, some timings are exposed for the basic operations. For completeness, some benchmarks of other libraries are also given.
|
||||
Due to their nature, homomorphic operations are 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 the benchmarks had been launched on an AWS m6i.metal with the following specifications:
|
||||
Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
|
||||
All 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.
|
||||
|
||||
## Booleans
|
||||
|
||||
This measures the execution time of a single binary boolean gate.
|
||||
|
||||
### thfe.rs::booleans
|
||||
### thfe.rs::booleans.
|
||||
|
||||
| Parameter set | concrete-fft | concrete-fft + avx512 |
|
||||
| --- | --- | --- |
|
||||
| DEFAULT_PARAMETERS | 8.8ms | 6.8ms |
|
||||
| TFHE_LIB_PARAMETERS | 13.6ms | 10.9ms |
|
||||
| Parameter set | concrete-fft | concrete-fft + avx512 |
|
||||
| --------------------- | ------------ | --------------------- |
|
||||
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
|
||||
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
|
||||
|
||||
### tfhe-lib
|
||||
### tfhe-lib.
|
||||
|
||||
| Parameter set | fftw | spqlios-fma|
|
||||
| --- | --- | --- |
|
||||
| default_128bit_gate_bootstrapping_parameters | 28.9ms | 15.7ms |
|
||||
| Parameter set | fftw | spqlios-fma |
|
||||
| ------------------------------------------------ | ------ | ----------- |
|
||||
| default\_128bit\_gate\_bootstrapping\_parameters | 28.9ms | 15.7ms |
|
||||
|
||||
### OpenFHE
|
||||
### OpenFHE.
|
||||
|
||||
| Parameter set | GINX | GINX (Intel HEXL) |
|
||||
| --- | --- | --- |
|
||||
| STD_128 | 172ms | 78ms |
|
||||
| MEDIUM | 113ms | 50.2ms |
|
||||
| Parameter set | GINX | GINX (Intel HEXL) |
|
||||
| ------------- | ----- | ----------------- |
|
||||
| STD\_128 | 172ms | 78ms |
|
||||
| MEDIUM | 113ms | 50.2ms |
|
||||
|
||||
## Shortints
|
||||
This measures the execution time for some operations and some parameter sets of shortints.
|
||||
## Shortint
|
||||
|
||||
This measures the execution time for some operations and some parameter sets of shortint.
|
||||
|
||||
### thfe.rs::shortint.
|
||||
|
||||
### thfe.rs::shortint
|
||||
This uses the concrete-fft + avx512 configuration.
|
||||
|
||||
|
||||
| Parameter set | unchecked_add | unchecked_mul_lsb | keyswitch_programmable_bootstrap |
|
||||
| --- | --- | --- | --- |
|
||||
| PARAM_MESSAGE_1_CARRY_1 | 337 ns | 10.1 ms | 9.91 ms |
|
||||
| PARAM_MESSAGE_2_CARRY_2 | 407 ns | 21.7 ms | 21.4 ms |
|
||||
| PARAM_MESSAGE_3_CARRY_3 | 3.06 µs | 161 ms | 159 ms |
|
||||
| PARAM_MESSAGE_4_CARRY_4 | 11.7 µs | 1.03 s | 956 ms |
|
||||
| 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 | 134 ms |
|
||||
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 945 ms |
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
|
||||
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.1.0", features = [ "boolean", "shortint", "x86_64-unix" ] }
|
||||
tfhe = { version = "0.2.0", features = [ "boolean", "shortint", "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
## Choosing your features
|
||||
@@ -17,36 +16,32 @@ tfhe = { version = "0.1.0", features = [ "boolean", "shortint", "x86_64-unix" ]
|
||||
|
||||
This crate exposes two kinds of data types. Each kind is enabled by activating its corresponding feature in the TOML line. Each kind may have multiple types:
|
||||
|
||||
| Kind | Features | Type(s) |
|
||||
| --------- | ------------- |------------------------------------------|
|
||||
| Booleans | `boolean` | Booleans |
|
||||
| ShortInts | `shortint` | Short unsigned integers |
|
||||
|
||||
| Kind | Features | Type(s) |
|
||||
| --------- | ---------- | ----------------------- |
|
||||
| Booleans | `boolean` | Booleans |
|
||||
| ShortInts | `shortint` | Short unsigned integers |
|
||||
|
||||
### Serialization.
|
||||
|
||||
The different data types and keys exposed by the crate can be serialized / deserialized.
|
||||
|
||||
More information can be found [here](../Booleans/serialization.md) for Booleans and [here](../shortint/serialization.md) for shortint.
|
||||
More information can be found [here](../Booleans/serialization.md) for boolean and [here](../shortint/serialization.md) for shortint.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (x86 with `RDSEED`
|
||||
instruction).
|
||||
TFHE-rs is supported on Linux (x86, aarch64), macOS (x86, aarch64) and Windows (x86 with `RDSEED` instruction).
|
||||
|
||||
| OS | x86 | aarch64 |
|
||||
| --------- | ------------- |------------------|
|
||||
| Linux | `x86_64-unix` | `aarch64-unix`* |
|
||||
| macOS | `x86_64-unix` | `aarch64-unix`* |
|
||||
| Windows | `x86_64` | Unsupported |
|
||||
| OS | x86 | aarch64 |
|
||||
| ------- | ------------- | ---------------- |
|
||||
| Linux | `x86_64-unix` | `aarch64-unix`\* |
|
||||
| macOS | `x86_64-unix` | `aarch64-unix`\* |
|
||||
| Windows | `x86_64` | Unsupported |
|
||||
|
||||
{% hint style="info" %}
|
||||
Users who have ARM devices can use `TFHE-rs` by compiling using the
|
||||
`nightly` toolchain.
|
||||
Users who have ARM devices can use `TFHE-rs` by compiling using the `nightly` toolchain.
|
||||
{% endhint %}
|
||||
|
||||
|
||||
### Using TFHE-rs with nightly toolchain
|
||||
### Using TFHE-rs with nightly toolchain.
|
||||
|
||||
First, install the needed Rust toolchain:
|
||||
|
||||
|
||||
@@ -2,52 +2,43 @@
|
||||
|
||||
## Boolean
|
||||
|
||||
The list of supported operations by the homomorphic booleans is:
|
||||
|
||||
|Operation Name | type |
|
||||
| ------ | ------ |
|
||||
| `not` | Unary |
|
||||
| `and` | Binary |
|
||||
| `or` | Binary |
|
||||
| `xor` | Binary |
|
||||
| `nor` | Binary |
|
||||
| `xnor` | Binary |
|
||||
| `cmux` | Ternary |
|
||||
The list of supported operations by the homomorphic Booleans is:
|
||||
|
||||
| Operation Name | type |
|
||||
| -------------- | ------- |
|
||||
| `not` | Unary |
|
||||
| `and` | Binary |
|
||||
| `or` | Binary |
|
||||
| `xor` | Binary |
|
||||
| `nor` | Binary |
|
||||
| `xnor` | Binary |
|
||||
| `cmux` | Ternary |
|
||||
|
||||
A walk-through using homomorphic Booleans can be found [here](../Booleans/tutorial.md).
|
||||
|
||||
|
||||
## ShortInt
|
||||
|
||||
In TFHE-rs, the shortints represent short unsigned integers encoded over 8 bits maximum. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
|
||||
|
||||
In TFHE-rs, shortint represents short unsigned integers encoded over a maximum of 8 bits. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| Operation name | Type |
|
||||
|--------------- | ------ |
|
||||
| Negation | Unary |
|
||||
| Addition | Binary |
|
||||
| Subtraction | Binary |
|
||||
| Multiplication | Binary |
|
||||
| Division* | Binary |
|
||||
| Modular reduction | Binary |
|
||||
| Comparisons | Binary |
|
||||
| Left/Right Shift | Binary |
|
||||
| And | Binary |
|
||||
| Or | Binary |
|
||||
| Xor | Binary |
|
||||
| Exact Function Evaluation | Unary/Binary |
|
||||
| Operation name | Type |
|
||||
| ------------------------- | ------------ |
|
||||
| Negation | Unary |
|
||||
| Addition | Binary |
|
||||
| Subtraction | Binary |
|
||||
| Multiplication | Binary |
|
||||
| Division\* | Binary |
|
||||
| Modular reduction | Binary |
|
||||
| Comparisons | Binary |
|
||||
| Left/Right Shift | Binary |
|
||||
| And | Binary |
|
||||
| Or | Binary |
|
||||
| Xor | Binary |
|
||||
| Exact Function Evaluation | Unary/Binary |
|
||||
|
||||
{% hint style="info" %}
|
||||
\* The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns 0.
|
||||
{% endhint %}
|
||||
|
||||
A walk-through example can be found [here](../shortint/tutorial.md) and more examples and
|
||||
explanations can be found [here](../shortint/operations.md)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A walk-through example can be found [here](../shortint/tutorial.md), and more examples and explanations can be found [here](../shortint/operations.md).[ ](../shortint/operations.md)
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
# Quick start
|
||||
# Quick Start
|
||||
|
||||
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans or short integers (named shortints in the rest of this documentation).
|
||||
It allows one to execute a circuit on an **untrusted server** because both circuit inputs and outputs are kept **private**.
|
||||
Data are indeed encrypted on the client side, before being sent to the server. On the server side every computation is performed on ciphertexts.
|
||||
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans 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. She can then decrypt it with her `secret key`.
|
||||
The server, however, has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. She can then decrypt it with her `secret key`.
|
||||
|
||||
## General method to write an homomorphic circuit program
|
||||
|
||||
## General method to write homomorphic circuit program
|
||||
The overall process to write an homomorphic program is the same for both Boolean and shortint types. In a nutshell, the basic steps for using the TFHE-rs library are the following:
|
||||
|
||||
The overall process to write an homomorphic program is the same for both Boolean and short integers types.
|
||||
In a nutshell, the basic steps for using the TFHE-rs library are the following:
|
||||
- Choose a data type (Boolean or shortint)
|
||||
- Import the library
|
||||
- Create client and server keys
|
||||
- Encrypt data with the client key
|
||||
- Compute over encrypted data using the server key
|
||||
- Decrypt data with the client key
|
||||
* 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
|
||||
|
||||
|
||||
### Boolean example
|
||||
### Boolean example.
|
||||
|
||||
Here is an example to illustrate how the library can be used to evaluate a Boolean circuit:
|
||||
|
||||
@@ -47,9 +43,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Shortint example
|
||||
### Shortint example.
|
||||
|
||||
and here is a full example using shortints:
|
||||
And here is a full example using shortint:
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -76,5 +72,4 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
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)).
|
||||
|
||||
@@ -1,74 +1,49 @@
|
||||
# Cryptography & Security
|
||||
# Security and Cryptography
|
||||
|
||||
# TFHE
|
||||
## TFHE
|
||||
|
||||
TFHE-rs is a cryptographic library dedicated to Fully Homomorphic Encryption. As its name
|
||||
suggests, it is based on the TFHE scheme.
|
||||
TFHE-rs is a cryptographic library dedicated to Fully Homomorphic Encryption. As its name suggests, it is based on the TFHE scheme.
|
||||
|
||||
It is interesting to understand some basics about TFHE,
|
||||
in order to apprehend where the limitations are coming from both
|
||||
in terms of precision (number of bits used to represent the plaintext values)
|
||||
and execution time (why TFHE operations are slower than native operations).
|
||||
It is 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
|
||||
## LWE ciphertexts
|
||||
|
||||
Although there are many kinds of ciphertexts in TFHE,
|
||||
all the encrypted values in TFHE-rs are mainly stored as LWE ciphertexts.
|
||||
Although there are many kinds of ciphertexts in TFHE, all the encrypted values in TFHE-rs are mainly stored as LWE ciphertexts.
|
||||
|
||||
The security of TFHE relies on the LWE problem which stands for Learning With Errors.
|
||||
The problem is believed to be secure against quantum attacks.
|
||||
The security of TFHE relies on the LWE problem, which stands for Learning With Errors. The problem is believed to be secure against quantum attacks.
|
||||
|
||||
An LWE Ciphertext is a collection of 32-bits or 64-bits unsigned integers.
|
||||
Before encrypting a message in an LWE ciphertext, one needs to first encode it as a plaintext.
|
||||
This is done by shifting the message to the most significant bits of the unsigned integer type used.
|
||||
An LWE Ciphertext is a collection of 32-bit or 64-bit unsigned integers. Before encrypting a message in an LWE ciphertext, one must first encode it as a plaintext. This is done by shifting the message to the most significant bits of the unsigned integer type used.
|
||||
|
||||
Then, a little random value called noise is added to the least significant bits.
|
||||
This noise (also called error for Learning With Errors) is crucial to the security of the ciphertext.
|
||||
Then, a little random value called noise is added to the least significant bits. This noise (also called error for Learning With Errors) is crucial to the security of the ciphertext.
|
||||
|
||||
$$ plaintext = (\Delta * m) + e $$
|
||||
$$plaintext = (\Delta * m) + e$$
|
||||
|
||||

|
||||

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

|
||||

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

|
||||

|
||||
|
||||
If you would like to know more about TFHE, you can find more information in our [TFHE Deep Dive](https://www.zama.ai/post/tfhe-deep-dive-part-1).
|
||||
|
||||
## Security
|
||||
### Security.
|
||||
|
||||
By default, the cryptographic parameters provided by `TFHE-rs` ensure at least 128 bits of security.
|
||||
The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
|
||||
By default, the cryptographic parameters provided by `TFHE-rs` ensure at least 128 bits of security. The security has been evaluated using the latest versions of the Lattice Estimator ([repository](https://github.com/malb/lattice-estimator)) with `red_cost_model = reduction.RC.BDGL16`.
|
||||
|
||||
For all sets of parameters, the error probability when computing a univariate function over one ciphertext is $$2^{-40}$$. Note that univariate functions might be performed when arithmetic functions are computed (for instance, the multiplication of two ciphertexts).
|
||||
|
||||
## Public key encryption
|
||||
### Public key encryption.
|
||||
|
||||
In public key encryption, the public key consists in providing a given number of message encrypting the value 0. By setting the number of encryptions of 0 in the public key at $$m = \lceil (n+1) \log(q) \rceil + \lambda$$, where $$n$$ is the LWE dimension, $$q$$ is the ciphertext modulus and $$\lambda$$ is the number of security bits. In a nutshell, this construction is secure due to the left-over-hash lemma, which is essentially related to the impossibility of breaking the underlying multiple subset sum problem. By using this formula, this guarantees both a high density subset sum and an exponentially large number of possible associated random vectors per LWE sample (a,b)
|
||||
In public key encryption, the public key contains a given number of ciphertexts all encrypting the value 0. By setting the number of encryptions 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).
|
||||
|
||||
@@ -1,54 +1,44 @@
|
||||
# How Shortint are represented
|
||||
# Operations
|
||||
|
||||
## How shortint is represented
|
||||
|
||||
In `shortint`, the encrypted data is stored in an LWE ciphertext.
|
||||
|
||||
Conceptually, the message stored in an LWE ciphertext, is divided into
|
||||
a **carry buffer** and a **message buffer**.
|
||||
Conceptually, the message stored in an LWE ciphertext is divided into a **carry buffer** and a **message buffer**.
|
||||
|
||||

|
||||

|
||||
|
||||
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus: the exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
|
||||
|
||||
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in incorrect outputs.
|
||||
|
||||
In order to ensure the computation correctness, we keep track of the maximum value encrypted in a
|
||||
ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to resume safely the computations. Therefore, in `shortint` the carry modulus is mainly considered as a means to do more computations.
|
||||
In order to ensure the correctness of the computation, we 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
|
||||
## Types of operations
|
||||
|
||||
The operations available via a `ServerKey` may come in different variants:
|
||||
|
||||
- operations that take their inputs as encrypted values.
|
||||
- scalar operations take at least one non-encrypted value as input.
|
||||
* operations that take their inputs as encrypted values.
|
||||
* scalar operations that take at least one non-encrypted value as input.
|
||||
|
||||
For example, the addition has both variants:
|
||||
|
||||
- `ServerKey::unchecked_add` which takes two encrypted values and adds them.
|
||||
- `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the
|
||||
so-called scalar) and adds them.
|
||||
* `ServerKey::unchecked_add` which takes two encrypted values and adds them.
|
||||
* `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the so-called scalar) and adds them.
|
||||
|
||||
Each operation may come in different 'flavors':
|
||||
|
||||
- `unchecked`: Always does the operation, without checking if the result may exceed the capacity of
|
||||
the plaintext space. Using this operations might have an impact on the correctness of the
|
||||
following operations;
|
||||
- `checked`: Checks are done before computing the operation, returning an error if operation
|
||||
cannot be done safely;
|
||||
- `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation
|
||||
will clear the carry modulus to make the operation possible.
|
||||
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
|
||||
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely;
|
||||
* `smart`: Always does the operation - if the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible.
|
||||
|
||||
Not all operations have these 3 flavors, as some of them are implemented in a way
|
||||
that the operation is always possible without ever exceeding the plaintext space capacity.
|
||||
Not all operations have these 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
|
||||
|
||||
# How to use operation types
|
||||
|
||||
Let's try to do a circuit evaluation using the different flavours of operations we already introduced.
|
||||
For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly.
|
||||
Otherwise, the `checked` and `smart` are the best options.
|
||||
|
||||
As an example, let's do a scalar multiplication, a subtraction and a multiplication.
|
||||
Let's try to do a circuit evaluation using the different flavours of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise, the `checked` and `smart` are the best options.
|
||||
|
||||
As an example, let's do a scalar multiplication, a subtraction, and a multiplication.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -78,10 +68,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
During this computation the carry buffer has been overflowed and as all the operations were `unchecked` the output
|
||||
may be incorrect.
|
||||
During this computation, the carry buffer has been overflowed and, as all the operations were `unchecked`, the output may be incorrect.
|
||||
|
||||
If we redo this same circuit but using the `checked` flavour, a panic will occur.
|
||||
If we redo this same circuit with the `checked` flavour, a panic will occur.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -123,12 +112,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Therefore, the `checked` flavour permits to manually manage the overflow of the carry buffer
|
||||
by raising an error if the correctness is not guaranteed.
|
||||
Therefore, the `checked` flavour permits manual management of 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 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::*;
|
||||
@@ -157,61 +143,60 @@ fn main() {
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64);
|
||||
}
|
||||
```
|
||||
#List of available operations
|
||||
|
||||
\#List of available operations
|
||||
|
||||
{% hint style="warning" %}
|
||||
|
||||
Currently, certain operations can only be used if the parameter set chosen is compatible with the
|
||||
bivariate programmable bootstrapping, meaning the carry buffer is larger or equal than the
|
||||
message buffer. These operations are marked with a star (*).
|
||||
|
||||
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:
|
||||
|
||||
The list of implemented operations for shortints is:
|
||||
- addition between two ciphertexts
|
||||
- addition between a ciphertext and an unencrypted scalar
|
||||
- comparisons `<`, `<=`, `>`, `>=`, `==` between a ciphertext and an unencrypted scalar
|
||||
- division of a ciphertext by an unencrypted scalar
|
||||
- LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
|
||||
- multiplication of a ciphertext by an unencrypted scalar
|
||||
- bitwise shift `<<`, `>>`
|
||||
- subtraction of a ciphertext by another ciphertext
|
||||
- subtraction of a ciphertext by an unencrypted scalar
|
||||
- negation of a ciphertext
|
||||
- bitwise and, or and xor (*)
|
||||
- comparisons `<`, `<=`, `>`, `>=`, `==` between two ciphertexts (*)
|
||||
- division between two ciphertexts (*)
|
||||
- MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (*)
|
||||
* addition between two ciphertexts
|
||||
* addition between a ciphertext and an unencrypted scalar
|
||||
* comparisons `<`, `<=`, `>`, `>=`, `==` between a ciphertext and an unencrypted scalar
|
||||
* division of a ciphertext by an unencrypted scalar
|
||||
* LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
|
||||
* multiplication of a ciphertext by an unencrypted scalar
|
||||
* bitwise shift `<<`, `>>`
|
||||
* subtraction of a ciphertext by another ciphertext
|
||||
* subtraction of a ciphertext by an unencrypted scalar
|
||||
* negation of a ciphertext
|
||||
* bitwise and, or and xor (\*)
|
||||
* comparisons `<`, `<=`, `>`, `>=`, `==` between two ciphertexts (\*)
|
||||
* division between two ciphertexts (\*)
|
||||
* MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (\*)
|
||||
|
||||
In what follows, some simple code examples are given.
|
||||
|
||||
## Public key encryption
|
||||
TFHE-rs supports both private and public key encryption methods. Note that the only difference
|
||||
between both lies into the encryption step: in this case, the encryption method is called using
|
||||
`public_key` instead of `client_key`.
|
||||
### Public key encryption.
|
||||
|
||||
TFHE-rs supports both private and public key encryption methods. Note that the only difference between both lies into the encryption step: in this case, the encryption method is called using `public_key` instead of `client_key`.
|
||||
|
||||
Here is a small example on how to use public encryption:
|
||||
|
||||
Here a small example on how to use public encryption:
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// Generate the client key and the server key:
|
||||
let (cks, mut sks) = gen_keys();
|
||||
let (cks, sks) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
|
||||
let pks = PublicKey::new(&cks);
|
||||
|
||||
let msg = 2;
|
||||
// Encryption of one message:
|
||||
let ct = pks.encrypt(true);
|
||||
let ct = pks.encrypt(&sks, msg);
|
||||
// Decryption:
|
||||
let dec = cks.decrypt(&ct);
|
||||
assert_eq!(true, dec);
|
||||
assert_eq!(dec, msg);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
In what follows, all examples are related to private key encryption.
|
||||
|
||||
## Arithmetic operations
|
||||
Classical arithmetic operations are supported by shortints:
|
||||
### Arithmetic operations.
|
||||
|
||||
Classical arithmetic operations are supported by shortint:
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -238,15 +223,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Bitwise operations
|
||||
#### bitwise operations
|
||||
|
||||
Short homomorphic integer types support some bitwise operations.
|
||||
|
||||
A simple example on how to use these operations:
|
||||
```rust
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -271,14 +254,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Comparisons
|
||||
#### comparisons
|
||||
|
||||
Short homomorphic integer types support comparison operations.
|
||||
|
||||
A simple example on how to use these operations:
|
||||
|
||||
```rust
|
||||
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -303,14 +285,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Univariate function evaluations
|
||||
#### univariate function evaluations
|
||||
|
||||
A simple example on how to use this operation to homomorphically compute
|
||||
the hamming weight (i.e., the number of bit equals to one) of an encrypted
|
||||
number.
|
||||
A simple example on how to use this operation to homomorphically compute the hamming weight (i.e., the number of bits equal to one) of an encrypted number.
|
||||
|
||||
```rust
|
||||
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -337,17 +316,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Bi-variate function evaluations
|
||||
#### bi-variate function evaluations
|
||||
|
||||
Using the shortint types offers the possibility to evaluate bi-variate functions, i.e.,
|
||||
functions that takes two ciphertexts as input. This requires to choose a parameter set
|
||||
such that the carry buffer size is at least as large as the message one i.e.,
|
||||
PARAM_MESSAGE_X_CARRY_Y with X <= Y.
|
||||
Using the shortint types offers the possibility to evaluate bi-variate functions, 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.
|
||||
|
||||
In what follows, a simple code example:
|
||||
Here is a simple code example:
|
||||
|
||||
```rust
|
||||
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -374,5 +349,3 @@ fn main() {
|
||||
assert_eq!(output, (msg1.count_ones() as u64 + msg2.count_ones() as u64) % modulus);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
# Cryptographic parameters
|
||||
|
||||
All parameter sets provides at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equals to $$2^{-40}$$ when computing a programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting_started/security_and_cryptography.md) for more details about the encryption process).
|
||||
# Cryptographic Parameters
|
||||
|
||||
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing 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
|
||||
|
||||
`shortint` comes with sets of parameters that permit to use the functionalities of the library securely and efficiently. Each parameter sets is associated to the message and carry precisions. Thus, each key pair is entangled to precision.
|
||||
`shortint` comes with sets of parameters that permit the use of the library functionalities securely and efficiently. Each parameter set is associated to the message and carry precisions. Thus, each key pair is entangled to precision.
|
||||
|
||||
The user is allowed to choose which set of parameters to use when creating the pair of keys.
|
||||
|
||||
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is:
|
||||
`PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
|
||||
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
|
||||
|
||||
In what follows, there is an example where keys are generated to have messages encoded over 3 bits i.e., computations are done modulus $$2^3 = 8$$), with 3 bits of carry.
|
||||
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
@@ -33,21 +30,19 @@ fn main() {
|
||||
|
||||
## Impact of parameters on the operations
|
||||
|
||||
As shown [here](../getting_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
|
||||
As shown [here](../getting\_started/benchmarks.md), the choice of the parameter set impacts the operations available and their efficiency.
|
||||
|
||||
### Generic bi-variate functions
|
||||
### Generic bi-variate functions.
|
||||
|
||||
The computations of bi-variate functions is based on a trick *concatenating* two ciphertexts into one. In the case where the carry buffer is not at least as large as the message one, this trick is not working anymore. Then, many bi-variate operations, such as comparisons cannot be correctly computed anymore. The only exception concerns the multiplication.
|
||||
The computations of bi-variate functions is based on a trick, _concatenating_ two ciphertexts into one. 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
|
||||
### Multiplication.
|
||||
|
||||
In the case of the multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication_algorithm#Quarter_square_multiplication). In order to correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM_MESSAGE_X_CARRY_Y with Y>=1). This method is in general slower than using the other one. Note that using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
|
||||
In the case of 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
|
||||
|
||||
Beyond the predefined parameter sets, this is possible to define new parameter sets.
|
||||
To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fulfill the
|
||||
`Parameter` structure fields.
|
||||
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:
|
||||
|
||||
@@ -77,12 +72,3 @@ fn main() {
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
# Serialization / Deserialization
|
||||
# Serialization/Deserialization
|
||||
|
||||
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that does the computations.
|
||||
As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are meant to be shared with the server that performs the computations.
|
||||
|
||||
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework, serde's Serialize and Deserialize are implemented on tfhe::shortint's types.
|
||||
|
||||
To be able to serialize our data, we need to pick a [data format](https://serde.rs/#data-formats), for our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
|
||||
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework. Serde's Serialize and Deserialize are then implemented on tfhe::shortint's types.
|
||||
|
||||
To 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
|
||||
@@ -15,7 +14,6 @@ To be able to serialize our data, we need to pick a [data format](https://serde.
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# Tutorial: Writing an homomorphic circuit using shortints
|
||||
# Tutorial
|
||||
|
||||
# 1. Key Generation
|
||||
## Writing an homomorphic circuit using shortint
|
||||
|
||||
## Key Generation
|
||||
|
||||
`tfhe::shortint` provides 2 key types:
|
||||
- `ClientKey`
|
||||
- `ServerKey`
|
||||
|
||||
* `ClientKey`
|
||||
* `ServerKey`
|
||||
|
||||
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.
|
||||
|
||||
@@ -12,7 +15,6 @@ The `ServerKey` is the key that is used to actually do the FHE computations. It
|
||||
|
||||
To reflect that, computation/operation methods are tied to the `ServerKey` type.
|
||||
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
@@ -22,8 +24,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# 2. Encrypting values
|
||||
## Encrypting values
|
||||
|
||||
Once the keys have been generated, the client key is used to encrypt data:
|
||||
|
||||
@@ -43,7 +44,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
# 2 bis. Encrypting values using a public key
|
||||
## Encrypting values using a public key
|
||||
|
||||
Once the keys have been generated, the client key is used to encrypt data:
|
||||
|
||||
@@ -64,11 +65,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Computing and decrypting
|
||||
|
||||
# 3. Computing and decrypting
|
||||
|
||||
With our `server_key`, and encrypted values, we can now do an addition
|
||||
and then decrypt the result.
|
||||
With our `server_key` and encrypted values, we can now do an addition and then decrypt the result.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use tfhe::shortint::keycache::{FileStorage, NamedParam, PersistentStorage};
|
||||
|
||||
use tfhe::shortint::parameters::ALL_PARAMETER_VEC;
|
||||
use tfhe::shortint::{gen_keys, ClientKey, ServerKey};
|
||||
use tfhe::shortint::keycache::{NamedParam, KEY_CACHE, KEY_CACHE_WOPBS};
|
||||
use tfhe::shortint::parameters::parameters_wopbs_message_carry::{
|
||||
WOPBS_PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_3_CARRY_3,
|
||||
WOPBS_PARAM_MESSAGE_4_CARRY_4,
|
||||
};
|
||||
use tfhe::shortint::parameters::{
|
||||
Parameters, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2,
|
||||
PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_4_CARRY_4,
|
||||
};
|
||||
|
||||
fn client_server_keys() {
|
||||
let file_storage = FileStorage::new("keys/shortint/client_server".to_string());
|
||||
|
||||
println!("Generating (ClientKey, ServerKey)");
|
||||
println!("Generating shortint (ClientKey, ServerKey)");
|
||||
for (i, params) in ALL_PARAMETER_VEC.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
@@ -15,17 +18,41 @@ fn client_server_keys() {
|
||||
params.name()
|
||||
);
|
||||
|
||||
let keys: Option<(ClientKey, ServerKey)> = file_storage.load(params);
|
||||
let _ = KEY_CACHE.get_from_param(params);
|
||||
// Clear keys as we go to avoid filling the RAM
|
||||
KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
|
||||
if keys.is_some() {
|
||||
continue;
|
||||
}
|
||||
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),
|
||||
];
|
||||
|
||||
let client_server_keys = gen_keys(params);
|
||||
file_storage.store(params, &client_server_keys);
|
||||
println!("Generating woPBS keys");
|
||||
for (i, (params_shortint, params_wopbs)) in WOPBS_PARAMS.iter().copied().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}, {}",
|
||||
i + 1,
|
||||
WOPBS_PARAMS.len(),
|
||||
params_shortint.name(),
|
||||
params_wopbs.name(),
|
||||
);
|
||||
|
||||
let _ = KEY_CACHE_WOPBS.get_from_param((params_shortint, params_wopbs));
|
||||
// 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()
|
||||
}
|
||||
|
||||
15
tfhe/katex-header.html
Normal file
15
tfhe/katex-header.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/katex.min.css" integrity="sha384-KiWOvVjnN8qwAZbuQyWDIbfCLFhLXNETzBQjA/92pIowpC0d2O3nppDGQVgwd2nB" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/katex.min.js" integrity="sha384-0fdwu/T/EQMsQlrHCCHoH10pkPLlKA1jL5dFyUOvB3lfeT2540/2g6YgSi2BL14p" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.15.3/dist/contrib/auto-render.min.js" integrity="sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
renderMathInElement(document.body, {
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! This module implements the ciphertext structure containing an encryption of a Boolean message.
|
||||
|
||||
use crate::core_crypto::prelude::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// A structure containing a ciphertext, meant to encrypt a Boolean message.
|
||||
@@ -10,7 +10,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
/// It is used to evaluate a Boolean circuits homomorphically.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Ciphertext {
|
||||
Encrypted(LweCiphertext32),
|
||||
Encrypted(LweCiphertextOwned<u32>),
|
||||
Trivial(bool),
|
||||
}
|
||||
|
||||
@@ -25,11 +25,9 @@ impl Serialize for Ciphertext {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
match self {
|
||||
Ciphertext::Encrypted(lwe) => {
|
||||
let ciphertext = ser_eng.serialize(lwe).map_err(serde::ser::Error::custom)?;
|
||||
let ciphertext = bincode::serialize(lwe).map_err(serde::ser::Error::custom)?;
|
||||
SerializableCiphertext::Encrypted(ciphertext)
|
||||
}
|
||||
Ciphertext::Trivial(b) => SerializableCiphertext::Trivial(*b),
|
||||
@@ -45,13 +43,10 @@ impl<'de> Deserialize<'de> for Ciphertext {
|
||||
{
|
||||
let thing = SerializableCiphertext::deserialize(deserializer)?;
|
||||
|
||||
let mut de_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(match thing {
|
||||
SerializableCiphertext::Encrypted(data) => {
|
||||
let lwe = de_eng
|
||||
.deserialize(data.as_slice())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
let lwe =
|
||||
bincode::deserialize(data.as_slice()).map_err(serde::de::Error::custom)?;
|
||||
Self::Encrypted(lwe)
|
||||
}
|
||||
SerializableCiphertext::Trivial(b) => Self::Trivial(b),
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
//! encryption and decryption methods.
|
||||
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::boolean::engine::{CpuBooleanEngine, WithThreadLocalEngine};
|
||||
use crate::boolean::engine::{BooleanEngine, WithThreadLocalEngine};
|
||||
use crate::boolean::parameters::BooleanParameters;
|
||||
use crate::core_crypto::prelude::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
@@ -20,8 +20,8 @@ use std::fmt::{Debug, Formatter};
|
||||
/// * `parameters` - the cryptographic parameter set.
|
||||
#[derive(Clone)]
|
||||
pub struct ClientKey {
|
||||
pub(crate) lwe_secret_key: LweSecretKey32,
|
||||
pub(crate) glwe_secret_key: GlweSecretKey32,
|
||||
pub(crate) lwe_secret_key: LweSecretKeyOwned<u32>,
|
||||
pub(crate) glwe_secret_key: GlweSecretKeyOwned<u32>,
|
||||
pub(crate) parameters: BooleanParameters,
|
||||
}
|
||||
|
||||
@@ -46,12 +46,11 @@ impl Debug for ClientKey {
|
||||
}
|
||||
|
||||
impl ClientKey {
|
||||
/// Encrypts a Boolean message using the client key.
|
||||
/// Encrypt a Boolean message using the client key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
@@ -65,19 +64,16 @@ impl ClientKey {
|
||||
/// let dec = cks.decrypt(&ct);
|
||||
/// assert_eq!(true, dec);
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn encrypt(&self, message: bool) -> Ciphertext {
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.encrypt(message, self))
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.encrypt(message, self))
|
||||
}
|
||||
|
||||
/// Decrypts a ciphertext encrypting a Boolean message using the client key.
|
||||
/// Decrypt a ciphertext encrypting a Boolean message using the client key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
@@ -91,24 +87,16 @@ impl ClientKey {
|
||||
/// let dec = cks.decrypt(&ct);
|
||||
/// assert_eq!(true, dec);
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn decrypt(&self, ct: &Ciphertext) -> bool {
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.decrypt(ct, self))
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.decrypt(ct, self))
|
||||
}
|
||||
|
||||
/// Allocates and generates a client key.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This will panic when the "cuda" feature is enabled and the parameters
|
||||
/// uses a GlweDimension > 1 (as it is not yet supported by the cuda backend).
|
||||
/// Allocate and generate a client key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::client_key::ClientKey;
|
||||
/// use tfhe::boolean::parameters::TFHE_LIB_PARAMETERS;
|
||||
@@ -117,23 +105,9 @@ impl ClientKey {
|
||||
/// // Generate the client key:
|
||||
/// let cks = ClientKey::new(&TFHE_LIB_PARAMETERS);
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::client_key::ClientKey;
|
||||
/// use tfhe::boolean::parameters::GPU_DEFAULT_PARAMETERS;
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
/// // Generate the client key:
|
||||
/// let cks = ClientKey::new(&GPU_DEFAULT_PARAMETERS);}
|
||||
/// ```
|
||||
pub fn new(parameter_set: &BooleanParameters) -> ClientKey {
|
||||
#[cfg(feature = "cuda")]
|
||||
{
|
||||
if parameter_set.glwe_dimension.0 > 1 {
|
||||
panic!("the cuda backend does not support support GlweSize greater than one");
|
||||
}
|
||||
}
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.create_client_key(*parameter_set))
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.create_client_key(*parameter_set))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,14 +123,10 @@ impl Serialize for ClientKey {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
let lwe_secret_key = ser_eng
|
||||
.serialize(&self.lwe_secret_key)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
let glwe_secret_key = ser_eng
|
||||
.serialize(&self.glwe_secret_key)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
let lwe_secret_key =
|
||||
bincode::serialize(&self.lwe_secret_key).map_err(serde::ser::Error::custom)?;
|
||||
let glwe_secret_key =
|
||||
bincode::serialize(&self.glwe_secret_key).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
SerializableClientKey {
|
||||
lwe_secret_key,
|
||||
@@ -174,14 +144,11 @@ impl<'de> Deserialize<'de> for ClientKey {
|
||||
{
|
||||
let thing =
|
||||
SerializableClientKey::deserialize(deserializer).map_err(serde::de::Error::custom)?;
|
||||
let mut de_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
lwe_secret_key: de_eng
|
||||
.deserialize(thing.lwe_secret_key.as_slice())
|
||||
lwe_secret_key: bincode::deserialize(thing.lwe_secret_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
glwe_secret_key: de_eng
|
||||
.deserialize(thing.glwe_secret_key.as_slice())
|
||||
glwe_secret_key: bincode::deserialize(thing.glwe_secret_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
parameters: thing.parameters,
|
||||
})
|
||||
|
||||
310
tfhe/src/boolean/engine/bootstrapping.rs
Normal file
310
tfhe/src/boolean/engine/bootstrapping.rs
Normal file
@@ -0,0 +1,310 @@
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::boolean::{ClientKey, PLAINTEXT_TRUE};
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::fft_impl::math::fft::Fft;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::error::Error;
|
||||
|
||||
/// Memory used as buffer for the bootstrap
|
||||
///
|
||||
/// It contains contiguous chunk which is then sliced and converted
|
||||
/// into core's View types.
|
||||
#[derive(Default)]
|
||||
struct Memory {
|
||||
buffer: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// Return a tuple with buffers that matches the server key.
|
||||
///
|
||||
/// - The first element is the accumulator for bootstrap step.
|
||||
/// - The second element is a lwe buffer where the result of the of the bootstrap should be
|
||||
/// written
|
||||
fn as_buffers(
|
||||
&mut self,
|
||||
server_key: &ServerKey,
|
||||
) -> (GlweCiphertextView<'_, u32>, LweCiphertextMutView<'_, u32>) {
|
||||
let num_elem_in_accumulator = server_key.bootstrapping_key.glwe_size().0
|
||||
* server_key.bootstrapping_key.polynomial_size().0;
|
||||
let num_elem_in_lwe = server_key
|
||||
.bootstrapping_key
|
||||
.output_lwe_dimension()
|
||||
.to_lwe_size()
|
||||
.0;
|
||||
let total_elem_needed = num_elem_in_accumulator + num_elem_in_lwe;
|
||||
|
||||
let all_elements = if self.buffer.len() < total_elem_needed {
|
||||
self.buffer.resize(total_elem_needed, 0u32);
|
||||
self.buffer.as_mut_slice()
|
||||
} else {
|
||||
&mut self.buffer[..total_elem_needed]
|
||||
};
|
||||
|
||||
let (accumulator_elements, lwe_elements) =
|
||||
all_elements.split_at_mut(num_elem_in_accumulator);
|
||||
|
||||
{
|
||||
let mut accumulator = GlweCiphertextMutView::from_container(
|
||||
accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
);
|
||||
|
||||
accumulator.get_mut_mask().as_mut().fill(0u32);
|
||||
accumulator.get_mut_body().as_mut().fill(PLAINTEXT_TRUE);
|
||||
}
|
||||
|
||||
let accumulator = GlweCiphertextView::from_container(
|
||||
accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
);
|
||||
|
||||
let lwe = LweCiphertextMutView::from_container(lwe_elements);
|
||||
|
||||
(accumulator, lwe)
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure containing the server public key.
|
||||
///
|
||||
/// This server key data lives on the CPU.
|
||||
///
|
||||
/// The server key is generated by the client and is meant to be published: the client
|
||||
/// sends it to the server so it can compute homomorphic Boolean circuits.
|
||||
///
|
||||
/// In more details, it contains:
|
||||
/// * `bootstrapping_key` - a public key, used to perform the bootstrapping operation.
|
||||
/// * `key_switching_key` - a public key, used to perform the key-switching operation.
|
||||
#[derive(Clone)]
|
||||
pub struct ServerKey {
|
||||
pub(crate) bootstrapping_key: FourierLweBootstrapKeyOwned,
|
||||
pub(crate) key_switching_key: LweKeyswitchKeyOwned<u32>,
|
||||
}
|
||||
|
||||
/// Perform ciphertext bootstraps on the CPU
|
||||
pub(crate) struct Bootstrapper {
|
||||
memory: Memory,
|
||||
/// A structure containing two CSPRNGs to generate material for encryption like public masks
|
||||
/// and secret errors.
|
||||
///
|
||||
/// The [`EncryptionRandomGenerator`] contains two CSPRNGs, one publicly seeded used to
|
||||
/// generate mask coefficients and one privately seeded used to generate errors during
|
||||
/// encryption.
|
||||
pub(crate) encryption_generator: EncryptionRandomGenerator<ActivatedRandomGenerator>,
|
||||
pub(crate) computation_buffers: ComputationBuffers,
|
||||
}
|
||||
|
||||
impl Bootstrapper {
|
||||
pub fn new(seeder: &mut dyn Seeder) -> Self {
|
||||
Bootstrapper {
|
||||
memory: Default::default(),
|
||||
encryption_generator: EncryptionRandomGenerator::<_>::new(seeder.seed(), seeder),
|
||||
computation_buffers: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_server_key(
|
||||
&mut self,
|
||||
cks: &ClientKey,
|
||||
) -> Result<ServerKey, Box<dyn std::error::Error>> {
|
||||
let standard_bootstraping_key: LweBootstrapKeyOwned<u32> =
|
||||
par_allocate_and_generate_new_lwe_bootstrap_key(
|
||||
&cks.lwe_secret_key,
|
||||
&cks.glwe_secret_key,
|
||||
cks.parameters.pbs_base_log,
|
||||
cks.parameters.pbs_level,
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
// creation of the bootstrapping key in the Fourier domain
|
||||
let mut fourier_bsk = FourierLweBootstrapKey::new(
|
||||
standard_bootstraping_key.input_lwe_dimension(),
|
||||
standard_bootstraping_key.glwe_size(),
|
||||
standard_bootstraping_key.polynomial_size(),
|
||||
standard_bootstraping_key.decomposition_base_log(),
|
||||
standard_bootstraping_key.decomposition_level_count(),
|
||||
);
|
||||
|
||||
let fft = Fft::new(standard_bootstraping_key.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
self.computation_buffers.resize(
|
||||
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized_scratch(fft)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
let stack = self.computation_buffers.stack();
|
||||
|
||||
// Conversion to fourier domain
|
||||
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized(
|
||||
&standard_bootstraping_key,
|
||||
&mut fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
|
||||
// Convert the GLWE secret key into an LWE secret key:
|
||||
let big_lwe_secret_key = cks.glwe_secret_key.clone().into_lwe_secret_key();
|
||||
|
||||
// creation of the key switching key
|
||||
let ksk = allocate_and_generate_new_lwe_keyswitch_key(
|
||||
&big_lwe_secret_key,
|
||||
&cks.lwe_secret_key,
|
||||
cks.parameters.ks_base_log,
|
||||
cks.parameters.ks_level,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
Ok(ServerKey {
|
||||
bootstrapping_key: fourier_bsk,
|
||||
key_switching_key: ksk,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap(
|
||||
&mut self,
|
||||
input: &LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<LweCiphertextOwned<u32>, Box<dyn Error>> {
|
||||
let (accumulator, mut buffer_after_pbs) = self.memory.as_buffers(server_key);
|
||||
|
||||
let fourier_bsk = &server_key.bootstrapping_key;
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
self.computation_buffers.resize(
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized_scratch::<u64>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
let stack = self.computation_buffers.stack();
|
||||
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
input,
|
||||
&mut buffer_after_pbs,
|
||||
&accumulator,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
|
||||
Ok(LweCiphertext::from_container(
|
||||
buffer_after_pbs.as_ref().to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn keyswitch(
|
||||
&mut self,
|
||||
input: &LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<LweCiphertextOwned<u32>, Box<dyn Error>> {
|
||||
// Allocate the output of the KS
|
||||
let mut output = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
);
|
||||
|
||||
keyswitch_lwe_ciphertext(&server_key.key_switching_key, input, &mut output);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap_keyswitch(
|
||||
&mut self,
|
||||
mut ciphertext: LweCiphertextOwned<u32>,
|
||||
server_key: &ServerKey,
|
||||
) -> Result<Ciphertext, Box<dyn Error>> {
|
||||
let (accumulator, mut buffer_lwe_after_pbs) = self.memory.as_buffers(server_key);
|
||||
|
||||
let fourier_bsk = &server_key.bootstrapping_key;
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
self.computation_buffers.resize(
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized_scratch::<u64>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
let stack = self.computation_buffers.stack();
|
||||
|
||||
// Compute a bootstrap
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
&ciphertext,
|
||||
&mut buffer_lwe_after_pbs,
|
||||
&accumulator,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
|
||||
// Compute a key switch to get back to input key
|
||||
keyswitch_lwe_ciphertext(
|
||||
&server_key.key_switching_key,
|
||||
&buffer_lwe_after_pbs,
|
||||
&mut ciphertext,
|
||||
);
|
||||
|
||||
Ok(Ciphertext::Encrypted(ciphertext))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializableServerKey {
|
||||
pub bootstrapping_key: Vec<u8>,
|
||||
pub key_switching_key: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Serialize for ServerKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let key_switching_key =
|
||||
bincode::serialize(&self.key_switching_key).map_err(serde::ser::Error::custom)?;
|
||||
let bootstrapping_key =
|
||||
bincode::serialize(&self.bootstrapping_key).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
SerializableServerKey {
|
||||
key_switching_key,
|
||||
bootstrapping_key,
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ServerKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let thing =
|
||||
SerializableServerKey::deserialize(deserializer).map_err(serde::de::Error::custom)?;
|
||||
|
||||
let key_switching_key = bincode::deserialize(thing.key_switching_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
let bootstrapping_key = bincode::deserialize(thing.bootstrapping_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
bootstrapping_key,
|
||||
key_switching_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::boolean::{ClientKey, PLAINTEXT_TRUE};
|
||||
use crate::core_crypto::prelude::*;
|
||||
use crate::seeders::new_seeder;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::error::Error;
|
||||
|
||||
use super::{BooleanServerKey, Bootstrapper};
|
||||
|
||||
/// Memory used as buffer for the bootstrap
|
||||
///
|
||||
/// It contains contiguous chunk which is then sliced and converted
|
||||
/// into core's View types.
|
||||
#[derive(Default)]
|
||||
struct Memory {
|
||||
elements: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// Returns a tuple with buffers that matches the server key.
|
||||
///
|
||||
/// - The first element is the accumulator for bootstrap step.
|
||||
/// - The second element is a lwe buffer where the result of the of the bootstrap should be
|
||||
/// written
|
||||
fn as_buffers(
|
||||
&mut self,
|
||||
engine: &mut DefaultEngine,
|
||||
server_key: &CpuBootstrapKey,
|
||||
) -> (GlweCiphertextView32, LweCiphertextMutView32) {
|
||||
let num_elem_in_accumulator = server_key
|
||||
.bootstrapping_key
|
||||
.glwe_dimension()
|
||||
.to_glwe_size()
|
||||
.0
|
||||
* server_key.bootstrapping_key.polynomial_size().0;
|
||||
let num_elem_in_lwe = server_key
|
||||
.bootstrapping_key
|
||||
.output_lwe_dimension()
|
||||
.to_lwe_size()
|
||||
.0;
|
||||
let total_elem_needed = num_elem_in_accumulator + num_elem_in_lwe;
|
||||
|
||||
let all_elements = if self.elements.len() < total_elem_needed {
|
||||
self.elements.resize(total_elem_needed, 0u32);
|
||||
self.elements.as_mut_slice()
|
||||
} else {
|
||||
&mut self.elements[..total_elem_needed]
|
||||
};
|
||||
|
||||
let (accumulator_elements, lwe_elements) =
|
||||
all_elements.split_at_mut(num_elem_in_accumulator);
|
||||
accumulator_elements
|
||||
[num_elem_in_accumulator - server_key.bootstrapping_key.polynomial_size().0..]
|
||||
.fill(PLAINTEXT_TRUE);
|
||||
|
||||
let accumulator = engine
|
||||
.create_glwe_ciphertext_from(
|
||||
&*accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
)
|
||||
.unwrap();
|
||||
let lwe = engine.create_lwe_ciphertext_from(lwe_elements).unwrap();
|
||||
|
||||
(accumulator, lwe)
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure containing the server public key.
|
||||
///
|
||||
/// This server key data lives on the CPU.
|
||||
///
|
||||
/// The server key is generated by the client and is meant to be published: the client
|
||||
/// sends it to the server so it can compute homomorphic Boolean circuits.
|
||||
///
|
||||
/// In more details, it contains:
|
||||
/// * `bootstrapping_key` - a public key, used to perform the bootstrapping operation.
|
||||
/// * `key_switching_key` - a public key, used to perform the key-switching operation.
|
||||
#[derive(Clone)]
|
||||
pub struct CpuBootstrapKey {
|
||||
pub(super) standard_bootstraping_key: LweBootstrapKey32,
|
||||
pub(super) bootstrapping_key: FftFourierLweBootstrapKey32,
|
||||
pub(super) key_switching_key: LweKeyswitchKey32,
|
||||
}
|
||||
|
||||
impl CpuBootstrapKey {}
|
||||
|
||||
impl BooleanServerKey for CpuBootstrapKey {
|
||||
fn lwe_size(&self) -> LweSize {
|
||||
self.bootstrapping_key.input_lwe_dimension().to_lwe_size()
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs ciphertext bootstraps on the CPU
|
||||
pub(crate) struct CpuBootstrapper {
|
||||
memory: Memory,
|
||||
engine: DefaultEngine,
|
||||
fourier_engine: FftEngine,
|
||||
}
|
||||
|
||||
impl CpuBootstrapper {
|
||||
pub(crate) fn new_server_key(
|
||||
&mut self,
|
||||
cks: &ClientKey,
|
||||
) -> Result<CpuBootstrapKey, Box<dyn std::error::Error>> {
|
||||
// convert into a variance for rlwe context
|
||||
let var_rlwe = Variance(cks.parameters.glwe_modular_std_dev.get_variance());
|
||||
// creation of the bootstrapping key in the Fourier domain
|
||||
|
||||
let standard_bootstraping_key: LweBootstrapKey32 =
|
||||
self.engine.generate_new_lwe_bootstrap_key(
|
||||
&cks.lwe_secret_key,
|
||||
&cks.glwe_secret_key,
|
||||
cks.parameters.pbs_base_log,
|
||||
cks.parameters.pbs_level,
|
||||
var_rlwe,
|
||||
)?;
|
||||
|
||||
let fourier_bsk = self
|
||||
.fourier_engine
|
||||
.convert_lwe_bootstrap_key(&standard_bootstraping_key)?;
|
||||
|
||||
// Convert the GLWE secret key into an LWE secret key:
|
||||
let big_lwe_secret_key = self
|
||||
.engine
|
||||
.transform_glwe_secret_key_to_lwe_secret_key(cks.glwe_secret_key.clone())?;
|
||||
|
||||
// convert into a variance for lwe context
|
||||
let var_lwe = Variance(cks.parameters.lwe_modular_std_dev.get_variance());
|
||||
// creation of the key switching key
|
||||
let ksk = self.engine.generate_new_lwe_keyswitch_key(
|
||||
&big_lwe_secret_key,
|
||||
&cks.lwe_secret_key,
|
||||
cks.parameters.ks_level,
|
||||
cks.parameters.ks_base_log,
|
||||
var_lwe,
|
||||
)?;
|
||||
|
||||
Ok(CpuBootstrapKey {
|
||||
standard_bootstraping_key,
|
||||
bootstrapping_key: fourier_bsk,
|
||||
key_switching_key: ksk,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CpuBootstrapper {
|
||||
fn default() -> Self {
|
||||
let engine =
|
||||
DefaultEngine::new(new_seeder()).expect("Unexpectedly failed to create a core engine");
|
||||
|
||||
let fourier_engine = FftEngine::new(()).unwrap();
|
||||
Self {
|
||||
memory: Default::default(),
|
||||
engine,
|
||||
fourier_engine,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bootstrapper for CpuBootstrapper {
|
||||
type ServerKey = CpuBootstrapKey;
|
||||
|
||||
fn bootstrap(
|
||||
&mut self,
|
||||
input: &LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<LweCiphertext32, Box<dyn Error>> {
|
||||
let (accumulator, mut buffer_lwe_after_pbs) =
|
||||
self.memory.as_buffers(&mut self.engine, server_key);
|
||||
|
||||
// Need to be able to get view from &Lwe
|
||||
let inner_data = self
|
||||
.engine
|
||||
.consume_retrieve_lwe_ciphertext(input.clone())
|
||||
.unwrap();
|
||||
let input = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(inner_data.as_slice())
|
||||
.unwrap();
|
||||
|
||||
self.fourier_engine.discard_bootstrap_lwe_ciphertext(
|
||||
&mut buffer_lwe_after_pbs,
|
||||
&input,
|
||||
&accumulator,
|
||||
&server_key.bootstrapping_key,
|
||||
)?;
|
||||
|
||||
let data = self
|
||||
.engine
|
||||
.consume_retrieve_lwe_ciphertext(buffer_lwe_after_pbs)
|
||||
.unwrap()
|
||||
.to_vec();
|
||||
let ct = self.engine.create_lwe_ciphertext_from(data)?;
|
||||
Ok(ct)
|
||||
}
|
||||
|
||||
fn keyswitch(
|
||||
&mut self,
|
||||
input: &LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<LweCiphertext32, Box<dyn Error>> {
|
||||
// Allocate the output of the KS
|
||||
let mut ct_ks = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])?;
|
||||
|
||||
self.engine.discard_keyswitch_lwe_ciphertext(
|
||||
&mut ct_ks,
|
||||
input,
|
||||
&server_key.key_switching_key,
|
||||
)?;
|
||||
|
||||
Ok(ct_ks)
|
||||
}
|
||||
|
||||
fn bootstrap_keyswitch(
|
||||
&mut self,
|
||||
ciphertext: LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<Ciphertext, Box<dyn Error>> {
|
||||
let (accumulator, mut buffer_lwe_after_pbs) =
|
||||
self.memory.as_buffers(&mut self.engine, server_key);
|
||||
|
||||
let mut inner_data = self
|
||||
.engine
|
||||
.consume_retrieve_lwe_ciphertext(ciphertext)
|
||||
.unwrap();
|
||||
let input_view = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(inner_data.as_slice())?;
|
||||
|
||||
self.fourier_engine.discard_bootstrap_lwe_ciphertext(
|
||||
&mut buffer_lwe_after_pbs,
|
||||
&input_view,
|
||||
&accumulator,
|
||||
&server_key.bootstrapping_key,
|
||||
)?;
|
||||
|
||||
// Convert from a mut view to a view
|
||||
let slice = self
|
||||
.engine
|
||||
.consume_retrieve_lwe_ciphertext(buffer_lwe_after_pbs)
|
||||
.unwrap();
|
||||
let buffer_lwe_after_pbs = self.engine.create_lwe_ciphertext_from(&*slice)?;
|
||||
|
||||
let mut output_view = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(inner_data.as_mut_slice())?;
|
||||
|
||||
// Compute a key switch to get back to input key
|
||||
self.engine.discard_keyswitch_lwe_ciphertext(
|
||||
&mut output_view,
|
||||
&buffer_lwe_after_pbs,
|
||||
&server_key.key_switching_key,
|
||||
)?;
|
||||
|
||||
let ciphertext = self.engine.create_lwe_ciphertext_from(inner_data)?;
|
||||
|
||||
Ok(Ciphertext::Encrypted(ciphertext))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializableCpuServerKey {
|
||||
pub standard_bootstraping_key: Vec<u8>,
|
||||
pub key_switching_key: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Serialize for CpuBootstrapKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
let key_switching_key = ser_eng
|
||||
.serialize(&self.key_switching_key)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
let standard_bootstraping_key = ser_eng
|
||||
.serialize(&self.standard_bootstraping_key)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
|
||||
SerializableCpuServerKey {
|
||||
key_switching_key,
|
||||
standard_bootstraping_key,
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for CpuBootstrapKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let thing = SerializableCpuServerKey::deserialize(deserializer)
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
|
||||
|
||||
let key_switching_key = ser_eng
|
||||
.deserialize(thing.key_switching_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
let standard_bootstraping_key = ser_eng
|
||||
.deserialize(thing.standard_bootstraping_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
let bootstrapping_key = FftEngine::new(())
|
||||
.unwrap()
|
||||
.convert_lwe_bootstrap_key(&standard_bootstraping_key)
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
standard_bootstraping_key,
|
||||
key_switching_key,
|
||||
bootstrapping_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
use super::{BooleanServerKey, Bootstrapper, CpuBootstrapKey};
|
||||
use crate::boolean::PLAINTEXT_TRUE;
|
||||
use crate::core_crypto::prelude::*;
|
||||
use crate::seeders::new_seeder;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
|
||||
pub(crate) struct CudaBootstrapKey {
|
||||
bootstrapping_key: CudaFourierLweBootstrapKey32,
|
||||
key_switching_key: CudaLweKeyswitchKey32,
|
||||
}
|
||||
|
||||
impl BooleanServerKey for CudaBootstrapKey {
|
||||
fn lwe_size(&self) -> LweSize {
|
||||
self.bootstrapping_key.input_lwe_dimension().to_lwe_size()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialOrd, PartialEq, Ord, Eq)]
|
||||
struct KeyId {
|
||||
// Both of these are for the accumulator
|
||||
glwe_dimension: GlweDimension,
|
||||
polynomial_size: PolynomialSize,
|
||||
lwe_dimension_after_bootstrap: LweDimension,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CudaMemory {
|
||||
cuda_buffers: BTreeMap<KeyId, CudaBuffers>,
|
||||
}
|
||||
|
||||
/// All the buffers needed to do a bootstrap or a keyswitch or bootstrap + keyswitch
|
||||
struct CudaBuffers {
|
||||
accumulator: CudaGlweCiphertext32,
|
||||
// Its size is the one of a ciphertext after pbs
|
||||
lwe_after_bootstrap: CudaLweCiphertext32,
|
||||
// Its size is the one of a ciphertext after a keyswitch
|
||||
// ie the size of a ciphertext before the bootstrap
|
||||
lwe_after_keyswitch: CudaLweCiphertext32,
|
||||
}
|
||||
|
||||
impl CudaMemory {
|
||||
/// Returns the buffers that matches the given key.
|
||||
fn as_buffers_for_key(
|
||||
&mut self,
|
||||
cpu_engine: &mut DefaultEngine,
|
||||
cuda_engine: &mut CudaEngine,
|
||||
server_key: &CudaBootstrapKey,
|
||||
) -> &mut CudaBuffers {
|
||||
let key_id = KeyId {
|
||||
glwe_dimension: server_key.bootstrapping_key.glwe_dimension(),
|
||||
polynomial_size: server_key.bootstrapping_key.polynomial_size(),
|
||||
lwe_dimension_after_bootstrap: server_key.bootstrapping_key.output_lwe_dimension(),
|
||||
};
|
||||
|
||||
self.cuda_buffers.entry(key_id).or_insert_with(|| {
|
||||
let output_lwe_size = server_key
|
||||
.bootstrapping_key
|
||||
.output_lwe_dimension()
|
||||
.to_lwe_size();
|
||||
let output_ciphertext = cpu_engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; output_lwe_size.0])
|
||||
.unwrap();
|
||||
let cuda_lwe_after_bootstrap = cuda_engine
|
||||
.convert_lwe_ciphertext(&output_ciphertext)
|
||||
.unwrap();
|
||||
|
||||
let num_elements = server_key
|
||||
.bootstrapping_key
|
||||
.glwe_dimension()
|
||||
.to_glwe_size()
|
||||
.0
|
||||
* server_key.bootstrapping_key.polynomial_size().0;
|
||||
let mut acc = vec![0u32; num_elements];
|
||||
acc[num_elements - server_key.bootstrapping_key.polynomial_size().0..]
|
||||
.fill(PLAINTEXT_TRUE);
|
||||
let accumulator = cpu_engine
|
||||
.create_glwe_ciphertext_from(acc, server_key.bootstrapping_key.polynomial_size())
|
||||
.unwrap();
|
||||
let cuda_accumulator = cuda_engine.convert_glwe_ciphertext(&accumulator).unwrap();
|
||||
|
||||
let lwe_size_after_keyswitch = server_key
|
||||
.key_switching_key
|
||||
.output_lwe_dimension()
|
||||
.to_lwe_size();
|
||||
let output_ciphertext = cpu_engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; lwe_size_after_keyswitch.0])
|
||||
.unwrap();
|
||||
let cuda_lwe_after_keyswitch = cuda_engine
|
||||
.convert_lwe_ciphertext(&output_ciphertext)
|
||||
.unwrap();
|
||||
|
||||
CudaBuffers {
|
||||
accumulator: cuda_accumulator,
|
||||
lwe_after_bootstrap: cuda_lwe_after_bootstrap,
|
||||
lwe_after_keyswitch: cuda_lwe_after_keyswitch,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CudaBootstrapper {
|
||||
cuda_engine: CudaEngine,
|
||||
cpu_engine: DefaultEngine,
|
||||
memory: CudaMemory,
|
||||
}
|
||||
|
||||
impl Default for CudaBootstrapper {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cuda_engine: CudaEngine::new(()).unwrap(),
|
||||
// Secret does not matter, we won't generate keys or ciphertext.
|
||||
cpu_engine: DefaultEngine::new(new_seeder()).unwrap(),
|
||||
memory: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CudaBootstrapper {
|
||||
pub(crate) fn new_serverk_key(
|
||||
&mut self,
|
||||
server_key: &CpuBootstrapKey,
|
||||
) -> Result<CudaBootstrapKey, Box<dyn std::error::Error>> {
|
||||
let bootstrapping_key = self
|
||||
.cuda_engine
|
||||
.convert_lwe_bootstrap_key(&server_key.standard_bootstraping_key)?;
|
||||
|
||||
let key_switching_key = self
|
||||
.cuda_engine
|
||||
.convert_lwe_keyswitch_key(&server_key.key_switching_key)?;
|
||||
|
||||
Ok(CudaBootstrapKey {
|
||||
bootstrapping_key,
|
||||
key_switching_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Bootstrapper for CudaBootstrapper {
|
||||
type ServerKey = CudaBootstrapKey;
|
||||
|
||||
fn bootstrap(
|
||||
&mut self,
|
||||
input: &LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<LweCiphertext32, Box<dyn std::error::Error>> {
|
||||
let cuda_buffers =
|
||||
self.memory
|
||||
.as_buffers_for_key(&mut self.cpu_engine, &mut self.cuda_engine, server_key);
|
||||
|
||||
// The output size of keyswitch is the one of regular boolean ciphertext
|
||||
// so we can use lwe_after_keyswitch
|
||||
self.cuda_engine
|
||||
.discard_convert_lwe_ciphertext(&mut cuda_buffers.lwe_after_keyswitch, input)?;
|
||||
|
||||
self.cuda_engine.discard_bootstrap_lwe_ciphertext(
|
||||
&mut cuda_buffers.lwe_after_bootstrap,
|
||||
&cuda_buffers.lwe_after_keyswitch,
|
||||
&cuda_buffers.accumulator,
|
||||
&server_key.bootstrapping_key,
|
||||
)?;
|
||||
|
||||
let output_ciphertext = self
|
||||
.cuda_engine
|
||||
.convert_lwe_ciphertext(&cuda_buffers.lwe_after_bootstrap)?;
|
||||
Ok(output_ciphertext)
|
||||
}
|
||||
|
||||
fn keyswitch(
|
||||
&mut self,
|
||||
input: &LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<LweCiphertext32, Box<dyn std::error::Error>> {
|
||||
let cuda_buffers =
|
||||
self.memory
|
||||
.as_buffers_for_key(&mut self.cpu_engine, &mut self.cuda_engine, server_key);
|
||||
|
||||
// The input of the function we implement must be a ciphertext that result of a bootstrap
|
||||
// so we can discard convert in the lwe ciphertext after bootstrap
|
||||
self.cuda_engine
|
||||
.discard_convert_lwe_ciphertext(&mut cuda_buffers.lwe_after_bootstrap, input)?;
|
||||
|
||||
self.cuda_engine.discard_keyswitch_lwe_ciphertext(
|
||||
&mut cuda_buffers.lwe_after_keyswitch,
|
||||
&cuda_buffers.lwe_after_bootstrap,
|
||||
&server_key.key_switching_key,
|
||||
)?;
|
||||
|
||||
let output_ciphertext = self
|
||||
.cuda_engine
|
||||
.convert_lwe_ciphertext(&cuda_buffers.lwe_after_keyswitch)?;
|
||||
Ok(output_ciphertext)
|
||||
}
|
||||
|
||||
fn bootstrap_keyswitch(
|
||||
&mut self,
|
||||
ciphertext: LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<Ciphertext, Box<dyn std::error::Error>> {
|
||||
// We re-implement instead of calling our bootstrap and then keyswitch fn
|
||||
// to avoid one extra conversion / copy cpu <-> gpu
|
||||
|
||||
let cuda_buffers =
|
||||
self.memory
|
||||
.as_buffers_for_key(&mut self.cpu_engine, &mut self.cuda_engine, server_key);
|
||||
|
||||
// The output size of keyswitch is the one of regular boolean ciphertext
|
||||
// so we can use it
|
||||
self.cuda_engine
|
||||
.discard_convert_lwe_ciphertext(&mut cuda_buffers.lwe_after_keyswitch, &ciphertext)?;
|
||||
|
||||
self.cuda_engine.discard_bootstrap_lwe_ciphertext(
|
||||
&mut cuda_buffers.lwe_after_bootstrap,
|
||||
&cuda_buffers.lwe_after_keyswitch,
|
||||
&cuda_buffers.accumulator,
|
||||
&server_key.bootstrapping_key,
|
||||
)?;
|
||||
|
||||
self.cuda_engine.discard_keyswitch_lwe_ciphertext(
|
||||
&mut cuda_buffers.lwe_after_keyswitch,
|
||||
&cuda_buffers.lwe_after_bootstrap,
|
||||
&server_key.key_switching_key,
|
||||
)?;
|
||||
|
||||
// We write the result from gpu to cpu avoiding an extra allocation
|
||||
let mut data = self
|
||||
.cpu_engine
|
||||
.consume_retrieve_lwe_ciphertext(ciphertext)?;
|
||||
let mut view = self
|
||||
.cpu_engine
|
||||
.create_lwe_ciphertext_from(data.as_mut_slice())?;
|
||||
self.cuda_engine
|
||||
.discard_convert_lwe_ciphertext(&mut view, &cuda_buffers.lwe_after_keyswitch)?;
|
||||
let output_ciphertext = self.cpu_engine.create_lwe_ciphertext_from(data)?;
|
||||
|
||||
Ok(Ciphertext::Encrypted(output_ciphertext))
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::core_crypto::prelude::{LweCiphertext32, LweSize};
|
||||
mod cpu;
|
||||
#[cfg(feature = "cuda")]
|
||||
mod cuda;
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
pub(crate) use cuda::{CudaBootstrapKey, CudaBootstrapper};
|
||||
|
||||
pub(crate) use cpu::{CpuBootstrapKey, CpuBootstrapper};
|
||||
|
||||
pub trait BooleanServerKey {
|
||||
/// The LweSize of the Ciphertexts that this key can bootstrap
|
||||
fn lwe_size(&self) -> LweSize;
|
||||
}
|
||||
|
||||
/// Trait for types which implement the bootstrapping + key switching
|
||||
/// of a ciphertext.
|
||||
///
|
||||
/// Meant to be implemented for different hardware (CPU, GPU) or for other bootstrapping
|
||||
/// technics.
|
||||
pub(crate) trait Bootstrapper: Default {
|
||||
type ServerKey: BooleanServerKey;
|
||||
|
||||
/// Shall return the result of the bootstrapping of the
|
||||
/// input ciphertext or an error if any
|
||||
fn bootstrap(
|
||||
&mut self,
|
||||
input: &LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<LweCiphertext32, Box<dyn std::error::Error>>;
|
||||
|
||||
/// Shall return the result of the key switching of the
|
||||
/// input ciphertext or an error if any
|
||||
fn keyswitch(
|
||||
&mut self,
|
||||
input: &LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<LweCiphertext32, Box<dyn std::error::Error>>;
|
||||
|
||||
/// Shall do the bootstrapping and key switching of the ciphertext.
|
||||
/// The result is returned as a new value.
|
||||
fn bootstrap_keyswitch(
|
||||
&mut self,
|
||||
ciphertext: LweCiphertext32,
|
||||
server_key: &Self::ServerKey,
|
||||
) -> Result<Ciphertext, Box<dyn std::error::Error>>;
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::boolean::parameters::BooleanParameters;
|
||||
use crate::boolean::{ClientKey, PublicKey, PLAINTEXT_FALSE, PLAINTEXT_TRUE};
|
||||
use crate::core_crypto::prelude::*;
|
||||
use bootstrapping::{BooleanServerKey, Bootstrapper, CpuBootstrapper};
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use std::cell::RefCell;
|
||||
pub mod bootstrapping;
|
||||
use crate::boolean::engine::bootstrapping::CpuBootstrapKey;
|
||||
use crate::core_crypto::backends::default::engines::ActivatedRandomGenerator;
|
||||
use crate::core_crypto::commons::crypto::secret::generators::DeterministicSeeder;
|
||||
use crate::boolean::engine::bootstrapping::{Bootstrapper, ServerKey};
|
||||
use crate::core_crypto::commons::generators::{
|
||||
DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
};
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::seeders::new_seeder;
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
use bootstrapping::{CudaBootstrapKey, CudaBootstrapper};
|
||||
|
||||
pub(crate) trait BinaryGatesEngine<L, R, K> {
|
||||
fn and(&mut self, ct_left: L, ct_right: R, server_key: &K) -> Ciphertext;
|
||||
fn nand(&mut self, ct_left: L, ct_right: R, server_key: &K) -> Ciphertext;
|
||||
@@ -39,75 +39,51 @@ pub(crate) trait WithThreadLocalEngine {
|
||||
F: FnOnce(&mut Self) -> R;
|
||||
}
|
||||
|
||||
pub(crate) type CpuBooleanEngine = BooleanEngine<CpuBootstrapper>;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub(crate) type CudaBooleanEngine = BooleanEngine<CudaBootstrapper>;
|
||||
|
||||
// All our thread local engines
|
||||
// that our exposed types will use internally to implement their methods
|
||||
thread_local! {
|
||||
static CPU_ENGINE: RefCell<BooleanEngine<CpuBootstrapper>> = RefCell::new(BooleanEngine::<_>::new());
|
||||
#[cfg(feature = "cuda")]
|
||||
static CUDA_ENGINE: RefCell<BooleanEngine<CudaBootstrapper>> = RefCell::new(BooleanEngine::<_>::new());
|
||||
static BOOLEAN_ENGINE: RefCell<BooleanEngine> = RefCell::new(BooleanEngine::new());
|
||||
}
|
||||
|
||||
impl WithThreadLocalEngine for CpuBooleanEngine {
|
||||
pub struct BooleanEngine {
|
||||
/// A structure containing a single CSPRNG to generate secret key coefficients.
|
||||
secret_generator: SecretRandomGenerator<ActivatedRandomGenerator>,
|
||||
/// A structure containing two CSPRNGs to generate material for encryption like public masks
|
||||
/// and secret errors.
|
||||
///
|
||||
/// The [`EncryptionRandomGenerator`] contains two CSPRNGs, one publicly seeded used to
|
||||
/// generate mask coefficients and one privately seeded used to generate errors during
|
||||
/// encryption.
|
||||
encryption_generator: EncryptionRandomGenerator<ActivatedRandomGenerator>,
|
||||
bootstrapper: Bootstrapper,
|
||||
}
|
||||
|
||||
impl WithThreadLocalEngine for BooleanEngine {
|
||||
fn with_thread_local_mut<R, F>(func: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
CPU_ENGINE.with(|engine_cell| func(&mut engine_cell.borrow_mut()))
|
||||
BOOLEAN_ENGINE.with(|engine_cell| func(&mut engine_cell.borrow_mut()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
impl WithThreadLocalEngine for CudaBooleanEngine {
|
||||
fn with_thread_local_mut<R, F>(func: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
CUDA_ENGINE.with(|engine_cell| func(&mut engine_cell.borrow_mut()))
|
||||
}
|
||||
}
|
||||
// We have q = 2^32 so log2q = 32
|
||||
const LOG2_Q_32: usize = 32;
|
||||
|
||||
pub(crate) struct BooleanEngine<B> {
|
||||
pub(crate) engine: DefaultEngine,
|
||||
bootstrapper: B,
|
||||
}
|
||||
|
||||
impl BooleanEngine<CpuBootstrapper> {
|
||||
pub fn create_server_key(&mut self, cks: &ClientKey) -> CpuBootstrapKey {
|
||||
let server_key = self.bootstrapper.new_server_key(cks).unwrap();
|
||||
|
||||
server_key
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
impl BooleanEngine<CudaBootstrapper> {
|
||||
pub fn create_server_key(&mut self, cpu_key: &CpuBootstrapKey) -> CudaBootstrapKey {
|
||||
let server_key = self.bootstrapper.new_serverk_key(cpu_key).unwrap();
|
||||
|
||||
server_key
|
||||
}
|
||||
}
|
||||
|
||||
// We have q = 32 so log2q = 5
|
||||
const LOG2_Q_32: usize = 5;
|
||||
|
||||
impl<B> BooleanEngine<B> {
|
||||
impl BooleanEngine {
|
||||
pub fn create_client_key(&mut self, parameters: BooleanParameters) -> ClientKey {
|
||||
// generate the lwe secret key
|
||||
let lwe_secret_key: LweSecretKey32 = self
|
||||
.engine
|
||||
.generate_new_lwe_secret_key(parameters.lwe_dimension)
|
||||
.unwrap();
|
||||
let lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
|
||||
parameters.lwe_dimension,
|
||||
&mut self.secret_generator,
|
||||
);
|
||||
|
||||
// generate the rlwe secret key
|
||||
let glwe_secret_key: GlweSecretKey32 = self
|
||||
.engine
|
||||
.generate_new_glwe_secret_key(parameters.glwe_dimension, parameters.polynomial_size)
|
||||
.unwrap();
|
||||
// generate the glwe secret key
|
||||
let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
parameters.glwe_dimension,
|
||||
parameters.polynomial_size,
|
||||
&mut self.secret_generator,
|
||||
);
|
||||
|
||||
ClientKey {
|
||||
lwe_secret_key,
|
||||
@@ -116,6 +92,12 @@ impl<B> BooleanEngine<B> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_server_key(&mut self, cks: &ClientKey) -> ServerKey {
|
||||
let server_key = self.bootstrapper.new_server_key(cks).unwrap();
|
||||
|
||||
server_key
|
||||
}
|
||||
|
||||
pub fn create_public_key(&mut self, client_key: &ClientKey) -> PublicKey {
|
||||
let client_parameters = client_key.parameters;
|
||||
|
||||
@@ -124,15 +106,15 @@ impl<B> BooleanEngine<B> {
|
||||
client_parameters.lwe_dimension.to_lwe_size().0 * LOG2_Q_32 + 128,
|
||||
);
|
||||
|
||||
let lwe_public_key: LwePublicKeyOwned<u32> = par_allocate_and_generate_new_lwe_public_key(
|
||||
&client_key.lwe_secret_key,
|
||||
zero_encryption_count,
|
||||
client_key.parameters.lwe_modular_std_dev,
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
PublicKey {
|
||||
lwe_public_key: self
|
||||
.engine
|
||||
.generate_new_lwe_public_key(
|
||||
&client_key.lwe_secret_key,
|
||||
Variance(client_key.parameters.lwe_modular_std_dev.get_variance()),
|
||||
zero_encryption_count,
|
||||
)
|
||||
.unwrap(),
|
||||
lwe_public_key,
|
||||
parameters: client_key.parameters.to_owned(),
|
||||
}
|
||||
}
|
||||
@@ -143,47 +125,42 @@ impl<B> BooleanEngine<B> {
|
||||
|
||||
pub fn encrypt(&mut self, message: bool, cks: &ClientKey) -> Ciphertext {
|
||||
// encode the boolean message
|
||||
let plain: Plaintext32 = if message {
|
||||
self.engine.create_plaintext_from(&PLAINTEXT_TRUE).unwrap()
|
||||
let plain: Plaintext<u32> = if message {
|
||||
Plaintext(PLAINTEXT_TRUE)
|
||||
} else {
|
||||
self.engine.create_plaintext_from(&PLAINTEXT_FALSE).unwrap()
|
||||
Plaintext(PLAINTEXT_FALSE)
|
||||
};
|
||||
|
||||
// convert into a variance
|
||||
let var = Variance(cks.parameters.lwe_modular_std_dev.get_variance());
|
||||
|
||||
// encryption
|
||||
let ct = self
|
||||
.engine
|
||||
.encrypt_lwe_ciphertext(&cks.lwe_secret_key, &plain, var)
|
||||
.unwrap();
|
||||
let ct = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
&cks.lwe_secret_key,
|
||||
plain,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
Ciphertext::Encrypted(ct)
|
||||
}
|
||||
|
||||
pub fn encrypt_with_public_key(&mut self, message: bool, pks: &PublicKey) -> Ciphertext {
|
||||
// encode the boolean message
|
||||
let plain: Plaintext32 = if message {
|
||||
self.engine.create_plaintext_from(&PLAINTEXT_TRUE).unwrap()
|
||||
let plain: Plaintext<u32> = if message {
|
||||
Plaintext(PLAINTEXT_TRUE)
|
||||
} else {
|
||||
self.engine.create_plaintext_from(&PLAINTEXT_FALSE).unwrap()
|
||||
Plaintext(PLAINTEXT_FALSE)
|
||||
};
|
||||
|
||||
let mut underlying_ciphertext = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; pks.parameters.lwe_dimension.to_lwe_size().0])
|
||||
.unwrap();
|
||||
let mut output = LweCiphertext::new(0u32, pks.parameters.lwe_dimension.to_lwe_size());
|
||||
|
||||
// encryption
|
||||
self.engine
|
||||
.discard_encrypt_lwe_ciphertext_with_public_key(
|
||||
&pks.lwe_public_key,
|
||||
&mut underlying_ciphertext,
|
||||
&plain,
|
||||
)
|
||||
.unwrap();
|
||||
encrypt_lwe_ciphertext_with_public_key(
|
||||
&pks.lwe_public_key,
|
||||
&mut output,
|
||||
plain,
|
||||
&mut self.secret_generator,
|
||||
);
|
||||
|
||||
Ciphertext::Encrypted(underlying_ciphertext)
|
||||
Ciphertext::Encrypted(output)
|
||||
}
|
||||
|
||||
pub fn decrypt(&mut self, ct: &Ciphertext, cks: &ClientKey) -> bool {
|
||||
@@ -191,16 +168,10 @@ impl<B> BooleanEngine<B> {
|
||||
Ciphertext::Trivial(b) => *b,
|
||||
Ciphertext::Encrypted(ciphertext) => {
|
||||
// decryption
|
||||
let decrypted = self
|
||||
.engine
|
||||
.decrypt_lwe_ciphertext(&cks.lwe_secret_key, ciphertext)
|
||||
.unwrap();
|
||||
let decrypted = decrypt_lwe_ciphertext(&cks.lwe_secret_key, ciphertext);
|
||||
|
||||
// cast as a u32
|
||||
let mut decrypted_u32: u32 = 0;
|
||||
self.engine
|
||||
.discard_retrieve_plaintext(&mut decrypted_u32, &decrypted)
|
||||
.unwrap();
|
||||
let decrypted_u32 = decrypted.0;
|
||||
|
||||
// return
|
||||
decrypted_u32 < (1 << 31)
|
||||
@@ -214,7 +185,7 @@ impl<B> BooleanEngine<B> {
|
||||
Ciphertext::Encrypted(ct_ct) => {
|
||||
// Compute the linear combination for NOT: -ct
|
||||
let mut ct_res = ct_ct.clone();
|
||||
self.engine.fuse_opp_lwe_ciphertext(&mut ct_res).unwrap(); // compute the negation
|
||||
lwe_ciphertext_opposite_assign(&mut ct_res);
|
||||
|
||||
// Output the result:
|
||||
Ciphertext::Encrypted(ct_res)
|
||||
@@ -226,35 +197,38 @@ impl<B> BooleanEngine<B> {
|
||||
match ct {
|
||||
Ciphertext::Trivial(message) => *message = !*message,
|
||||
Ciphertext::Encrypted(ct_ct) => {
|
||||
self.engine.fuse_opp_lwe_ciphertext(ct_ct).unwrap(); // compute the negation
|
||||
lwe_ciphertext_opposite_assign(ct_ct); // compute the negation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> BooleanEngine<B>
|
||||
where
|
||||
B: Bootstrapper,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
let root_seeder = new_seeder();
|
||||
impl Default for BooleanEngine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
Self::new_from_seeder(root_seeder)
|
||||
impl BooleanEngine {
|
||||
pub fn new() -> Self {
|
||||
let mut root_seeder = new_seeder();
|
||||
|
||||
Self::new_from_seeder(root_seeder.as_mut())
|
||||
}
|
||||
|
||||
pub fn new_from_seeder(mut root_seeder: Box<dyn Seeder>) -> Self {
|
||||
pub fn new_from_seeder(root_seeder: &mut dyn Seeder) -> Self {
|
||||
let mut deterministic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(root_seeder.seed());
|
||||
|
||||
let default_engine_seeder = Box::new(DeterministicSeeder::<ActivatedRandomGenerator>::new(
|
||||
deterministic_seeder.seed(),
|
||||
));
|
||||
|
||||
let engine =
|
||||
DefaultEngine::new(default_engine_seeder).expect("Failed to create a DefaultEngine");
|
||||
// Note that the operands are evaluated from left to right for Rust Struct expressions
|
||||
// See: https://doc.rust-lang.org/stable/reference/expressions.html?highlight=left#evaluation-order-of-operands
|
||||
Self {
|
||||
engine,
|
||||
bootstrapper: Default::default(),
|
||||
secret_generator: SecretRandomGenerator::<_>::new(deterministic_seeder.seed()),
|
||||
encryption_generator: EncryptionRandomGenerator::<_>::new(
|
||||
deterministic_seeder.seed(),
|
||||
&mut deterministic_seeder,
|
||||
),
|
||||
bootstrapper: Bootstrapper::new(&mut deterministic_seeder),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,20 +236,24 @@ where
|
||||
fn convert_into_lwe_ciphertext_32(
|
||||
&mut self,
|
||||
ct: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
) -> LweCiphertext32 {
|
||||
server_key: &ServerKey,
|
||||
) -> LweCiphertextOwned<u32> {
|
||||
match ct {
|
||||
Ciphertext::Encrypted(ct_ct) => ct_ct.clone(),
|
||||
Ciphertext::Trivial(message) => {
|
||||
// encode the boolean message
|
||||
let plain: Plaintext32 = if *message {
|
||||
self.engine.create_plaintext_from(&PLAINTEXT_TRUE).unwrap()
|
||||
let plain: Plaintext<u32> = if *message {
|
||||
Plaintext(PLAINTEXT_TRUE)
|
||||
} else {
|
||||
self.engine.create_plaintext_from(&PLAINTEXT_FALSE).unwrap()
|
||||
Plaintext(PLAINTEXT_FALSE)
|
||||
};
|
||||
self.engine
|
||||
.trivially_encrypt_lwe_ciphertext(server_key.lwe_size(), &plain)
|
||||
.unwrap()
|
||||
allocate_and_trivially_encrypt_new_lwe_ciphertext(
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
plain,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +263,7 @@ where
|
||||
ct_condition: &Ciphertext,
|
||||
ct_then: &Ciphertext,
|
||||
ct_else: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
// In theory MUX gate = (ct_condition AND ct_then) + (!ct_condition AND ct_else)
|
||||
|
||||
@@ -325,36 +303,30 @@ where
|
||||
let ct_then_ct = self.convert_into_lwe_ciphertext_32(ct_then, server_key);
|
||||
let ct_else_ct = self.convert_into_lwe_ciphertext_32(ct_else, server_key);
|
||||
|
||||
let mut buffer_lwe_before_pbs_o = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])
|
||||
.unwrap();
|
||||
let mut buffer_lwe_before_pbs_o = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
);
|
||||
|
||||
let buffer_lwe_before_pbs = &mut buffer_lwe_before_pbs_o;
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
// Compute the linear combination for first AND: ct_condition + ct_then +
|
||||
// (0,...,0,-1/8)
|
||||
self.engine
|
||||
.discard_add_lwe_ciphertext(buffer_lwe_before_pbs, ct_condition_ct, &ct_then_ct)
|
||||
.unwrap(); // ct_condition + ct_then
|
||||
let cst = self.engine.create_plaintext_from(&PLAINTEXT_FALSE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(buffer_lwe_before_pbs, &cst)
|
||||
.unwrap(); //
|
||||
// - 1/8
|
||||
lwe_ciphertext_add(buffer_lwe_before_pbs, ct_condition_ct, &ct_then_ct);
|
||||
let cst = Plaintext(PLAINTEXT_FALSE);
|
||||
lwe_ciphertext_plaintext_add_assign(buffer_lwe_before_pbs, cst); // - 1/8
|
||||
|
||||
// Compute the linear combination for second AND: - ct_condition + ct_else +
|
||||
// (0,...,0,-1/8)
|
||||
let mut ct_temp_2 = ct_condition_ct.clone(); // ct_condition
|
||||
self.engine.fuse_opp_lwe_ciphertext(&mut ct_temp_2).unwrap(); // compute the negation
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext(&mut ct_temp_2, &ct_else_ct)
|
||||
.unwrap(); // + ct_else
|
||||
let cst = self.engine.create_plaintext_from(&PLAINTEXT_FALSE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(&mut ct_temp_2, &cst)
|
||||
.unwrap(); //
|
||||
// - 1/8
|
||||
lwe_ciphertext_opposite_assign(&mut ct_temp_2); // compute the negation
|
||||
lwe_ciphertext_add_assign(&mut ct_temp_2, &ct_else_ct); // + ct_else
|
||||
let cst = Plaintext(PLAINTEXT_FALSE);
|
||||
lwe_ciphertext_plaintext_add_assign(&mut ct_temp_2, cst); // - 1/8
|
||||
|
||||
// Compute the first programmable bootstrapping with fixed test polynomial:
|
||||
let mut ct_pbs_1 = bootstrapper
|
||||
@@ -365,13 +337,9 @@ where
|
||||
|
||||
// Compute the linear combination to add the two results:
|
||||
// buffer_lwe_pbs + ct_pbs_2 + (0,...,0, +1/8)
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext(&mut ct_pbs_1, &ct_pbs_2)
|
||||
.unwrap(); // + buffer_lwe_pbs
|
||||
let cst = self.engine.create_plaintext_from(&PLAINTEXT_TRUE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(&mut ct_pbs_1, &cst)
|
||||
.unwrap(); // + 1/8
|
||||
lwe_ciphertext_add_assign(&mut ct_pbs_1, &ct_pbs_2); // + buffer_lwe_pbs
|
||||
let cst = Plaintext(PLAINTEXT_TRUE);
|
||||
lwe_ciphertext_plaintext_add_assign(&mut ct_pbs_1, cst); // + 1/8
|
||||
|
||||
let ct_ks = bootstrapper.keyswitch(&ct_pbs_1, server_key).unwrap();
|
||||
|
||||
@@ -382,15 +350,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> BinaryGatesEngine<&Ciphertext, &Ciphertext, B::ServerKey> for BooleanEngine<B>
|
||||
where
|
||||
B: Bootstrapper,
|
||||
{
|
||||
impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
fn and(
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
match (ct_left, ct_right) {
|
||||
(Ciphertext::Trivial(message_left), Ciphertext::Trivial(message_right)) => {
|
||||
@@ -403,21 +368,22 @@ where
|
||||
self.and(*message_left, ct_right, server_key)
|
||||
}
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])
|
||||
.unwrap();
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
);
|
||||
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
// compute the linear combination for AND: ct_left + ct_right + (0,...,0,-1/8)
|
||||
self.engine
|
||||
.discard_add_lwe_ciphertext(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct)
|
||||
.unwrap(); // ct_left + ct_right
|
||||
let cst = self.engine.create_plaintext_from(&PLAINTEXT_FALSE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(&mut buffer_lwe_before_pbs, &cst)
|
||||
.unwrap(); //
|
||||
// - 1/8
|
||||
// ct_left + ct_right
|
||||
lwe_ciphertext_add(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct);
|
||||
let cst = Plaintext(PLAINTEXT_FALSE);
|
||||
// - 1/8
|
||||
lwe_ciphertext_plaintext_add_assign(&mut buffer_lwe_before_pbs, cst);
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
@@ -431,7 +397,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
match (ct_left, ct_right) {
|
||||
(Ciphertext::Trivial(message_left), Ciphertext::Trivial(message_right)) => {
|
||||
@@ -444,23 +410,22 @@ where
|
||||
self.nand(*message_left, ct_right, server_key)
|
||||
}
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])
|
||||
.unwrap();
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
// Compute the linear combination for NAND: - ct_left - ct_right + (0,...,0,1/8)
|
||||
self.engine
|
||||
.discard_add_lwe_ciphertext(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct)
|
||||
.unwrap(); // ct_left + ct_right
|
||||
self.engine
|
||||
.fuse_opp_lwe_ciphertext(&mut buffer_lwe_before_pbs)
|
||||
.unwrap(); // compute the negation
|
||||
let cst = self.engine.create_plaintext_from(&PLAINTEXT_TRUE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(&mut buffer_lwe_before_pbs, &cst)
|
||||
.unwrap(); // + 1/8
|
||||
// ct_left + ct_right
|
||||
lwe_ciphertext_add(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct);
|
||||
lwe_ciphertext_opposite_assign(&mut buffer_lwe_before_pbs);
|
||||
let cst = Plaintext(PLAINTEXT_TRUE);
|
||||
// + 1/8
|
||||
lwe_ciphertext_plaintext_add_assign(&mut buffer_lwe_before_pbs, cst);
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
@@ -474,7 +439,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
match (ct_left, ct_right) {
|
||||
(Ciphertext::Trivial(message_left), Ciphertext::Trivial(message_right)) => {
|
||||
@@ -487,24 +452,23 @@ where
|
||||
self.nor(*message_left, ct_right, server_key)
|
||||
}
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])
|
||||
.unwrap();
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
// Compute the linear combination for NOR: - ct_left - ct_right + (0,...,0,-1/8)
|
||||
self.engine
|
||||
.discard_add_lwe_ciphertext(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct)
|
||||
.unwrap(); // ct_left + ct_right
|
||||
self.engine
|
||||
.fuse_opp_lwe_ciphertext(&mut buffer_lwe_before_pbs)
|
||||
.unwrap(); // compute the negation
|
||||
let cst = self.engine.create_plaintext_from(&PLAINTEXT_FALSE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(&mut buffer_lwe_before_pbs, &cst)
|
||||
.unwrap(); //
|
||||
// - 1/8
|
||||
// ct_left + ct_right
|
||||
lwe_ciphertext_add(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct);
|
||||
// compute the negation
|
||||
lwe_ciphertext_opposite_assign(&mut buffer_lwe_before_pbs);
|
||||
let cst = Plaintext(PLAINTEXT_FALSE);
|
||||
// - 1/8
|
||||
lwe_ciphertext_plaintext_add_assign(&mut buffer_lwe_before_pbs, cst);
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
@@ -518,7 +482,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
match (ct_left, ct_right) {
|
||||
(Ciphertext::Trivial(message_left), Ciphertext::Trivial(message_right)) => {
|
||||
@@ -531,20 +495,21 @@ where
|
||||
self.or(*message_left, ct_right, server_key)
|
||||
}
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])
|
||||
.unwrap();
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
// Compute the linear combination for OR: ct_left + ct_right + (0,...,0,+1/8)
|
||||
self.engine
|
||||
.discard_add_lwe_ciphertext(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct)
|
||||
.unwrap(); // ct_left + ct_right
|
||||
let cst = self.engine.create_plaintext_from(&PLAINTEXT_TRUE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(&mut buffer_lwe_before_pbs, &cst)
|
||||
.unwrap(); // + 1/8
|
||||
// ct_left + ct_right
|
||||
lwe_ciphertext_add(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct);
|
||||
let cst = Plaintext(PLAINTEXT_TRUE);
|
||||
// + 1/8
|
||||
lwe_ciphertext_plaintext_add_assign(&mut buffer_lwe_before_pbs, cst);
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
@@ -558,7 +523,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
match (ct_left, ct_right) {
|
||||
(Ciphertext::Trivial(message_left), Ciphertext::Trivial(message_right)) => {
|
||||
@@ -571,24 +536,24 @@ where
|
||||
self.xor(*message_left, ct_right, server_key)
|
||||
}
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])
|
||||
.unwrap();
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
// Compute the linear combination for XOR: 2*(ct_left + ct_right) + (0,...,0,1/4)
|
||||
self.engine
|
||||
.discard_add_lwe_ciphertext(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct)
|
||||
.unwrap(); // ct_left + ct_right
|
||||
let cst_add = self.engine.create_plaintext_from(&PLAINTEXT_TRUE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(&mut buffer_lwe_before_pbs, &cst_add)
|
||||
.unwrap(); // + 1/8
|
||||
let cst_mul = self.engine.create_cleartext_from(&2u32).unwrap();
|
||||
self.engine
|
||||
.fuse_mul_lwe_ciphertext_cleartext(&mut buffer_lwe_before_pbs, &cst_mul)
|
||||
.unwrap(); //* 2
|
||||
// ct_left + ct_right
|
||||
lwe_ciphertext_add(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct);
|
||||
let cst_add = Plaintext(PLAINTEXT_TRUE);
|
||||
// + 1/8
|
||||
lwe_ciphertext_plaintext_add_assign(&mut buffer_lwe_before_pbs, cst_add);
|
||||
let cst_mul = Cleartext(2u32);
|
||||
//* 2
|
||||
lwe_ciphertext_cleartext_mul_assign(&mut buffer_lwe_before_pbs, cst_mul);
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
@@ -602,7 +567,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
match (ct_left, ct_right) {
|
||||
(Ciphertext::Trivial(message_left), Ciphertext::Trivial(message_right)) => {
|
||||
@@ -615,27 +580,26 @@ where
|
||||
self.xnor(*message_left, ct_right, server_key)
|
||||
}
|
||||
(Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
|
||||
let mut buffer_lwe_before_pbs = self
|
||||
.engine
|
||||
.create_lwe_ciphertext_from(vec![0u32; server_key.lwe_size().0])
|
||||
.unwrap();
|
||||
let mut buffer_lwe_before_pbs = LweCiphertext::new(
|
||||
0u32,
|
||||
server_key
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
// Compute the linear combination for XNOR: 2*(-ct_left - ct_right + (0,...,0,-1/8))
|
||||
self.engine
|
||||
.discard_add_lwe_ciphertext(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct)
|
||||
.unwrap(); // ct_left + ct_right
|
||||
let cst_add = self.engine.create_plaintext_from(&PLAINTEXT_TRUE).unwrap();
|
||||
self.engine
|
||||
.fuse_add_lwe_ciphertext_plaintext(&mut buffer_lwe_before_pbs, &cst_add)
|
||||
.unwrap(); // + 1/8
|
||||
self.engine
|
||||
.fuse_opp_lwe_ciphertext(&mut buffer_lwe_before_pbs)
|
||||
.unwrap(); // compute the negation
|
||||
let cst_mul = self.engine.create_cleartext_from(&2u32).unwrap();
|
||||
self.engine
|
||||
.fuse_mul_lwe_ciphertext_cleartext(&mut buffer_lwe_before_pbs, &cst_mul)
|
||||
.unwrap(); //* 2
|
||||
// ct_left + ct_right
|
||||
lwe_ciphertext_add(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct);
|
||||
let cst_add = Plaintext(PLAINTEXT_TRUE);
|
||||
// + 1/8
|
||||
lwe_ciphertext_plaintext_add_assign(&mut buffer_lwe_before_pbs, cst_add);
|
||||
// compute the negation
|
||||
lwe_ciphertext_opposite_assign(&mut buffer_lwe_before_pbs);
|
||||
let cst_mul = Cleartext(2u32);
|
||||
//* 2
|
||||
lwe_ciphertext_cleartext_mul_assign(&mut buffer_lwe_before_pbs, cst_mul);
|
||||
|
||||
// compute the bootstrap and the key switch
|
||||
bootstrapper
|
||||
@@ -646,15 +610,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> BinaryGatesAssignEngine<&mut Ciphertext, &Ciphertext, B::ServerKey> for BooleanEngine<B>
|
||||
where
|
||||
B: Bootstrapper,
|
||||
{
|
||||
impl BinaryGatesAssignEngine<&mut Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
fn and_assign(
|
||||
&mut self,
|
||||
ct_left: &mut Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.and(&ct_left_clone, ct_right, server_key);
|
||||
@@ -664,7 +625,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &mut Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.nand(&ct_left_clone, ct_right, server_key);
|
||||
@@ -674,7 +635,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &mut Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.nor(&ct_left_clone, ct_right, server_key);
|
||||
@@ -684,7 +645,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &mut Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.or(&ct_left_clone, ct_right, server_key);
|
||||
@@ -694,7 +655,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &mut Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.xor(&ct_left_clone, ct_right, server_key);
|
||||
@@ -704,93 +665,79 @@ where
|
||||
&mut self,
|
||||
ct_left: &mut Ciphertext,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
server_key: &ServerKey,
|
||||
) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.xnor(&ct_left_clone, ct_right, server_key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> BinaryGatesAssignEngine<&mut Ciphertext, bool, B::ServerKey> for BooleanEngine<B>
|
||||
where
|
||||
B: Bootstrapper,
|
||||
{
|
||||
fn and_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &B::ServerKey) {
|
||||
impl BinaryGatesAssignEngine<&mut Ciphertext, bool, ServerKey> for BooleanEngine {
|
||||
fn and_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &ServerKey) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.and(&ct_left_clone, ct_right, server_key);
|
||||
}
|
||||
|
||||
fn nand_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &B::ServerKey) {
|
||||
fn nand_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &ServerKey) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.nand(&ct_left_clone, ct_right, server_key);
|
||||
}
|
||||
|
||||
fn nor_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &B::ServerKey) {
|
||||
fn nor_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &ServerKey) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.nor(&ct_left_clone, ct_right, server_key);
|
||||
}
|
||||
|
||||
fn or_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &B::ServerKey) {
|
||||
fn or_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &ServerKey) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.or(&ct_left_clone, ct_right, server_key);
|
||||
}
|
||||
|
||||
fn xor_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &B::ServerKey) {
|
||||
fn xor_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &ServerKey) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.xor(&ct_left_clone, ct_right, server_key);
|
||||
}
|
||||
|
||||
fn xnor_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &B::ServerKey) {
|
||||
fn xnor_assign(&mut self, ct_left: &mut Ciphertext, ct_right: bool, server_key: &ServerKey) {
|
||||
let ct_left_clone = ct_left.clone();
|
||||
*ct_left = self.xnor(&ct_left_clone, ct_right, server_key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> BinaryGatesAssignEngine<bool, &mut Ciphertext, B::ServerKey> for BooleanEngine<B>
|
||||
where
|
||||
B: Bootstrapper,
|
||||
{
|
||||
fn and_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &B::ServerKey) {
|
||||
impl BinaryGatesAssignEngine<bool, &mut Ciphertext, ServerKey> for BooleanEngine {
|
||||
fn and_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &ServerKey) {
|
||||
let ct_right_clone = ct_right.clone();
|
||||
*ct_right = self.and(ct_left, &ct_right_clone, server_key);
|
||||
}
|
||||
|
||||
fn nand_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &B::ServerKey) {
|
||||
fn nand_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &ServerKey) {
|
||||
let ct_right_clone = ct_right.clone();
|
||||
*ct_right = self.nand(ct_left, &ct_right_clone, server_key);
|
||||
}
|
||||
|
||||
fn nor_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &B::ServerKey) {
|
||||
fn nor_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &ServerKey) {
|
||||
let ct_right_clone = ct_right.clone();
|
||||
*ct_right = self.nor(ct_left, &ct_right_clone, server_key);
|
||||
}
|
||||
|
||||
fn or_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &B::ServerKey) {
|
||||
fn or_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &ServerKey) {
|
||||
let ct_right_clone = ct_right.clone();
|
||||
*ct_right = self.or(ct_left, &ct_right_clone, server_key);
|
||||
}
|
||||
|
||||
fn xor_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &B::ServerKey) {
|
||||
fn xor_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &ServerKey) {
|
||||
let ct_right_clone = ct_right.clone();
|
||||
*ct_right = self.xor(ct_left, &ct_right_clone, server_key);
|
||||
}
|
||||
|
||||
fn xnor_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &B::ServerKey) {
|
||||
fn xnor_assign(&mut self, ct_left: bool, ct_right: &mut Ciphertext, server_key: &ServerKey) {
|
||||
let ct_right_clone = ct_right.clone();
|
||||
*ct_right = self.xnor(ct_left, &ct_right_clone, server_key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> BinaryGatesEngine<&Ciphertext, bool, B::ServerKey> for BooleanEngine<B>
|
||||
where
|
||||
B: Bootstrapper,
|
||||
{
|
||||
fn and(
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: bool,
|
||||
_server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
impl BinaryGatesEngine<&Ciphertext, bool, ServerKey> for BooleanEngine {
|
||||
fn and(&mut self, ct_left: &Ciphertext, ct_right: bool, _server_key: &ServerKey) -> Ciphertext {
|
||||
if ct_right {
|
||||
// ct AND true = ct
|
||||
ct_left.clone()
|
||||
@@ -804,7 +751,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: bool,
|
||||
_server_key: &B::ServerKey,
|
||||
_server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
if ct_right {
|
||||
// NOT (ct AND true) = NOT(ct)
|
||||
@@ -815,12 +762,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn nor(
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: bool,
|
||||
_server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
fn nor(&mut self, ct_left: &Ciphertext, ct_right: bool, _server_key: &ServerKey) -> Ciphertext {
|
||||
if ct_right {
|
||||
// NOT (ct OR true) = NOT(true) = false
|
||||
self.trivial_encrypt(false)
|
||||
@@ -830,12 +772,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn or(
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: bool,
|
||||
_server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
fn or(&mut self, ct_left: &Ciphertext, ct_right: bool, _server_key: &ServerKey) -> Ciphertext {
|
||||
if ct_right {
|
||||
// ct OR true = true
|
||||
self.trivial_encrypt(true)
|
||||
@@ -845,12 +782,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn xor(
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: bool,
|
||||
_server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
fn xor(&mut self, ct_left: &Ciphertext, ct_right: bool, _server_key: &ServerKey) -> Ciphertext {
|
||||
if ct_right {
|
||||
// ct XOR true = NOT(ct)
|
||||
self.not(ct_left)
|
||||
@@ -864,7 +796,7 @@ where
|
||||
&mut self,
|
||||
ct_left: &Ciphertext,
|
||||
ct_right: bool,
|
||||
_server_key: &B::ServerKey,
|
||||
_server_key: &ServerKey,
|
||||
) -> Ciphertext {
|
||||
if ct_right {
|
||||
// NOT(ct XOR true) = NOT(NOT(ct)) = ct
|
||||
@@ -876,61 +808,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> BinaryGatesEngine<bool, &Ciphertext, B::ServerKey> for BooleanEngine<B>
|
||||
where
|
||||
B: Bootstrapper,
|
||||
{
|
||||
fn and(
|
||||
&mut self,
|
||||
ct_left: bool,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
impl BinaryGatesEngine<bool, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
fn and(&mut self, ct_left: bool, ct_right: &Ciphertext, server_key: &ServerKey) -> Ciphertext {
|
||||
self.and(ct_right, ct_left, server_key)
|
||||
}
|
||||
|
||||
fn nand(
|
||||
&mut self,
|
||||
ct_left: bool,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
fn nand(&mut self, ct_left: bool, ct_right: &Ciphertext, server_key: &ServerKey) -> Ciphertext {
|
||||
self.nand(ct_right, ct_left, server_key)
|
||||
}
|
||||
|
||||
fn nor(
|
||||
&mut self,
|
||||
ct_left: bool,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
fn nor(&mut self, ct_left: bool, ct_right: &Ciphertext, server_key: &ServerKey) -> Ciphertext {
|
||||
self.nor(ct_right, ct_left, server_key)
|
||||
}
|
||||
|
||||
fn or(
|
||||
&mut self,
|
||||
ct_left: bool,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
fn or(&mut self, ct_left: bool, ct_right: &Ciphertext, server_key: &ServerKey) -> Ciphertext {
|
||||
self.or(ct_right, ct_left, server_key)
|
||||
}
|
||||
|
||||
fn xor(
|
||||
&mut self,
|
||||
ct_left: bool,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
fn xor(&mut self, ct_left: bool, ct_right: &Ciphertext, server_key: &ServerKey) -> Ciphertext {
|
||||
self.xor(ct_right, ct_left, server_key)
|
||||
}
|
||||
|
||||
fn xnor(
|
||||
&mut self,
|
||||
ct_left: bool,
|
||||
ct_right: &Ciphertext,
|
||||
server_key: &B::ServerKey,
|
||||
) -> Ciphertext {
|
||||
fn xnor(&mut self, ct_left: bool, ct_right: &Ciphertext, server_key: &ServerKey) -> Ciphertext {
|
||||
self.xnor(ct_right, ct_left, server_key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
//! Welcome the the tfhe.rs `boolean` module documentation!
|
||||
//! Welcome to the TFHE-rs `boolean` module documentation!
|
||||
//!
|
||||
//! # Description
|
||||
//! This library makes it possible to execute boolean gates over encrypted bits.
|
||||
@@ -18,7 +17,6 @@
|
||||
//! homomorphically.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # #[cfg(not(feature = "cuda"))]
|
||||
//! # fn main() {
|
||||
//!
|
||||
//! use tfhe::boolean::gen_keys;
|
||||
@@ -54,9 +52,6 @@
|
||||
//! let output_3 = client_key.decrypt(&ct_9);
|
||||
//! assert_eq!(output_3, true);
|
||||
//! # }
|
||||
//!
|
||||
//! # #[cfg(feature = "cuda")]
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
|
||||
use crate::boolean::client_key::ClientKey;
|
||||
@@ -114,15 +109,12 @@ pub(crate) fn random_integer() -> u32 {
|
||||
/// meant to be published (the client sends it to the server).
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::gen_keys;
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
/// // generate the client key and the server key:
|
||||
/// let (cks, sks) = gen_keys();
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn gen_keys() -> (ClientKey, ServerKey) {
|
||||
// generate the client key
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
//! This is an unsafe operation as failing to properly fix the parameters will potentially result
|
||||
//! with an incorrect and/or insecure computation.
|
||||
|
||||
pub use crate::core_crypto::prelude::{
|
||||
pub use crate::core_crypto::commons::dispersion::StandardDev;
|
||||
pub use crate::core_crypto::commons::parameters::{
|
||||
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
StandardDev,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -107,18 +107,3 @@ pub const TFHE_LIB_PARAMETERS: BooleanParameters = BooleanParameters {
|
||||
ks_base_log: DecompositionBaseLog(5),
|
||||
ks_level: DecompositionLevelCount(3),
|
||||
};
|
||||
|
||||
/// This parameter set ensures a probability of error upper-bounded by $2^{-40}$ and 128-bit
|
||||
/// security.
|
||||
/// They are GPU-compliant.
|
||||
pub const GPU_DEFAULT_PARAMETERS: BooleanParameters = BooleanParameters {
|
||||
lwe_dimension: LweDimension(686),
|
||||
glwe_dimension: GlweDimension(1),
|
||||
polynomial_size: PolynomialSize(1024),
|
||||
lwe_modular_std_dev: StandardDev(0.000019703241702943194),
|
||||
glwe_modular_std_dev: StandardDev(0.00000004053919869756513),
|
||||
pbs_base_log: DecompositionBaseLog(6),
|
||||
pbs_level: DecompositionLevelCount(3),
|
||||
ks_base_log: DecompositionBaseLog(2),
|
||||
ks_level: DecompositionLevelCount(6),
|
||||
};
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::boolean::client_key::ClientKey;
|
||||
use crate::boolean::engine::{CpuBooleanEngine, WithThreadLocalEngine};
|
||||
use crate::boolean::engine::{BooleanEngine, WithThreadLocalEngine};
|
||||
use crate::boolean::parameters::BooleanParameters;
|
||||
use crate::core_crypto::prelude::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// A structure containing a public key.
|
||||
#[derive(Clone)]
|
||||
pub struct PublicKey {
|
||||
pub(crate) lwe_public_key: LwePublicKey32,
|
||||
pub(crate) lwe_public_key: LwePublicKeyOwned<u32>,
|
||||
pub(crate) parameters: BooleanParameters,
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
/// Encrypts a Boolean message using the client key.
|
||||
/// Encrypt a Boolean message using the client key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
@@ -36,26 +35,16 @@ impl PublicKey {
|
||||
/// let dec = cks.decrypt(&ct_res);
|
||||
/// assert_eq!(false, dec);
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn encrypt(&self, message: bool) -> Ciphertext {
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| {
|
||||
engine.encrypt_with_public_key(message, self)
|
||||
})
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.encrypt_with_public_key(message, self))
|
||||
}
|
||||
|
||||
/// Allocates and generates a client key.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This will panic when the "cuda" feature is enabled and the parameters
|
||||
/// uses a GlweDimension > 1 (as it is not yet supported by the cuda backend).
|
||||
/// Allocate and generate a client key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(feature = "cuda"))]
|
||||
/// # fn main() {
|
||||
/// use tfhe::boolean::prelude::*;
|
||||
///
|
||||
@@ -64,11 +53,9 @@ impl PublicKey {
|
||||
///
|
||||
/// let pks = PublicKey::new(&cks);
|
||||
/// # }
|
||||
/// # #[cfg(feature = "cuda")]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn new(client_key: &ClientKey) -> PublicKey {
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.create_public_key(client_key))
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.create_public_key(client_key))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +70,8 @@ impl Serialize for PublicKey {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut ser_eng = DefaultSerializationEngine::new(()).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
let lwe_public_key = ser_eng
|
||||
.serialize(&self.lwe_public_key)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
let lwe_public_key =
|
||||
bincode::serialize(&self.lwe_public_key).map_err(serde::ser::Error::custom)?;
|
||||
|
||||
SerializablePublicKey {
|
||||
lwe_public_key,
|
||||
@@ -104,11 +88,9 @@ impl<'de> Deserialize<'de> for PublicKey {
|
||||
{
|
||||
let thing =
|
||||
SerializablePublicKey::deserialize(deserializer).map_err(serde::de::Error::custom)?;
|
||||
let mut de_eng = DefaultSerializationEngine::new(()).map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
lwe_public_key: de_eng
|
||||
.deserialize(thing.lwe_public_key.as_slice())
|
||||
lwe_public_key: bincode::deserialize(thing.lwe_public_key.as_slice())
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
parameters: thing.parameters,
|
||||
})
|
||||
|
||||
@@ -8,18 +8,12 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::boolean::ciphertext::Ciphertext;
|
||||
use crate::boolean::client_key::ClientKey;
|
||||
use crate::boolean::engine::bootstrapping::CpuBootstrapKey;
|
||||
#[cfg(feature = "cuda")]
|
||||
use crate::boolean::engine::{bootstrapping::CudaBootstrapKey, CudaBooleanEngine};
|
||||
pub use crate::boolean::engine::bootstrapping::ServerKey;
|
||||
use crate::boolean::engine::{
|
||||
BinaryGatesAssignEngine, BinaryGatesEngine, CpuBooleanEngine, WithThreadLocalEngine,
|
||||
BinaryGatesAssignEngine, BinaryGatesEngine, BooleanEngine, WithThreadLocalEngine,
|
||||
};
|
||||
#[cfg(feature = "cuda")]
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait BinaryBooleanGates<L, R> {
|
||||
fn and(&self, ct_left: L, ct_right: R) -> Ciphertext;
|
||||
@@ -39,156 +33,103 @@ pub trait BinaryBooleanGatesAssign<L, R> {
|
||||
fn xnor_assign(&self, ct_left: L, ct_right: R);
|
||||
}
|
||||
|
||||
trait RefFromServerKey {
|
||||
fn get_ref(server_key: &ServerKey) -> &Self;
|
||||
}
|
||||
|
||||
trait DefaultImplementation {
|
||||
type Engine: WithThreadLocalEngine;
|
||||
type BootsrapKey: RefFromServerKey;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerKey {
|
||||
cpu_key: CpuBootstrapKey,
|
||||
#[cfg(feature = "cuda")]
|
||||
cuda_key: Arc<CudaBootstrapKey>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
mod implementation {
|
||||
use super::*;
|
||||
|
||||
impl RefFromServerKey for CpuBootstrapKey {
|
||||
fn get_ref(server_key: &ServerKey) -> &Self {
|
||||
&server_key.cpu_key
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultImplementation for ServerKey {
|
||||
type Engine = CpuBooleanEngine;
|
||||
type BootsrapKey = CpuBootstrapKey;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
mod implementation {
|
||||
use super::*;
|
||||
|
||||
impl RefFromServerKey for CudaBootstrapKey {
|
||||
fn get_ref(server_key: &ServerKey) -> &Self {
|
||||
&server_key.cuda_key
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultImplementation for ServerKey {
|
||||
type Engine = CudaBooleanEngine;
|
||||
type BootsrapKey = CudaBootstrapKey;
|
||||
type Engine = BooleanEngine;
|
||||
}
|
||||
}
|
||||
|
||||
impl<Lhs, Rhs> BinaryBooleanGates<Lhs, Rhs> for ServerKey
|
||||
where
|
||||
<ServerKey as DefaultImplementation>::Engine:
|
||||
BinaryGatesEngine<Lhs, Rhs, <ServerKey as DefaultImplementation>::BootsrapKey>,
|
||||
<ServerKey as DefaultImplementation>::Engine: BinaryGatesEngine<Lhs, Rhs, ServerKey>,
|
||||
{
|
||||
fn and(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.and(ct_left, ct_right, bootstrap_key)
|
||||
engine.and(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn nand(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.nand(ct_left, ct_right, bootstrap_key)
|
||||
engine.nand(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn nor(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.nor(ct_left, ct_right, bootstrap_key)
|
||||
engine.nor(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn or(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.or(ct_left, ct_right, bootstrap_key)
|
||||
engine.or(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn xor(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.xor(ct_left, ct_right, bootstrap_key)
|
||||
engine.xor(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn xnor(&self, ct_left: Lhs, ct_right: Rhs) -> Ciphertext {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.xnor(ct_left, ct_right, bootstrap_key)
|
||||
engine.xnor(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Lhs, Rhs> BinaryBooleanGatesAssign<Lhs, Rhs> for ServerKey
|
||||
where
|
||||
<ServerKey as DefaultImplementation>::Engine:
|
||||
BinaryGatesAssignEngine<Lhs, Rhs, <ServerKey as DefaultImplementation>::BootsrapKey>,
|
||||
<ServerKey as DefaultImplementation>::Engine: BinaryGatesAssignEngine<Lhs, Rhs, ServerKey>,
|
||||
{
|
||||
fn and_assign(&self, ct_left: Lhs, ct_right: Rhs) {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.and_assign(ct_left, ct_right, bootstrap_key)
|
||||
engine.and_assign(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn nand_assign(&self, ct_left: Lhs, ct_right: Rhs) {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.nand_assign(ct_left, ct_right, bootstrap_key)
|
||||
engine.nand_assign(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn nor_assign(&self, ct_left: Lhs, ct_right: Rhs) {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.nor_assign(ct_left, ct_right, bootstrap_key)
|
||||
engine.nor_assign(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn or_assign(&self, ct_left: Lhs, ct_right: Rhs) {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.or_assign(ct_left, ct_right, bootstrap_key)
|
||||
engine.or_assign(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn xor_assign(&self, ct_left: Lhs, ct_right: Rhs) {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.xor_assign(ct_left, ct_right, bootstrap_key)
|
||||
engine.xor_assign(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
|
||||
fn xnor_assign(&self, ct_left: Lhs, ct_right: Rhs) {
|
||||
let bootstrap_key = <ServerKey as DefaultImplementation>::BootsrapKey::get_ref(self);
|
||||
<ServerKey as DefaultImplementation>::Engine::with_thread_local_mut(|engine| {
|
||||
engine.xnor_assign(ct_left, ct_right, bootstrap_key)
|
||||
engine.xnor_assign(ct_left, ct_right, self)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerKey {
|
||||
pub fn new(cks: &ClientKey) -> Self {
|
||||
let cpu_key =
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.create_server_key(cks));
|
||||
|
||||
Self::from(cpu_key)
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.create_server_key(cks))
|
||||
}
|
||||
|
||||
pub fn trivial_encrypt(&self, message: bool) -> Ciphertext {
|
||||
@@ -196,11 +137,11 @@ impl ServerKey {
|
||||
}
|
||||
|
||||
pub fn not(&self, ct: &Ciphertext) -> Ciphertext {
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.not(ct))
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.not(ct))
|
||||
}
|
||||
|
||||
pub fn not_assign(&self, ct: &mut Ciphertext) {
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| engine.not_assign(ct))
|
||||
BooleanEngine::with_thread_local_mut(|engine| engine.not_assign(ct))
|
||||
}
|
||||
|
||||
pub fn mux(
|
||||
@@ -209,56 +150,8 @@ impl ServerKey {
|
||||
ct_then: &Ciphertext,
|
||||
ct_else: &Ciphertext,
|
||||
) -> Ciphertext {
|
||||
#[cfg(feature = "cuda")]
|
||||
{
|
||||
CudaBooleanEngine::with_thread_local_mut(|engine| {
|
||||
engine.mux(ct_condition, ct_then, ct_else, &self.cuda_key)
|
||||
})
|
||||
}
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
{
|
||||
CpuBooleanEngine::with_thread_local_mut(|engine| {
|
||||
engine.mux(ct_condition, ct_then, ct_else, &self.cpu_key)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CpuBootstrapKey> for ServerKey {
|
||||
fn from(cpu_key: CpuBootstrapKey) -> Self {
|
||||
#[cfg(feature = "cuda")]
|
||||
{
|
||||
let cuda_key = CudaBooleanEngine::with_thread_local_mut(|engine| {
|
||||
engine.create_server_key(&cpu_key)
|
||||
});
|
||||
|
||||
let cuda_key = Arc::new(cuda_key);
|
||||
|
||||
Self { cpu_key, cuda_key }
|
||||
}
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
{
|
||||
Self { cpu_key }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ServerKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.cpu_key.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ServerKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let cpu_key = CpuBootstrapKey::deserialize(deserializer)?;
|
||||
|
||||
Ok(Self::from(cpu_key))
|
||||
BooleanEngine::with_thread_local_mut(|engine| {
|
||||
engine.mux(ct_condition, ct_then, ct_else, self)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ const NB_CT: usize = 8;
|
||||
/// Number of gates computed in the deep circuit test
|
||||
const NB_GATE: usize = 1 << 11;
|
||||
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
mod default_parameters_tests {
|
||||
use super::*;
|
||||
use crate::boolean::parameters::DEFAULT_PARAMETERS;
|
||||
@@ -60,7 +59,6 @@ mod default_parameters_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
mod tfhe_lib_parameters_tests {
|
||||
use super::*;
|
||||
use crate::boolean::parameters::TFHE_LIB_PARAMETERS;
|
||||
@@ -107,52 +105,6 @@ mod tfhe_lib_parameters_tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod gpu_default_parameters_tests {
|
||||
use super::*;
|
||||
use crate::boolean::parameters::GPU_DEFAULT_PARAMETERS;
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_lwe_secret_key_default_parameters() {
|
||||
test_encrypt_decrypt_lwe_secret_key(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_and_gate_default_parameters() {
|
||||
test_and_gate(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_nand_gate_default_parameters() {
|
||||
test_nand_gate(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_or_gate_default_parameters() {
|
||||
test_or_gate(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_nor_gate_default_parameters() {
|
||||
test_nor_gate(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_xor_gate_default_parameters() {
|
||||
test_xor_gate(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_xnor_gate_default_parameters() {
|
||||
test_xnor_gate(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_not_gate_default_parameters() {
|
||||
test_not_gate(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_mux_gate_default_parameters() {
|
||||
test_mux_gate(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
#[test]
|
||||
fn test_deep_circuit_default_parameters() {
|
||||
test_deep_circuit(GPU_DEFAULT_PARAMETERS);
|
||||
}
|
||||
}
|
||||
|
||||
/// test encryption and decryption with the LWE secret key
|
||||
fn test_encrypt_decrypt_lwe_secret_key(parameters: BooleanParameters) {
|
||||
// generate the client key set
|
||||
|
||||
@@ -109,7 +109,7 @@ pub unsafe extern "C" fn boolean_trivial_encrypt(
|
||||
check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
|
||||
let heap_allocated_result = Box::new(BooleanCiphertext(
|
||||
boolean::engine::CpuBooleanEngine::with_thread_local_mut(|engine| {
|
||||
boolean::engine::BooleanEngine::with_thread_local_mut(|engine| {
|
||||
engine.trivial_encrypt(message)
|
||||
}),
|
||||
));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::c_api::utils::*;
|
||||
use crate::core_crypto::prelude::{
|
||||
use crate::core_crypto::commons::dispersion::StandardDev;
|
||||
use crate::core_crypto::commons::parameters::{
|
||||
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
StandardDev,
|
||||
};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#[cfg(feature = "boolean-c-api")]
|
||||
pub mod boolean;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::c_api::utils::*;
|
||||
use crate::core_crypto::prelude::{
|
||||
pub use crate::core_crypto::commons::dispersion::StandardDev;
|
||||
pub use crate::core_crypto::commons::parameters::{
|
||||
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
StandardDev,
|
||||
};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ pub type AccumulatorCallback = Option<extern "C" fn(u64) -> u64>;
|
||||
pub type BivariateAccumulatorCallback = Option<extern "C" fn(u64, u64) -> u64>;
|
||||
|
||||
pub struct ShortintPBSAccumulator(
|
||||
pub(in crate::c_api) crate::core_crypto::prelude::GlweCiphertext64,
|
||||
pub(in crate::c_api) crate::core_crypto::entities::GlweCiphertextOwned<u64>,
|
||||
);
|
||||
pub struct ShortintBivariatePBSAccumulator(
|
||||
pub(in crate::c_api) crate::core_crypto::prelude::GlweCiphertext64,
|
||||
pub(in crate::c_api) crate::core_crypto::entities::GlweCiphertextOwned<u64>,
|
||||
);
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
317
tfhe/src/core_crypto/algorithms/ggsw_encryption.rs
Normal file
317
tfhe/src/core_crypto/algorithms/ggsw_encryption.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::math::decomposition::DecompositionLevel;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// Encrypt a plaintext in a [`GGSW ciphertext`](`GgswCiphertext`).
|
||||
///
|
||||
/// See the [`formal definition`](`GgswCiphertext#ggsw-encryption`) for the definition of the
|
||||
/// encryption algorithm.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GgswCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the GlweSecretKey
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let encoded_msg = 3u64 << 60;
|
||||
/// let plaintext = Plaintext(encoded_msg);
|
||||
///
|
||||
/// // Create a new GgswCiphertext
|
||||
/// let mut ggsw = GgswCiphertext::new(
|
||||
/// 0u64,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_ggsw_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut ggsw,
|
||||
/// plaintext,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn encrypt_ggsw_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
output: &mut GgswCiphertext<OutputCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.polynomial_size() == glwe_secret_key.polynomial_size(),
|
||||
"Mismatch between polynomial sizes of output cipertexts and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output.polynomial_size(),
|
||||
glwe_secret_key.polynomial_size()
|
||||
);
|
||||
|
||||
assert!(
|
||||
output.glwe_size().to_glwe_dimension() == glwe_secret_key.glwe_dimension(),
|
||||
"Mismatch between GlweDimension of output cipertexts and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output.glwe_size().to_glwe_dimension(),
|
||||
glwe_secret_key.glwe_dimension()
|
||||
);
|
||||
|
||||
// Generators used to have same sequential and parallel key generation
|
||||
let gen_iter = generator
|
||||
.fork_ggsw_to_ggsw_levels::<Scalar>(
|
||||
output.decomposition_level_count(),
|
||||
output.glwe_size(),
|
||||
output.polynomial_size(),
|
||||
)
|
||||
.expect("Failed to split generator into ggsw levels");
|
||||
|
||||
let output_glwe_size = output.glwe_size();
|
||||
let output_polynomial_size = output.polynomial_size();
|
||||
let decomp_base_log = output.decomposition_base_log();
|
||||
|
||||
for (level_index, (mut level_matrix, mut generator)) in
|
||||
output.iter_mut().zip(gen_iter).enumerate()
|
||||
{
|
||||
let decomp_level = DecompositionLevel(level_index + 1);
|
||||
let factor = encoded
|
||||
.0
|
||||
.wrapping_neg()
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
|
||||
|
||||
// We iterate over the rows of the level matrix, the last row needs special treatment
|
||||
let gen_iter = generator
|
||||
.fork_ggsw_level_to_glwe::<Scalar>(output_glwe_size, output_polynomial_size)
|
||||
.expect("Failed to split generator into glwe");
|
||||
|
||||
let last_row_index = level_matrix.glwe_size().0 - 1;
|
||||
|
||||
for ((row_index, mut row_as_glwe), mut generator) in level_matrix
|
||||
.as_mut_glwe_list()
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.zip(gen_iter)
|
||||
{
|
||||
encrypt_ggsw_level_matrix_row(
|
||||
glwe_secret_key,
|
||||
(row_index, last_row_index),
|
||||
factor,
|
||||
&mut row_as_glwe,
|
||||
noise_parameters,
|
||||
&mut generator,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel variant of [`encrypt_ggsw_ciphertext`].
|
||||
///
|
||||
/// See the [`formal definition`](`GgswCiphertext#ggsw-encryption`) for the definition of the
|
||||
/// encryption algorithm.
|
||||
///
|
||||
/// New tasks are created per level matrix and per row of each level matrix.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GgswCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the GlweSecretKey
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let encoded_msg = 3u64 << 60;
|
||||
/// let plaintext = Plaintext(encoded_msg);
|
||||
///
|
||||
/// // Create a new GgswCiphertext
|
||||
/// let mut ggsw = GgswCiphertext::new(
|
||||
/// 0u64,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// );
|
||||
///
|
||||
/// par_encrypt_ggsw_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut ggsw,
|
||||
/// plaintext,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn par_encrypt_ggsw_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
output: &mut GgswCiphertext<OutputCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
KeyCont: Container<Element = Scalar> + Sync,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.polynomial_size() == glwe_secret_key.polynomial_size(),
|
||||
"Mismatch between polynomial sizes of output cipertexts and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output.polynomial_size(),
|
||||
glwe_secret_key.polynomial_size()
|
||||
);
|
||||
|
||||
assert!(
|
||||
output.glwe_size().to_glwe_dimension() == glwe_secret_key.glwe_dimension(),
|
||||
"Mismatch between GlweDimension of output cipertexts and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output.glwe_size().to_glwe_dimension(),
|
||||
glwe_secret_key.glwe_dimension()
|
||||
);
|
||||
|
||||
// Generators used to have same sequential and parallel key generation
|
||||
let gen_iter = generator
|
||||
.par_fork_ggsw_to_ggsw_levels::<Scalar>(
|
||||
output.decomposition_level_count(),
|
||||
output.glwe_size(),
|
||||
output.polynomial_size(),
|
||||
)
|
||||
.expect("Failed to split generator into ggsw levels");
|
||||
|
||||
let output_glwe_size = output.glwe_size();
|
||||
let output_polynomial_size = output.polynomial_size();
|
||||
let decomp_base_log = output.decomposition_base_log();
|
||||
|
||||
output.par_iter_mut().zip(gen_iter).enumerate().for_each(
|
||||
|(level_index, (mut level_matrix, mut generator))| {
|
||||
let decomp_level = DecompositionLevel(level_index + 1);
|
||||
let factor = encoded
|
||||
.0
|
||||
.wrapping_neg()
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
|
||||
|
||||
// We iterate over the rows of the level matrix, the last row needs special treatment
|
||||
let gen_iter = generator
|
||||
.par_fork_ggsw_level_to_glwe::<Scalar>(output_glwe_size, output_polynomial_size)
|
||||
.expect("Failed to split generator into glwe");
|
||||
|
||||
let last_row_index = level_matrix.glwe_size().0 - 1;
|
||||
|
||||
level_matrix
|
||||
.as_mut_glwe_list()
|
||||
.par_iter_mut()
|
||||
.enumerate()
|
||||
.zip(gen_iter)
|
||||
.for_each(|((row_index, mut row_as_glwe), mut generator)| {
|
||||
encrypt_ggsw_level_matrix_row(
|
||||
glwe_secret_key,
|
||||
(row_index, last_row_index),
|
||||
factor,
|
||||
&mut row_as_glwe,
|
||||
noise_parameters,
|
||||
&mut generator,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience function to encrypt a row of a [`GgswLevelMatrix`] irrespective of the current row
|
||||
/// being encrypted. Allows to share code between sequential ([`encrypt_ggsw_ciphertext`]) and
|
||||
/// parallel ([`par_encrypt_ggsw_ciphertext`]) variants of the GGSW ciphertext encryption.
|
||||
///
|
||||
/// You probably don't want to use this function directly.
|
||||
fn encrypt_ggsw_level_matrix_row<Scalar, KeyCont, OutputCont, Gen>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
(row_index, last_row_index): (usize, usize),
|
||||
factor: Scalar,
|
||||
row_as_glwe: &mut GlweCiphertext<OutputCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
if row_index < last_row_index {
|
||||
// Not the last row
|
||||
let sk_poly_list = glwe_secret_key.as_polynomial_list();
|
||||
let sk_poly = sk_poly_list.get(row_index);
|
||||
|
||||
// Copy the key polynomial to the output body, to avoid allocating a temporary buffer
|
||||
let mut body = row_as_glwe.get_mut_body();
|
||||
body.as_mut().copy_from_slice(sk_poly.as_ref());
|
||||
|
||||
slice_wrapping_scalar_mul_assign(body.as_mut(), factor);
|
||||
|
||||
encrypt_glwe_ciphertext_assign(glwe_secret_key, row_as_glwe, noise_parameters, generator);
|
||||
} else {
|
||||
// The last row needs a slightly different treatment
|
||||
let mut body = row_as_glwe.get_mut_body();
|
||||
|
||||
body.as_mut().fill(Scalar::ZERO);
|
||||
body.as_mut()[0] = factor.wrapping_neg();
|
||||
|
||||
encrypt_glwe_ciphertext_assign(glwe_secret_key, row_as_glwe, noise_parameters, generator);
|
||||
}
|
||||
}
|
||||
681
tfhe/src/core_crypto/algorithms/glwe_encryption.rs
Normal file
681
tfhe/src/core_crypto/algorithms/glwe_encryption.rs
Normal file
@@ -0,0 +1,681 @@
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Variant of [`encrypt_glwe_ciphertext`] which assumes that the plaintexts to encrypt are already
|
||||
/// loaded in the body of the output [`GLWE ciphertext`](`GlweCiphertext`), this is sometimes useful
|
||||
/// to avoid allocating a [`PlaintextList`] in situ.
|
||||
///
|
||||
/// See this [`formal definition`](`encrypt_glwe_ciphertext#formal-definition`) for the definition
|
||||
/// of the GLWE encryption algorithm.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the GlweSecretKey
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let encoded_msg = msg << 60;
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
///
|
||||
/// // Manually fill the body with the encoded message
|
||||
/// glwe.get_mut_body().as_mut().fill(encoded_msg);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext_assign(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut glwe,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// decrypt_glwe_ciphertext(&glwe_secret_key, &glwe, &mut output_plaintext_list);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// output_plaintext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext_list = output_plaintext_list.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext_list.iter_mut().for_each(|elt| *elt = *elt >> 60);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext_list = cleartext_list;
|
||||
///
|
||||
/// // Check we recovered the original message for each plaintext we encrypted
|
||||
/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg));
|
||||
/// ```
|
||||
pub fn encrypt_glwe_ciphertext_assign<Scalar, KeyCont, OutputCont, Gen>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
output: &mut GlweCiphertext<OutputCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.glwe_size().to_glwe_dimension() == glwe_secret_key.glwe_dimension(),
|
||||
"Mismatch between GlweDimension of output cipertext and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output.glwe_size().to_glwe_dimension(),
|
||||
glwe_secret_key.glwe_dimension()
|
||||
);
|
||||
assert!(
|
||||
output.polynomial_size() == glwe_secret_key.polynomial_size(),
|
||||
"Mismatch between PolynomialSize of output cipertext and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output.polynomial_size(),
|
||||
glwe_secret_key.polynomial_size()
|
||||
);
|
||||
|
||||
let (mut mask, mut body) = output.get_mut_mask_and_body();
|
||||
|
||||
generator.fill_slice_with_random_mask(mask.as_mut());
|
||||
|
||||
generator
|
||||
.unsigned_torus_slice_wrapping_add_random_noise_assign(body.as_mut(), noise_parameters);
|
||||
|
||||
polynomial_wrapping_add_multisum_assign(
|
||||
&mut body.as_mut_polynomial(),
|
||||
&mask.as_polynomial_list(),
|
||||
&glwe_secret_key.as_polynomial_list(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Encrypt a (scalar) plaintext list in a [`GLWE ciphertext`](`GlweCiphertext`).
|
||||
///
|
||||
/// # Formal Definition
|
||||
///
|
||||
/// ## GLWE Encryption
|
||||
/// ###### inputs:
|
||||
/// - $\mathsf{PT}\in\mathcal{R}\_q$: a plaintext
|
||||
/// - $\vec{S} \in\mathcal{R}\_q^k$: a secret key
|
||||
/// - $\mathcal{D\_{\sigma^2,\mu}}$: a normal distribution of variance $\sigma^2$ and mean $\mu$
|
||||
///
|
||||
/// ###### outputs:
|
||||
/// - $\mathsf{CT} = \left( \vec{A} , B \right) \in \mathsf{GLWE}\_{\vec{S}}( \mathsf{PT} )\subseteq
|
||||
/// \mathcal{R}\_q^{k+1}$: a GLWE ciphertext
|
||||
///
|
||||
/// ###### algorithm:
|
||||
/// 1. uniformly sample each coefficient of the polynomial vector $\vec{A}\in\mathcal{R}^k\_q$
|
||||
/// 2. sample each integer error coefficient of an error polynomial $E\in\mathcal{R}\_q$ from
|
||||
/// $\mathcal{D\_{\sigma^2,\mu}}$ 3. compute $B = \left\langle \vec{A} , \vec{S} \right\rangle +
|
||||
/// \mathsf{PT} + E \in\mathcal{R}\_q$ 4. output $\left( \vec{A} , B \right)$
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the GlweSecretKey
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let encoded_msg = msg << 60;
|
||||
/// let plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut glwe,
|
||||
/// &plaintext_list,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list = PlaintextList::new(0u64, plaintext_list.plaintext_count());
|
||||
///
|
||||
/// decrypt_glwe_ciphertext(&glwe_secret_key, &glwe, &mut output_plaintext_list);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// output_plaintext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext_list = output_plaintext_list.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext_list.iter_mut().for_each(|elt| *elt = *elt >> 60);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext_list = cleartext_list;
|
||||
///
|
||||
/// // Check we recovered the original message for each plaintext we encrypted
|
||||
/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg));
|
||||
/// ```
|
||||
pub fn encrypt_glwe_ciphertext<Scalar, KeyCont, InputCont, OutputCont, Gen>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
|
||||
input_plaintext_list: &PlaintextList<InputCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output_glwe_ciphertext.polynomial_size().0 == input_plaintext_list.plaintext_count().0,
|
||||
"Mismatch between PolynomialSize of output cipertext PlaintextCount of input. \
|
||||
Got {:?} in output, and {:?} in input.",
|
||||
output_glwe_ciphertext.polynomial_size(),
|
||||
input_plaintext_list.plaintext_count()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_ciphertext.glwe_size().to_glwe_dimension() == glwe_secret_key.glwe_dimension(),
|
||||
"Mismatch between GlweDimension of output cipertext and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
|
||||
glwe_secret_key.glwe_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_ciphertext.polynomial_size() == glwe_secret_key.polynomial_size(),
|
||||
"Mismatch between PolynomialSize of output cipertext and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output_glwe_ciphertext.polynomial_size(),
|
||||
glwe_secret_key.polynomial_size()
|
||||
);
|
||||
|
||||
let (mut mask, mut body) = output_glwe_ciphertext.get_mut_mask_and_body();
|
||||
|
||||
generator.fill_slice_with_random_mask(mask.as_mut());
|
||||
|
||||
generator.fill_slice_with_random_noise(body.as_mut(), noise_parameters);
|
||||
|
||||
polynomial_wrapping_add_assign(
|
||||
&mut body.as_mut_polynomial(),
|
||||
&input_plaintext_list.as_polynomial(),
|
||||
);
|
||||
|
||||
polynomial_wrapping_add_multisum_assign(
|
||||
&mut body.as_mut_polynomial(),
|
||||
&mask.as_polynomial_list(),
|
||||
&glwe_secret_key.as_polynomial_list(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Encrypt a (scalar) plaintext list in [`GLWE ciphertexts`](`GlweCiphertext`) of the output
|
||||
/// [`GLWE ciphertext list`](`GlweCiphertextList`).
|
||||
///
|
||||
/// See this [`formal definition`](`encrypt_glwe_ciphertext#formal-definition`) for the definition
|
||||
/// of the GLWE encryption algorithm.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let glwe_count = GlweCiphertextCount(2);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the GlweSecretKey
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let encoded_msg = msg << 60;
|
||||
/// let plaintext_list = PlaintextList::new(
|
||||
/// encoded_msg,
|
||||
/// PlaintextCount(polynomial_size.0 * glwe_count.0),
|
||||
/// );
|
||||
///
|
||||
/// // Create a new GlweCiphertextList
|
||||
/// let mut glwe_list = GlweCiphertextList::new(0u64, glwe_size, polynomial_size, glwe_count);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext_list(
|
||||
/// &glwe_secret_key,
|
||||
/// &plaintext_list,
|
||||
/// &mut glwe_list,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list = PlaintextList::new(0u64, plaintext_list.plaintext_count());
|
||||
///
|
||||
/// decrypt_glwe_ciphertext_list(&glwe_secret_key, &glwe_list, &mut output_plaintext_list);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// output_plaintext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext_list = output_plaintext_list.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext_list.iter_mut().for_each(|elt| *elt = *elt >> 60);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext_list = cleartext_list;
|
||||
///
|
||||
/// // Check we recovered the original message for each plaintext we encrypted
|
||||
/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg));
|
||||
/// ```
|
||||
pub fn encrypt_glwe_ciphertext_list<Scalar, KeyCont, InputCont, OutputCont, Gen>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
input_plaintext_list: &PlaintextList<InputCont>,
|
||||
output_glwe_ciphertext_list: &mut GlweCiphertextList<OutputCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output_glwe_ciphertext_list.polynomial_size().0
|
||||
* output_glwe_ciphertext_list.glwe_ciphertext_count().0
|
||||
== input_plaintext_list.plaintext_count().0,
|
||||
"Mismatch between required number of plaintexts: {} ({:?} * {:?}) and input \
|
||||
PlaintextCount: {:?}",
|
||||
output_glwe_ciphertext_list.polynomial_size().0
|
||||
* output_glwe_ciphertext_list.glwe_ciphertext_count().0,
|
||||
output_glwe_ciphertext_list.polynomial_size(),
|
||||
output_glwe_ciphertext_list.glwe_ciphertext_count(),
|
||||
input_plaintext_list.plaintext_count()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_ciphertext_list.glwe_size().to_glwe_dimension()
|
||||
== glwe_secret_key.glwe_dimension(),
|
||||
"Mismatch between GlweDimension of output cipertext and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output_glwe_ciphertext_list.glwe_size().to_glwe_dimension(),
|
||||
glwe_secret_key.glwe_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_ciphertext_list.polynomial_size() == glwe_secret_key.polynomial_size(),
|
||||
"Mismatch between PolynomialSize of output cipertext and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output_glwe_ciphertext_list.polynomial_size(),
|
||||
glwe_secret_key.polynomial_size()
|
||||
);
|
||||
|
||||
let polynomial_size = output_glwe_ciphertext_list.polynomial_size();
|
||||
for (mut ciphertext, encoded) in output_glwe_ciphertext_list
|
||||
.iter_mut()
|
||||
.zip(input_plaintext_list.chunks_exact(polynomial_size.0))
|
||||
{
|
||||
encrypt_glwe_ciphertext(
|
||||
glwe_secret_key,
|
||||
&mut ciphertext,
|
||||
&encoded,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypt a [`GLWE ciphertext`](`GlweCiphertext`) in a (scalar) plaintext list.
|
||||
///
|
||||
/// See [`encrypt_glwe_ciphertext`] for usage.
|
||||
///
|
||||
/// # Formal Definition
|
||||
///
|
||||
/// ## GLWE Decryption
|
||||
/// ###### inputs:
|
||||
/// - $\mathsf{CT} = \left( \vec{A} , B \right) \in \mathsf{GLWE}\_{\vec{S}}( \mathsf{PT} )\subseteq
|
||||
/// \mathcal{R}\_q^{k+1}$: an GLWE ciphertext
|
||||
/// - $\vec{S} \in\mathcal{R}\_q^k$: a secret key
|
||||
///
|
||||
/// ###### outputs:
|
||||
/// - $\mathsf{PT}\in\mathcal{R}\_q$: a plaintext
|
||||
///
|
||||
/// ###### algorithm:
|
||||
///
|
||||
/// 1. compute $\mathsf{PT} = B - \left\langle \vec{A} , \vec{S} \right\rangle \in\mathcal{R}\_q$
|
||||
/// 2. output $\mathsf{PT}$
|
||||
///
|
||||
/// **Remark:** Observe that the decryption is followed by a decoding phase that will contain a
|
||||
/// rounding.
|
||||
pub fn decrypt_glwe_ciphertext<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
|
||||
output_plaintext_list: &mut PlaintextList<OutputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
output_plaintext_list.plaintext_count().0 == input_glwe_ciphertext.polynomial_size().0,
|
||||
"Mismatched output PlaintextCount {:?} and input PolynomialSize {:?}",
|
||||
output_plaintext_list.plaintext_count(),
|
||||
input_glwe_ciphertext.polynomial_size()
|
||||
);
|
||||
assert!(
|
||||
glwe_secret_key.glwe_dimension() == input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
|
||||
"Mismatched GlweDimension between glwe_secret_key {:?} and input_glwe_ciphertext {:?}",
|
||||
glwe_secret_key.glwe_dimension(),
|
||||
input_glwe_ciphertext.glwe_size().to_glwe_dimension()
|
||||
);
|
||||
assert!(
|
||||
glwe_secret_key.polynomial_size() == input_glwe_ciphertext.polynomial_size(),
|
||||
"Mismatched PolynomialSize between glwe_secret_key {:?} and input_glwe_ciphertext {:?}",
|
||||
glwe_secret_key.polynomial_size(),
|
||||
input_glwe_ciphertext.polynomial_size()
|
||||
);
|
||||
|
||||
let (mask, body) = input_glwe_ciphertext.get_mask_and_body();
|
||||
output_plaintext_list
|
||||
.as_mut()
|
||||
.copy_from_slice(body.as_ref());
|
||||
polynomial_wrapping_sub_multisum_assign(
|
||||
&mut output_plaintext_list.as_mut_polynomial(),
|
||||
&mask.as_polynomial_list(),
|
||||
&glwe_secret_key.as_polynomial_list(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Decrypt a [`GLWE ciphertext list`](`GlweCiphertextList`) in a (scalar) plaintext list.
|
||||
///
|
||||
/// See [`encrypt_glwe_ciphertext_list`] for usage.
|
||||
pub fn decrypt_glwe_ciphertext_list<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
input_glwe_ciphertext_list: &GlweCiphertextList<InputCont>,
|
||||
output_plaintext_list: &mut PlaintextList<OutputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
output_plaintext_list.plaintext_count().0
|
||||
== input_glwe_ciphertext_list.polynomial_size().0
|
||||
* input_glwe_ciphertext_list.glwe_ciphertext_count().0,
|
||||
"Mismatched output PlaintextCount {:?} and input PolynomialSize ({:?}) * \
|
||||
GlweCiphertextCount ({:?}) = {:?}",
|
||||
output_plaintext_list.plaintext_count(),
|
||||
input_glwe_ciphertext_list.polynomial_size(),
|
||||
input_glwe_ciphertext_list.glwe_ciphertext_count(),
|
||||
input_glwe_ciphertext_list.polynomial_size().0
|
||||
* input_glwe_ciphertext_list.glwe_ciphertext_count().0
|
||||
);
|
||||
assert!(
|
||||
glwe_secret_key.glwe_dimension()
|
||||
== input_glwe_ciphertext_list.glwe_size().to_glwe_dimension(),
|
||||
"Mismatched GlweDimension between glwe_secret_key {:?} and input_glwe_ciphertext_list {:?}",
|
||||
glwe_secret_key.glwe_dimension(),
|
||||
input_glwe_ciphertext_list.glwe_size().to_glwe_dimension()
|
||||
);
|
||||
assert!(
|
||||
glwe_secret_key.polynomial_size() == input_glwe_ciphertext_list.polynomial_size(),
|
||||
"Mismatched PolynomialSize between glwe_secret_key {:?} and input_glwe_ciphertext_list {:?}",
|
||||
glwe_secret_key.polynomial_size(),
|
||||
input_glwe_ciphertext_list.polynomial_size()
|
||||
);
|
||||
|
||||
for (ciphertext, mut output_sublist) in input_glwe_ciphertext_list
|
||||
.iter()
|
||||
.zip(output_plaintext_list.chunks_exact_mut(input_glwe_ciphertext_list.polynomial_size().0))
|
||||
{
|
||||
decrypt_glwe_ciphertext(glwe_secret_key, &ciphertext, &mut output_sublist);
|
||||
}
|
||||
}
|
||||
|
||||
/// A trivial encryption uses a zero mask and no noise.
|
||||
///
|
||||
/// It is absolutely not secure, as the body contains a direct copy of the plaintext.
|
||||
/// However, it is useful for some FHE algorithms taking public information as input. For
|
||||
/// example, a trivial GLWE encryption of a public lookup table is used in the programmable
|
||||
/// bootstrap.
|
||||
///
|
||||
/// By definition a trivial encryption can be decrypted by any [`GLWE secret key`](`GlweSecretKey`).
|
||||
///
|
||||
/// Trivially encrypt an input (scalar) plaintext list in a [`GLWE ciphertext`](`GlweCiphertext`).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::SecretRandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let encoded_msg = msg << 60;
|
||||
/// let plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
///
|
||||
/// trivially_encrypt_glwe_ciphertext(&mut glwe, &plaintext_list);
|
||||
///
|
||||
/// // Here we show the content of the trivial encryption is actually the input data in clear and
|
||||
/// // that the mask is full of 0s
|
||||
/// assert_eq!(glwe.get_body().as_ref(), plaintext_list.as_ref());
|
||||
/// glwe.get_mask()
|
||||
/// .as_ref()
|
||||
/// .iter()
|
||||
/// .for_each(|&elt| assert_eq!(elt, 0));
|
||||
///
|
||||
/// // Now we demonstrate that any random GlweSecretKey can be used to decrypt it.
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list = PlaintextList::new(0u64, plaintext_list.plaintext_count());
|
||||
///
|
||||
/// decrypt_glwe_ciphertext(&glwe_secret_key, &glwe, &mut output_plaintext_list);
|
||||
///
|
||||
/// // Again the trivial encryption encrypts _nothing_
|
||||
/// assert_eq!(output_plaintext_list.as_ref(), glwe.get_body().as_ref());
|
||||
/// ```
|
||||
pub fn trivially_encrypt_glwe_ciphertext<Scalar, InputCont, OutputCont>(
|
||||
output: &mut GlweCiphertext<OutputCont>,
|
||||
encoded: &PlaintextList<InputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
encoded.plaintext_count().0 == output.polynomial_size().0,
|
||||
"Mismatched input PlaintextCount {:?} and output PolynomialSize {:?}",
|
||||
encoded.plaintext_count(),
|
||||
output.polynomial_size()
|
||||
);
|
||||
|
||||
let (mut mask, mut body) = output.get_mut_mask_and_body();
|
||||
|
||||
mask.as_mut().fill(Scalar::ZERO);
|
||||
body.as_mut().copy_from_slice(encoded.as_ref());
|
||||
}
|
||||
|
||||
/// A trivial encryption uses a zero mask and no noise.
|
||||
///
|
||||
/// It is absolutely not secure, as the body contains a direct copy of the plaintext.
|
||||
/// However, it is useful for some FHE algorithms taking public information as input. For
|
||||
/// example, a trivial GLWE encryption of a public lookup table is used in the programmable
|
||||
/// bootstrap.
|
||||
///
|
||||
/// By definition a trivial encryption can be decrypted by any [`GLWE secret key`](`GlweSecretKey`).
|
||||
///
|
||||
/// Allocate a new [`GLWE ciphertext`](`GlweCiphertext`) and trivially encrypt an input (scalar)
|
||||
/// plaintext list in it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::SecretRandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let encoded_msg = msg << 60;
|
||||
/// let plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut glwe = allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &plaintext_list);
|
||||
///
|
||||
/// // Here we show the content of the trivial encryption is actually the input data in clear and
|
||||
/// // that the mask is full of 0s
|
||||
/// assert_eq!(glwe.get_body().as_ref(), plaintext_list.as_ref());
|
||||
/// glwe.get_mask()
|
||||
/// .as_ref()
|
||||
/// .iter()
|
||||
/// .for_each(|&elt| assert_eq!(elt, 0));
|
||||
///
|
||||
/// // Now we demonstrate that any random GlweSecretKey can be used to decrypt it.
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list = PlaintextList::new(0u64, plaintext_list.plaintext_count());
|
||||
///
|
||||
/// decrypt_glwe_ciphertext(&glwe_secret_key, &glwe, &mut output_plaintext_list);
|
||||
///
|
||||
/// // Again the trivial encryption encrypts _nothing_
|
||||
/// assert_eq!(output_plaintext_list.as_ref(), glwe.get_body().as_ref());
|
||||
/// ```
|
||||
pub fn allocate_and_trivially_encrypt_new_glwe_ciphertext<Scalar, InputCont>(
|
||||
glwe_size: GlweSize,
|
||||
encoded: &PlaintextList<InputCont>,
|
||||
) -> GlweCiphertextOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
{
|
||||
let polynomial_size = PolynomialSize(encoded.plaintext_count().0);
|
||||
|
||||
let mut new_ct = GlweCiphertextOwned::new(Scalar::ZERO, glwe_size, polynomial_size);
|
||||
|
||||
let mut body = new_ct.get_mut_body();
|
||||
body.as_mut().copy_from_slice(encoded.as_ref());
|
||||
|
||||
new_ct
|
||||
}
|
||||
128
tfhe/src/core_crypto/algorithms/glwe_sample_extraction.rs
Normal file
128
tfhe/src/core_crypto/algorithms/glwe_sample_extraction.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
use crate::core_crypto::commons::parameters::{MonomialDegree, *};
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Extract the nth coefficient from the body of a [`GLWE Ciphertext`](`GlweCiphertext`) as an
|
||||
/// [`LWE ciphertext`](`LweCiphertext`).
|
||||
///
|
||||
/// # Formal definition
|
||||
///
|
||||
/// This operation is usually referred to as a _sample extract_ in the literature.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the GlweSecretKey
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let encoded_msg = msg << 60;
|
||||
/// let mut plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// let special_value = 15;
|
||||
/// *plaintext_list.get_mut(42).0 = 15 << 60;
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut glwe,
|
||||
/// &plaintext_list,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Now we get the equivalent LweSecretKey from the GlweSecretKey
|
||||
/// let equivalent_lwe_sk = glwe_secret_key.clone().into_lwe_secret_key();
|
||||
///
|
||||
/// let mut extracted_sample =
|
||||
/// LweCiphertext::new(0u64, equivalent_lwe_sk.lwe_dimension().to_lwe_size());
|
||||
///
|
||||
/// // Here we chose to extract sample at index 42 (corresponding to the MonomialDegree(42))
|
||||
/// extract_lwe_sample_from_glwe_ciphertext(&glwe, &mut extracted_sample, MonomialDegree(42));
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&equivalent_lwe_sk, &extracted_sample);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// let recovered_message = decomposer.closest_representable(decrypted_plaintext.0) >> 60;
|
||||
///
|
||||
/// // We check we recover our special value instead of the 3 stored in all other slots of the
|
||||
/// // GlweCiphertext
|
||||
/// assert_eq!(special_value, recovered_message);
|
||||
/// ```
|
||||
pub fn extract_lwe_sample_from_glwe_ciphertext<Scalar, InputCont, OutputCont>(
|
||||
input_glwe: &GlweCiphertext<InputCont>,
|
||||
output_lwe: &mut LweCiphertext<OutputCont>,
|
||||
nth: MonomialDegree,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
input_glwe.glwe_size().to_glwe_dimension().0 * input_glwe.polynomial_size().0
|
||||
== output_lwe.lwe_size().to_lwe_dimension().0,
|
||||
"Mismatch between equivalent LweDimension of input ciphertext and output ciphertext. \
|
||||
Got {:?} for input and {:?} for output.",
|
||||
LweDimension(input_glwe.glwe_size().to_glwe_dimension().0 * input_glwe.polynomial_size().0),
|
||||
output_lwe.lwe_size().to_lwe_dimension(),
|
||||
);
|
||||
|
||||
// We retrieve the bodies and masks of the two ciphertexts.
|
||||
let (mut lwe_mask, lwe_body) = output_lwe.get_mut_mask_and_body();
|
||||
let (glwe_mask, glwe_body) = input_glwe.get_mask_and_body();
|
||||
|
||||
// We copy the body
|
||||
*lwe_body.0 = glwe_body.as_ref()[nth.0];
|
||||
|
||||
// We copy the mask (each polynomial is in the wrong order)
|
||||
lwe_mask.as_mut().copy_from_slice(glwe_mask.as_ref());
|
||||
|
||||
// We compute the number of elements which must be
|
||||
// turned into their opposite
|
||||
let opposite_count = input_glwe.polynomial_size().0 - nth.0 - 1;
|
||||
|
||||
// We loop through the polynomials
|
||||
for lwe_mask_poly in lwe_mask.as_mut().chunks_mut(input_glwe.polynomial_size().0) {
|
||||
// We reverse the polynomial
|
||||
lwe_mask_poly.reverse();
|
||||
// We compute the opposite of the proper coefficients
|
||||
slice_wrapping_opposite_assign(&mut lwe_mask_poly[0..opposite_count]);
|
||||
// We rotate the polynomial properly
|
||||
lwe_mask_poly.rotate_left(opposite_count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::core_crypto::commons::generators::SecretRandomGenerator;
|
||||
use crate::core_crypto::commons::math::random::{RandomGenerable, UniformBinary};
|
||||
use crate::core_crypto::commons::numeric::Numeric;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Allocate a new [`GLWE secret key`](`GlweSecretKey`) and fill it with uniformly random binary
|
||||
/// coefficients.
|
||||
///
|
||||
/// See [`encrypt_glwe_ciphertext`](`super::glwe_encryption::encrypt_glwe_ciphertext`)
|
||||
/// for usage.
|
||||
pub fn allocate_and_generate_new_binary_glwe_secret_key<Scalar, Gen>(
|
||||
glwe_dimension: GlweDimension,
|
||||
polynomial_size: PolynomialSize,
|
||||
generator: &mut SecretRandomGenerator<Gen>,
|
||||
) -> GlweSecretKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: RandomGenerable<UniformBinary> + Numeric,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut glwe_secret_key =
|
||||
GlweSecretKeyOwned::new(Scalar::ZERO, glwe_dimension, polynomial_size);
|
||||
|
||||
generate_binary_glwe_secret_key(&mut glwe_secret_key, generator);
|
||||
|
||||
glwe_secret_key
|
||||
}
|
||||
|
||||
/// Fill a [`GLWE secret key`](`GlweSecretKey`) with uniformly random binary coefficients.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::SecretRandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// let mut glwe_secret_key =
|
||||
/// GlweSecretKey::new(0u64, glwe_size.to_glwe_dimension(), polynomial_size);
|
||||
///
|
||||
/// generate_binary_glwe_secret_key(&mut glwe_secret_key, &mut secret_generator);
|
||||
///
|
||||
/// // Check all coefficients are not zero as we just generated a new key
|
||||
/// // Note probability of this assert failing is (1/2)^polynomial_size or ~5.6 * 10^-309 for a
|
||||
/// // polynomial size of 1024.
|
||||
/// assert!(glwe_secret_key.as_ref().iter().all(|&elt| elt == 0) == false);
|
||||
/// ```
|
||||
pub fn generate_binary_glwe_secret_key<Scalar, InCont, Gen>(
|
||||
glwe_secret_key: &mut GlweSecretKey<InCont>,
|
||||
generator: &mut SecretRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: RandomGenerable<UniformBinary>,
|
||||
InCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
generator.fill_slice_with_random_uniform_binary(glwe_secret_key.as_mut())
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::fft_impl::crypto::bootstrap::FourierLweBootstrapKey;
|
||||
use crate::core_crypto::fft_impl::crypto::ggsw::fill_with_forward_fourier_scratch;
|
||||
use crate::core_crypto::fft_impl::math::fft::{Fft, FftView};
|
||||
use concrete_fft::c64;
|
||||
use dyn_stack::{DynStack, SizeOverflow, StackReq};
|
||||
|
||||
/// Convert an [`LWE bootstrap key`](`LweBootstrapKey`) with standard coefficients to the Fourier
|
||||
/// domain.
|
||||
pub fn convert_standard_lwe_bootstrap_key_to_fourier<Scalar, InputCont, OutputCont>(
|
||||
input_bsk: &LweBootstrapKey<InputCont>,
|
||||
output_bsk: &mut FourierLweBootstrapKey<OutputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = c64>,
|
||||
{
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
|
||||
let fft = Fft::new(input_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
buffers.resize(
|
||||
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized_scratch(fft)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
|
||||
let stack = buffers.stack();
|
||||
|
||||
output_bsk
|
||||
.as_mut_view()
|
||||
.fill_with_forward_fourier(input_bsk.as_view(), fft, stack);
|
||||
}
|
||||
|
||||
/// Memory optimized version of [`convert_standard_lwe_bootstrap_key_to_fourier`].
|
||||
pub fn convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized<Scalar, InputCont, OutputCont>(
|
||||
input_bsk: &LweBootstrapKey<InputCont>,
|
||||
output_bsk: &mut FourierLweBootstrapKey<OutputCont>,
|
||||
fft: FftView<'_>,
|
||||
stack: DynStack<'_>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = c64>,
|
||||
{
|
||||
output_bsk
|
||||
.as_mut_view()
|
||||
.fill_with_forward_fourier(input_bsk.as_view(), fft, stack);
|
||||
}
|
||||
|
||||
/// Return the required memory for [`convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized`].
|
||||
pub fn convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized_scratch(
|
||||
fft: FftView<'_>,
|
||||
) -> Result<StackReq, SizeOverflow> {
|
||||
fill_with_forward_fourier_scratch(fft)
|
||||
}
|
||||
310
tfhe/src/core_crypto/algorithms/lwe_bootstrap_key_generation.rs
Normal file
310
tfhe/src/core_crypto/algorithms/lwe_bootstrap_key_generation.rs
Normal file
@@ -0,0 +1,310 @@
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub fn generate_lwe_bootstrap_key<Scalar, InputKeyCont, OutputKeyCont, OutputCont, Gen>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
output: &mut LweBootstrapKey<OutputCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.input_lwe_dimension() == input_lwe_secret_key.lwe_dimension(),
|
||||
"Mismatched LweDimension between input LWE secret key and LWE bootstrap key. \
|
||||
Input LWE secret key LweDimension: {:?}, LWE bootstrap key input LweDimension {:?}.",
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
output.input_lwe_dimension()
|
||||
);
|
||||
|
||||
assert!(
|
||||
output.glwe_size() == output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
"Mismatched GlweSize between output GLWE secret key and LWE bootstrap key. \
|
||||
Output GLWE secret key GlweSize: {:?}, LWE bootstrap key GlweSize {:?}.",
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output.glwe_size()
|
||||
);
|
||||
|
||||
assert!(
|
||||
output.polynomial_size() == output_glwe_secret_key.polynomial_size(),
|
||||
"Mismatched PolynomialSize between output GLWE secret key and LWE bootstrap key. \
|
||||
Output GLWE secret key PolynomialSize: {:?}, LWE bootstrap key PolynomialSize {:?}.",
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
output.polynomial_size()
|
||||
);
|
||||
|
||||
let gen_iter = generator
|
||||
.fork_bsk_to_ggsw::<Scalar>(
|
||||
output.input_lwe_dimension(),
|
||||
output.decomposition_level_count(),
|
||||
output.glwe_size(),
|
||||
output.polynomial_size(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for ((mut ggsw, &input_key_element), mut generator) in output
|
||||
.iter_mut()
|
||||
.zip(input_lwe_secret_key.as_ref())
|
||||
.zip(gen_iter)
|
||||
{
|
||||
encrypt_ggsw_ciphertext(
|
||||
output_glwe_secret_key,
|
||||
&mut ggsw,
|
||||
Plaintext(input_key_element),
|
||||
noise_parameters,
|
||||
&mut generator,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate_and_generate_new_lwe_bootstrap_key<Scalar, InputKeyCont, OutputKeyCont, Gen>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweBootstrapKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut bsk = LweBootstrapKeyOwned::new(
|
||||
Scalar::ZERO,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
);
|
||||
|
||||
generate_lwe_bootstrap_key(
|
||||
input_lwe_secret_key,
|
||||
output_glwe_secret_key,
|
||||
&mut bsk,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
|
||||
bsk
|
||||
}
|
||||
|
||||
/// Parallel variant of [`generate_lwe_bootstrap_key`], it is recommended to use this function for
|
||||
/// better key generation times as LWE bootstrapping keys can be quite large.
|
||||
pub fn par_generate_lwe_bootstrap_key<Scalar, InputKeyCont, OutputKeyCont, OutputCont, Gen>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
output: &mut LweBootstrapKey<OutputCont>,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar> + Sync,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.input_lwe_dimension() == input_lwe_secret_key.lwe_dimension(),
|
||||
"Mismatched LweDimension between input LWE secret key and LWE bootstrap key. \
|
||||
Input LWE secret key LweDimension: {:?}, LWE bootstrap key input LweDimension {:?}.",
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
output.input_lwe_dimension()
|
||||
);
|
||||
|
||||
assert!(
|
||||
output.glwe_size() == output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
"Mismatched GlweSize between output GLWE secret key and LWE bootstrap key. \
|
||||
Output GLWE secret key GlweSize: {:?}, LWE bootstrap key GlweSize {:?}.",
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output.glwe_size()
|
||||
);
|
||||
|
||||
assert!(
|
||||
output.polynomial_size() == output_glwe_secret_key.polynomial_size(),
|
||||
"Mismatched PolynomialSize between output GLWE secret key and LWE bootstrap key. \
|
||||
Output GLWE secret key PolynomialSize: {:?}, LWE bootstrap key PolynomialSize {:?}.",
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
output.polynomial_size()
|
||||
);
|
||||
|
||||
let gen_iter = generator
|
||||
.par_fork_bsk_to_ggsw::<Scalar>(
|
||||
output.input_lwe_dimension(),
|
||||
output.decomposition_level_count(),
|
||||
output.glwe_size(),
|
||||
output.polynomial_size(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
output
|
||||
.par_iter_mut()
|
||||
.zip(input_lwe_secret_key.as_ref().par_iter())
|
||||
.zip(gen_iter)
|
||||
.for_each(|((mut ggsw, &input_key_element), mut generator)| {
|
||||
par_encrypt_ggsw_ciphertext(
|
||||
output_glwe_secret_key,
|
||||
&mut ggsw,
|
||||
Plaintext(input_key_element),
|
||||
noise_parameters,
|
||||
&mut generator,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn par_allocate_and_generate_new_lwe_bootstrap_key<Scalar, InputKeyCont, OutputKeyCont, Gen>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweBootstrapKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar> + Sync,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
let mut bsk = LweBootstrapKeyOwned::new(
|
||||
Scalar::ZERO,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
);
|
||||
|
||||
par_generate_lwe_bootstrap_key(
|
||||
input_lwe_secret_key,
|
||||
output_glwe_secret_key,
|
||||
&mut bsk,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
|
||||
bsk
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod parallel_test {
|
||||
use crate::core_crypto::algorithms::{
|
||||
allocate_and_generate_new_binary_glwe_secret_key,
|
||||
allocate_and_generate_new_binary_lwe_secret_key, generate_lwe_bootstrap_key,
|
||||
par_generate_lwe_bootstrap_key,
|
||||
};
|
||||
use crate::core_crypto::commons::dispersion::StandardDev;
|
||||
use crate::core_crypto::commons::generators::{DeterministicSeeder, EncryptionRandomGenerator};
|
||||
use crate::core_crypto::commons::math::random::Seed;
|
||||
use crate::core_crypto::commons::math::torus::UnsignedTorus;
|
||||
use crate::core_crypto::commons::parameters::{
|
||||
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
};
|
||||
use crate::core_crypto::commons::test_tools::new_secret_random_generator;
|
||||
use crate::core_crypto::entities::LweBootstrapKeyOwned;
|
||||
use concrete_csprng::generators::SoftwareRandomGenerator;
|
||||
|
||||
fn test_refactored_bsk_parallel_gen_equivalence<T: UnsignedTorus + Send + Sync>() {
|
||||
for _ in 0..10 {
|
||||
let lwe_dim =
|
||||
LweDimension(crate::core_crypto::commons::test_tools::random_usize_between(5..10));
|
||||
let glwe_dim =
|
||||
GlweDimension(crate::core_crypto::commons::test_tools::random_usize_between(5..10));
|
||||
let poly_size = PolynomialSize(
|
||||
crate::core_crypto::commons::test_tools::random_usize_between(5..10),
|
||||
);
|
||||
let level = DecompositionLevelCount(
|
||||
crate::core_crypto::commons::test_tools::random_usize_between(2..5),
|
||||
);
|
||||
let base_log = DecompositionBaseLog(
|
||||
crate::core_crypto::commons::test_tools::random_usize_between(2..5),
|
||||
);
|
||||
let mask_seed = Seed(crate::core_crypto::commons::test_tools::any_usize() as u128);
|
||||
let deterministic_seeder_seed =
|
||||
Seed(crate::core_crypto::commons::test_tools::any_usize() as u128);
|
||||
|
||||
let mut secret_generator = new_secret_random_generator();
|
||||
let lwe_sk =
|
||||
allocate_and_generate_new_binary_lwe_secret_key(lwe_dim, &mut secret_generator);
|
||||
let glwe_sk = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
glwe_dim,
|
||||
poly_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
|
||||
let mut parallel_bsk = LweBootstrapKeyOwned::new(
|
||||
T::ZERO,
|
||||
glwe_dim.to_glwe_size(),
|
||||
poly_size,
|
||||
base_log,
|
||||
level,
|
||||
lwe_dim,
|
||||
);
|
||||
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(
|
||||
mask_seed,
|
||||
&mut DeterministicSeeder::<SoftwareRandomGenerator>::new(
|
||||
deterministic_seeder_seed,
|
||||
),
|
||||
);
|
||||
|
||||
par_generate_lwe_bootstrap_key(
|
||||
&lwe_sk,
|
||||
&glwe_sk,
|
||||
&mut parallel_bsk,
|
||||
StandardDev::from_standard_dev(10.),
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let mut sequential_bsk = LweBootstrapKeyOwned::new(
|
||||
T::ZERO,
|
||||
glwe_dim.to_glwe_size(),
|
||||
poly_size,
|
||||
base_log,
|
||||
level,
|
||||
lwe_dim,
|
||||
);
|
||||
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(
|
||||
mask_seed,
|
||||
&mut DeterministicSeeder::<SoftwareRandomGenerator>::new(
|
||||
deterministic_seeder_seed,
|
||||
),
|
||||
);
|
||||
|
||||
generate_lwe_bootstrap_key(
|
||||
&lwe_sk,
|
||||
&glwe_sk,
|
||||
&mut sequential_bsk,
|
||||
StandardDev::from_standard_dev(10.),
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
assert_eq!(parallel_bsk.as_ref(), sequential_bsk.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_refactored_bsk_parallel_gen_equivalence_u32() {
|
||||
test_refactored_bsk_parallel_gen_equivalence::<u32>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_refactored_bsk_parallel_gen_equivalence_u64() {
|
||||
test_refactored_bsk_parallel_gen_equivalence::<u64>()
|
||||
}
|
||||
}
|
||||
791
tfhe/src/core_crypto/algorithms/lwe_encryption.rs
Normal file
791
tfhe/src/core_crypto/algorithms/lwe_encryption.rs
Normal file
@@ -0,0 +1,791 @@
|
||||
//! Module containing functions related to LWE ciphertext encryption and decryption
|
||||
|
||||
use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::{EncryptionRandomGenerator, SecretRandomGenerator};
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// Encrypt an input plaintext in an output [`LWE ciphertext`](`LweCiphertext`).
|
||||
///
|
||||
/// # Formal Definition
|
||||
///
|
||||
/// ## LWE Encryption
|
||||
/// ###### inputs:
|
||||
/// - $\mathsf{pt}\in\mathbb{Z}\_q$: a plaintext
|
||||
/// - $\vec{s}\in\mathbb{Z}\_q^n$: a secret key
|
||||
/// - $\mathcal{D\_{\sigma^2,\mu}}$: a normal distribution of variance $\sigma^2$ and a mean $\mu$
|
||||
///
|
||||
/// ###### outputs:
|
||||
/// - $\mathsf{ct} = \left( \vec{a} , b\right) \in \mathsf{LWE}^n\_{\vec{s}}( \mathsf{pt} )\subseteq
|
||||
/// \mathbb{Z}\_q^{(n+1)}$: an LWE ciphertext
|
||||
///
|
||||
/// ###### algorithm:
|
||||
/// 1. uniformly sample a vector $\vec{a}\in\mathbb{Z}\_q^n$
|
||||
/// 2. sample an integer error term $e \hookleftarrow \mathcal{D\_{\sigma^2,\mu}}$
|
||||
/// 3. compute $b = \left\langle \vec{a} , \vec{s} \right\rangle + \mathsf{pt} + e \in\mathbb{Z}\_q$
|
||||
/// 4. output $\left( \vec{a} , b\right)$
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
|
||||
///
|
||||
/// encrypt_lwe_ciphertext(
|
||||
/// &lwe_secret_key,
|
||||
/// &mut lwe,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// let rounded = decomposer.closest_representable(decrypted_plaintext.0);
|
||||
///
|
||||
/// // Remove the encoding
|
||||
/// let cleartext = rounded >> 60;
|
||||
///
|
||||
/// // Check we recovered the original message
|
||||
/// assert_eq!(cleartext, msg);
|
||||
/// ```
|
||||
pub fn encrypt_lwe_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.lwe_size().to_lwe_dimension() == lwe_secret_key.lwe_dimension(),
|
||||
"Mismatch between LweDimension of output cipertext and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
output.lwe_size().to_lwe_dimension(),
|
||||
lwe_secret_key.lwe_dimension()
|
||||
);
|
||||
|
||||
let (mut mask, body) = output.get_mut_mask_and_body();
|
||||
|
||||
generator.fill_slice_with_random_mask(mask.as_mut());
|
||||
|
||||
// generate an error from the normal distribution described by std_dev
|
||||
*body.0 = generator.random_noise(noise_parameters);
|
||||
|
||||
// compute the multisum between the secret key and the mask
|
||||
*body.0 = (*body.0).wrapping_add(slice_wrapping_dot_product(
|
||||
mask.as_ref(),
|
||||
lwe_secret_key.as_ref(),
|
||||
));
|
||||
|
||||
*body.0 = (*body.0).wrapping_add(encoded.0);
|
||||
}
|
||||
|
||||
/// Allocate a new [`LWE ciphertext`](`LweCiphertext`) and encrypt an input plaintext in it.
|
||||
///
|
||||
/// See this [`formal definition`](`encrypt_lwe_ciphertext#formal-definition`) for the definition
|
||||
/// of the LWE encryption algorithm.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let lwe = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// let rounded = decomposer.closest_representable(decrypted_plaintext.0);
|
||||
///
|
||||
/// // Remove the encoding
|
||||
/// let cleartext = rounded >> 60;
|
||||
///
|
||||
/// // Check we recovered the original message
|
||||
/// assert_eq!(cleartext, msg);
|
||||
/// ```
|
||||
pub fn allocate_and_encrypt_new_lwe_ciphertext<Scalar, KeyCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweCiphertextOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut new_ct =
|
||||
LweCiphertextOwned::new(Scalar::ZERO, lwe_secret_key.lwe_dimension().to_lwe_size());
|
||||
|
||||
encrypt_lwe_ciphertext(
|
||||
lwe_secret_key,
|
||||
&mut new_ct,
|
||||
encoded,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
|
||||
new_ct
|
||||
}
|
||||
|
||||
/// A trivial encryption uses a zero mask and no noise.
|
||||
///
|
||||
/// It is absolutely not secure, as the body contains a direct copy of the plaintext.
|
||||
/// However, it is useful for some FHE algorithms taking public information as input.
|
||||
///
|
||||
/// By definition a trivial encryption can be decrypted by any [`LWE secret key`](`LweSecretKey`).
|
||||
///
|
||||
/// Trivially encrypt an input plaintext in an [`LWE ciphertext`](`LweCiphertext`).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::SecretRandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
|
||||
///
|
||||
/// trivially_encrypt_lwe_ciphertext(&mut lwe, plaintext);
|
||||
///
|
||||
/// // Here we show the content of the trivial encryption is actually the input data in clear and
|
||||
/// // that the mask is full of 0s
|
||||
/// assert_eq!(*lwe.get_body().0, plaintext.0);
|
||||
/// lwe.get_mask()
|
||||
/// .as_ref()
|
||||
/// .iter()
|
||||
/// .for_each(|&elt| assert_eq!(elt, 0));
|
||||
///
|
||||
/// // Now we demonstrate that any random LweSecretKey can be used to decrypt it.
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe);
|
||||
///
|
||||
/// // Again the trivial encryption encrypts _nothing_
|
||||
/// assert_eq!(decrypted_plaintext.0, *lwe.get_body().0);
|
||||
/// ```
|
||||
pub fn trivially_encrypt_lwe_ciphertext<Scalar, OutputCont>(
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
output
|
||||
.get_mut_mask()
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.for_each(|elt| *elt = Scalar::ZERO);
|
||||
|
||||
*output.get_mut_body().0 = encoded.0
|
||||
}
|
||||
|
||||
/// A trivial encryption uses a zero mask and no noise.
|
||||
///
|
||||
/// It is absolutely not secure, as the body contains a direct copy of the plaintext.
|
||||
/// However, it is useful for some FHE algorithms taking public information as input.
|
||||
///
|
||||
/// By definition a trivial encryption can be decrypted by any [`LWE secret key`](`LweSecretKey`).
|
||||
///
|
||||
/// Allocate a new [`LWE ciphertext`](`LweCiphertext`) and trivially encrypt an input plaintext in
|
||||
/// it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::SecretRandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe =
|
||||
/// allocate_and_trivially_encrypt_new_lwe_ciphertext(lwe_dimension.to_lwe_size(), plaintext);
|
||||
///
|
||||
/// // Here we show the content of the trivial encryption is actually the input data in clear and
|
||||
/// // that the mask is full of 0s
|
||||
/// assert_eq!(*lwe.get_body().0, plaintext.0);
|
||||
/// lwe.get_mask()
|
||||
/// .as_ref()
|
||||
/// .iter()
|
||||
/// .for_each(|&elt| assert_eq!(elt, 0));
|
||||
///
|
||||
/// // Now we demonstrate that any random LweSecretKey can be used to decrypt it.
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe);
|
||||
///
|
||||
/// // Again the trivial encryption encrypts _nothing_
|
||||
/// assert_eq!(decrypted_plaintext.0, *lwe.get_body().0);
|
||||
/// ```
|
||||
pub fn allocate_and_trivially_encrypt_new_lwe_ciphertext<Scalar>(
|
||||
lwe_size: LweSize,
|
||||
encoded: Plaintext<Scalar>,
|
||||
) -> LweCiphertextOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
{
|
||||
let mut new_ct = LweCiphertextOwned::new(Scalar::ZERO, lwe_size);
|
||||
|
||||
*new_ct.get_mut_body().0 = encoded.0;
|
||||
|
||||
new_ct
|
||||
}
|
||||
|
||||
/// Decrypt an [`LWE ciphertext`](`LweCiphertext`) and return a noisy plaintext.
|
||||
///
|
||||
/// See [`encrypt_lwe_ciphertext`] for usage.
|
||||
///
|
||||
/// # Formal Definition
|
||||
///
|
||||
/// ## LWE Decryption
|
||||
/// ###### inputs:
|
||||
/// - $\mathsf{ct} = \left( \vec{a} , b\right) \in \mathsf{LWE}^n\_{\vec{s}}( \mathsf{pt} )\subseteq
|
||||
/// \mathbb{Z}\_q^{(n+1)}$: an LWE ciphertext
|
||||
/// - $\vec{s}\in\mathbb{Z}\_q^n$: a secret key
|
||||
///
|
||||
/// ###### outputs:
|
||||
/// - $\mathsf{pt}\in\mathbb{Z}\_q$: a plaintext
|
||||
///
|
||||
/// ###### algorithm:
|
||||
/// 1. compute $\mathsf{pt} = b - \left\langle \vec{a} , \vec{s} \right\rangle \in\mathbb{Z}\_q$
|
||||
/// 2. output $\mathsf{pt}$
|
||||
///
|
||||
/// **Remark:** Observe that the decryption is followed by a decoding phase that will contain a
|
||||
/// rounding.
|
||||
pub fn decrypt_lwe_ciphertext<Scalar, KeyCont, InputCont>(
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
lwe_ciphertext: &LweCiphertext<InputCont>,
|
||||
) -> Plaintext<Scalar>
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
lwe_ciphertext.lwe_size().to_lwe_dimension() == lwe_secret_key.lwe_dimension(),
|
||||
"Mismatch between LweDimension of output cipertext and input secret key. \
|
||||
Got {:?} in output, and {:?} in secret key.",
|
||||
lwe_ciphertext.lwe_size().to_lwe_dimension(),
|
||||
lwe_secret_key.lwe_dimension()
|
||||
);
|
||||
|
||||
let (mask, body) = lwe_ciphertext.get_mask_and_body();
|
||||
|
||||
Plaintext(body.0.wrapping_sub(slice_wrapping_dot_product(
|
||||
mask.as_ref(),
|
||||
lwe_secret_key.as_ref(),
|
||||
)))
|
||||
}
|
||||
|
||||
/// Encrypt an input plaintext list in an output [`LWE ciphertext list`](`LweCiphertextList`).
|
||||
///
|
||||
/// See this [`formal definition`](`encrypt_lwe_ciphertext#formal-definition`) for the definition
|
||||
/// of the LWE encryption algorithm.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_ciphertext_count = LweCiphertextCount(2);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let encoded_msg = msg << 60;
|
||||
/// let plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(lwe_ciphertext_count.0));
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe_list =
|
||||
/// LweCiphertextList::new(0u64, lwe_dimension.to_lwe_size(), lwe_ciphertext_count);
|
||||
///
|
||||
/// encrypt_lwe_ciphertext_list(
|
||||
/// &lwe_secret_key,
|
||||
/// &mut lwe_list,
|
||||
/// &plaintext_list,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list =
|
||||
/// PlaintextList::new(0u64, PlaintextCount(lwe_list.lwe_ciphertext_count().0));
|
||||
/// decrypt_lwe_ciphertext_list(&lwe_secret_key, &lwe_list, &mut output_plaintext_list);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// output_plaintext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext_list = output_plaintext_list.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext_list.iter_mut().for_each(|elt| *elt = *elt >> 60);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext_list = cleartext_list;
|
||||
///
|
||||
/// // Check we recovered the original message for each plaintext we encrypted
|
||||
/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg));
|
||||
/// ```
|
||||
pub fn encrypt_lwe_ciphertext_list<Scalar, KeyCont, OutputCont, InputCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
output: &mut LweCiphertextList<OutputCont>,
|
||||
encoded: &PlaintextList<InputCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.lwe_ciphertext_count().0 == encoded.plaintext_count().0,
|
||||
"Mismatch between number of output cipertexts and input plaintexts. \
|
||||
Got {:?} plaintexts, and {:?} ciphertext.",
|
||||
encoded.plaintext_count(),
|
||||
output.lwe_ciphertext_count()
|
||||
);
|
||||
|
||||
for (encoded_plaintext_ref, mut ciphertext) in encoded.iter().zip(output.iter_mut()) {
|
||||
encrypt_lwe_ciphertext(
|
||||
lwe_secret_key,
|
||||
&mut ciphertext,
|
||||
encoded_plaintext_ref.into(),
|
||||
noise_parameters,
|
||||
generator,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel variant of [`encrypt_lwe_ciphertext_list`].
|
||||
///
|
||||
/// See this [`formal definition`](`encrypt_lwe_ciphertext#formal-definition`) for the definition
|
||||
/// of the LWE encryption algorithm.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_ciphertext_count = LweCiphertextCount(2);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let encoded_msg = msg << 60;
|
||||
/// let plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(lwe_ciphertext_count.0));
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe_list =
|
||||
/// LweCiphertextList::new(0u64, lwe_dimension.to_lwe_size(), lwe_ciphertext_count);
|
||||
///
|
||||
/// par_encrypt_lwe_ciphertext_list(
|
||||
/// &lwe_secret_key,
|
||||
/// &mut lwe_list,
|
||||
/// &plaintext_list,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list =
|
||||
/// PlaintextList::new(0u64, PlaintextCount(lwe_list.lwe_ciphertext_count().0));
|
||||
/// decrypt_lwe_ciphertext_list(&lwe_secret_key, &lwe_list, &mut output_plaintext_list);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// output_plaintext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext_list = output_plaintext_list.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext_list.iter_mut().for_each(|elt| *elt = *elt >> 60);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext_list = cleartext_list;
|
||||
///
|
||||
/// // Check we recovered the original message for each plaintext we encrypted
|
||||
/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg));
|
||||
/// ```
|
||||
pub fn par_encrypt_lwe_ciphertext_list<Scalar, KeyCont, OutputCont, InputCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
output: &mut LweCiphertextList<OutputCont>,
|
||||
encoded: &PlaintextList<InputCont>,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
KeyCont: Container<Element = Scalar> + Sync,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.lwe_ciphertext_count().0 == encoded.plaintext_count().0,
|
||||
"Mismatch between number of output cipertexts and input plaintexts. \
|
||||
Got {:?} plaintexts, and {:?} ciphertext.",
|
||||
encoded.plaintext_count(),
|
||||
output.lwe_ciphertext_count()
|
||||
);
|
||||
|
||||
let gen_iter = generator
|
||||
.par_fork_lwe_list_to_lwe::<Scalar>(output.lwe_ciphertext_count(), output.lwe_size())
|
||||
.unwrap();
|
||||
|
||||
encoded
|
||||
.par_iter()
|
||||
.zip(output.par_iter_mut())
|
||||
.zip(gen_iter)
|
||||
.for_each(|((encoded_plaintext_ref, mut ciphertext), mut generator)| {
|
||||
encrypt_lwe_ciphertext(
|
||||
lwe_secret_key,
|
||||
&mut ciphertext,
|
||||
encoded_plaintext_ref.into(),
|
||||
noise_parameters,
|
||||
&mut generator,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/// Decrypt an [`LWE ciphertext list`](`LweCiphertextList`) in a plaintext list.
|
||||
///
|
||||
/// See [`encrypt_lwe_ciphertext_list`] for usage.
|
||||
///
|
||||
/// See this [`formal definition`](`decrypt_lwe_ciphertext#formal-definition`) for the definition
|
||||
/// of the LWE decryption algorithm.
|
||||
pub fn decrypt_lwe_ciphertext_list<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
input_lwe_ciphertext_list: &LweCiphertextList<InputCont>,
|
||||
output_plaintext_list: &mut PlaintextList<OutputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
output_plaintext_list.plaintext_count().0
|
||||
== input_lwe_ciphertext_list.lwe_ciphertext_count().0,
|
||||
"Mismatched output PlaintextCount {:?} and input LweCiphertextCount ({:?}).",
|
||||
output_plaintext_list.plaintext_count(),
|
||||
input_lwe_ciphertext_list.lwe_ciphertext_count(),
|
||||
);
|
||||
|
||||
for (ciphertext, output_plaintext) in input_lwe_ciphertext_list
|
||||
.iter()
|
||||
.zip(output_plaintext_list.iter_mut())
|
||||
{
|
||||
*output_plaintext.0 = decrypt_lwe_ciphertext(lwe_secret_key, &ciphertext).0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypt an input plaintext in an output [`LWE ciphertext`](`LweCiphertext`) using an
|
||||
/// [`LWE public key`](`LwePublicKey`). The ciphertext can be decrypted using the
|
||||
/// [`LWE secret key`](`LweSecretKey`) that was used to generate the public key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let zero_encryption_count =
|
||||
/// LwePublicKeyZeroEncryptionCount(lwe_dimension.to_lwe_size().0 * 64 + 128);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// let lwe_public_key = allocate_and_generate_new_lwe_public_key(
|
||||
/// &lwe_secret_key,
|
||||
/// zero_encryption_count,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
|
||||
///
|
||||
/// encrypt_lwe_ciphertext_with_public_key(
|
||||
/// &lwe_public_key,
|
||||
/// &mut lwe,
|
||||
/// plaintext,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// let rounded = decomposer.closest_representable(decrypted_plaintext.0);
|
||||
///
|
||||
/// // Remove the encoding
|
||||
/// let cleartext = rounded >> 60;
|
||||
///
|
||||
/// // Check we recovered the original message
|
||||
/// assert_eq!(cleartext, msg);
|
||||
/// ```
|
||||
pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
lwe_public_key: &LwePublicKey<KeyCont>,
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
generator: &mut SecretRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.lwe_size().to_lwe_dimension() == lwe_public_key.lwe_size().to_lwe_dimension(),
|
||||
"Mismatch between LweDimension of output cipertext and input public key. \
|
||||
Got {:?} in output, and {:?} in public key.",
|
||||
output.lwe_size().to_lwe_dimension(),
|
||||
lwe_public_key.lwe_size().to_lwe_dimension()
|
||||
);
|
||||
|
||||
output.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
let mut ct_choice = vec![Scalar::ZERO; lwe_public_key.zero_encryption_count().0];
|
||||
|
||||
generator.fill_slice_with_random_uniform_binary(&mut ct_choice);
|
||||
|
||||
// Add the public encryption of zeros to get the zero encryption
|
||||
for (&chosen, public_encryption_of_zero) in ct_choice.iter().zip(lwe_public_key.iter()) {
|
||||
if chosen == Scalar::ONE {
|
||||
lwe_ciphertext_add_assign(output, &public_encryption_of_zero);
|
||||
}
|
||||
}
|
||||
|
||||
// Add encoded plaintext
|
||||
let body = output.get_mut_body();
|
||||
*body.0 = (*body.0).wrapping_add(encoded.0);
|
||||
}
|
||||
|
||||
pub fn rc_encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
lwe_public_key: &LwePublicKey<KeyCont>,
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
generator: &mut SecretRandomGenerator<Gen>,
|
||||
) -> Vec<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output.lwe_size().to_lwe_dimension() == lwe_public_key.lwe_size().to_lwe_dimension(),
|
||||
"Mismatch between LweDimension of output cipertext and input public key. \
|
||||
Got {:?} in output, and {:?} in public key.",
|
||||
output.lwe_size().to_lwe_dimension(),
|
||||
lwe_public_key.lwe_size().to_lwe_dimension()
|
||||
);
|
||||
|
||||
output.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
let mut ct_choice = vec![Scalar::ZERO; lwe_public_key.zero_encryption_count().0];
|
||||
|
||||
generator.fill_slice_with_random_uniform_binary(&mut ct_choice);
|
||||
|
||||
// Add the public encryption of zeros to get the zero encryption
|
||||
for (&chosen, public_encryption_of_zero) in ct_choice.iter().zip(lwe_public_key.iter()) {
|
||||
if chosen == Scalar::ONE {
|
||||
lwe_ciphertext_add_assign(output, &public_encryption_of_zero);
|
||||
}
|
||||
}
|
||||
|
||||
// Add encoded plaintext
|
||||
let body = output.get_mut_body();
|
||||
*body.0 = (*body.0).wrapping_add(encoded.0);
|
||||
ct_choice
|
||||
}
|
||||
190
tfhe/src/core_crypto/algorithms/lwe_keyswitch.rs
Normal file
190
tfhe/src/core_crypto/algorithms/lwe_keyswitch.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Keyswitch an [`LWE ciphertext`](`LweCiphertext`) encrytped under an
|
||||
/// [`LWE secret key`](`LweSecretKey`) to another [`LWE secret key`](`LweSecretKey`).
|
||||
///
|
||||
/// # Formal Definition
|
||||
///
|
||||
/// ## LWE Keyswitch
|
||||
///
|
||||
/// This homomorphic procedure transforms an input
|
||||
/// [`LWE ciphertext`](`crate::core_crypto::entities::LweCiphertext`)
|
||||
/// $\mathsf{ct}\_{\mathsf{in}} =
|
||||
/// \left( \vec{a}\_{\mathsf{in}} , b\_{\mathsf{in}}\right) \in \mathsf{LWE}^{n\_{\mathsf{in}}}\_
|
||||
/// {\vec{s}\_{\mathsf{in}}}( \mathsf{pt} ) \subseteq \mathbb{Z}\_q^{(n\_{\mathsf{in}}+1)}$ into an
|
||||
/// output [`LWE ciphertext`](`crate::core_crypto::entities::LweCiphertext`)
|
||||
/// $\mathsf{ct}\_{\mathsf{out}} =
|
||||
/// \left( \vec{a}\_{\mathsf{out}} , b\_{\mathsf{out}}\right) \in
|
||||
/// \mathsf{LWE}^{n\_{\mathsf{out}}}\_{\vec{s}\_{\mathsf{out}}}( \mathsf{pt} )\subseteq
|
||||
/// \mathbb{Z}\_q^{(n\_{\mathsf{out}}+1)}$ where $n\_{\mathsf{in}} = |\vec{s}\_{\mathsf{in}}|$ and
|
||||
/// $n\_{\mathsf{out}} = |\vec{s}\_{\mathsf{out}}|$. It requires a
|
||||
/// [`key switching key`](`crate::core_crypto::entities::LweKeyswitchKey`).
|
||||
/// The input ciphertext is encrypted under the
|
||||
/// [`LWE secret key`](`crate::core_crypto::entities::LweSecretKey`)
|
||||
/// $\vec{s}\_{\mathsf{in}}$ and the output ciphertext is
|
||||
/// encrypted under the [`LWE secret key`](`crate::core_crypto::entities::LweSecretKey`)
|
||||
/// $\vec{s}\_{\mathsf{out}}$.
|
||||
///
|
||||
/// $$\mathsf{ct}\_{\mathsf{in}} \in \mathsf{LWE}^{n\_{\mathsf{in}}}\_{\vec{s}\_{\mathsf{in}}}(
|
||||
/// \mathsf{pt} ) ~~~~~~~~~~\mathsf{KSK}\_{\vec{s}\_{\mathsf{in}}\rightarrow
|
||||
/// \vec{s}\_{\mathsf{out}}}$$ $$ \mathsf{keyswitch}\left(\mathsf{ct}\_{\mathsf{in}} , \mathsf{KSK}
|
||||
/// \right) \rightarrow \mathsf{ct}\_{\mathsf{out}} \in
|
||||
/// \mathsf{LWE}^{n\_{\mathsf{out}}}\_{\vec{s}\_{\mathsf{out}}} \left( \mathsf{pt} \right)$$
|
||||
///
|
||||
/// ## Algorithm
|
||||
/// ###### inputs:
|
||||
/// - $\mathsf{ct}\_{\mathsf{in}} = \left( \vec{a}\_{\mathsf{in}} , b\_{\mathsf{in}}\right) \in
|
||||
/// \mathsf{LWE}^{n\_{\mathsf{in}}}\_{\vec{s}\_{\mathsf{in}}}( \mathsf{pt} )$: an [`LWE
|
||||
/// ciphertext`](`LweCiphertext`) with $\vec{a}\_{\mathsf{in}}=\left(a\_0, \cdots
|
||||
/// a\_{n\_{\mathsf{in}}-1}\right)$
|
||||
/// - $\mathsf{KSK}\_{\vec{s}\_{\mathsf{in}}\rightarrow \vec{s}\_{\mathsf{out}}}$: a
|
||||
/// [`key switching key`](`crate::core_crypto::entities::LweKeyswitchKey`)
|
||||
///
|
||||
/// ###### outputs:
|
||||
/// - $\mathsf{ct}\_{\mathsf{out}} \in \mathsf{LWE}^{n\_{\mathsf{out}}}\_{\vec{s}\_{\mathsf{out}}}
|
||||
/// \left( \mathsf{pt} \right)$: an
|
||||
/// [`LWE ciphertext`](`crate::core_crypto::entities::LweCiphertext`)
|
||||
///
|
||||
/// ###### algorithm:
|
||||
/// 1. set $\mathsf{ct}=\left( 0 , \cdots , 0 , b\_{\mathsf{in}} \right) \in
|
||||
/// \mathbb{Z}\_q^{(n\_{\mathsf{out}}+1)}$
|
||||
/// 2. compute $\mathsf{ct}\_{\mathsf{out}} = \mathsf{ct} -
|
||||
/// \sum\_{i=0}^{n\_{\mathsf{in}}-1} \mathsf{decompProduct}\left( a\_i , \overline{\mathsf{ct}\_i}
|
||||
/// \right)$
|
||||
/// 3. output $\mathsf{ct}\_{\mathsf{out}}$
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let input_lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let output_lwe_dimension = LweDimension(2048);
|
||||
/// let decomp_base_log = DecompositionBaseLog(3);
|
||||
/// let decomp_level_count = DecompositionLevelCount(5);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let input_lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(input_lwe_dimension, &mut secret_generator);
|
||||
/// let output_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
|
||||
/// output_lwe_dimension,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let ksk = allocate_and_generate_new_lwe_keyswitch_key(
|
||||
/// &input_lwe_secret_key,
|
||||
/// &output_lwe_secret_key,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let input_lwe = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
/// &input_lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_lwe = LweCiphertext::new(0, output_lwe_secret_key.lwe_dimension().to_lwe_size());
|
||||
///
|
||||
/// keyswitch_lwe_ciphertext(&ksk, &input_lwe, &mut output_lwe);
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&output_lwe_secret_key, &output_lwe);
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// let rounded = decomposer.closest_representable(decrypted_plaintext.0);
|
||||
///
|
||||
/// // Remove the encoding
|
||||
/// let cleartext = rounded >> 60;
|
||||
///
|
||||
/// // Check we recovered the original message
|
||||
/// assert_eq!(cleartext, msg);
|
||||
/// ```
|
||||
pub fn keyswitch_lwe_ciphertext<Scalar, KSKCont, InputCont, OutputCont>(
|
||||
lwe_keyswitch_key: &LweKeyswitchKey<KSKCont>,
|
||||
input_lwe_ciphertext: &LweCiphertext<InputCont>,
|
||||
output_lwe_ciphertext: &mut LweCiphertext<OutputCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
KSKCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
lwe_keyswitch_key.input_key_lwe_dimension()
|
||||
== input_lwe_ciphertext.lwe_size().to_lwe_dimension(),
|
||||
"Mismatched input LweDimension. \
|
||||
LweKeyswitchKey input LweDimension: {:?}, input LweCiphertext LweDimension {:?}.",
|
||||
lwe_keyswitch_key.input_key_lwe_dimension(),
|
||||
input_lwe_ciphertext.lwe_size().to_lwe_dimension(),
|
||||
);
|
||||
assert!(
|
||||
lwe_keyswitch_key.output_key_lwe_dimension()
|
||||
== output_lwe_ciphertext.lwe_size().to_lwe_dimension(),
|
||||
"Mismatched output LweDimension. \
|
||||
LweKeyswitchKey output LweDimension: {:?}, output LweCiphertext LweDimension {:?}.",
|
||||
lwe_keyswitch_key.output_key_lwe_dimension(),
|
||||
output_lwe_ciphertext.lwe_size().to_lwe_dimension(),
|
||||
);
|
||||
|
||||
// Clear the output ciphertext, as it will get updated gradually
|
||||
output_lwe_ciphertext.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
// Copy the input body to the output ciphertext
|
||||
*output_lwe_ciphertext.get_mut_body().0 = *input_lwe_ciphertext.get_body().0;
|
||||
|
||||
// We instantiate a decomposer
|
||||
let decomposer = SignedDecomposer::new(
|
||||
lwe_keyswitch_key.decomposition_base_log(),
|
||||
lwe_keyswitch_key.decomposition_level_count(),
|
||||
);
|
||||
|
||||
for (keyswitch_key_block, &input_mask_element) in lwe_keyswitch_key
|
||||
.iter()
|
||||
.zip(input_lwe_ciphertext.get_mask().as_ref())
|
||||
{
|
||||
let decomposition_iter = decomposer.decompose(input_mask_element);
|
||||
// loop over the number of levels in reverse (from highest to lowest)
|
||||
for (level_key_ciphertext, decomposed) in
|
||||
keyswitch_key_block.iter().rev().zip(decomposition_iter)
|
||||
{
|
||||
slice_wrapping_sub_scalar_mul_assign(
|
||||
output_lwe_ciphertext.as_mut(),
|
||||
level_key_ciphertext.as_ref(),
|
||||
decomposed.value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
159
tfhe/src/core_crypto/algorithms/lwe_keyswitch_key_generation.rs
Normal file
159
tfhe/src/core_crypto/algorithms/lwe_keyswitch_key_generation.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Fill an [`LWE keyswitch key`](`LweKeyswitchKey`) with an actual keyswitching key constructed
|
||||
/// from an input and an output key [`LWE secret key`](`LweSecretKey`).
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::{
|
||||
/// EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
/// };
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let input_lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let output_lwe_dimension = LweDimension(2048);
|
||||
/// let decomp_base_log = DecompositionBaseLog(3);
|
||||
/// let decomp_level_count = DecompositionLevelCount(5);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let input_lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(input_lwe_dimension, &mut secret_generator);
|
||||
/// let output_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
|
||||
/// output_lwe_dimension,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut ksk = LweKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// output_lwe_dimension,
|
||||
/// );
|
||||
///
|
||||
/// generate_lwe_keyswitch_key(
|
||||
/// &input_lwe_secret_key,
|
||||
/// &output_lwe_secret_key,
|
||||
/// &mut ksk,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// assert!(ksk.as_ref().iter().all(|&x| x == 0) == false);
|
||||
/// ```
|
||||
pub fn generate_lwe_keyswitch_key<Scalar, InputKeyCont, OutputKeyCont, KSKeyCont, Gen>(
|
||||
input_lwe_sk: &LweSecretKey<InputKeyCont>,
|
||||
output_lwe_sk: &LweSecretKey<OutputKeyCont>,
|
||||
lwe_keyswitch_key: &mut LweKeyswitchKey<KSKeyCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar>,
|
||||
KSKeyCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
lwe_keyswitch_key.input_key_lwe_dimension() == input_lwe_sk.lwe_dimension(),
|
||||
"The destination LweKeyswitchKey input LweDimension is not equal \
|
||||
to the input LweSecretKey LweDimension. Destination: {:?}, input: {:?}",
|
||||
lwe_keyswitch_key.input_key_lwe_dimension(),
|
||||
input_lwe_sk.lwe_dimension()
|
||||
);
|
||||
assert!(
|
||||
lwe_keyswitch_key.output_key_lwe_dimension() == output_lwe_sk.lwe_dimension(),
|
||||
"The destination LweKeyswitchKey output LweDimension is not equal \
|
||||
to the output LweSecretKey LweDimension. Destination: {:?}, output: {:?}",
|
||||
lwe_keyswitch_key.output_key_lwe_dimension(),
|
||||
input_lwe_sk.lwe_dimension()
|
||||
);
|
||||
|
||||
let decomp_base_log = lwe_keyswitch_key.decomposition_base_log();
|
||||
let decomp_level_count = lwe_keyswitch_key.decomposition_level_count();
|
||||
|
||||
// The plaintexts used to encrypt a key element will be stored in this buffer
|
||||
let mut decomposition_plaintexts_buffer =
|
||||
PlaintextListOwned::new(Scalar::ZERO, PlaintextCount(decomp_level_count.0));
|
||||
|
||||
// Iterate over the input key elements and the destination lwe_keyswitch_key memory
|
||||
for (input_key_element, mut keyswitch_key_block) in input_lwe_sk
|
||||
.as_ref()
|
||||
.iter()
|
||||
.zip(lwe_keyswitch_key.iter_mut())
|
||||
{
|
||||
// We fill the buffer with the powers of the key elmements
|
||||
for (level, message) in (1..=decomp_level_count.0)
|
||||
.map(DecompositionLevel)
|
||||
.zip(decomposition_plaintexts_buffer.iter_mut())
|
||||
{
|
||||
*message.0 = DecompositionTerm::new(level, decomp_base_log, *input_key_element)
|
||||
.to_recomposition_summand();
|
||||
}
|
||||
|
||||
encrypt_lwe_ciphertext_list(
|
||||
output_lwe_sk,
|
||||
&mut keyswitch_key_block,
|
||||
&decomposition_plaintexts_buffer,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a new [`LWE keyswitch key`](`LweKeyswitchKey`) and fill it with an actual keyswitching
|
||||
/// key constructed from an input and an output key [`LWE secret key`](`LweSecretKey`).
|
||||
///
|
||||
/// See [`keyswitch_lwe_ciphertext`] for usage.
|
||||
pub fn allocate_and_generate_new_lwe_keyswitch_key<Scalar, InputKeyCont, OutputKeyCont, Gen>(
|
||||
input_lwe_sk: &LweSecretKey<InputKeyCont>,
|
||||
output_lwe_sk: &LweSecretKey<OutputKeyCont>,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweKeyswitchKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut new_lwe_keyswitch_key = LweKeyswitchKeyOwned::new(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_sk.lwe_dimension(),
|
||||
output_lwe_sk.lwe_dimension(),
|
||||
);
|
||||
|
||||
generate_lwe_keyswitch_key(
|
||||
input_lwe_sk,
|
||||
output_lwe_sk,
|
||||
&mut new_lwe_keyswitch_key,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
|
||||
new_lwe_keyswitch_key
|
||||
}
|
||||
85
tfhe/src/core_crypto/algorithms/lwe_linear_algebra.rs
Normal file
85
tfhe/src/core_crypto/algorithms/lwe_linear_algebra.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
//! Module containing functions related to LWE ciphertext linear algebra, like addition,
|
||||
//! multiplication, etc.
|
||||
|
||||
use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
pub fn lwe_ciphertext_add_assign<Scalar, LhsCont, RhsCont>(
|
||||
lhs: &mut LweCiphertext<LhsCont>,
|
||||
rhs: &LweCiphertext<RhsCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
LhsCont: ContainerMut<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
slice_wrapping_add_assign(lhs.as_mut(), rhs.as_ref());
|
||||
}
|
||||
|
||||
pub fn lwe_ciphertext_add<Scalar, OutputCont, LhsCont, RhsCont>(
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
lhs: &LweCiphertext<LhsCont>,
|
||||
rhs: &LweCiphertext<RhsCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
LhsCont: Container<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
slice_wrapping_add(output.as_mut(), lhs.as_ref(), rhs.as_ref());
|
||||
}
|
||||
|
||||
pub fn lwe_ciphertext_plaintext_add_assign<Scalar, InCont>(
|
||||
lhs: &mut LweCiphertext<InCont>,
|
||||
rhs: Plaintext<Scalar>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
InCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
let body = lhs.get_mut_body();
|
||||
|
||||
*body.0 = (*body.0).wrapping_add(rhs.0);
|
||||
}
|
||||
|
||||
pub fn lwe_ciphertext_opposite_assign<Scalar, InCont>(ct: &mut LweCiphertext<InCont>)
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
InCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
slice_wrapping_opposite_assign(ct.as_mut());
|
||||
}
|
||||
|
||||
pub fn lwe_ciphertext_cleartext_mul_assign<Scalar, InCont>(
|
||||
lhs: &mut LweCiphertext<InCont>,
|
||||
rhs: Cleartext<Scalar>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
InCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
slice_wrapping_scalar_mul_assign(lhs.as_mut(), rhs.0);
|
||||
}
|
||||
|
||||
pub fn lwe_ciphertext_sub_assign<Scalar, LhsCont, RhsCont>(
|
||||
lhs: &mut LweCiphertext<LhsCont>,
|
||||
rhs: &LweCiphertext<RhsCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
LhsCont: ContainerMut<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
slice_wrapping_sub_assign(lhs.as_mut(), rhs.as_ref());
|
||||
}
|
||||
|
||||
pub fn lwe_ciphertext_cleartext_mul<Scalar, InputCont, OutputCont>(
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
lhs: &LweCiphertext<InputCont>,
|
||||
rhs: Cleartext<Scalar>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
output.as_mut().copy_from_slice(lhs.as_ref());
|
||||
lwe_ciphertext_cleartext_mul_assign(output, rhs);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
pub fn private_functional_keyswitch_lwe_ciphertext_into_glwe_ciphertext<
|
||||
Scalar,
|
||||
KeyCont,
|
||||
InputCont,
|
||||
OutputCont,
|
||||
>(
|
||||
lwe_pfpksk: &LwePrivateFunctionalPackingKeyswitchKey<KeyCont>,
|
||||
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
|
||||
input_lwe_ciphertext: &LweCiphertext<InputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
lwe_pfpksk.input_lwe_key_dimension().0
|
||||
== input_lwe_ciphertext.lwe_size().to_lwe_dimension().0
|
||||
);
|
||||
assert!(
|
||||
lwe_pfpksk.output_glwe_key_dimension().0
|
||||
== output_glwe_ciphertext.glwe_size().to_glwe_dimension().0
|
||||
);
|
||||
|
||||
// We reset the output
|
||||
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
// We instantiate a decomposer
|
||||
let decomposer = SignedDecomposer::new(
|
||||
lwe_pfpksk.decomposition_base_log(),
|
||||
lwe_pfpksk.decomposition_level_count(),
|
||||
);
|
||||
|
||||
for (keyswitch_key_block, &input_lwe_element) in
|
||||
lwe_pfpksk.iter().zip(input_lwe_ciphertext.as_ref().iter())
|
||||
{
|
||||
// We decompose
|
||||
let rounded = decomposer.closest_representable(input_lwe_element);
|
||||
let decomp = decomposer.decompose(rounded);
|
||||
|
||||
// Loop over the number of levels:
|
||||
// We compute the multiplication of a ciphertext from the private functional
|
||||
// keyswitching key with a piece of the decomposition and subtract it to the buffer
|
||||
for (level_key_cipher, decomposed) in keyswitch_key_block.iter().rev().zip(decomp) {
|
||||
slice_wrapping_sub_scalar_mul_assign(
|
||||
output_glwe_ciphertext.as_mut(),
|
||||
level_key_cipher.as_ref(),
|
||||
decomposed.value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn private_functional_keyswitch_lwe_ciphertext_list_and_pack_in_glwe_cipheretext<
|
||||
Scalar,
|
||||
KeyCont,
|
||||
InputCont,
|
||||
OutputCont,
|
||||
>(
|
||||
lwe_pfpksk: &LwePrivateFunctionalPackingKeyswitchKey<KeyCont>,
|
||||
output: &mut GlweCiphertext<OutputCont>,
|
||||
input: &LweCiphertextList<InputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar> + Clone,
|
||||
{
|
||||
assert!(input.lwe_ciphertext_count().0 <= output.polynomial_size().0);
|
||||
output.as_mut().fill(Scalar::ZERO);
|
||||
let mut buffer = output.clone();
|
||||
// for each ciphertext, call mono_key_switch
|
||||
for (degree, input_ciphertext) in input.iter().enumerate() {
|
||||
private_functional_keyswitch_lwe_ciphertext_into_glwe_ciphertext(
|
||||
lwe_pfpksk,
|
||||
&mut buffer,
|
||||
&input_ciphertext,
|
||||
);
|
||||
buffer
|
||||
.as_mut_polynomial_list()
|
||||
.iter_mut()
|
||||
.for_each(|mut poly| {
|
||||
polynomial_wrapping_monic_monomial_mul_assign(&mut poly, MonomialDegree(degree))
|
||||
});
|
||||
slice_wrapping_add_assign(output.as_mut(), buffer.as_ref());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub fn generate_lwe_private_functional_packing_keyswitch_key<
|
||||
Scalar,
|
||||
InputKeyCont,
|
||||
OutputKeyCont,
|
||||
KSKeyCont,
|
||||
Gen,
|
||||
ScalarFunc,
|
||||
PolyCont,
|
||||
>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
lwe_pfpksk: &mut LwePrivateFunctionalPackingKeyswitchKey<KSKeyCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
f: ScalarFunc,
|
||||
polynomial: &Polynomial<PolyCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar>,
|
||||
KSKeyCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
ScalarFunc: Fn(Scalar) -> Scalar,
|
||||
PolyCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
input_lwe_secret_key.lwe_dimension() == lwe_pfpksk.input_lwe_key_dimension(),
|
||||
"Mismatched LweDimension between input_lwe_secret_key {:?} and lwe_pfpksk input dimension \
|
||||
{:?}.",
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
lwe_pfpksk.input_lwe_key_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_secret_key.glwe_dimension() == lwe_pfpksk.output_glwe_key_dimension(),
|
||||
"Mismatched GlweDimension between output_glwe_secret_key {:?} and lwe_pfpksk output \
|
||||
dimension {:?}.",
|
||||
output_glwe_secret_key.glwe_dimension(),
|
||||
lwe_pfpksk.output_glwe_key_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_secret_key.polynomial_size() == lwe_pfpksk.output_polynomial_size(),
|
||||
"Mismatched PolynomialSize between output_glwe_secret_key {:?} and lwe_pfpksk output \
|
||||
polynomial size {:?}.",
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
lwe_pfpksk.output_polynomial_size()
|
||||
);
|
||||
|
||||
// We instantiate a buffer
|
||||
let mut messages = PlaintextListOwned::new(
|
||||
Scalar::ZERO,
|
||||
PlaintextCount(
|
||||
lwe_pfpksk.decomposition_level_count().0 * lwe_pfpksk.output_polynomial_size().0,
|
||||
),
|
||||
);
|
||||
|
||||
// We retrieve decomposition arguments
|
||||
let decomp_level_count = lwe_pfpksk.decomposition_level_count();
|
||||
let decomp_base_log = lwe_pfpksk.decomposition_base_log();
|
||||
let polynomial_size = lwe_pfpksk.output_polynomial_size();
|
||||
|
||||
let last_key_iter_bit = [Scalar::MAX];
|
||||
// add minus one for the function which will be applied to the decomposed body
|
||||
// ( Scalar::MAX = -Scalar::ONE )
|
||||
let input_key_bit_iter = input_lwe_secret_key
|
||||
.as_ref()
|
||||
.iter()
|
||||
.chain(last_key_iter_bit.iter());
|
||||
|
||||
let gen_iter = generator
|
||||
.fork_pfpksk_to_pfpksk_chunks::<Scalar>(
|
||||
decomp_level_count,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
input_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// loop over the before key blocks
|
||||
for ((&input_key_bit, mut keyswitch_key_block), mut loop_generator) in
|
||||
input_key_bit_iter.zip(lwe_pfpksk.iter_mut()).zip(gen_iter)
|
||||
{
|
||||
// We fill the buffer with the powers of the key bits
|
||||
for (level, mut message) in (1..=decomp_level_count.0)
|
||||
.map(DecompositionLevel)
|
||||
.zip(messages.chunks_exact_mut(polynomial_size.0))
|
||||
{
|
||||
slice_wrapping_add_scalar_mul_assign(
|
||||
message.as_mut(),
|
||||
polynomial.as_ref(),
|
||||
DecompositionTerm::new(
|
||||
level,
|
||||
decomp_base_log,
|
||||
f(Scalar::ONE).wrapping_mul(input_key_bit),
|
||||
)
|
||||
.to_recomposition_summand(),
|
||||
);
|
||||
}
|
||||
|
||||
// We encrypt the buffer
|
||||
encrypt_glwe_ciphertext_list(
|
||||
output_glwe_secret_key,
|
||||
&messages,
|
||||
&mut keyswitch_key_block,
|
||||
noise_parameters,
|
||||
&mut loop_generator,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel variant of [`generate_lwe_private_functional_packing_keyswitch_key`]. You may want to
|
||||
/// use this variant for bette key generation times.
|
||||
pub fn par_generate_lwe_private_functional_packing_keyswitch_key<
|
||||
Scalar,
|
||||
InputKeyCont,
|
||||
OutputKeyCont,
|
||||
KSKeyCont,
|
||||
Gen,
|
||||
ScalarFunc,
|
||||
PolyCont,
|
||||
>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
lwe_pfpksk: &mut LwePrivateFunctionalPackingKeyswitchKey<KSKeyCont>,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
f: ScalarFunc,
|
||||
polynomial: &Polynomial<PolyCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar> + Sync,
|
||||
KSKeyCont: ContainerMut<Element = Scalar> + Sync,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
ScalarFunc: Fn(Scalar) -> Scalar + Sync,
|
||||
PolyCont: Container<Element = Scalar> + Sync,
|
||||
{
|
||||
assert!(
|
||||
input_lwe_secret_key.lwe_dimension() == lwe_pfpksk.input_lwe_key_dimension(),
|
||||
"Mismatched LweDimension between input_lwe_secret_key {:?} and lwe_pfpksk input dimension \
|
||||
{:?}.",
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
lwe_pfpksk.input_lwe_key_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_secret_key.glwe_dimension() == lwe_pfpksk.output_glwe_key_dimension(),
|
||||
"Mismatched GlweDimension between output_glwe_secret_key {:?} and lwe_pfpksk output \
|
||||
dimension {:?}.",
|
||||
output_glwe_secret_key.glwe_dimension(),
|
||||
lwe_pfpksk.output_glwe_key_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_secret_key.polynomial_size() == lwe_pfpksk.output_polynomial_size(),
|
||||
"Mismatched PolynomialSize between output_glwe_secret_key {:?} and lwe_pfpksk output \
|
||||
polynomial size {:?}.",
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
lwe_pfpksk.output_polynomial_size()
|
||||
);
|
||||
|
||||
// We retrieve decomposition arguments
|
||||
let decomp_level_count = lwe_pfpksk.decomposition_level_count();
|
||||
let decomp_base_log = lwe_pfpksk.decomposition_base_log();
|
||||
let polynomial_size = lwe_pfpksk.output_polynomial_size();
|
||||
|
||||
let last_key_iter_bit = [Scalar::MAX];
|
||||
// add minus one for the function which will be applied to the decomposed body
|
||||
// ( Scalar::MAX = -Scalar::ONE )
|
||||
let input_key_bit_iter = input_lwe_secret_key
|
||||
.as_ref()
|
||||
.par_iter()
|
||||
.chain(last_key_iter_bit.par_iter());
|
||||
|
||||
let gen_iter = generator
|
||||
.par_fork_pfpksk_to_pfpksk_chunks::<Scalar>(
|
||||
decomp_level_count,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
input_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let plaintext_count = PlaintextCount(
|
||||
lwe_pfpksk.decomposition_level_count().0 * lwe_pfpksk.output_polynomial_size().0,
|
||||
);
|
||||
|
||||
// loop over the before key blocks
|
||||
input_key_bit_iter
|
||||
.zip(lwe_pfpksk.par_iter_mut())
|
||||
.zip(gen_iter)
|
||||
.for_each(
|
||||
|((&input_key_bit, mut keyswitch_key_block), mut loop_generator)| {
|
||||
// We instantiate a buffer
|
||||
let mut messages = PlaintextListOwned::new(Scalar::ZERO, plaintext_count);
|
||||
|
||||
// We fill the buffer with the powers of the key bits
|
||||
for (level, mut message) in (1..=decomp_level_count.0)
|
||||
.map(DecompositionLevel)
|
||||
.zip(messages.chunks_exact_mut(polynomial_size.0))
|
||||
{
|
||||
slice_wrapping_add_scalar_mul_assign(
|
||||
message.as_mut(),
|
||||
polynomial.as_ref(),
|
||||
DecompositionTerm::new(
|
||||
level,
|
||||
decomp_base_log,
|
||||
f(Scalar::ONE).wrapping_mul(input_key_bit),
|
||||
)
|
||||
.to_recomposition_summand(),
|
||||
);
|
||||
}
|
||||
|
||||
// We encrypt the buffer
|
||||
encrypt_glwe_ciphertext_list(
|
||||
output_glwe_secret_key,
|
||||
&messages,
|
||||
&mut keyswitch_key_block,
|
||||
noise_parameters,
|
||||
&mut loop_generator,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::fft_impl::crypto::bootstrap::{bootstrap_scratch, FourierLweBootstrapKey};
|
||||
use crate::core_crypto::fft_impl::crypto::wop_pbs::blind_rotate_assign_scratch;
|
||||
use crate::core_crypto::fft_impl::math::fft::{Fft, FftView};
|
||||
use concrete_fft::c64;
|
||||
use dyn_stack::{DynStack, SizeOverflow, StackReq};
|
||||
|
||||
pub fn blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
input: &LweCiphertext<InputCont>,
|
||||
lut: &mut GlweCiphertext<OutputCont>,
|
||||
fourier_bsk: &FourierLweBootstrapKey<KeyCont>,
|
||||
) where
|
||||
// CastInto required for PBS modulus switch which returns a usize
|
||||
Scalar: UnsignedTorus + CastInto<usize>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
KeyCont: Container<Element = c64>,
|
||||
{
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
buffers.resize(
|
||||
blind_rotate_assign_mem_optimized_scratch::<Scalar>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
|
||||
let stack = buffers.stack();
|
||||
|
||||
blind_rotate_assign_mem_optimized(input, lut, fourier_bsk, fft, stack);
|
||||
}
|
||||
|
||||
pub fn blind_rotate_assign_mem_optimized<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
input: &LweCiphertext<InputCont>,
|
||||
lut: &mut GlweCiphertext<OutputCont>,
|
||||
fourier_bsk: &FourierLweBootstrapKey<KeyCont>,
|
||||
fft: FftView<'_>,
|
||||
stack: DynStack<'_>,
|
||||
) where
|
||||
// CastInto required for PBS modulus switch which returns a usize
|
||||
Scalar: UnsignedTorus + CastInto<usize>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
KeyCont: Container<Element = c64>,
|
||||
{
|
||||
fourier_bsk
|
||||
.as_view()
|
||||
.blind_rotate_assign(lut.as_mut_view(), input.as_ref(), fft, stack);
|
||||
}
|
||||
|
||||
/// Return the required memory for [`blind_rotate_assign_mem_optimized`].
|
||||
pub fn blind_rotate_assign_mem_optimized_scratch<Scalar>(
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
fft: FftView<'_>,
|
||||
) -> Result<StackReq, SizeOverflow> {
|
||||
blind_rotate_assign_scratch::<Scalar>(glwe_size, polynomial_size, fft)
|
||||
}
|
||||
|
||||
pub fn programmable_bootstrap_lwe_ciphertext<Scalar, InputCont, OutputCont, AccCont, KeyCont>(
|
||||
input: &LweCiphertext<InputCont>,
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
accumulator: &GlweCiphertext<AccCont>,
|
||||
fourier_bsk: &FourierLweBootstrapKey<KeyCont>,
|
||||
) where
|
||||
// CastInto required for PBS modulus switch which returns a usize
|
||||
Scalar: UnsignedTorus + CastInto<usize>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
AccCont: Container<Element = Scalar>,
|
||||
KeyCont: Container<Element = c64>,
|
||||
{
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
buffers.resize(
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized_scratch::<Scalar>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
|
||||
let stack = buffers.stack();
|
||||
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
input,
|
||||
output,
|
||||
accumulator,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn programmable_bootstrap_lwe_ciphertext_mem_optimized<
|
||||
Scalar,
|
||||
InputCont,
|
||||
OutputCont,
|
||||
AccCont,
|
||||
KeyCont,
|
||||
>(
|
||||
input: &LweCiphertext<InputCont>,
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
accumulator: &GlweCiphertext<AccCont>,
|
||||
fourier_bsk: &FourierLweBootstrapKey<KeyCont>,
|
||||
fft: FftView<'_>,
|
||||
stack: DynStack<'_>,
|
||||
) where
|
||||
// CastInto required for PBS modulus switch which returns a usize
|
||||
Scalar: UnsignedTorus + CastInto<usize>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
AccCont: Container<Element = Scalar>,
|
||||
KeyCont: Container<Element = c64>,
|
||||
{
|
||||
fourier_bsk.as_view().bootstrap(
|
||||
output.as_mut(),
|
||||
input.as_ref(),
|
||||
accumulator.as_view(),
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the required memory for [`programmable_bootstrap_lwe_ciphertext_mem_optimized`].
|
||||
pub fn programmable_bootstrap_lwe_ciphertext_mem_optimized_scratch<Scalar>(
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
fft: FftView<'_>,
|
||||
) -> Result<StackReq, SizeOverflow> {
|
||||
bootstrap_scratch::<Scalar>(glwe_size, polynomial_size, fft)
|
||||
}
|
||||
102
tfhe/src/core_crypto/algorithms/lwe_public_key_generation.rs
Normal file
102
tfhe/src/core_crypto/algorithms/lwe_public_key_generation.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
pub fn generate_lwe_public_key<Scalar, InputKeyCont, OutputKeyCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output: &mut LwePublicKey<OutputKeyCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
lwe_secret_key.lwe_dimension() == output.lwe_size().to_lwe_dimension(),
|
||||
"Mismatched LweDimension between input LweSecretKey {:?} and output LwePublicKey {:?}",
|
||||
lwe_secret_key.lwe_dimension(),
|
||||
output.lwe_size().to_lwe_dimension(),
|
||||
);
|
||||
|
||||
let zeros = PlaintextListOwned::new(
|
||||
Scalar::ZERO,
|
||||
PlaintextCount(output.zero_encryption_count().0),
|
||||
);
|
||||
|
||||
encrypt_lwe_ciphertext_list(lwe_secret_key, output, &zeros, noise_parameters, generator)
|
||||
}
|
||||
|
||||
pub fn allocate_and_generate_new_lwe_public_key<Scalar, InputKeyCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
zero_encryption_count: LwePublicKeyZeroEncryptionCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LwePublicKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut pk = LwePublicKeyOwned::new(
|
||||
Scalar::ZERO,
|
||||
lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
zero_encryption_count,
|
||||
);
|
||||
|
||||
generate_lwe_public_key(lwe_secret_key, &mut pk, noise_parameters, generator);
|
||||
|
||||
pk
|
||||
}
|
||||
|
||||
pub fn par_generate_lwe_public_key<Scalar, InputKeyCont, OutputKeyCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output: &mut LwePublicKey<OutputKeyCont>,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
InputKeyCont: Container<Element = Scalar> + Sync,
|
||||
OutputKeyCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
lwe_secret_key.lwe_dimension() == output.lwe_size().to_lwe_dimension(),
|
||||
"Mismatch LweDimension between lwe_secret_key {:?} and public key {:?}",
|
||||
lwe_secret_key.lwe_dimension(),
|
||||
output.lwe_size().to_lwe_dimension()
|
||||
);
|
||||
|
||||
let zeros = PlaintextListOwned::new(
|
||||
Scalar::ZERO,
|
||||
PlaintextCount(output.zero_encryption_count().0),
|
||||
);
|
||||
|
||||
par_encrypt_lwe_ciphertext_list(lwe_secret_key, output, &zeros, noise_parameters, generator)
|
||||
}
|
||||
|
||||
pub fn par_allocate_and_generate_new_lwe_public_key<Scalar, InputKeyCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
zero_encryption_count: LwePublicKeyZeroEncryptionCount,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LwePublicKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
InputKeyCont: Container<Element = Scalar> + Sync,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
let mut pk = LwePublicKeyOwned::new(
|
||||
Scalar::ZERO,
|
||||
lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
zero_encryption_count,
|
||||
);
|
||||
|
||||
generate_lwe_public_key(lwe_secret_key, &mut pk, noise_parameters, generator);
|
||||
|
||||
pk
|
||||
}
|
||||
67
tfhe/src/core_crypto/algorithms/lwe_secret_key_generation.rs
Normal file
67
tfhe/src/core_crypto/algorithms/lwe_secret_key_generation.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use crate::core_crypto::commons::generators::SecretRandomGenerator;
|
||||
use crate::core_crypto::commons::math::random::{RandomGenerable, UniformBinary};
|
||||
use crate::core_crypto::commons::numeric::Numeric;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Allocate a new [`LWE secret key`](`LweSecretKey`) and fill it with uniformly random binary
|
||||
/// coefficients.
|
||||
///
|
||||
/// See [`encrypt_lwe_ciphertext`](`super::lwe_encryption::encrypt_lwe_ciphertext`) for usage.
|
||||
pub fn allocate_and_generate_new_binary_lwe_secret_key<Scalar, Gen>(
|
||||
lwe_dimension: LweDimension,
|
||||
generator: &mut SecretRandomGenerator<Gen>,
|
||||
) -> LweSecretKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: RandomGenerable<UniformBinary> + Numeric,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut lwe_secret_key = LweSecretKeyOwned::new(Scalar::ZERO, lwe_dimension);
|
||||
|
||||
generate_binary_lwe_secret_key(&mut lwe_secret_key, generator);
|
||||
|
||||
lwe_secret_key
|
||||
}
|
||||
|
||||
/// Fill an [`LWE secret key`](`LweSecretKey`) with uniformly random binary coefficients.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::commons::generators::SecretRandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// use tfhe::seeders::new_seeder;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
//
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// let mut lwe_secret_key =
|
||||
/// LweSecretKey::new(0u64, lwe_dimension);
|
||||
///
|
||||
/// generate_binary_lwe_secret_key(&mut lwe_secret_key, &mut secret_generator);
|
||||
///
|
||||
/// // Check all coefficients are not zero as we just generated a new key
|
||||
/// // Note probability of this assert failing is (1/2)^lwe_dimension or ~4.3 * 10^-224 for an LWE
|
||||
/// // dimension of 742.
|
||||
/// assert!(lwe_secret_key.as_ref().iter().all(|&elt| elt == 0) == false);
|
||||
/// ```
|
||||
pub fn generate_binary_lwe_secret_key<Scalar, InCont, Gen>(
|
||||
lwe_secret_key: &mut LweSecretKey<InCont>,
|
||||
generator: &mut SecretRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: RandomGenerable<UniformBinary>,
|
||||
InCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
generator.fill_slice_with_random_uniform_binary(lwe_secret_key.as_mut())
|
||||
}
|
||||
356
tfhe/src/core_crypto/algorithms/lwe_wopbs.rs
Normal file
356
tfhe/src/core_crypto/algorithms/lwe_wopbs.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::fft_impl::crypto::bootstrap::FourierLweBootstrapKey;
|
||||
use crate::core_crypto::fft_impl::crypto::wop_pbs::{
|
||||
circuit_bootstrap_boolean_vertical_packing, circuit_bootstrap_boolean_vertical_packing_scratch,
|
||||
extract_bits, extract_bits_scratch,
|
||||
};
|
||||
use crate::core_crypto::fft_impl::math::fft::FftView;
|
||||
use concrete_fft::c64;
|
||||
use dyn_stack::{DynStack, SizeOverflow, StackReq};
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub fn allocate_and_generate_new_circuit_bootstrap_lwe_pfpksk_list<
|
||||
Scalar,
|
||||
LweKeyCont,
|
||||
GlweKeyCont,
|
||||
Gen,
|
||||
>(
|
||||
input_lwe_secret_key: &LweSecretKey<LweKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<GlweKeyCont>,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LwePrivateFunctionalPackingKeyswitchKeyListOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
LweKeyCont: Container<Element = Scalar>,
|
||||
GlweKeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut cbs_pfpksk_list = LwePrivateFunctionalPackingKeyswitchKeyListOwned::new(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
FunctionalPackingKeyswitchKeyCount(
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size().0,
|
||||
),
|
||||
);
|
||||
|
||||
generate_circuit_bootstrap_lwe_pfpksk_list(
|
||||
&mut cbs_pfpksk_list,
|
||||
input_lwe_secret_key,
|
||||
output_glwe_secret_key,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
|
||||
cbs_pfpksk_list
|
||||
}
|
||||
|
||||
pub fn generate_circuit_bootstrap_lwe_pfpksk_list<
|
||||
Scalar,
|
||||
OutputCont,
|
||||
LweKeyCont,
|
||||
GlweKeyCont,
|
||||
Gen,
|
||||
>(
|
||||
output_cbs_pfpksk_list: &mut LwePrivateFunctionalPackingKeyswitchKeyList<OutputCont>,
|
||||
input_lwe_secret_key: &LweSecretKey<LweKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<GlweKeyCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
LweKeyCont: Container<Element = Scalar>,
|
||||
GlweKeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output_cbs_pfpksk_list.lwe_pfpksk_count().0
|
||||
== output_glwe_secret_key.glwe_dimension().to_glwe_size().0,
|
||||
"Current list has {} pfpksk, need to have {} \
|
||||
(output_glwe_key.glwe_dimension().to_glwe_size())",
|
||||
output_cbs_pfpksk_list.lwe_pfpksk_count().0,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size().0
|
||||
);
|
||||
|
||||
let decomp_level_count = output_cbs_pfpksk_list.decomposition_level_count();
|
||||
|
||||
let gen_iter = generator
|
||||
.fork_cbs_pfpksk_to_pfpksk::<Scalar>(
|
||||
decomp_level_count,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
input_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
output_cbs_pfpksk_list.lwe_pfpksk_count(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut last_polynomial_as_list = PolynomialListOwned::new(
|
||||
Scalar::ZERO,
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
PolynomialCount(1),
|
||||
);
|
||||
// We apply the x -> -x function so instead of putting one in the first coeff of the
|
||||
// polynomial, we put Scalar::MAX == - Sclar::One so that we can use a single function in
|
||||
// the loop avoiding branching
|
||||
last_polynomial_as_list.get_mut(0)[0] = Scalar::MAX;
|
||||
|
||||
for ((mut lwe_pfpksk, polynomial_to_encrypt), mut loop_generator) in output_cbs_pfpksk_list
|
||||
.iter_mut()
|
||||
.zip(
|
||||
output_glwe_secret_key
|
||||
.as_polynomial_list()
|
||||
.iter()
|
||||
.chain(last_polynomial_as_list.iter()),
|
||||
)
|
||||
.zip(gen_iter)
|
||||
{
|
||||
generate_lwe_private_functional_packing_keyswitch_key(
|
||||
input_lwe_secret_key,
|
||||
output_glwe_secret_key,
|
||||
&mut lwe_pfpksk,
|
||||
noise_parameters,
|
||||
&mut loop_generator,
|
||||
|x| Scalar::ZERO.wrapping_sub(x),
|
||||
&polynomial_to_encrypt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn par_allocate_and_generate_new_circuit_bootstrap_lwe_pfpksk_list<
|
||||
Scalar,
|
||||
LweKeyCont,
|
||||
GlweKeyCont,
|
||||
Gen,
|
||||
>(
|
||||
input_lwe_secret_key: &LweSecretKey<LweKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<GlweKeyCont>,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LwePrivateFunctionalPackingKeyswitchKeyListOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
LweKeyCont: Container<Element = Scalar> + Sync,
|
||||
GlweKeyCont: Container<Element = Scalar> + Sync,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
let mut cbs_pfpksk_list = LwePrivateFunctionalPackingKeyswitchKeyListOwned::new(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
FunctionalPackingKeyswitchKeyCount(
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size().0,
|
||||
),
|
||||
);
|
||||
|
||||
par_generate_circuit_bootstrap_lwe_pfpksk_list(
|
||||
&mut cbs_pfpksk_list,
|
||||
input_lwe_secret_key,
|
||||
output_glwe_secret_key,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
|
||||
cbs_pfpksk_list
|
||||
}
|
||||
|
||||
pub fn par_generate_circuit_bootstrap_lwe_pfpksk_list<
|
||||
Scalar,
|
||||
OutputCont,
|
||||
LweKeyCont,
|
||||
GlweKeyCont,
|
||||
Gen,
|
||||
>(
|
||||
output_cbs_pfpksk_list: &mut LwePrivateFunctionalPackingKeyswitchKeyList<OutputCont>,
|
||||
input_lwe_secret_key: &LweSecretKey<LweKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<GlweKeyCont>,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
LweKeyCont: Container<Element = Scalar> + Sync,
|
||||
GlweKeyCont: Container<Element = Scalar> + Sync,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
output_cbs_pfpksk_list.lwe_pfpksk_count().0
|
||||
== output_glwe_secret_key.glwe_dimension().to_glwe_size().0,
|
||||
"Current list has {} pfpksk, need to have {} \
|
||||
(output_glwe_key.glwe_dimension().to_glwe_size())",
|
||||
output_cbs_pfpksk_list.lwe_pfpksk_count().0,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size().0
|
||||
);
|
||||
|
||||
let decomp_level_count = output_cbs_pfpksk_list.decomposition_level_count();
|
||||
|
||||
let gen_iter = generator
|
||||
.par_fork_cbs_pfpksk_to_pfpksk::<Scalar>(
|
||||
decomp_level_count,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
input_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
output_cbs_pfpksk_list.lwe_pfpksk_count(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut last_polynomial_as_list = PolynomialListOwned::new(
|
||||
Scalar::ZERO,
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
PolynomialCount(1),
|
||||
);
|
||||
// We apply the x -> -x function so instead of putting one in the first coeff of the
|
||||
// polynomial, we put Scalar::MAX == - Sclar::One so that we can use a single function in
|
||||
// the loop avoiding branching
|
||||
last_polynomial_as_list.get_mut(0)[0] = Scalar::MAX;
|
||||
|
||||
output_cbs_pfpksk_list
|
||||
.par_iter_mut()
|
||||
.zip(
|
||||
output_glwe_secret_key
|
||||
.as_polynomial_list()
|
||||
.par_iter()
|
||||
.chain(last_polynomial_as_list.par_iter()),
|
||||
)
|
||||
.zip(gen_iter)
|
||||
.for_each(
|
||||
|((mut lwe_pfpksk, polynomial_to_encrypt), mut loop_generator)| {
|
||||
par_generate_lwe_private_functional_packing_keyswitch_key(
|
||||
input_lwe_secret_key,
|
||||
output_glwe_secret_key,
|
||||
&mut lwe_pfpksk,
|
||||
noise_parameters,
|
||||
&mut loop_generator,
|
||||
|x| Scalar::ZERO.wrapping_sub(x),
|
||||
&polynomial_to_encrypt,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn extract_bits_from_lwe_ciphertext<Scalar, InputCont, OutputCont, BskCont, KSKCont>(
|
||||
lwe_in: &LweCiphertext<InputCont>,
|
||||
lwe_list_out: &mut LweCiphertextList<OutputCont>,
|
||||
fourier_bsk: &FourierLweBootstrapKey<BskCont>,
|
||||
ksk: &LweKeyswitchKey<KSKCont>,
|
||||
delta_log: DeltaLog,
|
||||
number_of_bits_to_extract: ExtractedBitsCount,
|
||||
fft: FftView<'_>,
|
||||
stack: DynStack<'_>,
|
||||
) where
|
||||
// CastInto required for PBS modulus switch which returns a usize
|
||||
Scalar: UnsignedTorus + CastInto<usize>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
BskCont: Container<Element = c64>,
|
||||
KSKCont: Container<Element = Scalar>,
|
||||
{
|
||||
extract_bits(
|
||||
lwe_list_out.as_mut_view(),
|
||||
lwe_in.as_view(),
|
||||
ksk.as_view(),
|
||||
fourier_bsk.as_view(),
|
||||
delta_log,
|
||||
number_of_bits_to_extract,
|
||||
fft,
|
||||
stack,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_bits_from_lwe_ciphertext_scratch<Scalar>(
|
||||
lwe_dimension: LweDimension,
|
||||
ksk_output_key_lwe_dimension: LweDimension,
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
fft: FftView<'_>,
|
||||
) -> Result<StackReq, SizeOverflow> {
|
||||
extract_bits_scratch::<Scalar>(
|
||||
lwe_dimension,
|
||||
ksk_output_key_lwe_dimension,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
fft,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn circuit_bootstrap_boolean_vertical_packing_lwe_ciphertext_list<
|
||||
Scalar,
|
||||
InputCont,
|
||||
OutputCont,
|
||||
LutCont,
|
||||
BskCont,
|
||||
PFPKSKCont,
|
||||
>(
|
||||
lwe_list_in: &LweCiphertextList<InputCont>,
|
||||
lwe_list_out: &mut LweCiphertextList<OutputCont>,
|
||||
big_lut_as_polynomial_list: &PolynomialList<LutCont>,
|
||||
fourier_bsk: &FourierLweBootstrapKey<BskCont>,
|
||||
pfpksk_list: &LwePrivateFunctionalPackingKeyswitchKeyList<PFPKSKCont>,
|
||||
level_cbs: DecompositionLevelCount,
|
||||
base_log_cbs: DecompositionBaseLog,
|
||||
fft: FftView<'_>,
|
||||
stack: DynStack<'_>,
|
||||
) where
|
||||
// CastInto required for PBS modulus switch which returns a usize
|
||||
Scalar: UnsignedTorus + CastInto<usize>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
LutCont: Container<Element = Scalar>,
|
||||
BskCont: Container<Element = c64>,
|
||||
PFPKSKCont: Container<Element = Scalar>,
|
||||
{
|
||||
circuit_bootstrap_boolean_vertical_packing(
|
||||
big_lut_as_polynomial_list.as_view(),
|
||||
fourier_bsk.as_view(),
|
||||
lwe_list_out.as_mut_view(),
|
||||
lwe_list_in.as_view(),
|
||||
pfpksk_list.as_view(),
|
||||
level_cbs,
|
||||
base_log_cbs,
|
||||
fft,
|
||||
stack,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn circuit_bootstrap_boolean_vertical_packing_lwe_ciphertext_list_scracth<Scalar>(
|
||||
lwe_list_in_count: LweCiphertextCount,
|
||||
lwe_list_out_count: LweCiphertextCount,
|
||||
lwe_in_size: LweSize,
|
||||
big_lut_polynomial_count: PolynomialCount,
|
||||
bsk_output_lwe_size: LweSize,
|
||||
glwe_size: GlweSize,
|
||||
fpksk_output_polynomial_size: PolynomialSize,
|
||||
level_cbs: DecompositionLevelCount,
|
||||
fft: FftView<'_>,
|
||||
) -> Result<StackReq, SizeOverflow> {
|
||||
circuit_bootstrap_boolean_vertical_packing_scratch::<Scalar>(
|
||||
lwe_list_in_count,
|
||||
lwe_list_out_count,
|
||||
lwe_in_size,
|
||||
big_lut_polynomial_count,
|
||||
bsk_output_lwe_size,
|
||||
glwe_size,
|
||||
fpksk_output_polynomial_size,
|
||||
level_cbs,
|
||||
fft,
|
||||
)
|
||||
}
|
||||
9
tfhe/src/core_crypto/algorithms/misc.rs
Normal file
9
tfhe/src/core_crypto/algorithms/misc.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use crate::core_crypto::prelude::*;
|
||||
|
||||
/// Convenience function using a bit trick to determine whether a scalar is a power of 2.
|
||||
pub fn is_power_of_two<Scalar>(scalar: Scalar) -> bool
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
(scalar != Scalar::ZERO) && ((scalar & (scalar - Scalar::ONE)) == Scalar::ZERO)
|
||||
}
|
||||
39
tfhe/src/core_crypto/algorithms/mod.rs
Normal file
39
tfhe/src/core_crypto/algorithms/mod.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
pub mod ggsw_encryption;
|
||||
pub mod glwe_encryption;
|
||||
pub mod glwe_sample_extraction;
|
||||
pub mod glwe_secret_key_generation;
|
||||
pub mod lwe_bootstrap_key_conversion;
|
||||
pub mod lwe_bootstrap_key_generation;
|
||||
pub mod lwe_encryption;
|
||||
pub mod lwe_keyswitch;
|
||||
pub mod lwe_keyswitch_key_generation;
|
||||
pub mod lwe_linear_algebra;
|
||||
pub mod lwe_private_functional_packing_keyswitch;
|
||||
pub mod lwe_private_functional_packing_keyswitch_key_generation;
|
||||
pub mod lwe_programmable_bootstrapping;
|
||||
pub mod lwe_public_key_generation;
|
||||
pub mod lwe_secret_key_generation;
|
||||
pub mod lwe_wopbs;
|
||||
pub mod misc;
|
||||
pub mod polynomial_algorithms;
|
||||
pub mod slice_algorithms;
|
||||
|
||||
// No pub use for slice and polynomial algorithms which would not interest higher level users
|
||||
// They can still be used via `use crate::core_crypto::algorithms::slice_algorithms::*;`
|
||||
pub use ggsw_encryption::*;
|
||||
pub use glwe_encryption::*;
|
||||
pub use glwe_sample_extraction::*;
|
||||
pub use glwe_secret_key_generation::*;
|
||||
pub use lwe_bootstrap_key_conversion::*;
|
||||
pub use lwe_bootstrap_key_generation::*;
|
||||
pub use lwe_encryption::*;
|
||||
pub use lwe_keyswitch::*;
|
||||
pub use lwe_keyswitch_key_generation::*;
|
||||
pub use lwe_linear_algebra::*;
|
||||
pub use lwe_private_functional_packing_keyswitch::*;
|
||||
pub use lwe_private_functional_packing_keyswitch_key_generation::*;
|
||||
pub use lwe_programmable_bootstrapping::*;
|
||||
pub use lwe_public_key_generation::*;
|
||||
pub use lwe_secret_key_generation::*;
|
||||
pub use lwe_wopbs::*;
|
||||
pub use misc::*;
|
||||
600
tfhe/src/core_crypto/algorithms/polynomial_algorithms.rs
Normal file
600
tfhe/src/core_crypto/algorithms/polynomial_algorithms.rs
Normal file
@@ -0,0 +1,600 @@
|
||||
//! Module providing algorithms to perform computations on polynomials modulo $X^{N} + 1$.
|
||||
|
||||
use crate::core_crypto::algorithms::misc::*;
|
||||
use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
use crate::core_crypto::commons::parameters::MonomialDegree;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Add a polynomial to the output polynomial.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let mut first = Polynomial::from_container(vec![1u8, 2, 3, 4, 5, 6]);
|
||||
/// let second = Polynomial::from_container(vec![255u8, 255, 255, 1, 2, 3]);
|
||||
/// polynomial_wrapping_add_assign(&mut first, &second);
|
||||
/// assert_eq!(first.as_ref(), &[0u8, 1, 2, 5, 7, 9]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_add_assign<Scalar, OutputCont, InputCont>(
|
||||
lhs: &mut Polynomial<OutputCont>,
|
||||
rhs: &Polynomial<InputCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(lhs.polynomial_size(), rhs.polynomial_size());
|
||||
slice_wrapping_add_assign(lhs.as_mut(), rhs.as_ref())
|
||||
}
|
||||
|
||||
/// Add the sum of the element-wise product between two lists of polynomials to the output
|
||||
/// polynomial.
|
||||
///
|
||||
/// I.e., if the output polynomial is $C(X)$, for a collection of polynomials $(P\_i(X)))\_i$
|
||||
/// and another collection of polynomials $(B\_i(X))\_i$ we perform the operation:
|
||||
/// $$
|
||||
/// C(X) := C(X) + \sum\_i P\_i(X) \times B\_i(X) mod (X^{N} + 1)
|
||||
/// $$
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::commons::parameters::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let poly_list = PolynomialList::from_container(vec![100_u8, 20, 3, 4, 5, 6], PolynomialSize(3));
|
||||
/// let bin_poly_list = PolynomialList::from_container(vec![0, 1, 1, 1, 0, 0], PolynomialSize(3));
|
||||
/// let mut output = Polynomial::new(250, PolynomialSize(3));
|
||||
/// polynomial_wrapping_add_multisum_assign(&mut output, &poly_list, &bin_poly_list);
|
||||
/// assert_eq!(output.as_ref(), &[231, 96, 120]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_add_multisum_assign<Scalar, OutputCont, InputCont1, InputCont2>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
poly_list_1: &PolynomialList<InputCont1>,
|
||||
poly_list_2: &PolynomialList<InputCont2>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont1: Container<Element = Scalar>,
|
||||
InputCont2: Container<Element = Scalar>,
|
||||
{
|
||||
for (poly_1, poly_2) in poly_list_1.iter().zip(poly_list_2.iter()) {
|
||||
polynomial_wrapping_add_mul_assign(output, &poly_1, &poly_2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the result of the product between two polynomials, reduced modulo $(X^{N}+1)$, to the
|
||||
/// output polynomial.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let poly_1 = Polynomial::from_container(vec![1_u8, 2, 3]);
|
||||
/// let poly_2 = Polynomial::from_container(vec![0, 1, 1]);
|
||||
/// let mut res = Polynomial::from_container(vec![1, 0, 253]);
|
||||
/// polynomial_wrapping_add_mul_assign(&mut res, &poly_1, &poly_2);
|
||||
/// assert_eq!(res.as_ref(), &[252, 254, 0]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_add_mul_assign<Scalar, OutputCont, InputCont1, InputCont2>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
lhs: &Polynomial<InputCont1>,
|
||||
rhs: &Polynomial<InputCont2>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont1: Container<Element = Scalar>,
|
||||
InputCont2: Container<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
output.polynomial_size() == lhs.polynomial_size(),
|
||||
"Output polynomial size {:?} is not the same as input lhs polynomial {:?}.",
|
||||
output.polynomial_size(),
|
||||
lhs.polynomial_size(),
|
||||
);
|
||||
assert!(
|
||||
output.polynomial_size() == rhs.polynomial_size(),
|
||||
"Output polynomial size {:?} is not the same as input rhs polynomial {:?}.",
|
||||
output.polynomial_size(),
|
||||
rhs.polynomial_size(),
|
||||
);
|
||||
let degree = output.degree();
|
||||
let polynomial_size = output.polynomial_size();
|
||||
|
||||
for (lhs_degree, &lhs_coeff) in lhs.iter().enumerate() {
|
||||
for (rhs_degree, &rhs_coeff) in rhs.iter().enumerate() {
|
||||
let target_degree = lhs_degree + rhs_degree;
|
||||
if target_degree <= degree {
|
||||
let output_coefficient = &mut output.as_mut()[target_degree];
|
||||
|
||||
*output_coefficient =
|
||||
(*output_coefficient).wrapping_add(lhs_coeff.wrapping_mul(rhs_coeff));
|
||||
} else {
|
||||
let target_degree = target_degree % polynomial_size.0;
|
||||
let output_coefficient = &mut output.as_mut()[target_degree];
|
||||
|
||||
*output_coefficient =
|
||||
(*output_coefficient).wrapping_sub(lhs_coeff.wrapping_mul(rhs_coeff));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Divides (mod $(X^{N}+1)$), the output polynomial with a monic monomial of a given degree i.e.
|
||||
/// $X^{degree}$.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::commons::parameters::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let mut poly = Polynomial::from_container(vec![1u8, 2, 3]);
|
||||
/// polynomial_wrapping_monic_monomial_div_assign(&mut poly, MonomialDegree(2));
|
||||
/// assert_eq!(poly.as_ref(), &[3, 255, 254]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_monic_monomial_div_assign<Scalar, OutputCont>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
monomial_degree: MonomialDegree,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
let full_cycles_count = monomial_degree.0 / output.as_ref().container_len();
|
||||
if full_cycles_count % 2 != 0 {
|
||||
output
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.for_each(|a| *a = a.wrapping_neg());
|
||||
}
|
||||
let remaining_degree = monomial_degree.0 % output.as_ref().container_len();
|
||||
output.as_mut().rotate_left(remaining_degree);
|
||||
output
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.take(remaining_degree)
|
||||
.for_each(|a| *a = a.wrapping_neg());
|
||||
}
|
||||
|
||||
/// Multiply (mod $(X^{N}+1)$), the output polynomial with a monic monomial of a given degree i.e.
|
||||
/// $X^{degree}$.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::commons::parameters::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let mut poly = Polynomial::from_container(vec![1u8, 2, 3]);
|
||||
/// polynomial_wrapping_monic_monomial_mul_assign(&mut poly, MonomialDegree(2));
|
||||
/// assert_eq!(poly.as_ref(), &[254, 253, 1]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_monic_monomial_mul_assign<Scalar, OutputCont>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
monomial_degree: MonomialDegree,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
let full_cycles_count = monomial_degree.0 / output.as_ref().container_len();
|
||||
if full_cycles_count % 2 != 0 {
|
||||
output
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.for_each(|a| *a = a.wrapping_neg());
|
||||
}
|
||||
let remaining_degree = monomial_degree.0 % output.as_ref().container_len();
|
||||
output.as_mut().rotate_right(remaining_degree);
|
||||
output
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.take(remaining_degree)
|
||||
.for_each(|a| *a = a.wrapping_neg());
|
||||
}
|
||||
|
||||
/// Subtract the sum of the element-wise product between two lists of polynomials, to the output
|
||||
/// polynomial.
|
||||
///
|
||||
/// I.e., if the output polynomial is $C(X)$, for two lists of polynomials $(P\_i(X)))\_i$ and
|
||||
/// $(B\_i(X))\_i$ we perform the operation:
|
||||
/// $$
|
||||
/// C(X) := C(X) + \sum\_i P\_i(X) \times B\_i(X) mod (X^{N} + 1)
|
||||
/// $$
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::commons::parameters::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let poly_list =
|
||||
/// PolynomialList::from_container(vec![100 as u8, 20, 3, 4, 5, 6], PolynomialSize(3));
|
||||
/// let bin_poly_list = PolynomialList::from_container(vec![0, 1, 1, 1, 0, 0], PolynomialSize(3));
|
||||
/// let mut output = Polynomial::new(250 as u8, PolynomialSize(3));
|
||||
/// polynomial_wrapping_sub_multisum_assign(&mut output, &poly_list, &bin_poly_list);
|
||||
/// assert_eq!(output.as_ref(), &[13, 148, 124]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_sub_multisum_assign<Scalar, OutputCont, InputCont1, InputCont2>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
poly_list_1: &PolynomialList<InputCont1>,
|
||||
poly_list_2: &PolynomialList<InputCont2>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont1: Container<Element = Scalar>,
|
||||
InputCont2: Container<Element = Scalar>,
|
||||
{
|
||||
for (poly_1, poly_2) in poly_list_1.iter().zip(poly_list_2.iter()) {
|
||||
polynomial_wrapping_sub_mul_assign(output, &poly_1, &poly_2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtract the result of the product between two polynomials, reduced modulo $(X^{N}+1)$, to the
|
||||
/// output polynomial.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let poly_1 = Polynomial::from_container(vec![1_u8, 2, 3]);
|
||||
/// let poly_2 = Polynomial::from_container(vec![0, 1, 1]);
|
||||
/// let mut res = Polynomial::from_container(vec![255, 255, 1]);
|
||||
/// polynomial_wrapping_sub_mul_assign(&mut res, &poly_1, &poly_2);
|
||||
/// assert_eq!(res.as_ref(), &[4, 1, 254]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_sub_mul_assign<Scalar, OutputCont, InputCont1, InputCont2>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
lhs: &Polynomial<InputCont1>,
|
||||
rhs: &Polynomial<InputCont2>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont1: Container<Element = Scalar>,
|
||||
InputCont2: Container<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
output.polynomial_size() == lhs.polynomial_size(),
|
||||
"Output polynomial size {:?} is not the same as input lhs polynomial {:?}.",
|
||||
output.polynomial_size(),
|
||||
lhs.polynomial_size(),
|
||||
);
|
||||
assert!(
|
||||
output.polynomial_size() == rhs.polynomial_size(),
|
||||
"Output polynomial size {:?} is not the same as input rhs polynomial {:?}.",
|
||||
output.polynomial_size(),
|
||||
rhs.polynomial_size(),
|
||||
);
|
||||
let degree = output.degree();
|
||||
let polynomial_size = output.polynomial_size();
|
||||
|
||||
for (lhs_degree, &lhs_coeff) in lhs.iter().enumerate() {
|
||||
for (rhs_degree, &rhs_coeff) in rhs.iter().enumerate() {
|
||||
let target_degree = lhs_degree + rhs_degree;
|
||||
if target_degree <= degree {
|
||||
let output_coefficient = &mut output.as_mut()[target_degree];
|
||||
|
||||
*output_coefficient =
|
||||
(*output_coefficient).wrapping_sub(lhs_coeff.wrapping_mul(rhs_coeff));
|
||||
} else {
|
||||
let target_degree = target_degree % polynomial_size.0;
|
||||
let output_coefficient = &mut output.as_mut()[target_degree];
|
||||
|
||||
*output_coefficient =
|
||||
(*output_coefficient).wrapping_add(lhs_coeff.wrapping_mul(rhs_coeff));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fill the ouptut polynomial, with the result of the product of two polynomials, reduced modulo
|
||||
/// $(X^{N} + 1)$ with the schoolbook algorithm Complexity: $O(N^{2})$
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::commons::parameters::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let lhs = Polynomial::from_container(vec![4_u8, 5, 0]);
|
||||
/// let rhs = Polynomial::from_container(vec![7_u8, 9, 0]);
|
||||
/// let mut output = Polynomial::new(2u8, PolynomialSize(3));
|
||||
/// polynomial_wrapping_mul(&mut output, &lhs, &rhs);
|
||||
/// assert_eq!(output.as_ref(), &[28, 71, 45]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_mul<Scalar, OutputCont, LhsCont, RhsCont>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
lhs: &Polynomial<LhsCont>,
|
||||
rhs: &Polynomial<RhsCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
LhsCont: Container<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
output.as_mut().fill(Scalar::ZERO);
|
||||
polynomial_wrapping_add_mul_assign(output, lhs, rhs);
|
||||
}
|
||||
|
||||
/// Fill the output polynomial, with the result of the product of two polynomials, reduced modulo
|
||||
/// $(X^{N} + 1)$ with the Karatsuba algorithm Complexity: $O(N^{1.58})$
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::commons::parameters::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let lhs = Polynomial::from_container(vec![1_u32; 128]);
|
||||
/// let rhs = Polynomial::from_container(vec![2_u32; 128]);
|
||||
/// let mut res_kara = Polynomial::new(0u32, PolynomialSize(128));
|
||||
/// let mut res_mul = Polynomial::new(0u32, PolynomialSize(128));
|
||||
/// polynomial_karatsuba_wrapping_mul(&mut res_kara, &lhs, &rhs);
|
||||
/// polynomial_wrapping_mul(&mut res_mul, &lhs, &rhs);
|
||||
/// assert_eq!(res_kara, res_mul);
|
||||
/// ```
|
||||
pub fn polynomial_karatsuba_wrapping_mul<Scalar, OutputCont, LhsCont, RhsCont>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
p: &Polynomial<LhsCont>,
|
||||
q: &Polynomial<RhsCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
LhsCont: Container<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
// check same dimensions
|
||||
assert!(
|
||||
output.polynomial_size() == p.polynomial_size(),
|
||||
"Output polynomial size {:?} is not the same as input lhs polynomial {:?}.",
|
||||
output.polynomial_size(),
|
||||
p.polynomial_size(),
|
||||
);
|
||||
assert!(
|
||||
output.polynomial_size() == q.polynomial_size(),
|
||||
"Output polynomial size {:?} is not the same as input rhs polynomial {:?}.",
|
||||
output.polynomial_size(),
|
||||
q.polynomial_size(),
|
||||
);
|
||||
|
||||
let poly_size = output.polynomial_size().0;
|
||||
|
||||
// check dimensions are a power of 2
|
||||
assert!(is_power_of_two::<u32>(poly_size.try_into().unwrap()));
|
||||
|
||||
// allocate slices for the rec
|
||||
let mut a0 = vec![Scalar::ZERO; poly_size];
|
||||
let mut a1 = vec![Scalar::ZERO; poly_size];
|
||||
let mut a2 = vec![Scalar::ZERO; poly_size];
|
||||
let mut input_a2_p = vec![Scalar::ZERO; poly_size / 2];
|
||||
let mut input_a2_q = vec![Scalar::ZERO; poly_size / 2];
|
||||
|
||||
// prepare for splitting
|
||||
let bottom = 0..(poly_size / 2);
|
||||
let top = (poly_size / 2)..poly_size;
|
||||
|
||||
// induction
|
||||
induction_karatsuba(&mut a0, &p[bottom.clone()], &q[bottom.clone()]);
|
||||
induction_karatsuba(&mut a1, &p[top.clone()], &q[top.clone()]);
|
||||
slice_wrapping_add(&mut input_a2_p, &p[bottom.clone()], &p[top.clone()]);
|
||||
slice_wrapping_add(&mut input_a2_q, &q[bottom.clone()], &q[top.clone()]);
|
||||
induction_karatsuba(&mut a2, &input_a2_p, &input_a2_q);
|
||||
|
||||
// rebuild the result
|
||||
let output: &mut [Scalar] = output.as_mut();
|
||||
slice_wrapping_sub(output, &a0, &a1);
|
||||
slice_wrapping_sub_assign(&mut output[bottom.clone()], &a2[top.clone()]);
|
||||
slice_wrapping_add_assign(&mut output[bottom.clone()], &a0[top.clone()]);
|
||||
slice_wrapping_add_assign(&mut output[bottom.clone()], &a1[top.clone()]);
|
||||
slice_wrapping_add_assign(&mut output[top.clone()], &a2[bottom.clone()]);
|
||||
slice_wrapping_sub_assign(&mut output[top.clone()], &a0[bottom.clone()]);
|
||||
slice_wrapping_sub_assign(&mut output[top], &a1[bottom]);
|
||||
}
|
||||
|
||||
/// Compute the induction for the karatsuba algorithm.
|
||||
fn induction_karatsuba<Scalar>(res: &mut [Scalar], p: &[Scalar], q: &[Scalar])
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
// stop the induction when polynomials have KARATUSBA_STOP elements
|
||||
const KARATUSBA_STOP: usize = 32;
|
||||
if p.len() == KARATUSBA_STOP {
|
||||
// schoolbook algorithm
|
||||
for (lhs_degree, &lhs_elt) in p.iter().enumerate() {
|
||||
for (rhs_degree, &rhs_elt) in q.iter().enumerate() {
|
||||
res[lhs_degree + rhs_degree] =
|
||||
res[lhs_degree + rhs_degree].wrapping_add(lhs_elt.wrapping_mul(rhs_elt))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let poly_size = res.len();
|
||||
|
||||
// allocate slices for the rec
|
||||
let mut a0 = vec![Scalar::ZERO; poly_size / 2];
|
||||
let mut a1 = vec![Scalar::ZERO; poly_size / 2];
|
||||
let mut a2 = vec![Scalar::ZERO; poly_size / 2];
|
||||
let mut input_a2_p = vec![Scalar::ZERO; poly_size / 4];
|
||||
let mut input_a2_q = vec![Scalar::ZERO; poly_size / 4];
|
||||
|
||||
// prepare for splitting
|
||||
let bottom = 0..(poly_size / 4);
|
||||
let top = (poly_size / 4)..(poly_size / 2);
|
||||
|
||||
// rec
|
||||
induction_karatsuba(&mut a0, &p[bottom.clone()], &q[bottom.clone()]);
|
||||
induction_karatsuba(&mut a1, &p[top.clone()], &q[top.clone()]);
|
||||
slice_wrapping_add(&mut input_a2_p, &p[bottom.clone()], &p[top.clone()]);
|
||||
slice_wrapping_add(&mut input_a2_q, &q[bottom], &q[top]);
|
||||
induction_karatsuba(&mut a2, &input_a2_p, &input_a2_q);
|
||||
|
||||
// rebuild the result
|
||||
slice_wrapping_sub(&mut res[(poly_size / 4)..(3 * poly_size / 4)], &a2, &a0);
|
||||
slice_wrapping_sub_assign(&mut res[(poly_size / 4)..(3 * poly_size / 4)], &a1);
|
||||
slice_wrapping_add_assign(&mut res[0..(poly_size / 2)], &a0);
|
||||
slice_wrapping_add_assign(&mut res[(poly_size / 2)..poly_size], &a1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::Rng;
|
||||
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::test_tools::*;
|
||||
|
||||
fn test_multiply_divide_unit_monomial<T: UnsignedTorus>() {
|
||||
//! tests if multiply_by_monomial and divide_by_monomial cancel each other
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut generator = new_random_generator();
|
||||
|
||||
// settings
|
||||
let polynomial_size = random_polynomial_size(2048);
|
||||
|
||||
// generate a random Torus polynomial
|
||||
let mut poly = Polynomial::new(T::ZERO, polynomial_size);
|
||||
generator.fill_slice_with_random_uniform(poly.as_mut());
|
||||
|
||||
let polynomial_size = polynomial_size.0;
|
||||
|
||||
// copy this polynomial
|
||||
let ground_truth = poly.clone();
|
||||
|
||||
// generate a random r
|
||||
let mut r: usize = rng.gen();
|
||||
r %= polynomial_size;
|
||||
|
||||
// multiply by X^r and then divides by X^r
|
||||
polynomial_wrapping_monic_monomial_mul_assign(&mut poly, MonomialDegree(r));
|
||||
polynomial_wrapping_monic_monomial_div_assign(&mut poly, MonomialDegree(r));
|
||||
|
||||
// test
|
||||
assert_eq!(&poly, &ground_truth);
|
||||
|
||||
// generate a random r_big
|
||||
let mut r_big: usize = rng.gen();
|
||||
r_big = r_big % polynomial_size + 2048;
|
||||
|
||||
// multiply by X^r_big and then divides by X^r_big
|
||||
polynomial_wrapping_monic_monomial_mul_assign(&mut poly, MonomialDegree(r_big));
|
||||
polynomial_wrapping_monic_monomial_div_assign(&mut poly, MonomialDegree(r_big));
|
||||
|
||||
// test
|
||||
assert_eq!(&poly, &ground_truth);
|
||||
|
||||
// divides by X^r_big and then multiply by X^r_big
|
||||
polynomial_wrapping_monic_monomial_mul_assign(&mut poly, MonomialDegree(r_big));
|
||||
polynomial_wrapping_monic_monomial_div_assign(&mut poly, MonomialDegree(r_big));
|
||||
|
||||
// test
|
||||
assert_eq!(&poly, &ground_truth);
|
||||
}
|
||||
|
||||
/// test if we have the same result when using schoolbook or karatsuba
|
||||
/// for random polynomial multiplication
|
||||
fn test_multiply_karatsuba<T: UnsignedTorus>() {
|
||||
// 50 times the test
|
||||
for _i in 0..50 {
|
||||
// random source
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// random settings settings
|
||||
let polynomial_log = (rng.gen::<usize>() % 7) + 6;
|
||||
let polynomial_size = PolynomialSize(1 << polynomial_log);
|
||||
let mut generator = new_random_generator();
|
||||
|
||||
// generate two random Torus polynomials
|
||||
let mut poly_1 = Polynomial::new(T::ZERO, polynomial_size);
|
||||
generator.fill_slice_with_random_uniform::<T>(poly_1.as_mut());
|
||||
let poly_1 = poly_1;
|
||||
|
||||
let mut poly_2 = Polynomial::new(T::ZERO, polynomial_size);
|
||||
generator.fill_slice_with_random_uniform::<T>(poly_2.as_mut());
|
||||
let poly_2 = poly_2;
|
||||
|
||||
// copy this polynomial
|
||||
let mut sb_mul = Polynomial::new(T::ZERO, polynomial_size);
|
||||
let mut ka_mul = Polynomial::new(T::ZERO, polynomial_size);
|
||||
|
||||
// compute the schoolbook
|
||||
polynomial_wrapping_mul(&mut sb_mul, &poly_1, &poly_2);
|
||||
|
||||
// compute the karatsuba
|
||||
polynomial_karatsuba_wrapping_mul(&mut ka_mul, &poly_1, &poly_2);
|
||||
|
||||
// test
|
||||
assert_eq!(&sb_mul, &ka_mul);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_multiply_divide_unit_monomial_u32() {
|
||||
test_multiply_divide_unit_monomial::<u32>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_multiply_divide_unit_monomial_u64() {
|
||||
test_multiply_divide_unit_monomial::<u64>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_multiply_karatsuba_u32() {
|
||||
test_multiply_karatsuba::<u32>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_multiply_karatsuba_u64() {
|
||||
test_multiply_karatsuba::<u64>()
|
||||
}
|
||||
}
|
||||
303
tfhe/src/core_crypto/algorithms/slice_algorithms.rs
Normal file
303
tfhe/src/core_crypto/algorithms/slice_algorithms.rs
Normal file
@@ -0,0 +1,303 @@
|
||||
//! Module providing algorithms to perform computations on raw slices.
|
||||
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
|
||||
/// Compute a dot product between two slices containing unsigned integers.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// let second = vec![255u8, 255, 255, 1, 2, 3];
|
||||
/// let dot_product = slice_wrapping_dot_product(&first, &second);
|
||||
/// assert_eq!(dot_product, 26);
|
||||
/// ```
|
||||
pub fn slice_wrapping_dot_product<Scalar>(lhs: &[Scalar], rhs: &[Scalar]) -> Scalar
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
assert!(
|
||||
lhs.len() == rhs.len(),
|
||||
"lhs (len: {}) and rhs (len: {}) must have the same length",
|
||||
lhs.len(),
|
||||
rhs.len()
|
||||
);
|
||||
|
||||
lhs.iter()
|
||||
.zip(rhs.iter())
|
||||
.fold(Scalar::ZERO, |acc, (&left, &right)| {
|
||||
acc.wrapping_add(left.wrapping_mul(right))
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a slice containing unsigned integers to another one element-wise.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// let second = vec![255u8, 255, 255, 1, 2, 3];
|
||||
/// let mut add = vec![0_u8; 6];
|
||||
/// slice_wrapping_add(&mut add, &first, &second);
|
||||
/// assert_eq!(&add, &[0u8, 1, 2, 5, 7, 9]);
|
||||
/// ```
|
||||
pub fn slice_wrapping_add<Scalar>(output: &mut [Scalar], lhs: &[Scalar], rhs: &[Scalar])
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
assert!(
|
||||
lhs.len() == rhs.len(),
|
||||
"lhs (len: {}) and rhs (len: {}) must have the same length",
|
||||
lhs.len(),
|
||||
rhs.len()
|
||||
);
|
||||
assert!(
|
||||
output.len() == lhs.len(),
|
||||
"output (len: {}) and rhs (lhs: {}) must have the same length",
|
||||
output.len(),
|
||||
lhs.len()
|
||||
);
|
||||
|
||||
output
|
||||
.iter_mut()
|
||||
.zip(lhs.iter().zip(rhs.iter()))
|
||||
.for_each(|(out, (&lhs, &rhs))| *out = lhs.wrapping_add(rhs));
|
||||
}
|
||||
|
||||
/// Add a slice containing unsigned integers to another one element-wise and in place.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// let second = vec![255u8, 255, 255, 1, 2, 3];
|
||||
/// slice_wrapping_add_assign(&mut first, &second);
|
||||
/// assert_eq!(&first, &[0u8, 1, 2, 5, 7, 9]);
|
||||
/// ```
|
||||
pub fn slice_wrapping_add_assign<Scalar>(lhs: &mut [Scalar], rhs: &[Scalar])
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
assert!(
|
||||
lhs.len() == rhs.len(),
|
||||
"lhs (len: {}) and rhs (len: {}) must have the same length",
|
||||
lhs.len(),
|
||||
rhs.len()
|
||||
);
|
||||
|
||||
lhs.iter_mut()
|
||||
.zip(rhs.iter())
|
||||
.for_each(|(lhs, &rhs)| *lhs = (*lhs).wrapping_add(rhs));
|
||||
}
|
||||
|
||||
/// Add a slice containing unsigned integers to another one mutiplied by a scalar.
|
||||
///
|
||||
/// Let *a*,*b* be two slices, let *c* be a scalar, this computes: *a <- a+bc*
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// let second = vec![255u8, 255, 255, 1, 2, 3];
|
||||
/// let scalar = 4u8;
|
||||
/// slice_wrapping_add_scalar_mul_assign(&mut first, &second, scalar);
|
||||
/// assert_eq!(&first, &[253u8, 254, 255, 8, 13, 18]);
|
||||
/// ```
|
||||
pub fn slice_wrapping_add_scalar_mul_assign<Scalar>(
|
||||
lhs: &mut [Scalar],
|
||||
rhs: &[Scalar],
|
||||
scalar: Scalar,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
assert!(
|
||||
lhs.len() == rhs.len(),
|
||||
"lhs (len: {}) and rhs (len: {}) must have the same length",
|
||||
lhs.len(),
|
||||
rhs.len()
|
||||
);
|
||||
lhs.iter_mut()
|
||||
.zip(rhs.iter())
|
||||
.for_each(|(lhs, &rhs)| *lhs = (*lhs).wrapping_add(rhs.wrapping_mul(scalar)));
|
||||
}
|
||||
|
||||
/// Subtract a slice containing unsigned integers to another one element-wise.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// let second = vec![255u8, 255, 255, 1, 2, 3];
|
||||
/// let mut add = vec![0_u8; 6];
|
||||
/// slice_wrapping_sub(&mut add, &first, &second);
|
||||
/// assert_eq!(&add, &[2, 3, 4, 3, 3, 3]);
|
||||
/// ```
|
||||
pub fn slice_wrapping_sub<Scalar>(output: &mut [Scalar], lhs: &[Scalar], rhs: &[Scalar])
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
assert!(
|
||||
lhs.len() == rhs.len(),
|
||||
"lhs (len: {}) and rhs (len: {}) must have the same length",
|
||||
lhs.len(),
|
||||
rhs.len()
|
||||
);
|
||||
assert!(
|
||||
output.len() == lhs.len(),
|
||||
"output (len: {}) and rhs (lhs: {}) must have the same length",
|
||||
output.len(),
|
||||
lhs.len()
|
||||
);
|
||||
|
||||
output
|
||||
.iter_mut()
|
||||
.zip(lhs.iter().zip(rhs.iter()))
|
||||
.for_each(|(out, (&lhs, &rhs))| *out = lhs.wrapping_sub(rhs));
|
||||
}
|
||||
|
||||
/// Subtract a slice containing unsigned integers to another one, element-wise and in place.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// let second = vec![255u8, 255, 255, 1, 2, 3];
|
||||
/// slice_wrapping_sub_assign(&mut first, &second);
|
||||
/// assert_eq!(&first, &[2u8, 3, 4, 3, 3, 3]);
|
||||
/// ```
|
||||
pub fn slice_wrapping_sub_assign<Scalar>(lhs: &mut [Scalar], rhs: &[Scalar])
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
assert!(
|
||||
lhs.len() == rhs.len(),
|
||||
"lhs (len: {}) and rhs (len: {}) must have the same length",
|
||||
lhs.len(),
|
||||
rhs.len()
|
||||
);
|
||||
|
||||
lhs.iter_mut()
|
||||
.zip(rhs.iter())
|
||||
.for_each(|(lhs, &rhs)| *lhs = (*lhs).wrapping_sub(rhs));
|
||||
}
|
||||
|
||||
/// Subtract a slice containing unsigned integers to another one mutiplied by a scalar,
|
||||
/// element-wise and in place.
|
||||
///
|
||||
/// Let *a*,*b* be two slices, let *c* be a scalar, this computes: *a <- a-bc*
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// let second = vec![255u8, 255, 255, 1, 2, 3];
|
||||
/// let scalar = 4u8;
|
||||
/// slice_wrapping_sub_scalar_mul_assign(&mut first, &second, scalar);
|
||||
/// assert_eq!(&first, &[5u8, 6, 7, 0, 253, 250]);
|
||||
pub fn slice_wrapping_sub_scalar_mul_assign<Scalar>(
|
||||
lhs: &mut [Scalar],
|
||||
rhs: &[Scalar],
|
||||
scalar: Scalar,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
assert!(
|
||||
lhs.len() == rhs.len(),
|
||||
"lhs (len: {}) and rhs (len: {}) must have the same length",
|
||||
lhs.len(),
|
||||
rhs.len()
|
||||
);
|
||||
lhs.iter_mut()
|
||||
.zip(rhs.iter())
|
||||
.for_each(|(lhs, &rhs)| *lhs = (*lhs).wrapping_sub(rhs.wrapping_mul(scalar)));
|
||||
}
|
||||
|
||||
/// Compute the opposite of a slice containing unsigned integers, element-wise and in place.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// slice_wrapping_opposite_assign(&mut first);
|
||||
/// assert_eq!(&first, &[255u8, 254, 253, 252, 251, 250]);
|
||||
/// ```
|
||||
pub fn slice_wrapping_opposite_assign<Scalar>(slice: &mut [Scalar])
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
slice
|
||||
.iter_mut()
|
||||
.for_each(|elt| *elt = (*elt).wrapping_neg());
|
||||
}
|
||||
|
||||
/// Multiply a slice containing unsigned integers by a scalar, element-wise and in place.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::slice_algorithms::*;
|
||||
/// let mut first = vec![1u8, 2, 3, 4, 5, 6];
|
||||
/// let scalar = 252;
|
||||
/// slice_wrapping_scalar_mul_assign(&mut first, scalar);
|
||||
/// assert_eq!(&first, &[252, 248, 244, 240, 236, 232]);
|
||||
/// ```
|
||||
pub fn slice_wrapping_scalar_mul_assign<Scalar>(lhs: &mut [Scalar], rhs: Scalar)
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
lhs.iter_mut()
|
||||
.for_each(|lhs| *lhs = (*lhs).wrapping_mul(rhs));
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::engines::{CudaEngine, CudaError};
|
||||
use crate::core_crypto::backends::cuda::implementation::entities::{
|
||||
CudaGlweCiphertext32, CudaGlweCiphertext64,
|
||||
};
|
||||
use crate::core_crypto::backends::cuda::private::crypto::glwe::ciphertext::CudaGlweCiphertext;
|
||||
use crate::core_crypto::commons::crypto::glwe::GlweCiphertext;
|
||||
use crate::core_crypto::commons::math::tensor::{AsRefSlice, AsRefTensor};
|
||||
use crate::core_crypto::prelude::{GlweCiphertext32, GlweCiphertext64, GlweCiphertextView64};
|
||||
use crate::core_crypto::specification::engines::{
|
||||
GlweCiphertextConversionEngine, GlweCiphertextConversionError,
|
||||
};
|
||||
use crate::core_crypto::specification::entities::GlweCiphertextEntity;
|
||||
|
||||
impl From<CudaError> for GlweCiphertextConversionError<CudaError> {
|
||||
fn from(err: CudaError) -> Self {
|
||||
Self::Engine(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert a GLWE ciphertext with 32 bits of precision from CPU to GPU 0.
|
||||
/// Only this conversion is necessary to run the bootstrap on the GPU.
|
||||
impl GlweCiphertextConversionEngine<GlweCiphertext32, CudaGlweCiphertext32> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let glwe_dimension = GlweDimension(2);
|
||||
/// let polynomial_size = PolynomialSize(4);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = vec![3_u32 << 20; polynomial_size.0];
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_plaintext_vector: PlaintextVector32 =
|
||||
/// default_engine.create_plaintext_vector_from(&input)?;
|
||||
/// let mut h_ciphertext: GlweCiphertext32 = default_engine
|
||||
/// .trivially_encrypt_glwe_ciphertext(glwe_dimension.to_glwe_size(), &h_plaintext_vector)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaGlweCiphertext32 = cuda_engine.convert_glwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(d_ciphertext.glwe_dimension(), glwe_dimension);
|
||||
/// assert_eq!(d_ciphertext.polynomial_size(), polynomial_size);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_glwe_ciphertext(
|
||||
&mut self,
|
||||
input: &GlweCiphertext32,
|
||||
) -> Result<CudaGlweCiphertext32, GlweCiphertextConversionError<CudaError>> {
|
||||
let stream = &self.streams[0];
|
||||
let data_per_gpu = input.glwe_dimension().to_glwe_size().0 * input.polynomial_size().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u32>() as u64;
|
||||
stream.check_device_memory(size)?;
|
||||
Ok(unsafe { self.convert_glwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_glwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &GlweCiphertext32,
|
||||
) -> CudaGlweCiphertext32 {
|
||||
// Copy the entire input vector over all GPUs
|
||||
let data_per_gpu = input.glwe_dimension().to_glwe_size().0 * input.polynomial_size().0;
|
||||
let stream = &self.streams[0];
|
||||
let mut vec = stream.malloc::<u32>(data_per_gpu as u32);
|
||||
let input_slice = input.0.as_tensor().as_slice();
|
||||
stream.copy_to_gpu::<u32>(&mut vec, input_slice);
|
||||
CudaGlweCiphertext32(CudaGlweCiphertext::<u32> {
|
||||
d_vec: vec,
|
||||
glwe_dimension: input.glwe_dimension(),
|
||||
polynomial_size: input.polynomial_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert a GLWE ciphertext vector with 32 bits of precision from GPU 0 to CPU.
|
||||
/// This conversion is not necessary to run the bootstrap on the GPU.
|
||||
/// It is implemented for testing purposes only.
|
||||
impl GlweCiphertextConversionEngine<CudaGlweCiphertext32, GlweCiphertext32> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let glwe_dimension = GlweDimension(2);
|
||||
/// let polynomial_size = PolynomialSize(4);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = vec![3_u32 << 20; polynomial_size.0];
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_plaintext_vector: PlaintextVector32 =
|
||||
/// default_engine.create_plaintext_vector_from(&input)?;
|
||||
/// let mut h_ciphertext: GlweCiphertext32 = default_engine
|
||||
/// .trivially_encrypt_glwe_ciphertext(glwe_dimension.to_glwe_size(), &h_plaintext_vector)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaGlweCiphertext32 = cuda_engine.convert_glwe_ciphertext(&h_ciphertext)?;
|
||||
/// let h_output_ciphertext: GlweCiphertext32 =
|
||||
/// cuda_engine.convert_glwe_ciphertext(&d_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(d_ciphertext.glwe_dimension(), glwe_dimension);
|
||||
/// assert_eq!(d_ciphertext.polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(h_ciphertext, h_output_ciphertext);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_glwe_ciphertext(
|
||||
&mut self,
|
||||
input: &CudaGlweCiphertext32,
|
||||
) -> Result<GlweCiphertext32, GlweCiphertextConversionError<CudaError>> {
|
||||
Ok(unsafe { self.convert_glwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_glwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &CudaGlweCiphertext32,
|
||||
) -> GlweCiphertext32 {
|
||||
// Copy the data from GPU 0 back to the CPU
|
||||
let mut output =
|
||||
vec![0u32; input.glwe_dimension().to_glwe_size().0 * input.polynomial_size().0];
|
||||
let stream = &self.streams[0];
|
||||
stream.copy_to_cpu::<u32>(&mut output, &input.0.d_vec);
|
||||
GlweCiphertext32(GlweCiphertext::from_container(
|
||||
output,
|
||||
input.polynomial_size(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert a GLWE ciphertext with 64 bits of precision from CPU to GPU 0.
|
||||
/// Only this conversion is necessary to run the bootstrap on the GPU.
|
||||
impl GlweCiphertextConversionEngine<GlweCiphertext64, CudaGlweCiphertext64> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let glwe_dimension = GlweDimension(2);
|
||||
/// let polynomial_size = PolynomialSize(4);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = vec![3_u64 << 20; polynomial_size.0];
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_plaintext_vector: PlaintextVector64 =
|
||||
/// default_engine.create_plaintext_vector_from(&input)?;
|
||||
/// let mut h_ciphertext: GlweCiphertext64 = default_engine
|
||||
/// .trivially_encrypt_glwe_ciphertext(glwe_dimension.to_glwe_size(), &h_plaintext_vector)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaGlweCiphertext64 = cuda_engine.convert_glwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(d_ciphertext.glwe_dimension(), glwe_dimension);
|
||||
/// assert_eq!(d_ciphertext.polynomial_size(), polynomial_size);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_glwe_ciphertext(
|
||||
&mut self,
|
||||
input: &GlweCiphertext64,
|
||||
) -> Result<CudaGlweCiphertext64, GlweCiphertextConversionError<CudaError>> {
|
||||
let stream = &self.streams[0];
|
||||
let data_per_gpu = input.glwe_dimension().to_glwe_size().0 * input.polynomial_size().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u64>() as u64;
|
||||
stream.check_device_memory(size)?;
|
||||
Ok(unsafe { self.convert_glwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_glwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &GlweCiphertext64,
|
||||
) -> CudaGlweCiphertext64 {
|
||||
// Copy the entire input vector over all GPUs
|
||||
let data_per_gpu = input.glwe_dimension().to_glwe_size().0 * input.polynomial_size().0;
|
||||
let stream = &self.streams[0];
|
||||
let mut vec = stream.malloc::<u64>(data_per_gpu as u32);
|
||||
let input_slice = input.0.as_tensor().as_slice();
|
||||
stream.copy_to_gpu::<u64>(&mut vec, input_slice);
|
||||
CudaGlweCiphertext64(CudaGlweCiphertext::<u64> {
|
||||
d_vec: vec,
|
||||
glwe_dimension: input.glwe_dimension(),
|
||||
polynomial_size: input.polynomial_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert a GLWE ciphertext vector with 64 bits of precision from GPU 0 to CPU.
|
||||
/// This conversion is not necessary to run the bootstrap on the GPU.
|
||||
/// It is implemented for testing purposes only.
|
||||
impl GlweCiphertextConversionEngine<CudaGlweCiphertext64, GlweCiphertext64> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let glwe_dimension = GlweDimension(2);
|
||||
/// let polynomial_size = PolynomialSize(4);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = vec![3_u64 << 20; polynomial_size.0];
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_plaintext_vector: PlaintextVector64 =
|
||||
/// default_engine.create_plaintext_vector_from(&input)?;
|
||||
/// let mut h_ciphertext: GlweCiphertext64 = default_engine
|
||||
/// .trivially_encrypt_glwe_ciphertext(glwe_dimension.to_glwe_size(), &h_plaintext_vector)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaGlweCiphertext64 = cuda_engine.convert_glwe_ciphertext(&h_ciphertext)?;
|
||||
/// let h_output_ciphertext: GlweCiphertext64 =
|
||||
/// cuda_engine.convert_glwe_ciphertext(&d_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(d_ciphertext.glwe_dimension(), glwe_dimension);
|
||||
/// assert_eq!(d_ciphertext.polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(h_ciphertext, h_output_ciphertext);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_glwe_ciphertext(
|
||||
&mut self,
|
||||
input: &CudaGlweCiphertext64,
|
||||
) -> Result<GlweCiphertext64, GlweCiphertextConversionError<CudaError>> {
|
||||
Ok(unsafe { self.convert_glwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_glwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &CudaGlweCiphertext64,
|
||||
) -> GlweCiphertext64 {
|
||||
// Copy the data from GPU 0 back to the CPU
|
||||
let mut output =
|
||||
vec![0u64; input.glwe_dimension().to_glwe_size().0 * input.polynomial_size().0];
|
||||
let stream = &self.streams[0];
|
||||
stream.copy_to_cpu::<u64>(&mut output, &input.0.d_vec);
|
||||
GlweCiphertext64(GlweCiphertext::from_container(
|
||||
output,
|
||||
input.polynomial_size(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert a view of a GLWE ciphertext with 64 bits of precision from CPU to GPU 0.
|
||||
impl GlweCiphertextConversionEngine<GlweCiphertextView64<'_>, CudaGlweCiphertext64> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let glwe_dimension = GlweDimension(2);
|
||||
/// let polynomial_size = PolynomialSize(4);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = vec![3_u64 << 20; polynomial_size.0];
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_plaintext_vector: PlaintextVector64 =
|
||||
/// default_engine.create_plaintext_vector_from(&input)?;
|
||||
/// let mut h_ciphertext: GlweCiphertext64 = default_engine
|
||||
/// .trivially_encrypt_glwe_ciphertext(glwe_dimension.to_glwe_size(), &h_plaintext_vector)?;
|
||||
/// let h_raw_ciphertext: Vec<u64> =
|
||||
/// default_engine.consume_retrieve_glwe_ciphertext(h_ciphertext)?;
|
||||
/// let mut h_view_ciphertext: GlweCiphertextView64 =
|
||||
/// default_engine.create_glwe_ciphertext_from(h_raw_ciphertext.as_slice(), polynomial_size)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaGlweCiphertext64 =
|
||||
/// cuda_engine.convert_glwe_ciphertext(&h_view_ciphertext)?;
|
||||
/// let h_output_ciphertext: GlweCiphertext64 =
|
||||
/// cuda_engine.convert_glwe_ciphertext(&d_ciphertext)?;
|
||||
///
|
||||
/// // Extracts the internal container
|
||||
/// let h_raw_output_ciphertext: Vec<u64> =
|
||||
/// default_engine.consume_retrieve_glwe_ciphertext(h_output_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(d_ciphertext.glwe_dimension(), glwe_dimension);
|
||||
/// assert_eq!(d_ciphertext.polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(h_raw_ciphertext, h_raw_output_ciphertext);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_glwe_ciphertext(
|
||||
&mut self,
|
||||
input: &GlweCiphertextView64,
|
||||
) -> Result<CudaGlweCiphertext64, GlweCiphertextConversionError<CudaError>> {
|
||||
let stream = &self.streams[0];
|
||||
let data_per_gpu = input.glwe_dimension().to_glwe_size().0 * input.polynomial_size().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u64>() as u64;
|
||||
stream.check_device_memory(size)?;
|
||||
Ok(unsafe { self.convert_glwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_glwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &GlweCiphertextView64,
|
||||
) -> CudaGlweCiphertext64 {
|
||||
// Copy the entire input vector over all GPUs
|
||||
let data_per_gpu = input.glwe_dimension().to_glwe_size().0 * input.polynomial_size().0;
|
||||
let stream = &self.streams[0];
|
||||
let mut vec = stream.malloc::<u64>(data_per_gpu as u32);
|
||||
let input_slice = input.0.as_tensor().as_slice();
|
||||
stream.copy_to_gpu::<u64>(&mut vec, input_slice);
|
||||
CudaGlweCiphertext64(CudaGlweCiphertext::<u64> {
|
||||
d_vec: vec,
|
||||
glwe_dimension: input.glwe_dimension(),
|
||||
polynomial_size: input.polynomial_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::engines::CudaError;
|
||||
use crate::core_crypto::backends::cuda::implementation::engines::{
|
||||
check_base_log, check_glwe_dim, CudaEngine,
|
||||
};
|
||||
use crate::core_crypto::backends::cuda::implementation::entities::{
|
||||
CudaFourierLweBootstrapKey32, CudaFourierLweBootstrapKey64,
|
||||
};
|
||||
use crate::core_crypto::backends::cuda::private::crypto::bootstrap::{
|
||||
convert_lwe_bootstrap_key_from_cpu_to_gpu, CudaBootstrapKey,
|
||||
};
|
||||
use crate::core_crypto::prelude::{LweBootstrapKey32, LweBootstrapKey64};
|
||||
use crate::core_crypto::specification::engines::{
|
||||
LweBootstrapKeyConversionEngine, LweBootstrapKeyConversionError,
|
||||
};
|
||||
use crate::core_crypto::specification::entities::LweBootstrapKeyEntity;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
impl From<CudaError> for LweBootstrapKeyConversionError<CudaError> {
|
||||
fn from(err: CudaError) -> Self {
|
||||
Self::Engine(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE bootstrap key corresponding to 32 bits of precision from the CPU to the GPU.
|
||||
/// The bootstrap key is copied entirely to all the GPUs and converted from the standard to the
|
||||
/// Fourier domain.
|
||||
|
||||
impl LweBootstrapKeyConversionEngine<LweBootstrapKey32, CudaFourierLweBootstrapKey32>
|
||||
for CudaEngine
|
||||
{
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::backends::cuda::private::device::GpuIndex;
|
||||
/// use tfhe::core_crypto::prelude::{
|
||||
/// DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
/// Variance, *,
|
||||
/// };
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let (lwe_dim, glwe_dim, poly_size) = (LweDimension(4), GlweDimension(1), PolynomialSize(512));
|
||||
/// let (dec_lc, dec_bl) = (DecompositionLevelCount(3), DecompositionBaseLog(5));
|
||||
/// let noise = Variance(2_f64.powf(-25.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let lwe_sk: LweSecretKey32 = default_engine.generate_new_lwe_secret_key(lwe_dim)?;
|
||||
/// let glwe_sk: GlweSecretKey32 =
|
||||
/// default_engine.generate_new_glwe_secret_key(glwe_dim, poly_size)?;
|
||||
/// let bsk: LweBootstrapKey32 =
|
||||
/// default_engine.generate_new_lwe_bootstrap_key(&lwe_sk, &glwe_sk, dec_bl, dec_lc, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_fourier_bsk: CudaFourierLweBootstrapKey32 =
|
||||
/// cuda_engine.convert_lwe_bootstrap_key(&bsk)?;
|
||||
///
|
||||
/// assert_eq!(d_fourier_bsk.glwe_dimension(), glwe_dim);
|
||||
/// assert_eq!(d_fourier_bsk.polynomial_size(), poly_size);
|
||||
/// assert_eq!(d_fourier_bsk.input_lwe_dimension(), lwe_dim);
|
||||
/// assert_eq!(d_fourier_bsk.decomposition_base_log(), dec_bl);
|
||||
/// assert_eq!(d_fourier_bsk.decomposition_level_count(), dec_lc);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_bootstrap_key(
|
||||
&mut self,
|
||||
input: &LweBootstrapKey32,
|
||||
) -> Result<CudaFourierLweBootstrapKey32, LweBootstrapKeyConversionError<CudaError>> {
|
||||
let poly_size = input.0.polynomial_size();
|
||||
check_poly_size!(poly_size);
|
||||
let glwe_dim = input.0.glwe_size().to_glwe_dimension();
|
||||
check_glwe_dim!(glwe_dim);
|
||||
let base_log = input.0.base_log();
|
||||
check_base_log!(base_log);
|
||||
let data_per_gpu = input.glwe_dimension().to_glwe_size().0
|
||||
* input.glwe_dimension().to_glwe_size().0
|
||||
* input.input_lwe_dimension().0
|
||||
* input.decomposition_level_count().0
|
||||
* input.polynomial_size().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u32>() as u64;
|
||||
for stream in self.streams.iter() {
|
||||
stream.check_device_memory(size)?;
|
||||
}
|
||||
Ok(unsafe { self.convert_lwe_bootstrap_key_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_bootstrap_key_unchecked(
|
||||
&mut self,
|
||||
input: &LweBootstrapKey32,
|
||||
) -> CudaFourierLweBootstrapKey32 {
|
||||
let vecs = convert_lwe_bootstrap_key_from_cpu_to_gpu::<u32, _>(
|
||||
self.get_cuda_streams(),
|
||||
&input.0,
|
||||
self.get_number_of_gpus(),
|
||||
);
|
||||
CudaFourierLweBootstrapKey32(CudaBootstrapKey::<u32> {
|
||||
d_vecs: vecs,
|
||||
polynomial_size: input.polynomial_size(),
|
||||
input_lwe_dimension: input.input_lwe_dimension(),
|
||||
glwe_dimension: input.glwe_dimension(),
|
||||
decomp_level: input.decomposition_level_count(),
|
||||
decomp_base_log: input.decomposition_base_log(),
|
||||
_phantom: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE bootstrap key corresponding to 64 bits of precision from the CPU to the GPU.
|
||||
/// The bootstrap key is copied entirely to all the GPUs and converted from the standard to the
|
||||
/// Fourier domain.
|
||||
|
||||
impl LweBootstrapKeyConversionEngine<LweBootstrapKey64, CudaFourierLweBootstrapKey64>
|
||||
for CudaEngine
|
||||
{
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::backends::cuda::private::device::GpuIndex;
|
||||
/// use tfhe::core_crypto::prelude::{
|
||||
/// DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
/// Variance, *,
|
||||
/// };
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let (lwe_dim, glwe_dim, poly_size) = (LweDimension(4), GlweDimension(1), PolynomialSize(512));
|
||||
/// let (dec_lc, dec_bl) = (DecompositionLevelCount(3), DecompositionBaseLog(5));
|
||||
/// let noise = Variance(2_f64.powf(-25.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let lwe_sk: LweSecretKey64 = default_engine.generate_new_lwe_secret_key(lwe_dim)?;
|
||||
/// let glwe_sk: GlweSecretKey64 =
|
||||
/// default_engine.generate_new_glwe_secret_key(glwe_dim, poly_size)?;
|
||||
/// let bsk: LweBootstrapKey64 =
|
||||
/// default_engine.generate_new_lwe_bootstrap_key(&lwe_sk, &glwe_sk, dec_bl, dec_lc, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_fourier_bsk: CudaFourierLweBootstrapKey64 =
|
||||
/// cuda_engine.convert_lwe_bootstrap_key(&bsk)?;
|
||||
///
|
||||
/// assert_eq!(d_fourier_bsk.glwe_dimension(), glwe_dim);
|
||||
/// assert_eq!(d_fourier_bsk.polynomial_size(), poly_size);
|
||||
/// assert_eq!(d_fourier_bsk.input_lwe_dimension(), lwe_dim);
|
||||
/// assert_eq!(d_fourier_bsk.decomposition_base_log(), dec_bl);
|
||||
/// assert_eq!(d_fourier_bsk.decomposition_level_count(), dec_lc);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_bootstrap_key(
|
||||
&mut self,
|
||||
input: &LweBootstrapKey64,
|
||||
) -> Result<CudaFourierLweBootstrapKey64, LweBootstrapKeyConversionError<CudaError>> {
|
||||
let poly_size = input.0.polynomial_size();
|
||||
check_poly_size!(poly_size);
|
||||
let glwe_dim = input.0.glwe_size().to_glwe_dimension();
|
||||
check_glwe_dim!(glwe_dim);
|
||||
let data_per_gpu = input.glwe_dimension().to_glwe_size().0
|
||||
* input.glwe_dimension().to_glwe_size().0
|
||||
* input.input_lwe_dimension().0
|
||||
* input.decomposition_level_count().0
|
||||
* input.polynomial_size().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u64>() as u64;
|
||||
for stream in self.streams.iter() {
|
||||
stream.check_device_memory(size)?;
|
||||
}
|
||||
Ok(unsafe { self.convert_lwe_bootstrap_key_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_bootstrap_key_unchecked(
|
||||
&mut self,
|
||||
input: &LweBootstrapKey64,
|
||||
) -> CudaFourierLweBootstrapKey64 {
|
||||
let vecs = convert_lwe_bootstrap_key_from_cpu_to_gpu::<u64, _>(
|
||||
self.get_cuda_streams(),
|
||||
&input.0,
|
||||
self.get_number_of_gpus(),
|
||||
);
|
||||
CudaFourierLweBootstrapKey64(CudaBootstrapKey::<u64> {
|
||||
d_vecs: vecs,
|
||||
polynomial_size: input.polynomial_size(),
|
||||
input_lwe_dimension: input.input_lwe_dimension(),
|
||||
glwe_dimension: input.glwe_dimension(),
|
||||
decomp_level: input.decomposition_level_count(),
|
||||
decomp_base_log: input.decomposition_base_log(),
|
||||
_phantom: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::implementation::engines::{CudaEngine, CudaError};
|
||||
use crate::core_crypto::backends::cuda::implementation::entities::{
|
||||
CudaLweCiphertext32, CudaLweCiphertext64,
|
||||
};
|
||||
use crate::core_crypto::backends::cuda::private::crypto::lwe::ciphertext::CudaLweCiphertext;
|
||||
use crate::core_crypto::commons::crypto::lwe::LweCiphertext;
|
||||
use crate::core_crypto::commons::math::tensor::{AsRefSlice, AsRefTensor};
|
||||
use crate::core_crypto::prelude::{LweCiphertext32, LweCiphertext64, LweCiphertextView64};
|
||||
use crate::core_crypto::specification::engines::{
|
||||
LweCiphertextConversionEngine, LweCiphertextConversionError,
|
||||
};
|
||||
use crate::core_crypto::specification::entities::LweCiphertextEntity;
|
||||
|
||||
impl From<CudaError> for LweCiphertextConversionError<CudaError> {
|
||||
fn from(err: CudaError) -> Self {
|
||||
Self::Engine(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE ciphertext with 32 bits of precision from CPU to GPU 0.
|
||||
impl LweCiphertextConversionEngine<LweCiphertext32, CudaLweCiphertext32> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = 3_u32 << 20;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey32 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext32 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext32 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(d_ciphertext.lwe_dimension(), lwe_dimension);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
input: &LweCiphertext32,
|
||||
) -> Result<CudaLweCiphertext32, LweCiphertextConversionError<CudaError>> {
|
||||
let stream = self.streams.first().unwrap();
|
||||
let data_per_gpu = input.lwe_dimension().to_lwe_size().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u32>() as u64;
|
||||
stream.check_device_memory(size)?;
|
||||
Ok(unsafe { self.convert_lwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &LweCiphertext32,
|
||||
) -> CudaLweCiphertext32 {
|
||||
let alloc_size = input.lwe_dimension().to_lwe_size().0 as u32;
|
||||
let input_slice = input.0.as_tensor().as_slice();
|
||||
let stream = self.streams.first().unwrap();
|
||||
let mut vec = stream.malloc::<u32>(alloc_size);
|
||||
stream.copy_to_gpu::<u32>(&mut vec, input_slice);
|
||||
CudaLweCiphertext32(CudaLweCiphertext::<u32> {
|
||||
d_vec: vec,
|
||||
lwe_dimension: input.lwe_dimension(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE ciphertext with 32 bits of precision from GPU 0 to CPU.
|
||||
impl LweCiphertextConversionEngine<CudaLweCiphertext32, LweCiphertext32> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = 3_u32 << 20;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey32 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext32 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext32 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// let h_ciphertext_output: LweCiphertext32 = cuda_engine.convert_lwe_ciphertext(&d_ciphertext)?;
|
||||
/// assert_eq!(h_ciphertext_output.lwe_dimension(), lwe_dimension);
|
||||
/// assert_eq!(h_ciphertext, h_ciphertext_output);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
input: &CudaLweCiphertext32,
|
||||
) -> Result<LweCiphertext32, LweCiphertextConversionError<CudaError>> {
|
||||
Ok(unsafe { self.convert_lwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &CudaLweCiphertext32,
|
||||
) -> LweCiphertext32 {
|
||||
let mut output = vec![0_u32; input.lwe_dimension().to_lwe_size().0];
|
||||
let stream = self.streams.first().unwrap();
|
||||
stream.copy_to_cpu::<u32>(&mut output, &input.0.d_vec);
|
||||
LweCiphertext32(LweCiphertext::from_container(output))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE ciphertext with 64 bits of precision from CPU to GPU 0.
|
||||
impl LweCiphertextConversionEngine<LweCiphertext64, CudaLweCiphertext64> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = 3_u64 << 20;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey64 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext64 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext64 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext64 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(d_ciphertext.lwe_dimension(), lwe_dimension);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
input: &LweCiphertext64,
|
||||
) -> Result<CudaLweCiphertext64, LweCiphertextConversionError<CudaError>> {
|
||||
let stream = self.streams.first().unwrap();
|
||||
let data_per_gpu = input.lwe_dimension().to_lwe_size().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u64>() as u64;
|
||||
stream.check_device_memory(size)?;
|
||||
Ok(unsafe { self.convert_lwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &LweCiphertext64,
|
||||
) -> CudaLweCiphertext64 {
|
||||
let alloc_size = input.lwe_dimension().to_lwe_size().0 as u32;
|
||||
let input_slice = input.0.as_tensor().as_slice();
|
||||
let stream = self.streams.first().unwrap();
|
||||
let mut vec = stream.malloc::<u64>(alloc_size);
|
||||
stream.copy_to_gpu::<u64>(&mut vec, input_slice);
|
||||
CudaLweCiphertext64(CudaLweCiphertext::<u64> {
|
||||
d_vec: vec,
|
||||
lwe_dimension: input.lwe_dimension(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE ciphertext with 64 bits of precision from GPU 0 to CPU.
|
||||
impl LweCiphertextConversionEngine<CudaLweCiphertext64, LweCiphertext64> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 50 bits)
|
||||
/// let input = 3_u64 << 50;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey64 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext64 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext64 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext64 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// let h_ciphertext_output: LweCiphertext64 = cuda_engine.convert_lwe_ciphertext(&d_ciphertext)?;
|
||||
/// assert_eq!(h_ciphertext_output.lwe_dimension(), lwe_dimension);
|
||||
/// assert_eq!(h_ciphertext, h_ciphertext_output);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
input: &CudaLweCiphertext64,
|
||||
) -> Result<LweCiphertext64, LweCiphertextConversionError<CudaError>> {
|
||||
Ok(unsafe { self.convert_lwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &CudaLweCiphertext64,
|
||||
) -> LweCiphertext64 {
|
||||
let mut output = vec![0_u64; input.lwe_dimension().to_lwe_size().0];
|
||||
let stream = self.streams.first().unwrap();
|
||||
stream.copy_to_cpu::<u64>(&mut output, &input.0.d_vec);
|
||||
LweCiphertext64(LweCiphertext::from_container(output))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert a view of an LWE ciphertext with 64 bits of precision from CPU to GPU 0.
|
||||
impl LweCiphertextConversionEngine<LweCiphertextView64<'_>, CudaLweCiphertext64> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = 3_u64 << 20;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey64 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext64 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext64 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// // Creates a LweCiphertextView64 object from LweCiphertext64
|
||||
/// let h_raw_ciphertext: Vec<u64> =
|
||||
/// default_engine.consume_retrieve_lwe_ciphertext(h_ciphertext)?;
|
||||
/// let mut h_view_ciphertext: LweCiphertextView64 =
|
||||
/// default_engine.create_lwe_ciphertext_from(h_raw_ciphertext.as_slice())?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext64 =
|
||||
/// cuda_engine.convert_lwe_ciphertext(&h_view_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(d_ciphertext.lwe_dimension(), lwe_dimension);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
input: &LweCiphertextView64,
|
||||
) -> Result<CudaLweCiphertext64, LweCiphertextConversionError<CudaError>> {
|
||||
let stream = &self.streams[0];
|
||||
let data_per_gpu = input.lwe_dimension().to_lwe_size().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u64>() as u64;
|
||||
stream.check_device_memory(size)?;
|
||||
Ok(unsafe { self.convert_lwe_ciphertext_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
input: &LweCiphertextView64,
|
||||
) -> CudaLweCiphertext64 {
|
||||
let alloc_size = input.lwe_dimension().to_lwe_size().0 as u32;
|
||||
let input_slice = input.0.as_tensor().as_slice();
|
||||
let stream = &self.streams[0];
|
||||
let mut d_vec = stream.malloc::<u64>(alloc_size);
|
||||
stream.copy_to_gpu::<u64>(&mut d_vec, input_slice);
|
||||
CudaLweCiphertext64(CudaLweCiphertext::<u64> {
|
||||
d_vec,
|
||||
lwe_dimension: input.lwe_dimension(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::engines::CudaError;
|
||||
use crate::core_crypto::backends::cuda::implementation::engines::{
|
||||
check_base_log, check_glwe_dim, CudaEngine,
|
||||
};
|
||||
use crate::core_crypto::backends::cuda::implementation::entities::{
|
||||
CudaFourierLweBootstrapKey32, CudaFourierLweBootstrapKey64, CudaGlweCiphertext32,
|
||||
CudaGlweCiphertext64, CudaLweCiphertext32, CudaLweCiphertext64,
|
||||
};
|
||||
use crate::core_crypto::backends::cuda::private::device::NumberOfSamples;
|
||||
use crate::core_crypto::prelude::LweCiphertextIndex;
|
||||
use crate::core_crypto::specification::engines::{
|
||||
LweCiphertextDiscardingBootstrapEngine, LweCiphertextDiscardingBootstrapError,
|
||||
};
|
||||
use crate::core_crypto::specification::entities::LweBootstrapKeyEntity;
|
||||
|
||||
impl From<CudaError> for LweCiphertextDiscardingBootstrapError<CudaError> {
|
||||
fn from(err: CudaError) -> Self {
|
||||
Self::Engine(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// A discard bootstrap on an input ciphertext with 32 bits of precision.
|
||||
/// The input bootstrap key is in the Fourier domain.
|
||||
impl
|
||||
LweCiphertextDiscardingBootstrapEngine<
|
||||
CudaFourierLweBootstrapKey32,
|
||||
CudaGlweCiphertext32,
|
||||
CudaLweCiphertext32,
|
||||
CudaLweCiphertext32,
|
||||
> for CudaEngine
|
||||
{
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// use tfhe::core_crypto::prelude::{
|
||||
/// DecompositionBaseLog, DecompositionLevelCount, GlweDimension, PolynomialSize,
|
||||
/// };
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let (lwe_dim, lwe_dim_output, glwe_dim, poly_size) = (
|
||||
/// LweDimension(130),
|
||||
/// LweDimension(512),
|
||||
/// GlweDimension(1),
|
||||
/// PolynomialSize(512),
|
||||
/// );
|
||||
/// let log_degree = f64::log2(poly_size.0 as f64) as i32;
|
||||
/// let val: u32 = ((poly_size.0 as f64 - (10. * f64::sqrt((lwe_dim.0 as f64) / 16.0)))
|
||||
/// * 2_f64.powi(32 - log_degree - 1)) as u32;
|
||||
/// let input = val;
|
||||
/// let noise = Variance(2_f64.powf(-29.));
|
||||
/// let (dec_lc, dec_bl) = (DecompositionLevelCount(3), DecompositionBaseLog(7));
|
||||
/// // An identity function is applied during the bootstrap
|
||||
/// let mut lut = vec![0u32; poly_size.0];
|
||||
/// for i in 0..poly_size.0 {
|
||||
/// let l = (i as f64 * 2_f64.powi(32 - log_degree - 1)) as u32;
|
||||
/// lut[i] = l;
|
||||
/// }
|
||||
///
|
||||
/// // 1. default engine
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// // create a vector of LWE ciphertexts
|
||||
/// let h_input_key: LweSecretKey32 = default_engine.generate_new_lwe_secret_key(lwe_dim)?;
|
||||
/// let h_input_plaintext: Plaintext32 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_input_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_input_key, &h_input_plaintext, noise)?;
|
||||
/// // create a GLWE ciphertext containing an encryption of the LUT
|
||||
/// let h_lut_plaintext_vector = default_engine.create_plaintext_vector_from(&lut)?;
|
||||
/// let h_lut_key: GlweSecretKey32 =
|
||||
/// default_engine.generate_new_glwe_secret_key(glwe_dim, poly_size)?;
|
||||
/// let mut h_lut: GlweCiphertext32 = default_engine
|
||||
/// .trivially_encrypt_glwe_ciphertext(glwe_dim.to_glwe_size(), &h_lut_plaintext_vector)?;
|
||||
/// // create a BSK
|
||||
/// let h_bootstrap_key: LweBootstrapKey32 = default_engine.generate_new_lwe_bootstrap_key(
|
||||
/// &h_input_key,
|
||||
/// &h_lut_key,
|
||||
/// dec_bl,
|
||||
/// dec_lc,
|
||||
/// noise,
|
||||
/// )?;
|
||||
/// // initialize an output LWE ciphertext vector
|
||||
/// let h_dummy_key: LweSecretKey32 = default_engine.generate_new_lwe_secret_key(lwe_dim_output)?;
|
||||
///
|
||||
/// // 2. cuda engine
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// // convert input to GPU 0
|
||||
/// let d_input_ciphertext: CudaLweCiphertext32 =
|
||||
/// cuda_engine.convert_lwe_ciphertext(&h_input_ciphertext)?;
|
||||
/// // convert accumulator to GPU
|
||||
/// let d_input_lut: CudaGlweCiphertext32 = cuda_engine.convert_glwe_ciphertext(&h_lut)?;
|
||||
/// // convert BSK to GPU (and from Standard to Fourier representations)
|
||||
/// let d_fourier_bsk: CudaFourierLweBootstrapKey32 =
|
||||
/// cuda_engine.convert_lwe_bootstrap_key(&h_bootstrap_key)?;
|
||||
/// // launch bootstrap on GPU
|
||||
/// let h_zero_output_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.zero_encrypt_lwe_ciphertext(&h_dummy_key, noise)?;
|
||||
/// let mut d_output_ciphertext: CudaLweCiphertext32 =
|
||||
/// cuda_engine.convert_lwe_ciphertext(&h_zero_output_ciphertext)?;
|
||||
/// cuda_engine.discard_bootstrap_lwe_ciphertext(
|
||||
/// &mut d_output_ciphertext,
|
||||
/// &d_input_ciphertext,
|
||||
/// &d_input_lut,
|
||||
/// &d_fourier_bsk,
|
||||
/// )?;
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn discard_bootstrap_lwe_ciphertext(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext32,
|
||||
input: &CudaLweCiphertext32,
|
||||
acc: &CudaGlweCiphertext32,
|
||||
bsk: &CudaFourierLweBootstrapKey32,
|
||||
) -> Result<(), LweCiphertextDiscardingBootstrapError<CudaError>> {
|
||||
LweCiphertextDiscardingBootstrapError::perform_generic_checks(output, input, acc, bsk)?;
|
||||
let poly_size = bsk.polynomial_size();
|
||||
check_poly_size!(poly_size);
|
||||
let glwe_dim = bsk.glwe_dimension();
|
||||
check_glwe_dim!(glwe_dim);
|
||||
let base_log = bsk.decomposition_base_log();
|
||||
check_base_log!(base_log);
|
||||
unsafe { self.discard_bootstrap_lwe_ciphertext_unchecked(output, input, acc, bsk) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn discard_bootstrap_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext32,
|
||||
input: &CudaLweCiphertext32,
|
||||
acc: &CudaGlweCiphertext32,
|
||||
bsk: &CudaFourierLweBootstrapKey32,
|
||||
) {
|
||||
let stream = self.streams.first().unwrap();
|
||||
let mut test_vector_indexes = stream.malloc::<u32>(1);
|
||||
stream.copy_to_gpu(&mut test_vector_indexes, &[0]);
|
||||
|
||||
stream.discard_bootstrap_low_latency_lwe_ciphertext_vector::<u32>(
|
||||
&mut output.0.d_vec,
|
||||
&acc.0.d_vec,
|
||||
&test_vector_indexes,
|
||||
&input.0.d_vec,
|
||||
bsk.0.d_vecs.first().unwrap(),
|
||||
input.0.lwe_dimension,
|
||||
bsk.glwe_dimension(),
|
||||
bsk.polynomial_size(),
|
||||
bsk.decomposition_base_log(),
|
||||
bsk.decomposition_level_count(),
|
||||
NumberOfSamples(1),
|
||||
LweCiphertextIndex(0),
|
||||
self.get_cuda_shared_memory(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// A discard bootstrap on an input ciphertext with 64 bits of precision.
|
||||
/// The input bootstrap key is in the Fourier domain.
|
||||
impl
|
||||
LweCiphertextDiscardingBootstrapEngine<
|
||||
CudaFourierLweBootstrapKey64,
|
||||
CudaGlweCiphertext64,
|
||||
CudaLweCiphertext64,
|
||||
CudaLweCiphertext64,
|
||||
> for CudaEngine
|
||||
{
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// use tfhe::core_crypto::prelude::{
|
||||
/// DecompositionBaseLog, DecompositionLevelCount, GlweDimension, PolynomialSize,
|
||||
/// };
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let (lwe_dim, lwe_dim_output, glwe_dim, poly_size) = (
|
||||
/// LweDimension(130),
|
||||
/// LweDimension(512),
|
||||
/// GlweDimension(1),
|
||||
/// PolynomialSize(512),
|
||||
/// );
|
||||
/// let log_degree = f64::log2(poly_size.0 as f64) as i64;
|
||||
/// let val: u64 = ((poly_size.0 as f64 - (10. * f64::sqrt((lwe_dim.0 as f64) / 16.0)))
|
||||
/// * 2_f64.powi((64 - log_degree - 1) as i32)) as u64;
|
||||
/// let input = val;
|
||||
/// let noise = Variance(2_f64.powf(-29.));
|
||||
/// let (dec_lc, dec_bl) = (DecompositionLevelCount(3), DecompositionBaseLog(7));
|
||||
/// // An identity function is applied during the bootstrap
|
||||
/// let mut lut = vec![0u64; poly_size.0];
|
||||
/// for i in 0..poly_size.0 {
|
||||
/// let l = (i as f64 * 2_f64.powi((64 - log_degree - 1) as i32)) as u64;
|
||||
/// lut[i] = l;
|
||||
/// }
|
||||
///
|
||||
/// // 1. default engine
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// // create a vector of LWE ciphertexts
|
||||
/// let h_input_key: LweSecretKey64 = default_engine.generate_new_lwe_secret_key(lwe_dim)?;
|
||||
/// let h_input_plaintext: Plaintext64 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_input_ciphertext: LweCiphertext64 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_input_key, &h_input_plaintext, noise)?;
|
||||
/// // create a GLWE ciphertext containing an encryption of the LUT
|
||||
/// let h_lut_plaintext_vector = default_engine.create_plaintext_vector_from(&lut)?;
|
||||
/// let h_lut_key: GlweSecretKey64 =
|
||||
/// default_engine.generate_new_glwe_secret_key(glwe_dim, poly_size)?;
|
||||
/// let mut h_lut: GlweCiphertext64 = default_engine
|
||||
/// .trivially_encrypt_glwe_ciphertext(glwe_dim.to_glwe_size(), &h_lut_plaintext_vector)?;
|
||||
/// // create a BSK
|
||||
/// let h_bootstrap_key: LweBootstrapKey64 = default_engine.generate_new_lwe_bootstrap_key(
|
||||
/// &h_input_key,
|
||||
/// &h_lut_key,
|
||||
/// dec_bl,
|
||||
/// dec_lc,
|
||||
/// noise,
|
||||
/// )?;
|
||||
/// // initialize an output LWE ciphertext vector
|
||||
/// let h_dummy_key: LweSecretKey64 = default_engine.generate_new_lwe_secret_key(lwe_dim_output)?;
|
||||
///
|
||||
/// // 2. cuda engine
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// // convert input to GPU 0
|
||||
/// let d_input_ciphertext: CudaLweCiphertext64 =
|
||||
/// cuda_engine.convert_lwe_ciphertext(&h_input_ciphertext)?;
|
||||
/// // convert accumulator to GPU
|
||||
/// let d_input_lut: CudaGlweCiphertext64 = cuda_engine.convert_glwe_ciphertext(&h_lut)?;
|
||||
/// // convert BSK to GPU (and from Standard to Fourier representations)
|
||||
/// let d_fourier_bsk: CudaFourierLweBootstrapKey64 =
|
||||
/// cuda_engine.convert_lwe_bootstrap_key(&h_bootstrap_key)?;
|
||||
/// // launch bootstrap on GPU
|
||||
/// let h_zero_output_ciphertext: LweCiphertext64 =
|
||||
/// default_engine.zero_encrypt_lwe_ciphertext(&h_dummy_key, noise)?;
|
||||
/// let mut d_output_ciphertext: CudaLweCiphertext64 =
|
||||
/// cuda_engine.convert_lwe_ciphertext(&h_zero_output_ciphertext)?;
|
||||
/// cuda_engine.discard_bootstrap_lwe_ciphertext(
|
||||
/// &mut d_output_ciphertext,
|
||||
/// &d_input_ciphertext,
|
||||
/// &d_input_lut,
|
||||
/// &d_fourier_bsk,
|
||||
/// )?;
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn discard_bootstrap_lwe_ciphertext(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext64,
|
||||
input: &CudaLweCiphertext64,
|
||||
acc: &CudaGlweCiphertext64,
|
||||
bsk: &CudaFourierLweBootstrapKey64,
|
||||
) -> Result<(), LweCiphertextDiscardingBootstrapError<CudaError>> {
|
||||
LweCiphertextDiscardingBootstrapError::perform_generic_checks(output, input, acc, bsk)?;
|
||||
let poly_size = bsk.polynomial_size();
|
||||
check_poly_size!(poly_size);
|
||||
let glwe_dim = bsk.glwe_dimension();
|
||||
check_glwe_dim!(glwe_dim);
|
||||
let base_log = bsk.decomposition_base_log();
|
||||
check_base_log!(base_log);
|
||||
unsafe { self.discard_bootstrap_lwe_ciphertext_unchecked(output, input, acc, bsk) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn discard_bootstrap_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext64,
|
||||
input: &CudaLweCiphertext64,
|
||||
acc: &CudaGlweCiphertext64,
|
||||
bsk: &CudaFourierLweBootstrapKey64,
|
||||
) {
|
||||
let stream = self.streams.first().unwrap();
|
||||
let mut test_vector_indexes = stream.malloc::<u32>(1);
|
||||
stream.copy_to_gpu(&mut test_vector_indexes, &[0]);
|
||||
|
||||
stream.discard_bootstrap_low_latency_lwe_ciphertext_vector::<u64>(
|
||||
&mut output.0.d_vec,
|
||||
&acc.0.d_vec,
|
||||
&test_vector_indexes,
|
||||
&input.0.d_vec,
|
||||
bsk.0.d_vecs.first().unwrap(),
|
||||
input.0.lwe_dimension,
|
||||
bsk.glwe_dimension(),
|
||||
bsk.polynomial_size(),
|
||||
bsk.decomposition_base_log(),
|
||||
bsk.decomposition_level_count(),
|
||||
NumberOfSamples(1),
|
||||
LweCiphertextIndex(0),
|
||||
self.get_cuda_shared_memory(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::implementation::engines::{CudaEngine, CudaError};
|
||||
use crate::core_crypto::backends::cuda::implementation::entities::{
|
||||
CudaLweCiphertext32, CudaLweCiphertext64,
|
||||
};
|
||||
use crate::core_crypto::commons::math::tensor::{AsMutSlice, AsRefSlice};
|
||||
use crate::core_crypto::prelude::{
|
||||
LweCiphertext32, LweCiphertextMutView32, LweCiphertextMutView64,
|
||||
};
|
||||
use crate::core_crypto::specification::engines::{
|
||||
LweCiphertextDiscardingConversionEngine, LweCiphertextDiscardingConversionError,
|
||||
};
|
||||
|
||||
/// # Description
|
||||
///
|
||||
/// Convert an LWE ciphertext with 32 bits of precision from GPU 0 to a view on the CPU.
|
||||
impl LweCiphertextDiscardingConversionEngine<CudaLweCiphertext32, LweCiphertextMutView32<'_>>
|
||||
for CudaEngine
|
||||
{
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// use std::borrow::BorrowMut;
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 25 bits)
|
||||
/// let input = 3_u32 << 25;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey32 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext32 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext32 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// // Prepares the output container
|
||||
/// let mut h_raw_output_ciphertext = vec![0_u32; lwe_dimension.0 + 1];
|
||||
/// let mut h_output_view_ciphertext: LweCiphertextMutView32 =
|
||||
/// default_engine.create_lwe_ciphertext_from(h_raw_output_ciphertext.as_mut_slice())?;
|
||||
///
|
||||
/// cuda_engine.discard_convert_lwe_ciphertext(&mut h_output_view_ciphertext, &d_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(h_output_view_ciphertext.lwe_dimension(), lwe_dimension);
|
||||
/// // Extracts the internal container
|
||||
/// let h_raw_input_ciphertext: Vec<u32> =
|
||||
/// default_engine.consume_retrieve_lwe_ciphertext(h_ciphertext)?;
|
||||
/// let h_raw_output_ciphertext: &[u32] =
|
||||
/// default_engine.consume_retrieve_lwe_ciphertext(h_output_view_ciphertext)?;
|
||||
/// assert_eq!(h_raw_input_ciphertext.as_slice(), h_raw_output_ciphertext);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn discard_convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
output: &mut LweCiphertextMutView32,
|
||||
input: &CudaLweCiphertext32,
|
||||
) -> Result<(), LweCiphertextDiscardingConversionError<CudaError>> {
|
||||
unsafe { self.discard_convert_lwe_ciphertext_unchecked(output, input) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn discard_convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
output: &mut LweCiphertextMutView32,
|
||||
input: &CudaLweCiphertext32,
|
||||
) {
|
||||
let stream = &self.streams[0];
|
||||
stream.copy_to_cpu::<u32>(output.0.tensor.as_mut_slice(), &input.0.d_vec);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
///
|
||||
/// Convert an LWE ciphertext with 32 bits of precision from GPU 0 to a ciphertext on the CPU.
|
||||
impl LweCiphertextDiscardingConversionEngine<CudaLweCiphertext32, LweCiphertext32> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// use std::borrow::BorrowMut;
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 25 bits)
|
||||
/// let input = 3_u32 << 25;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey32 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext32 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext32 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// // Prepares the output container
|
||||
/// let h_raw_output_ciphertext = vec![0_u32; lwe_dimension.0 + 1];
|
||||
/// let mut h_output_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.create_lwe_ciphertext_from(h_raw_output_ciphertext)?;
|
||||
///
|
||||
/// cuda_engine.discard_convert_lwe_ciphertext(&mut h_output_ciphertext, &d_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(h_output_ciphertext.lwe_dimension(), lwe_dimension);
|
||||
/// // Extracts the internal container
|
||||
/// let h_raw_input_ciphertext: Vec<u32> =
|
||||
/// default_engine.consume_retrieve_lwe_ciphertext(h_ciphertext)?;
|
||||
/// let h_raw_output_ciphertext: Vec<u32> =
|
||||
/// default_engine.consume_retrieve_lwe_ciphertext(h_output_ciphertext)?;
|
||||
/// assert_eq!(h_raw_input_ciphertext, h_raw_output_ciphertext);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn discard_convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
output: &mut LweCiphertext32,
|
||||
input: &CudaLweCiphertext32,
|
||||
) -> Result<(), LweCiphertextDiscardingConversionError<CudaError>> {
|
||||
unsafe { self.discard_convert_lwe_ciphertext_unchecked(output, input) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn discard_convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
output: &mut LweCiphertext32,
|
||||
input: &CudaLweCiphertext32,
|
||||
) {
|
||||
let stream = &self.streams[0];
|
||||
stream.copy_to_cpu::<u32>(output.0.tensor.as_mut_slice(), &input.0.d_vec);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
///
|
||||
/// Convert an LWE ciphertext with 32 bits of precision from CPU to a ciphertext on the GPU 0.
|
||||
impl LweCiphertextDiscardingConversionEngine<LweCiphertext32, CudaLweCiphertext32> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// use std::borrow::BorrowMut;
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 25 bits)
|
||||
/// let input = 3_u32 << 25;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey32 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext32 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let mut d_ciphertext: CudaLweCiphertext32 =
|
||||
/// cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// let h_ciphertext_out: LweCiphertext32 = cuda_engine.convert_lwe_ciphertext(&d_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(h_ciphertext, h_ciphertext_out);
|
||||
///
|
||||
/// // Prepare input for discarding convert
|
||||
/// let input_2 = 5_u32 << 25;
|
||||
/// let h_plaintext_2: Plaintext32 = default_engine.create_plaintext_from(&input_2)?;
|
||||
/// let mut h_ciphertext_2: LweCiphertext32 = default_engine
|
||||
/// .trivially_encrypt_lwe_ciphertext(lwe_dimension.to_lwe_size(), &h_plaintext_2)?;
|
||||
///
|
||||
/// cuda_engine.discard_convert_lwe_ciphertext(&mut d_ciphertext, &h_ciphertext_2)?;
|
||||
///
|
||||
/// let h_ciphertext_out_2: LweCiphertext32 = cuda_engine.convert_lwe_ciphertext(&d_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(h_ciphertext_2, h_ciphertext_out_2);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn discard_convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext32,
|
||||
input: &LweCiphertext32,
|
||||
) -> Result<(), LweCiphertextDiscardingConversionError<CudaError>> {
|
||||
unsafe { self.discard_convert_lwe_ciphertext_unchecked(output, input) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn discard_convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext32,
|
||||
input: &LweCiphertext32,
|
||||
) {
|
||||
let stream = &self.streams[0];
|
||||
stream.copy_to_gpu::<u32>(&mut output.0.d_vec, input.0.tensor.as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
///
|
||||
/// Convert an LWE ciphertext with 64 bits of precision from GPU 0 to a view on the CPU.
|
||||
impl LweCiphertextDiscardingConversionEngine<CudaLweCiphertext64, LweCiphertextMutView64<'_>>
|
||||
for CudaEngine
|
||||
{
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// use std::borrow::BorrowMut;
|
||||
/// let lwe_dimension = LweDimension(6);
|
||||
/// // Here a hard-set encoding is applied (shift by 25 bits)
|
||||
/// let input = 3_u64 << 50;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let h_key: LweSecretKey64 = default_engine.generate_new_lwe_secret_key(lwe_dimension)?;
|
||||
/// let h_plaintext: Plaintext64 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext64 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&h_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext64 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
///
|
||||
/// // Prepares the output container
|
||||
/// let mut h_raw_output_ciphertext = vec![0_u64; lwe_dimension.0 + 1];
|
||||
/// let mut h_view_output_ciphertext: LweCiphertextMutView64 =
|
||||
/// default_engine.create_lwe_ciphertext_from(h_raw_output_ciphertext.as_mut_slice())?;
|
||||
///
|
||||
/// cuda_engine
|
||||
/// .discard_convert_lwe_ciphertext(h_view_output_ciphertext.borrow_mut(), &d_ciphertext)?;
|
||||
///
|
||||
/// assert_eq!(h_view_output_ciphertext.lwe_dimension(), lwe_dimension);
|
||||
/// // Extracts the internal container
|
||||
/// let h_raw_input_ciphertext: Vec<u64> =
|
||||
/// default_engine.consume_retrieve_lwe_ciphertext(h_ciphertext)?;
|
||||
/// let h_raw_output_ciphertext: &[u64] =
|
||||
/// default_engine.consume_retrieve_lwe_ciphertext(h_view_output_ciphertext)?;
|
||||
/// assert_eq!(h_raw_input_ciphertext, h_raw_output_ciphertext.to_vec());
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn discard_convert_lwe_ciphertext(
|
||||
&mut self,
|
||||
output: &mut LweCiphertextMutView64,
|
||||
input: &CudaLweCiphertext64,
|
||||
) -> Result<(), LweCiphertextDiscardingConversionError<CudaError>> {
|
||||
unsafe { self.discard_convert_lwe_ciphertext_unchecked(output, input) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn discard_convert_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
output: &mut LweCiphertextMutView64,
|
||||
input: &CudaLweCiphertext64,
|
||||
) {
|
||||
let stream = &self.streams[0];
|
||||
stream.copy_to_cpu::<u64>(output.0.tensor.as_mut_slice(), &input.0.d_vec);
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::engines::CudaError;
|
||||
use crate::core_crypto::backends::cuda::implementation::engines::CudaEngine;
|
||||
use crate::core_crypto::backends::cuda::implementation::entities::{
|
||||
CudaLweCiphertext32, CudaLweCiphertext64, CudaLweKeyswitchKey32, CudaLweKeyswitchKey64,
|
||||
};
|
||||
use crate::core_crypto::backends::cuda::private::device::NumberOfSamples;
|
||||
use crate::core_crypto::specification::engines::{
|
||||
LweCiphertextDiscardingKeyswitchEngine, LweCiphertextDiscardingKeyswitchError,
|
||||
};
|
||||
use crate::core_crypto::specification::entities::LweKeyswitchKeyEntity;
|
||||
|
||||
impl From<CudaError> for LweCiphertextDiscardingKeyswitchError<CudaError> {
|
||||
fn from(err: CudaError) -> Self {
|
||||
Self::Engine(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// A discard keyswitch on a vector of input ciphertext vectors with 32 bits of precision.
|
||||
impl
|
||||
LweCiphertextDiscardingKeyswitchEngine<
|
||||
CudaLweKeyswitchKey32,
|
||||
CudaLweCiphertext32,
|
||||
CudaLweCiphertext32,
|
||||
> for CudaEngine
|
||||
{
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let input_lwe_dimension = LweDimension(6);
|
||||
/// let output_lwe_dimension = LweDimension(3);
|
||||
/// let decomposition_level_count = DecompositionLevelCount(2);
|
||||
/// let decomposition_base_log = DecompositionBaseLog(8);
|
||||
///
|
||||
/// // Here a hard-set encoding is applied (shift by 20 bits)
|
||||
/// let input = 3_u32 << 20;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// // Generate two secret keys
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let input_key: LweSecretKey32 =
|
||||
/// default_engine.generate_new_lwe_secret_key(input_lwe_dimension)?;
|
||||
/// let output_key: LweSecretKey32 =
|
||||
/// default_engine.generate_new_lwe_secret_key(output_lwe_dimension)?;
|
||||
///
|
||||
/// // Generate keyswitch keys to switch between first_key and second_key
|
||||
/// let h_ksk = default_engine.generate_new_lwe_keyswitch_key(
|
||||
/// &input_key,
|
||||
/// &output_key,
|
||||
/// decomposition_level_count,
|
||||
/// decomposition_base_log,
|
||||
/// noise,
|
||||
/// )?;
|
||||
///
|
||||
/// // Encrypt something
|
||||
/// let h_plaintext: Plaintext32 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&input_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// // Copy to the GPU
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext32 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
/// let d_ksk: CudaLweKeyswitchKey32 = cuda_engine.convert_lwe_keyswitch_key(&h_ksk)?;
|
||||
///
|
||||
/// // launch keyswitch on GPU
|
||||
/// let h_dummy_key: LweSecretKey32 =
|
||||
/// default_engine.generate_new_lwe_secret_key(output_lwe_dimension)?;
|
||||
/// let h_zero_ciphertext: LweCiphertext32 =
|
||||
/// default_engine.zero_encrypt_lwe_ciphertext(&h_dummy_key, noise)?;
|
||||
///
|
||||
/// let mut d_keyswitched_ciphertext: CudaLweCiphertext32 =
|
||||
/// cuda_engine.convert_lwe_ciphertext(&h_zero_ciphertext)?;
|
||||
/// cuda_engine.discard_keyswitch_lwe_ciphertext(
|
||||
/// &mut d_keyswitched_ciphertext,
|
||||
/// &d_ciphertext,
|
||||
/// &d_ksk,
|
||||
/// )?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// d_keyswitched_ciphertext.lwe_dimension(),
|
||||
/// output_lwe_dimension
|
||||
/// );
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn discard_keyswitch_lwe_ciphertext(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext32,
|
||||
input: &CudaLweCiphertext32,
|
||||
ksk: &CudaLweKeyswitchKey32,
|
||||
) -> Result<(), LweCiphertextDiscardingKeyswitchError<CudaError>> {
|
||||
LweCiphertextDiscardingKeyswitchError::perform_generic_checks(output, input, ksk)?;
|
||||
unsafe { self.discard_keyswitch_lwe_ciphertext_unchecked(output, input, ksk) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn discard_keyswitch_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext32,
|
||||
input: &CudaLweCiphertext32,
|
||||
ksk: &CudaLweKeyswitchKey32,
|
||||
) {
|
||||
let stream = &self.streams[0];
|
||||
|
||||
stream.discard_keyswitch_lwe_ciphertext_vector::<u32>(
|
||||
&mut output.0.d_vec,
|
||||
&input.0.d_vec,
|
||||
input.0.lwe_dimension,
|
||||
output.0.lwe_dimension,
|
||||
ksk.0.d_vecs.first().unwrap(),
|
||||
ksk.decomposition_base_log(),
|
||||
ksk.decomposition_level_count(),
|
||||
NumberOfSamples(1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// A discard keyswitch on a vector of input ciphertext vectors with 64 bits of precision.
|
||||
impl
|
||||
LweCiphertextDiscardingKeyswitchEngine<
|
||||
CudaLweKeyswitchKey64,
|
||||
CudaLweCiphertext64,
|
||||
CudaLweCiphertext64,
|
||||
> for CudaEngine
|
||||
{
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::{LweCiphertextCount, LweDimension, Variance, *};
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let input_lwe_dimension = LweDimension(6);
|
||||
/// let output_lwe_dimension = LweDimension(3);
|
||||
/// let decomposition_level_count = DecompositionLevelCount(2);
|
||||
/// let decomposition_base_log = DecompositionBaseLog(8);
|
||||
///
|
||||
/// // Here a hard-set encoding is applied (shift by 50 bits)
|
||||
/// let input = 3_u64 << 50;
|
||||
/// let noise = Variance(2_f64.powf(-50.));
|
||||
///
|
||||
/// // Generate two secret keys
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let input_key: LweSecretKey64 =
|
||||
/// default_engine.generate_new_lwe_secret_key(input_lwe_dimension)?;
|
||||
/// let output_key: LweSecretKey64 =
|
||||
/// default_engine.generate_new_lwe_secret_key(output_lwe_dimension)?;
|
||||
///
|
||||
/// // Generate keyswitch keys to switch between first_key and second_key
|
||||
/// let h_ksk = default_engine.generate_new_lwe_keyswitch_key(
|
||||
/// &input_key,
|
||||
/// &output_key,
|
||||
/// decomposition_level_count,
|
||||
/// decomposition_base_log,
|
||||
/// noise,
|
||||
/// )?;
|
||||
///
|
||||
/// // Encrypt something
|
||||
/// let h_plaintext: Plaintext64 = default_engine.create_plaintext_from(&input)?;
|
||||
/// let mut h_ciphertext: LweCiphertext64 =
|
||||
/// default_engine.encrypt_lwe_ciphertext(&input_key, &h_plaintext, noise)?;
|
||||
///
|
||||
/// // Copy to the GPU
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ciphertext: CudaLweCiphertext64 = cuda_engine.convert_lwe_ciphertext(&h_ciphertext)?;
|
||||
/// let d_ksk: CudaLweKeyswitchKey64 = cuda_engine.convert_lwe_keyswitch_key(&h_ksk)?;
|
||||
///
|
||||
/// // launch keyswitch on GPU
|
||||
/// let h_dummy_key: LweSecretKey64 =
|
||||
/// default_engine.generate_new_lwe_secret_key(output_lwe_dimension)?;
|
||||
/// let h_zero_ciphertext: LweCiphertext64 =
|
||||
/// default_engine.zero_encrypt_lwe_ciphertext(&h_dummy_key, noise)?;
|
||||
///
|
||||
/// let mut d_keyswitched_ciphertext: CudaLweCiphertext64 =
|
||||
/// cuda_engine.convert_lwe_ciphertext(&h_zero_ciphertext)?;
|
||||
/// cuda_engine.discard_keyswitch_lwe_ciphertext(
|
||||
/// &mut d_keyswitched_ciphertext,
|
||||
/// &d_ciphertext,
|
||||
/// &d_ksk,
|
||||
/// )?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// d_keyswitched_ciphertext.lwe_dimension(),
|
||||
/// output_lwe_dimension
|
||||
/// );
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn discard_keyswitch_lwe_ciphertext(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext64,
|
||||
input: &CudaLweCiphertext64,
|
||||
ksk: &CudaLweKeyswitchKey64,
|
||||
) -> Result<(), LweCiphertextDiscardingKeyswitchError<CudaError>> {
|
||||
LweCiphertextDiscardingKeyswitchError::perform_generic_checks(output, input, ksk)?;
|
||||
unsafe { self.discard_keyswitch_lwe_ciphertext_unchecked(output, input, ksk) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn discard_keyswitch_lwe_ciphertext_unchecked(
|
||||
&mut self,
|
||||
output: &mut CudaLweCiphertext64,
|
||||
input: &CudaLweCiphertext64,
|
||||
ksk: &CudaLweKeyswitchKey64,
|
||||
) {
|
||||
let stream = &self.streams[0];
|
||||
|
||||
stream.discard_keyswitch_lwe_ciphertext_vector::<u64>(
|
||||
&mut output.0.d_vec,
|
||||
&input.0.d_vec,
|
||||
input.0.lwe_dimension,
|
||||
output.0.lwe_dimension,
|
||||
ksk.0.d_vecs.first().unwrap(),
|
||||
ksk.decomposition_base_log(),
|
||||
ksk.decomposition_level_count(),
|
||||
NumberOfSamples(1),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::engines::CudaError;
|
||||
use crate::core_crypto::backends::cuda::implementation::engines::CudaEngine;
|
||||
use crate::core_crypto::backends::cuda::implementation::entities::{
|
||||
CudaLweKeyswitchKey32, CudaLweKeyswitchKey64,
|
||||
};
|
||||
use crate::core_crypto::backends::cuda::private::crypto::keyswitch::CudaLweKeyswitchKey;
|
||||
use crate::core_crypto::commons::crypto::lwe::LweKeyswitchKey;
|
||||
use crate::core_crypto::commons::math::tensor::{AsRefSlice, AsRefTensor};
|
||||
use crate::core_crypto::prelude::{LweKeyswitchKey32, LweKeyswitchKey64};
|
||||
use crate::core_crypto::specification::engines::{
|
||||
LweKeyswitchKeyConversionEngine, LweKeyswitchKeyConversionError,
|
||||
};
|
||||
use crate::core_crypto::specification::entities::LweKeyswitchKeyEntity;
|
||||
|
||||
impl From<CudaError> for LweKeyswitchKeyConversionError<CudaError> {
|
||||
fn from(err: CudaError) -> Self {
|
||||
Self::Engine(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE keyswitch key corresponding to 32 bits of precision from the CPU to the GPU.
|
||||
/// We only support the conversion from CPU to GPU: the conversion from GPU to CPU is not
|
||||
/// necessary at this stage to support the keyswitch. The keyswitch key is copied entirely to all
|
||||
/// the GPUs.
|
||||
impl LweKeyswitchKeyConversionEngine<LweKeyswitchKey32, CudaLweKeyswitchKey32> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::backends::cuda::private::device::GpuIndex;
|
||||
/// use tfhe::core_crypto::prelude::{
|
||||
/// DecompositionBaseLog, DecompositionLevelCount, LweDimension, Variance, *,
|
||||
/// };
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let input_lwe_dimension = LweDimension(6);
|
||||
/// let output_lwe_dimension = LweDimension(3);
|
||||
/// let decomposition_level_count = DecompositionLevelCount(2);
|
||||
/// let decomposition_base_log = DecompositionBaseLog(8);
|
||||
/// let noise = Variance(2_f64.powf(-25.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let input_key: LweSecretKey32 =
|
||||
/// default_engine.generate_new_lwe_secret_key(input_lwe_dimension)?;
|
||||
/// let output_key: LweSecretKey32 =
|
||||
/// default_engine.generate_new_lwe_secret_key(output_lwe_dimension)?;
|
||||
/// let ksk = default_engine.generate_new_lwe_keyswitch_key(
|
||||
/// &input_key,
|
||||
/// &output_key,
|
||||
/// decomposition_level_count,
|
||||
/// decomposition_base_log,
|
||||
/// noise,
|
||||
/// )?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ksk: CudaLweKeyswitchKey32 = cuda_engine.convert_lwe_keyswitch_key(&ksk)?;
|
||||
///
|
||||
/// assert_eq!(d_ksk.input_lwe_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(d_ksk.output_lwe_dimension(), output_lwe_dimension);
|
||||
/// assert_eq!(d_ksk.decomposition_level_count(), decomposition_level_count);
|
||||
/// assert_eq!(d_ksk.decomposition_base_log(), decomposition_base_log);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_keyswitch_key(
|
||||
&mut self,
|
||||
input: &LweKeyswitchKey32,
|
||||
) -> Result<CudaLweKeyswitchKey32, LweKeyswitchKeyConversionError<CudaError>> {
|
||||
for gpu_index in 0..self.get_number_of_gpus().0 {
|
||||
let stream = &self.streams[gpu_index];
|
||||
let data_per_gpu = input.decomposition_level_count().0
|
||||
* (input.output_lwe_dimension().0 + 1)
|
||||
* input.input_lwe_dimension().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u32>() as u64;
|
||||
stream.check_device_memory(size)?;
|
||||
}
|
||||
Ok(unsafe { self.convert_lwe_keyswitch_key_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_keyswitch_key_unchecked(
|
||||
&mut self,
|
||||
input: &LweKeyswitchKey32,
|
||||
) -> CudaLweKeyswitchKey32 {
|
||||
// Copy the entire input vector over all GPUs
|
||||
let mut d_vecs = Vec::with_capacity(self.get_number_of_gpus().0);
|
||||
|
||||
let data_per_gpu = input.decomposition_level_count().0
|
||||
* input.output_lwe_dimension().to_lwe_size().0
|
||||
* input.input_lwe_dimension().0;
|
||||
for stream in self.streams.iter() {
|
||||
let mut d_vec = stream.malloc::<u32>(data_per_gpu as u32);
|
||||
stream.copy_to_gpu(&mut d_vec, input.0.as_tensor().as_slice());
|
||||
d_vecs.push(d_vec);
|
||||
}
|
||||
CudaLweKeyswitchKey32(CudaLweKeyswitchKey::<u32> {
|
||||
d_vecs,
|
||||
input_lwe_dimension: input.input_lwe_dimension(),
|
||||
output_lwe_dimension: input.output_lwe_dimension(),
|
||||
decomp_level: input.decomposition_level_count(),
|
||||
decomp_base_log: input.decomposition_base_log(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE keyswitch key corresponding to 32 bits of precision from the GPU to the CPU.
|
||||
/// We assume consistency between all the available GPUs and simply copy what is in the one with
|
||||
/// index 0.
|
||||
impl LweKeyswitchKeyConversionEngine<CudaLweKeyswitchKey32, LweKeyswitchKey32> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::backends::cuda::private::device::GpuIndex;
|
||||
/// use tfhe::core_crypto::prelude::{
|
||||
/// DecompositionBaseLog, DecompositionLevelCount, LweDimension, Variance, *,
|
||||
/// };
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let input_lwe_dimension = LweDimension(6);
|
||||
/// let output_lwe_dimension = LweDimension(3);
|
||||
/// let decomposition_level_count = DecompositionLevelCount(2);
|
||||
/// let decomposition_base_log = DecompositionBaseLog(8);
|
||||
/// let noise = Variance(2_f64.powf(-25.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let input_key: LweSecretKey32 =
|
||||
/// default_engine.generate_new_lwe_secret_key(input_lwe_dimension)?;
|
||||
/// let output_key: LweSecretKey32 =
|
||||
/// default_engine.generate_new_lwe_secret_key(output_lwe_dimension)?;
|
||||
/// let h_ksk = default_engine.generate_new_lwe_keyswitch_key(
|
||||
/// &input_key,
|
||||
/// &output_key,
|
||||
/// decomposition_level_count,
|
||||
/// decomposition_base_log,
|
||||
/// noise,
|
||||
/// )?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ksk: CudaLweKeyswitchKey32 = cuda_engine.convert_lwe_keyswitch_key(&h_ksk)?;
|
||||
/// let h_output_ksk: LweKeyswitchKey32 = cuda_engine.convert_lwe_keyswitch_key(&d_ksk)?;
|
||||
///
|
||||
/// assert_eq!(d_ksk.input_lwe_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(d_ksk.output_lwe_dimension(), output_lwe_dimension);
|
||||
/// assert_eq!(d_ksk.decomposition_level_count(), decomposition_level_count);
|
||||
/// assert_eq!(d_ksk.decomposition_base_log(), decomposition_base_log);
|
||||
/// assert_eq!(h_output_ksk, h_ksk);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_keyswitch_key(
|
||||
&mut self,
|
||||
input: &CudaLweKeyswitchKey32,
|
||||
) -> Result<LweKeyswitchKey32, LweKeyswitchKeyConversionError<CudaError>> {
|
||||
Ok(unsafe { self.convert_lwe_keyswitch_key_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_keyswitch_key_unchecked(
|
||||
&mut self,
|
||||
input: &CudaLweKeyswitchKey32,
|
||||
) -> LweKeyswitchKey32 {
|
||||
let data_per_gpu = input.decomposition_level_count().0
|
||||
* input.output_lwe_dimension().to_lwe_size().0
|
||||
* input.input_lwe_dimension().0;
|
||||
|
||||
// Copy the data from GPU 0 back to the CPU
|
||||
let mut output = vec![0u32; data_per_gpu];
|
||||
let stream = self.streams.first().unwrap();
|
||||
stream.copy_to_cpu::<u32>(&mut output, input.0.d_vecs.first().unwrap());
|
||||
|
||||
LweKeyswitchKey32(LweKeyswitchKey::from_container(
|
||||
output,
|
||||
input.decomposition_base_log(),
|
||||
input.decomposition_level_count(),
|
||||
input.output_lwe_dimension(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Description
|
||||
/// Convert an LWE keyswitch key corresponding to 64 bits of precision from the CPU to the GPU.
|
||||
/// We only support the conversion from CPU to GPU: the conversion from GPU to CPU is not
|
||||
/// necessary at this stage to support the keyswitch. The keyswitch key is copied entirely to all
|
||||
/// the GPUs.
|
||||
impl LweKeyswitchKeyConversionEngine<LweKeyswitchKey64, CudaLweKeyswitchKey64> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::backends::cuda::private::device::GpuIndex;
|
||||
/// use tfhe::core_crypto::prelude::{
|
||||
/// DecompositionBaseLog, DecompositionLevelCount, LweDimension, Variance, *,
|
||||
/// };
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let input_lwe_dimension = LweDimension(6);
|
||||
/// let output_lwe_dimension = LweDimension(3);
|
||||
/// let decomposition_level_count = DecompositionLevelCount(2);
|
||||
/// let decomposition_base_log = DecompositionBaseLog(8);
|
||||
/// let noise = Variance(2_f64.powf(-25.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let input_key: LweSecretKey64 =
|
||||
/// default_engine.generate_new_lwe_secret_key(input_lwe_dimension)?;
|
||||
/// let output_key: LweSecretKey64 =
|
||||
/// default_engine.generate_new_lwe_secret_key(output_lwe_dimension)?;
|
||||
/// let ksk = default_engine.generate_new_lwe_keyswitch_key(
|
||||
/// &input_key,
|
||||
/// &output_key,
|
||||
/// decomposition_level_count,
|
||||
/// decomposition_base_log,
|
||||
/// noise,
|
||||
/// )?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ksk: CudaLweKeyswitchKey64 = cuda_engine.convert_lwe_keyswitch_key(&ksk)?;
|
||||
///
|
||||
/// assert_eq!(d_ksk.input_lwe_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(d_ksk.output_lwe_dimension(), output_lwe_dimension);
|
||||
/// assert_eq!(d_ksk.decomposition_level_count(), decomposition_level_count);
|
||||
/// assert_eq!(d_ksk.decomposition_base_log(), decomposition_base_log);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_keyswitch_key(
|
||||
&mut self,
|
||||
input: &LweKeyswitchKey64,
|
||||
) -> Result<CudaLweKeyswitchKey64, LweKeyswitchKeyConversionError<CudaError>> {
|
||||
for stream in self.streams.iter() {
|
||||
let data_per_gpu = input.decomposition_level_count().0
|
||||
* input.output_lwe_dimension().to_lwe_size().0
|
||||
* input.input_lwe_dimension().0;
|
||||
let size = data_per_gpu as u64 * std::mem::size_of::<u64>() as u64;
|
||||
stream.check_device_memory(size)?;
|
||||
}
|
||||
Ok(unsafe { self.convert_lwe_keyswitch_key_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_keyswitch_key_unchecked(
|
||||
&mut self,
|
||||
input: &LweKeyswitchKey64,
|
||||
) -> CudaLweKeyswitchKey64 {
|
||||
// Copy the entire input vector over all GPUs
|
||||
let mut d_vecs = Vec::with_capacity(self.get_number_of_gpus().0);
|
||||
|
||||
let data_per_gpu = input.decomposition_level_count().0
|
||||
* input.output_lwe_dimension().to_lwe_size().0
|
||||
* input.input_lwe_dimension().0;
|
||||
for stream in self.streams.iter() {
|
||||
let mut d_vec = stream.malloc::<u64>(data_per_gpu as u32);
|
||||
stream.copy_to_gpu(&mut d_vec, input.0.as_tensor().as_slice());
|
||||
d_vecs.push(d_vec);
|
||||
}
|
||||
CudaLweKeyswitchKey64(CudaLweKeyswitchKey::<u64> {
|
||||
d_vecs,
|
||||
input_lwe_dimension: input.input_lwe_dimension(),
|
||||
output_lwe_dimension: input.output_lwe_dimension(),
|
||||
decomp_level: input.decomposition_level_count(),
|
||||
decomp_base_log: input.decomposition_base_log(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl LweKeyswitchKeyConversionEngine<CudaLweKeyswitchKey64, LweKeyswitchKey64> for CudaEngine {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::backends::cuda::private::device::GpuIndex;
|
||||
/// use tfhe::core_crypto::prelude::{
|
||||
/// DecompositionBaseLog, DecompositionLevelCount, LweDimension, Variance, *,
|
||||
/// };
|
||||
/// # use std::error::Error;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
|
||||
/// let input_lwe_dimension = LweDimension(6);
|
||||
/// let output_lwe_dimension = LweDimension(3);
|
||||
/// let decomposition_level_count = DecompositionLevelCount(2);
|
||||
/// let decomposition_base_log = DecompositionBaseLog(8);
|
||||
/// let noise = Variance(2_f64.powf(-25.));
|
||||
///
|
||||
/// const UNSAFE_SECRET: u128 = 0;
|
||||
/// let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
|
||||
/// let input_key: LweSecretKey64 =
|
||||
/// default_engine.generate_new_lwe_secret_key(input_lwe_dimension)?;
|
||||
/// let output_key: LweSecretKey64 =
|
||||
/// default_engine.generate_new_lwe_secret_key(output_lwe_dimension)?;
|
||||
/// let h_ksk = default_engine.generate_new_lwe_keyswitch_key(
|
||||
/// &input_key,
|
||||
/// &output_key,
|
||||
/// decomposition_level_count,
|
||||
/// decomposition_base_log,
|
||||
/// noise,
|
||||
/// )?;
|
||||
///
|
||||
/// let mut cuda_engine = CudaEngine::new(())?;
|
||||
/// let d_ksk: CudaLweKeyswitchKey64 = cuda_engine.convert_lwe_keyswitch_key(&h_ksk)?;
|
||||
/// let h_output_ksk: LweKeyswitchKey64 = cuda_engine.convert_lwe_keyswitch_key(&d_ksk)?;
|
||||
///
|
||||
/// assert_eq!(d_ksk.input_lwe_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(d_ksk.output_lwe_dimension(), output_lwe_dimension);
|
||||
/// assert_eq!(d_ksk.decomposition_level_count(), decomposition_level_count);
|
||||
/// assert_eq!(d_ksk.decomposition_base_log(), decomposition_base_log);
|
||||
/// assert_eq!(h_output_ksk, h_ksk);
|
||||
///
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn convert_lwe_keyswitch_key(
|
||||
&mut self,
|
||||
input: &CudaLweKeyswitchKey64,
|
||||
) -> Result<LweKeyswitchKey64, LweKeyswitchKeyConversionError<CudaError>> {
|
||||
Ok(unsafe { self.convert_lwe_keyswitch_key_unchecked(input) })
|
||||
}
|
||||
|
||||
unsafe fn convert_lwe_keyswitch_key_unchecked(
|
||||
&mut self,
|
||||
input: &CudaLweKeyswitchKey64,
|
||||
) -> LweKeyswitchKey64 {
|
||||
let data_per_gpu = input.decomposition_level_count().0
|
||||
* input.output_lwe_dimension().to_lwe_size().0
|
||||
* input.input_lwe_dimension().0;
|
||||
|
||||
// Copy the data from GPU 0 back to the CPU
|
||||
let mut output = vec![0u64; data_per_gpu];
|
||||
let stream = self.streams.first().unwrap();
|
||||
stream.copy_to_cpu::<u64>(&mut output, input.0.d_vecs.first().unwrap());
|
||||
|
||||
LweKeyswitchKey64(LweKeyswitchKey::from_container(
|
||||
output,
|
||||
input.decomposition_base_log(),
|
||||
input.decomposition_level_count(),
|
||||
input.output_lwe_dimension(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::private::device::{CudaStream, GpuIndex, NumberOfGpus};
|
||||
use crate::core_crypto::prelude::sealed::AbstractEngineSeal;
|
||||
use crate::core_crypto::prelude::{AbstractEngine, CudaError, SharedMemoryAmount};
|
||||
use concrete_cuda::cuda_bind::cuda_get_number_of_gpus;
|
||||
/// The main engine exposed by the cuda backend.
|
||||
///
|
||||
/// This engine handles single-GPU and multi-GPU computations for the user. It always associates
|
||||
/// one Cuda stream to each available Nvidia GPU, and splits the input ciphertexts evenly over
|
||||
/// the GPUs (the last GPU may be a bit more loaded if the number of GPUs does not divide the
|
||||
/// number of input ciphertexts). This engine does not give control over the streams, nor the GPU
|
||||
/// load balancing. In this way, we can overlap computations done on different GPUs, but not
|
||||
/// computations done on a given GPU, which are executed in a sequence.
|
||||
// A finer access to streams could allow for more overlapping of computations
|
||||
// on a given device. We'll probably want to support it in the future, in an AdvancedCudaEngine
|
||||
// for example.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CudaEngine {
|
||||
streams: Vec<CudaStream>,
|
||||
max_shared_memory: usize,
|
||||
}
|
||||
|
||||
impl AbstractEngineSeal for CudaEngine {}
|
||||
|
||||
impl AbstractEngine for CudaEngine {
|
||||
type EngineError = CudaError;
|
||||
|
||||
type Parameters = ();
|
||||
|
||||
fn new(_parameters: Self::Parameters) -> Result<Self, Self::EngineError> {
|
||||
let number_of_gpus = unsafe { cuda_get_number_of_gpus() as usize };
|
||||
if number_of_gpus == 0 {
|
||||
Err(CudaError::DeviceNotFound)
|
||||
} else {
|
||||
let mut streams: Vec<CudaStream> = Vec::new();
|
||||
for gpu_index in 0..number_of_gpus {
|
||||
streams.push(CudaStream::new(GpuIndex(gpu_index))?);
|
||||
}
|
||||
let max_shared_memory = streams[0].get_max_shared_memory()?;
|
||||
|
||||
Ok(CudaEngine {
|
||||
streams,
|
||||
max_shared_memory: max_shared_memory as usize,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CudaEngine {
|
||||
/// Get the number of available GPUs from the engine
|
||||
pub fn get_number_of_gpus(&self) -> NumberOfGpus {
|
||||
NumberOfGpus(self.streams.len())
|
||||
}
|
||||
/// Get the Cuda streams from the engine
|
||||
pub fn get_cuda_streams(&self) -> &Vec<CudaStream> {
|
||||
&self.streams
|
||||
}
|
||||
/// Get the size of the shared memory (on device 0)
|
||||
pub fn get_cuda_shared_memory(&self) -> SharedMemoryAmount {
|
||||
SharedMemoryAmount(self.max_shared_memory)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! check_poly_size {
|
||||
($poly_size: ident) => {
|
||||
if $poly_size.0 != 512 && $poly_size.0 != 1024 && $poly_size.0 != 2048 {
|
||||
return Err(CudaError::PolynomialSizeNotSupported.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod glwe_ciphertext_conversion;
|
||||
mod lwe_bootstrap_key_conversion;
|
||||
mod lwe_ciphertext_conversion;
|
||||
mod lwe_ciphertext_discarding_bootstrap;
|
||||
mod lwe_ciphertext_discarding_conversion;
|
||||
mod lwe_ciphertext_discarding_keyswitch;
|
||||
mod lwe_keyswitch_key_conversion;
|
||||
@@ -1,87 +0,0 @@
|
||||
//! A module containing the [engines](crate::core_crypto::specification::engines) exposed by
|
||||
//! the cuda backend.
|
||||
|
||||
use crate::core_crypto::backends::cuda::private::device::GpuIndex;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
mod cuda_engine;
|
||||
pub use cuda_engine::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct SharedMemoryAmount(pub usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CudaError {
|
||||
DeviceNotFound,
|
||||
SharedMemoryNotFound(GpuIndex),
|
||||
NotEnoughDeviceMemory(GpuIndex),
|
||||
InvalidDeviceIndex(GpuIndex),
|
||||
UnspecifiedDeviceError(GpuIndex),
|
||||
PolynomialSizeNotSupported,
|
||||
GlweDimensionNotSupported,
|
||||
BaseLogNotSupported,
|
||||
}
|
||||
impl Display for CudaError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
CudaError::DeviceNotFound => {
|
||||
write!(f, "No GPU detected on the machine.")
|
||||
}
|
||||
CudaError::SharedMemoryNotFound(gpu_index) => {
|
||||
write!(f, "No shared memory detected on the GPU #{}.", gpu_index.0)
|
||||
}
|
||||
CudaError::NotEnoughDeviceMemory(gpu_index) => {
|
||||
write!(
|
||||
f,
|
||||
"The GPU #{} does not have enough global memory to hold all the data.",
|
||||
gpu_index.0
|
||||
)
|
||||
}
|
||||
CudaError::InvalidDeviceIndex(gpu_index) => {
|
||||
write!(
|
||||
f,
|
||||
"The specified GPU index, {}, does not exist.",
|
||||
gpu_index.0
|
||||
)
|
||||
}
|
||||
CudaError::PolynomialSizeNotSupported => {
|
||||
write!(
|
||||
f,
|
||||
"The polynomial size should be a power of 2. Values strictly lower than \
|
||||
512, and strictly greater than 8192, are not supported."
|
||||
)
|
||||
}
|
||||
CudaError::GlweDimensionNotSupported => {
|
||||
write!(f, "The only supported GLWE dimension is 1.")
|
||||
}
|
||||
CudaError::BaseLogNotSupported => {
|
||||
write!(f, "The base log has to be lower or equal to 16.")
|
||||
}
|
||||
CudaError::UnspecifiedDeviceError(gpu_index) => {
|
||||
write!(f, "Unspecified device error on GPU #{}.", gpu_index.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CudaError {}
|
||||
|
||||
macro_rules! check_glwe_dim {
|
||||
($glwe_dimension: ident) => {
|
||||
if $glwe_dimension.0 != 1 {
|
||||
return Err(CudaError::GlweDimensionNotSupported.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_base_log {
|
||||
($base_log: ident) => {
|
||||
if $base_log.0 > 16 {
|
||||
return Err(CudaError::BaseLogNotSupported.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use {check_base_log, check_glwe_dim};
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::core_crypto::prelude::{GlweDimension, PolynomialSize};
|
||||
|
||||
use crate::core_crypto::backends::cuda::private::crypto::glwe::ciphertext::CudaGlweCiphertext;
|
||||
use crate::core_crypto::specification::entities::markers::GlweCiphertextKind;
|
||||
use crate::core_crypto::specification::entities::{AbstractEntity, GlweCiphertextEntity};
|
||||
|
||||
/// A structure representing a vector of GLWE ciphertexts with 32 bits of precision on the GPU.
|
||||
/// It is used as input to the Cuda bootstrap for the array of lookup tables.
|
||||
#[derive(Debug)]
|
||||
pub struct CudaGlweCiphertext32(pub(crate) CudaGlweCiphertext<u32>);
|
||||
|
||||
impl AbstractEntity for CudaGlweCiphertext32 {
|
||||
type Kind = GlweCiphertextKind;
|
||||
}
|
||||
|
||||
impl GlweCiphertextEntity for CudaGlweCiphertext32 {
|
||||
fn glwe_dimension(&self) -> GlweDimension {
|
||||
self.0.glwe_dimension
|
||||
}
|
||||
|
||||
fn polynomial_size(&self) -> PolynomialSize {
|
||||
self.0.polynomial_size
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing a vector of GLWE ciphertexts with 64 bits of precision on the GPU.
|
||||
/// It is used as input to the Cuda bootstrap for the array of lookup tables.
|
||||
#[derive(Debug)]
|
||||
pub struct CudaGlweCiphertext64(pub(crate) CudaGlweCiphertext<u64>);
|
||||
|
||||
impl AbstractEntity for CudaGlweCiphertext64 {
|
||||
type Kind = GlweCiphertextKind;
|
||||
}
|
||||
|
||||
impl GlweCiphertextEntity for CudaGlweCiphertext64 {
|
||||
fn glwe_dimension(&self) -> GlweDimension {
|
||||
self.0.glwe_dimension
|
||||
}
|
||||
|
||||
fn polynomial_size(&self) -> PolynomialSize {
|
||||
self.0.polynomial_size
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
use crate::core_crypto::prelude::{
|
||||
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
};
|
||||
|
||||
use crate::core_crypto::backends::cuda::private::crypto::bootstrap::CudaBootstrapKey;
|
||||
use crate::core_crypto::specification::entities::markers::LweBootstrapKeyKind;
|
||||
use crate::core_crypto::specification::entities::{AbstractEntity, LweBootstrapKeyEntity};
|
||||
|
||||
/// A structure representing a Fourier bootstrap key for 32 bits precision ciphertexts on the GPU.
|
||||
#[derive(Debug)]
|
||||
pub struct CudaFourierLweBootstrapKey32(pub(crate) CudaBootstrapKey<u32>);
|
||||
|
||||
impl AbstractEntity for CudaFourierLweBootstrapKey32 {
|
||||
type Kind = LweBootstrapKeyKind;
|
||||
}
|
||||
|
||||
impl LweBootstrapKeyEntity for CudaFourierLweBootstrapKey32 {
|
||||
fn glwe_dimension(&self) -> GlweDimension {
|
||||
self.0.glwe_dimension
|
||||
}
|
||||
|
||||
fn polynomial_size(&self) -> PolynomialSize {
|
||||
self.0.polynomial_size
|
||||
}
|
||||
|
||||
fn input_lwe_dimension(&self) -> LweDimension {
|
||||
self.0.input_lwe_dimension
|
||||
}
|
||||
|
||||
fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.0.decomp_base_log
|
||||
}
|
||||
|
||||
fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.0.decomp_level
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing a Fourier bootstrap key for 64 bits precision ciphertexts on the GPU.
|
||||
#[derive(Debug)]
|
||||
pub struct CudaFourierLweBootstrapKey64(pub(crate) CudaBootstrapKey<u64>);
|
||||
|
||||
impl AbstractEntity for CudaFourierLweBootstrapKey64 {
|
||||
type Kind = LweBootstrapKeyKind;
|
||||
}
|
||||
|
||||
impl LweBootstrapKeyEntity for CudaFourierLweBootstrapKey64 {
|
||||
fn glwe_dimension(&self) -> GlweDimension {
|
||||
self.0.glwe_dimension
|
||||
}
|
||||
|
||||
fn polynomial_size(&self) -> PolynomialSize {
|
||||
self.0.polynomial_size
|
||||
}
|
||||
|
||||
fn input_lwe_dimension(&self) -> LweDimension {
|
||||
self.0.input_lwe_dimension
|
||||
}
|
||||
|
||||
fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.0.decomp_base_log
|
||||
}
|
||||
|
||||
fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.0.decomp_level
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::core_crypto::prelude::LweDimension;
|
||||
|
||||
use crate::core_crypto::backends::cuda::private::crypto::lwe::ciphertext::CudaLweCiphertext;
|
||||
use crate::core_crypto::specification::entities::markers::LweCiphertextKind;
|
||||
use crate::core_crypto::specification::entities::{AbstractEntity, LweCiphertextEntity};
|
||||
|
||||
/// A structure representing a vector of LWE ciphertexts with 32 bits of precision on the GPU.
|
||||
#[derive(Debug)]
|
||||
pub struct CudaLweCiphertext32(pub(crate) CudaLweCiphertext<u32>);
|
||||
|
||||
impl AbstractEntity for CudaLweCiphertext32 {
|
||||
type Kind = LweCiphertextKind;
|
||||
}
|
||||
|
||||
impl LweCiphertextEntity for CudaLweCiphertext32 {
|
||||
fn lwe_dimension(&self) -> LweDimension {
|
||||
self.0.lwe_dimension
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing a vector of LWE ciphertexts with 64 bits of precision on the GPU.
|
||||
#[derive(Debug)]
|
||||
pub struct CudaLweCiphertext64(pub(crate) CudaLweCiphertext<u64>);
|
||||
|
||||
impl AbstractEntity for CudaLweCiphertext64 {
|
||||
type Kind = LweCiphertextKind;
|
||||
}
|
||||
|
||||
impl LweCiphertextEntity for CudaLweCiphertext64 {
|
||||
fn lwe_dimension(&self) -> LweDimension {
|
||||
self.0.lwe_dimension
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use crate::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount, LweDimension};
|
||||
|
||||
use crate::core_crypto::backends::cuda::private::crypto::keyswitch::CudaLweKeyswitchKey;
|
||||
use crate::core_crypto::specification::entities::markers::LweKeyswitchKeyKind;
|
||||
use crate::core_crypto::specification::entities::{AbstractEntity, LweKeyswitchKeyEntity};
|
||||
|
||||
/// A structure representing a keyswitch key for 32 bits precision ciphertexts on the GPU.
|
||||
#[derive(Debug)]
|
||||
pub struct CudaLweKeyswitchKey32(pub(crate) CudaLweKeyswitchKey<u32>);
|
||||
|
||||
impl AbstractEntity for CudaLweKeyswitchKey32 {
|
||||
type Kind = LweKeyswitchKeyKind;
|
||||
}
|
||||
|
||||
impl LweKeyswitchKeyEntity for CudaLweKeyswitchKey32 {
|
||||
fn input_lwe_dimension(&self) -> LweDimension {
|
||||
self.0.input_lwe_dimension
|
||||
}
|
||||
|
||||
fn output_lwe_dimension(&self) -> LweDimension {
|
||||
self.0.output_lwe_dimension
|
||||
}
|
||||
|
||||
fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.0.decomp_level
|
||||
}
|
||||
|
||||
fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.0.decomp_base_log
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing a keyswitch key for 64 bits precision ciphertexts on the GPU.
|
||||
#[derive(Debug)]
|
||||
pub struct CudaLweKeyswitchKey64(pub(crate) CudaLweKeyswitchKey<u64>);
|
||||
|
||||
impl AbstractEntity for CudaLweKeyswitchKey64 {
|
||||
type Kind = LweKeyswitchKeyKind;
|
||||
}
|
||||
|
||||
impl LweKeyswitchKeyEntity for CudaLweKeyswitchKey64 {
|
||||
fn input_lwe_dimension(&self) -> LweDimension {
|
||||
self.0.input_lwe_dimension
|
||||
}
|
||||
|
||||
fn output_lwe_dimension(&self) -> LweDimension {
|
||||
self.0.output_lwe_dimension
|
||||
}
|
||||
|
||||
fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.0.decomp_level
|
||||
}
|
||||
|
||||
fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.0.decomp_base_log
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
//! A module containing all the [entities](crate::core_crypto::specification::entities)
|
||||
//! exposed by the cuda backend.
|
||||
|
||||
mod glwe_ciphertext;
|
||||
mod lwe_bootstrap_key;
|
||||
mod lwe_ciphertext;
|
||||
mod lwe_keyswitch_key;
|
||||
|
||||
pub use glwe_ciphertext::*;
|
||||
pub use lwe_bootstrap_key::*;
|
||||
pub use lwe_ciphertext::*;
|
||||
pub use lwe_keyswitch_key::*;
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod engines;
|
||||
pub mod entities;
|
||||
@@ -1,13 +0,0 @@
|
||||
//! A module containing the cuda backend implementation.
|
||||
//!
|
||||
//! This module contains CUDA GPU implementations of some FHE cryptographic primitives. In
|
||||
//! particular, it makes it possible to execute bootstraps on a vector of ciphertext vectors, with a
|
||||
//! vector of LUT and a bootstrap key as other inputs. To do so, the backend also exposes functions
|
||||
//! to transfer data to and from the GPU, via conversion functions.
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod private;
|
||||
|
||||
pub(crate) mod implementation;
|
||||
|
||||
pub use implementation::{engines, entities};
|
||||
@@ -1,67 +0,0 @@
|
||||
//! Bootstrap key with Cuda.
|
||||
use crate::core_crypto::backends::cuda::private::device::{CudaStream, NumberOfGpus};
|
||||
use crate::core_crypto::backends::cuda::private::vec::CudaVec;
|
||||
use crate::core_crypto::commons::crypto::bootstrap::StandardBootstrapKey;
|
||||
use crate::core_crypto::commons::math::tensor::{AsRefSlice, AsRefTensor};
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
use crate::core_crypto::prelude::{
|
||||
DecompositionBaseLog, DecompositionLevelCount, GlweDimension, LweDimension, PolynomialSize,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CudaBootstrapKey<T: UnsignedInteger> {
|
||||
// Pointers to GPU data: one cuda vec per GPU
|
||||
pub(crate) d_vecs: Vec<CudaVec<f64>>,
|
||||
// Input LWE dimension
|
||||
pub(crate) input_lwe_dimension: LweDimension,
|
||||
// Size of polynomials in the key
|
||||
pub(crate) polynomial_size: PolynomialSize,
|
||||
// GLWE dimension
|
||||
pub(crate) glwe_dimension: GlweDimension,
|
||||
// Number of decomposition levels
|
||||
pub(crate) decomp_level: DecompositionLevelCount,
|
||||
// Value of the base log for the decomposition
|
||||
pub(crate) decomp_base_log: DecompositionBaseLog,
|
||||
// Field to hold type T
|
||||
pub(crate) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for CudaBootstrapKey<T> where T: Send + UnsignedInteger {}
|
||||
unsafe impl<T> Sync for CudaBootstrapKey<T> where T: Sync + UnsignedInteger {}
|
||||
|
||||
pub(crate) unsafe fn convert_lwe_bootstrap_key_from_cpu_to_gpu<T: UnsignedInteger, Cont>(
|
||||
streams: &[CudaStream],
|
||||
input: &StandardBootstrapKey<Cont>,
|
||||
number_of_gpus: NumberOfGpus,
|
||||
) -> Vec<CudaVec<f64>>
|
||||
where
|
||||
Cont: AsRefSlice<Element = T>,
|
||||
{
|
||||
// Copy the entire input vector over all GPUs
|
||||
let mut vecs = Vec::with_capacity(number_of_gpus.0);
|
||||
// TODO
|
||||
// Check if it would be better to have GPU 0 compute the BSK and copy it back to the
|
||||
// CPU, then copy the BSK to the other GPUs. The order of instructions varies depending on
|
||||
// the Cuda warp scheduling, which we cannot assume is deterministic, so we'll end up with
|
||||
// slightly different BSKs on the GPUs. It is unclear how significantly this affects the
|
||||
// noise after the bootstrap.
|
||||
let total_polynomials =
|
||||
input.key_size().0 * input.glwe_size().0 * input.glwe_size().0 * input.level_count().0;
|
||||
let alloc_size = total_polynomials * input.polynomial_size().0;
|
||||
for stream in streams.iter() {
|
||||
let mut d_vec = stream.malloc::<f64>(alloc_size as u32);
|
||||
let input_slice = input.as_tensor().as_slice();
|
||||
stream.initialize_twiddles(input.polynomial_size());
|
||||
stream.convert_lwe_bootstrap_key::<T>(
|
||||
&mut d_vec,
|
||||
input_slice,
|
||||
input.key_size(),
|
||||
input.glwe_size().to_glwe_dimension(),
|
||||
input.level_count(),
|
||||
input.polynomial_size(),
|
||||
);
|
||||
vecs.push(d_vec);
|
||||
}
|
||||
vecs
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use crate::core_crypto::backends::cuda::private::vec::CudaVec;
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
use crate::core_crypto::prelude::{GlweDimension, PolynomialSize};
|
||||
|
||||
/// One GLWE ciphertext on GPU 0.
|
||||
///
|
||||
/// There is no multi GPU support at this stage since the user cannot
|
||||
/// specify on which GPU to convert the data.
|
||||
// Fields with `d_` are data in the GPU
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CudaGlweCiphertext<T: UnsignedInteger> {
|
||||
// Pointer to GPU data: one cuda vec on GPU 0
|
||||
pub(crate) d_vec: CudaVec<T>,
|
||||
// Glwe dimension
|
||||
pub(crate) glwe_dimension: GlweDimension,
|
||||
// Polynomial size
|
||||
pub(crate) polynomial_size: PolynomialSize,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
//! GLWE ciphertexts and ciphertext vectors with Cuda.
|
||||
|
||||
pub(crate) mod ciphertext;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user