mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-11 07:38:08 -05:00
Compare commits
53 Commits
blogpost_e
...
0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2384e0d1f | ||
|
|
37da2f1f1e | ||
|
|
8c775e5a27 | ||
|
|
43ba7e103d | ||
|
|
448e634748 | ||
|
|
6268752ac9 | ||
|
|
e0ed2d91c6 | ||
|
|
fef389e002 | ||
|
|
ae30f7c086 | ||
|
|
3f719a30f6 | ||
|
|
d28880ac30 | ||
|
|
ca9cdc0e73 | ||
|
|
f768e62d89 | ||
|
|
ee96a0ff18 | ||
|
|
ee944b3129 | ||
|
|
672f855770 | ||
|
|
362992a4ba | ||
|
|
2b24eb304d | ||
|
|
b484b8a851 | ||
|
|
6dea738725 | ||
|
|
3bb342879f | ||
|
|
9f024e2dac | ||
|
|
190b483d23 | ||
|
|
e799d240a7 | ||
|
|
16596137c1 | ||
|
|
03cd7ef15a | ||
|
|
4cda0a7211 | ||
|
|
9b668c1d50 | ||
|
|
dc4d9c7968 | ||
|
|
e3e7abd652 | ||
|
|
4265fbe67e | ||
|
|
337400ce3d | ||
|
|
be650d8e6b | ||
|
|
47604a6297 | ||
|
|
95d6fc5b1b | ||
|
|
19a6855b82 | ||
|
|
f894c33bfd | ||
|
|
6578aff8a4 | ||
|
|
9096c62f32 | ||
|
|
22f186af17 | ||
|
|
7820523d1f | ||
|
|
c0386c7e54 | ||
|
|
1ea73a68c4 | ||
|
|
6a02ae04e1 | ||
|
|
becd11b45f | ||
|
|
366964f1e6 | ||
|
|
32f8561af1 | ||
|
|
063ad26b9e | ||
|
|
dba18a889a | ||
|
|
0f5e1f0141 | ||
|
|
d4c7aff90b | ||
|
|
1d9f8c57da | ||
|
|
aa58748d33 |
2
.github/workflows/aws_tfhe_tests.yml
vendored
2
.github/workflows/aws_tfhe_tests.yml
vendored
@@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
- name: Run high-level API tests
|
||||
run: |
|
||||
BIG_TESTS_INSTANCE=TRUE make test_typed_api
|
||||
BIG_TESTS_INSTANCE=TRUE make test_high_level_api
|
||||
|
||||
- name: Slack Notification
|
||||
if: ${{ always() }}
|
||||
|
||||
15
.github/workflows/boolean_benchmark.yml
vendored
15
.github/workflows/boolean_benchmark.yml
vendored
@@ -19,10 +19,6 @@ on:
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
matrix_item:
|
||||
description: 'Build matrix item'
|
||||
type: string
|
||||
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -40,7 +36,6 @@ jobs:
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
echo "Matrix item: ${{ inputs.matrix_item }}"
|
||||
|
||||
- name: Get benchmark date
|
||||
run: |
|
||||
@@ -71,12 +66,13 @@ jobs:
|
||||
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
|
||||
COMMIT_HASH="$(git describe --tags --dirty)"
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--database tfhe_rs_benchmarks \
|
||||
--database tfhe_rs \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--project-version "${COMMIT_HASH}" \
|
||||
--branch ${{ github.ref_name }} \
|
||||
--commit-date "${COMMIT_DATE}" \
|
||||
--bench-date "${{ env.BENCH_DATE }}" \
|
||||
--walk-subdirs \
|
||||
--throughput
|
||||
|
||||
- name: Remove previous raw results
|
||||
@@ -92,6 +88,7 @@ jobs:
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--name-suffix avx512 \
|
||||
--walk-subdirs \
|
||||
--throughput \
|
||||
--append-results
|
||||
|
||||
@@ -120,14 +117,16 @@ jobs:
|
||||
|
||||
- name: Send data to Slab
|
||||
shell: bash
|
||||
env:
|
||||
COMPRESSED_RESULTS : ${{ env.RESULTS_FILENAME }}.gz
|
||||
run: |
|
||||
echo "Computing HMac on downloaded artifact"
|
||||
echo "Computing HMac on results file"
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
|
||||
echo "Sending results to Slab..."
|
||||
curl -v -k \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: store_data" \
|
||||
-H "X-Slab-Command: store_data_v2" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @${{ env.RESULTS_FILENAME }} \
|
||||
${{ secrets.SLAB_URL }}
|
||||
|
||||
120
.github/workflows/integer_benchmark.yml
vendored
Normal file
120
.github/workflows/integer_benchmark.yml
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
# Run integer benchmarks on an AWS instance and return parsed results to Slab CI bot.
|
||||
name: Integer benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: 'Instance ID'
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: 'Instance AMI ID'
|
||||
type: string
|
||||
instance_type:
|
||||
description: 'Instance product type'
|
||||
type: string
|
||||
runner_name:
|
||||
description: 'Action runner name'
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
|
||||
|
||||
jobs:
|
||||
run-integer-benchmarks:
|
||||
name: Execute integer benchmarks in EC2
|
||||
runs-on: ${{ github.event.inputs.runner_name }}
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Instance configuration used
|
||||
run: |
|
||||
echo "IDs: ${{ inputs.instance_id }}"
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
|
||||
- name: Get benchmark date
|
||||
run: |
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up home
|
||||
# "Install rust" step require root user to have a HOME directory which is not set.
|
||||
run: |
|
||||
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
make bench_integer
|
||||
|
||||
- name: Parse results
|
||||
run: |
|
||||
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
|
||||
COMMIT_HASH="$(git describe --tags --dirty)"
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--database tfhe_rs \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--project-version "${COMMIT_HASH}" \
|
||||
--branch ${{ github.ref_name }} \
|
||||
--commit-date "${COMMIT_DATE}" \
|
||||
--bench-date "${{ env.BENCH_DATE }}" \
|
||||
--walk-subdirs \
|
||||
--throughput
|
||||
|
||||
- name: Remove previous raw results
|
||||
run: |
|
||||
rm -rf target/criterion
|
||||
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
make AVX512_SUPPORT=ON bench_integer
|
||||
|
||||
- name: Parse AVX512 results
|
||||
run: |
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--walk-subdirs \
|
||||
--name-suffix avx512 \
|
||||
--throughput \
|
||||
--append-results
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
with:
|
||||
name: ${{ github.sha }}_integer
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
|
||||
|
||||
- name: Send data to Slab
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Computing HMac on results file"
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
|
||||
echo "Sending results to Slab..."
|
||||
curl -v -k \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: store_data_v2" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @${{ env.RESULTS_FILENAME }} \
|
||||
${{ secrets.SLAB_URL }}
|
||||
5
.github/workflows/pbs_benchmark.yml
vendored
5
.github/workflows/pbs_benchmark.yml
vendored
@@ -67,13 +67,14 @@ jobs:
|
||||
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
|
||||
COMMIT_HASH="$(git describe --tags --dirty)"
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--database tfhe_rs_benchmarks \
|
||||
--database tfhe_rs \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--project-version "${COMMIT_HASH}" \
|
||||
--branch ${{ github.ref_name }} \
|
||||
--commit-date "${COMMIT_DATE}" \
|
||||
--bench-date "${{ env.BENCH_DATE }}" \
|
||||
--name-suffix avx512 \
|
||||
--walk-subdirs \
|
||||
--throughput
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
@@ -98,7 +99,7 @@ jobs:
|
||||
curl -v -k \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: store_data" \
|
||||
-H "X-Slab-Command: store_data_v2" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @${{ env.RESULTS_FILENAME }} \
|
||||
${{ secrets.SLAB_URL }}
|
||||
|
||||
10
.github/workflows/shortint_benchmark.yml
vendored
10
.github/workflows/shortint_benchmark.yml
vendored
@@ -19,9 +19,6 @@ on:
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
type: string
|
||||
matrix_item:
|
||||
description: 'Build matrix item'
|
||||
type: string
|
||||
|
||||
|
||||
env:
|
||||
@@ -40,7 +37,6 @@ jobs:
|
||||
echo "AMI: ${{ inputs.instance_image_id }}"
|
||||
echo "Type: ${{ inputs.instance_type }}"
|
||||
echo "Request ID: ${{ inputs.request_id }}"
|
||||
echo "Matrix item: ${{ inputs.matrix_item }}"
|
||||
|
||||
- name: Get benchmark date
|
||||
run: |
|
||||
@@ -71,7 +67,7 @@ jobs:
|
||||
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
|
||||
COMMIT_HASH="$(git describe --tags --dirty)"
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--database tfhe_rs_benchmarks \
|
||||
--database tfhe_rs \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--project-version "${COMMIT_HASH}" \
|
||||
--branch ${{ github.ref_name }} \
|
||||
@@ -123,13 +119,13 @@ jobs:
|
||||
- name: Send data to Slab
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Computing HMac on downloaded artifact"
|
||||
echo "Computing HMac on results file"
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
|
||||
echo "Sending results to Slab..."
|
||||
curl -v -k \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: store_data" \
|
||||
-H "X-Slab-Command: store_data_v2" \
|
||||
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
|
||||
-d @${{ env.RESULTS_FILENAME }} \
|
||||
${{ secrets.SLAB_URL }}
|
||||
|
||||
4
.github/workflows/start_benchmarks.yml
vendored
4
.github/workflows/start_benchmarks.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
start-benchmarks:
|
||||
strategy:
|
||||
matrix:
|
||||
command: [boolean_bench, shortint_bench, pbs_bench]
|
||||
command: [boolean_bench, shortint_bench, integer_bench, pbs_bench]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Slab repo
|
||||
@@ -23,11 +23,11 @@ jobs:
|
||||
|
||||
- name: Start AWS job in Slab
|
||||
shell: bash
|
||||
# TODO: step result must be correlated to HTTP return code.
|
||||
run: |
|
||||
echo -n '{"command": "${{ matrix.command }}", "git_ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' > command.json
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh command.json '${{ secrets.JOB_SECRET }}')"
|
||||
curl -v -k \
|
||||
--fail-with-body \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Slab-Repository: ${{ github.repository }}" \
|
||||
-H "X-Slab-Command: start_aws" \
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@ target/
|
||||
|
||||
**/Cargo.lock
|
||||
**/*.bin
|
||||
|
||||
# Some of our bench outputs
|
||||
/tfhe/benchmarks_parameters
|
||||
|
||||
@@ -7,3 +7,9 @@ lto = "fat"
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
# Compiles much faster for tests and allows reasonable performance for iterating
|
||||
[profile.devo]
|
||||
inherits = "dev"
|
||||
opt-level = 3
|
||||
lto = "off"
|
||||
|
||||
58
Makefile
58
Makefile
@@ -6,6 +6,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)
|
||||
CARGO_PROFILE?=release
|
||||
MIN_RUST_VERSION:=1.65
|
||||
AVX512_SUPPORT?=OFF
|
||||
WASM_RUSTFLAGS:=
|
||||
@@ -119,54 +120,58 @@ clippy_all_targets:
|
||||
clippy_all: clippy clippy_boolean clippy_shortint clippy_integer clippy_all_targets clippy_c_api \
|
||||
clippy_js_wasm_api clippy_tasks clippy_core
|
||||
|
||||
.PHONY: clippy_fast # Run main clippy targets
|
||||
clippy_fast: clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core
|
||||
|
||||
.PHONY: gen_key_cache # Run the script to generate keys and cache them for shortint tests
|
||||
gen_key_cache: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --profile $(CARGO_PROFILE) \
|
||||
--example generates_test_keys \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe
|
||||
|
||||
.PHONY: build_core # Build core_crypto without experimental features
|
||||
build_core: install_rs_build_toolchain install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE) -p tfhe
|
||||
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),$(AVX512_FEATURE) -p tfhe; \
|
||||
fi
|
||||
|
||||
.PHONY: build_core_experimental # Build core_crypto with experimental features
|
||||
build_core_experimental: install_rs_build_toolchain install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental -p tfhe
|
||||
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe; \
|
||||
fi
|
||||
|
||||
.PHONY: build_boolean # Build with boolean enabled
|
||||
build_boolean: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe --all-targets
|
||||
|
||||
.PHONY: build_shortint # Build with shortint enabled
|
||||
build_shortint: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint -p tfhe --all-targets
|
||||
|
||||
.PHONY: build_integer # Build with integer enabled
|
||||
build_integer: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),integer -p tfhe --all-targets
|
||||
|
||||
.PHONY: build_tfhe_full # Build with boolean, shortint and integer enabled
|
||||
build_tfhe_full: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer -p tfhe --all-targets
|
||||
|
||||
.PHONY: build_c_api # Build the C API for boolean and shortint
|
||||
build_c_api: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api -p tfhe --all-targets
|
||||
build_c_api: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api \
|
||||
-p tfhe
|
||||
|
||||
.PHONY: build_web_js_api # Build the js API targeting the web browser
|
||||
build_web_js_api: install_rs_build_toolchain
|
||||
@@ -184,16 +189,16 @@ build_node_js_api: install_rs_build_toolchain
|
||||
|
||||
.PHONY: test_core_crypto # Run the tests of the core_crypto module including experimental ones
|
||||
test_core_crypto: install_rs_build_toolchain install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental -p tfhe -- core_crypto::
|
||||
@if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe -- core_crypto::; \
|
||||
fi
|
||||
|
||||
.PHONY: test_boolean # Run the tests of the boolean module
|
||||
test_boolean: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean::
|
||||
|
||||
.PHONY: test_c_api # Run the tests for the C API
|
||||
@@ -207,7 +212,7 @@ test_shortint_ci: install_rs_build_toolchain install_cargo_nextest
|
||||
|
||||
.PHONY: test_shortint # Run all the tests for shortint
|
||||
test_shortint: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- shortint::
|
||||
|
||||
.PHONY: test_integer_ci # Run the tests for integer ci
|
||||
@@ -217,17 +222,17 @@ test_integer_ci: install_rs_build_toolchain install_cargo_nextest
|
||||
|
||||
.PHONY: test_integer # Run all the tests for integer
|
||||
test_integer: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache -p tfhe -- integer::
|
||||
|
||||
.PHONY: test_typed_api # Run all the tests for typed_api
|
||||
test_typed_api: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe -- typed_api::
|
||||
.PHONY: test_high_level_api # Run all the tests for high_level_api
|
||||
test_high_level_api: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe -- high_level_api::
|
||||
|
||||
.PHONY: test_user_doc # Run tests from the .md documentation
|
||||
test_user_doc: install_rs_build_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release --doc \
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) --doc \
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \
|
||||
-- test_user_docs::
|
||||
|
||||
@@ -279,6 +284,12 @@ test_nodejs_wasm_api: build_node_js_api
|
||||
no_tfhe_typo:
|
||||
@./scripts/no_tfhe_typo.sh
|
||||
|
||||
.PHONY: bench_integer # Run benchmarks for integer
|
||||
bench_integer: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
|
||||
--bench integer-bench \
|
||||
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p tfhe
|
||||
|
||||
.PHONY: bench_shortint # Run benchmarks for shortint
|
||||
bench_shortint: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \
|
||||
@@ -312,6 +323,9 @@ measure_boolean_key_sizes: install_rs_check_toolchain
|
||||
.PHONY: pcc # pcc stands for pre commit checks
|
||||
pcc: no_tfhe_typo check_fmt doc clippy_all check_compile_tests
|
||||
|
||||
.PHONY: fpcc # pcc stands for pre commit checks, the f stands for fast
|
||||
fpcc: no_tfhe_typo check_fmt doc clippy_fast check_compile_tests
|
||||
|
||||
.PHONY: conformance # Automatically fix problems that can be fixed
|
||||
conformance: fmt
|
||||
|
||||
|
||||
31
README.md
31
README.md
@@ -1,29 +1,23 @@
|
||||
<p align="center">
|
||||
<!-- product name logo -->
|
||||
<img width=600 src="https://user-images.githubusercontent.com/86411313/201107820-b1b861be-6b3f-46cc-bccd-ed051201781a.png">
|
||||
<img width=600 src="https://user-images.githubusercontent.com/5758427/231206749-8f146b97-3c5a-4201-8388-3ffa88580415.png">
|
||||
</p>
|
||||
<hr/>
|
||||
<p align="center">
|
||||
<a href="https://docs.zama.ai/tfhe-rs"> 📒 Read documentation</a> | <a href="https://zama.ai/community"> 💛 Community support</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<!-- Version badge using shields.io -->
|
||||
<a href="https://github.com/zama-ai/tfhe-rs/releases">
|
||||
<img src="https://img.shields.io/github/v/release/zama-ai/tfhe-rs?style=flat-square">
|
||||
</a>
|
||||
<!-- Link to docs badge using shields.io -->
|
||||
<a href="https://docs.zama.ai/tfhe-rs">
|
||||
<img src="https://img.shields.io/badge/read-documentation-yellow?style=flat-square">
|
||||
</a>
|
||||
<!-- Community forum badge using shields.io -->
|
||||
<a href="https://community.zama.ai">
|
||||
<img src="https://img.shields.io/badge/community%20forum-online-brightgreen?style=flat-square">
|
||||
</a>
|
||||
<!-- Open source badge using shields.io -->
|
||||
<a href="https://docs.zama.ai/tfhe-rs/developers/contributing">
|
||||
<img src="https://img.shields.io/badge/we're%20open%20source-contributing.md-blue?style=flat-square">
|
||||
</a>
|
||||
<!-- Follow on twitter badge using shields.io -->
|
||||
<a href="https://twitter.com/zama_fhe">
|
||||
<img src="https://img.shields.io/badge/follow-zama_fhe-blue?logo=twitter&style=flat-square">
|
||||
<!-- Zama Bounty Program -->
|
||||
<a href="https://github.com/zama-ai/bounty-program">
|
||||
<img src="https://img.shields.io/badge/Contribute-Zama%20Bounty%20Program-yellow?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
<hr/>
|
||||
|
||||
|
||||
**TFHE-rs** is a pure Rust implementation of TFHE for boolean and integer
|
||||
arithmetics over encrypted data. It includes:
|
||||
@@ -168,6 +162,11 @@ Only approved contributors can send pull requests, so please make sure to get in
|
||||
This library uses several dependencies and we would like to thank the contributors of those
|
||||
libraries.
|
||||
|
||||
## Need support?
|
||||
<a target="_blank" href="https://community.zama.ai">
|
||||
<img src="https://user-images.githubusercontent.com/5758427/231115030-21195b55-2629-4c01-9809-be5059243999.png">
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
This software is distributed under the BSD-3-Clause-Clear license. If you have any questions,
|
||||
|
||||
@@ -42,6 +42,8 @@ parser.add_argument('--key-sizes', dest='key_sizes', action='store_true',
|
||||
parser.add_argument('--throughput', dest='throughput', action='store_true',
|
||||
help='Compute and append number of operations per millisecond and'
|
||||
'operations per dollar')
|
||||
parser.add_argument('--backend', dest='backend', default='cpu',
|
||||
help='Backend on which benchmarks have run')
|
||||
|
||||
|
||||
def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throughput=False,
|
||||
@@ -57,39 +59,95 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
|
||||
dollar
|
||||
:param hardware_hourly_cost: hourly cost of the hardware used in dollar
|
||||
|
||||
:return: :class:`list` of data points
|
||||
:return: tuple of :class:`list` as (data points, parsing failures)
|
||||
"""
|
||||
excluded_directories = ["child_generate", "fork", "parent_generate", "report"]
|
||||
result_values = []
|
||||
parsing_failures = []
|
||||
bench_class = "evaluate"
|
||||
|
||||
for dire in directory.iterdir():
|
||||
if dire.name in excluded_directories or not dire.is_dir():
|
||||
continue
|
||||
for subdir in dire.iterdir():
|
||||
if walk_subdirs:
|
||||
subdir = subdir.joinpath("new")
|
||||
if not subdir.exists():
|
||||
continue
|
||||
if subdir.name == "new":
|
||||
pass
|
||||
else:
|
||||
subdir = subdir.joinpath("new")
|
||||
if not subdir.exists():
|
||||
continue
|
||||
elif subdir.name != "new":
|
||||
continue
|
||||
|
||||
test_name = parse_benchmark_file(subdir)
|
||||
full_name, test_name = parse_benchmark_file(subdir)
|
||||
if test_name is None:
|
||||
parsing_failures.append((full_name, "'function_id' field is null in report"))
|
||||
continue
|
||||
|
||||
try:
|
||||
params, display_name, operator = get_parameters(test_name)
|
||||
except Exception as err:
|
||||
parsing_failures.append((full_name, f"failed to get parameters: {err}"))
|
||||
continue
|
||||
|
||||
for stat_name, value in parse_estimate_file(subdir).items():
|
||||
test_name_parts = list(filter(None, [test_name, stat_name, name_suffix]))
|
||||
result_values.append({"value": value, "test": "_".join(test_name_parts)})
|
||||
result_values.append(
|
||||
_create_point(
|
||||
value,
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"latency",
|
||||
operator,
|
||||
params,
|
||||
display_name=display_name
|
||||
)
|
||||
)
|
||||
|
||||
if stat_name == "mean" and compute_throughput:
|
||||
test_name_parts.append("ops-per-ms")
|
||||
result_values.append({"value": compute_ops_per_millisecond(value),
|
||||
"test": "_".join(test_name_parts)})
|
||||
test_suffix = "ops-per-ms"
|
||||
test_name_parts.append(test_suffix)
|
||||
result_values.append(
|
||||
_create_point(
|
||||
compute_ops_per_millisecond(value),
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"throughput",
|
||||
operator,
|
||||
params,
|
||||
display_name="_".join([display_name, test_suffix])
|
||||
)
|
||||
)
|
||||
test_name_parts.pop()
|
||||
|
||||
if hardware_hourly_cost is not None:
|
||||
test_name_parts.append("ops-per-dollar")
|
||||
result_values.append({
|
||||
"value": compute_ops_per_dollar(value, hardware_hourly_cost),
|
||||
"test": "_".join(test_name_parts)})
|
||||
test_suffix = "ops-per-dollar"
|
||||
test_name_parts.append(test_suffix)
|
||||
result_values.append(
|
||||
_create_point(
|
||||
compute_ops_per_dollar(value, hardware_hourly_cost),
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"throughput",
|
||||
operator,
|
||||
params,
|
||||
display_name="_".join([display_name, test_suffix])
|
||||
)
|
||||
)
|
||||
|
||||
return result_values
|
||||
return result_values, parsing_failures
|
||||
|
||||
|
||||
def _create_point(value, test_name, bench_class, bench_type, operator, params, display_name=None):
|
||||
return {
|
||||
"value": value,
|
||||
"test": test_name,
|
||||
"name": display_name,
|
||||
"class": bench_class,
|
||||
"type": bench_type,
|
||||
"operator": operator,
|
||||
"params": params}
|
||||
|
||||
|
||||
def parse_benchmark_file(directory):
|
||||
@@ -101,7 +159,7 @@ def parse_benchmark_file(directory):
|
||||
:return: name of the test as :class:`str`
|
||||
"""
|
||||
raw_res = _parse_file_to_json(directory, "benchmark.json")
|
||||
return raw_res["full_id"].replace(" ", "_")
|
||||
return raw_res["full_id"], raw_res["function_id"]
|
||||
|
||||
|
||||
def parse_estimate_file(directory):
|
||||
@@ -125,15 +183,51 @@ def parse_key_sizes(result_file):
|
||||
|
||||
:param result_file: results file as :class:`pathlib.Path`
|
||||
|
||||
:return: :class:`list` of data points
|
||||
:return: tuple of :class:`list` as (data points, parsing failures)
|
||||
"""
|
||||
result_values = []
|
||||
parsing_failures = []
|
||||
|
||||
with result_file.open() as csv_file:
|
||||
reader = csv.reader(csv_file)
|
||||
for (test_name, value) in reader:
|
||||
result_values.append({"value": int(value), "test": test_name})
|
||||
try:
|
||||
params, display_name, operator = get_parameters(test_name)
|
||||
except Exception as err:
|
||||
parsing_failures.append((test_name, f"failed to get parameters: {err}"))
|
||||
continue
|
||||
|
||||
return result_values
|
||||
result_values.append({
|
||||
"value": int(value),
|
||||
"test": test_name,
|
||||
"name": display_name,
|
||||
"class": "keygen",
|
||||
"type": "keysize",
|
||||
"operator": operator,
|
||||
"params": params})
|
||||
|
||||
return result_values, parsing_failures
|
||||
|
||||
|
||||
def get_parameters(bench_id):
|
||||
"""
|
||||
Get benchmarks parameters recorded for a given benchmark case.
|
||||
|
||||
:param bench_id: function name used for the benchmark case
|
||||
|
||||
:return: :class:`tuple` as ``(benchmark parameters, display name, operator type)``
|
||||
"""
|
||||
params_dir = pathlib.Path("tfhe", "benchmarks_parameters", bench_id)
|
||||
params = _parse_file_to_json(params_dir, "parameters.json")
|
||||
|
||||
display_name = params.pop("display_name")
|
||||
operator = params.pop("operator_type")
|
||||
|
||||
# Put cryptographic parameters at the same level as the others parameters
|
||||
crypto_params = params.pop("crypto_parameters")
|
||||
params.update(crypto_params)
|
||||
|
||||
return params, display_name, operator
|
||||
|
||||
|
||||
def compute_ops_per_dollar(data_point, product_hourly_cost):
|
||||
@@ -172,6 +266,9 @@ def dump_results(parsed_results, filename, input_args):
|
||||
:param filename: filename for dump file as :class:`pathlib.Path`
|
||||
:param input_args: CLI input arguments
|
||||
"""
|
||||
for point in parsed_results:
|
||||
point["backend"] = input_args.backend
|
||||
|
||||
if input_args.append_results:
|
||||
parsed_content = json.loads(filename.read_text())
|
||||
parsed_content["points"].extend(parsed_results)
|
||||
@@ -219,6 +316,7 @@ if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
check_mandatory_args(args)
|
||||
|
||||
#failures = []
|
||||
raw_results = pathlib.Path(args.results)
|
||||
if not args.key_sizes:
|
||||
print("Parsing benchmark results... ")
|
||||
@@ -234,11 +332,11 @@ if __name__ == "__main__":
|
||||
print(f"Cannot find hardware hourly cost for '{args.hardware}'")
|
||||
sys.exit(1)
|
||||
|
||||
results = recursive_parse(raw_results, args.walk_subdirs, args.name_suffix, args.throughput,
|
||||
hardware_cost)
|
||||
results, failures = recursive_parse(raw_results, args.walk_subdirs, args.name_suffix,
|
||||
args.throughput, hardware_cost)
|
||||
else:
|
||||
print("Parsing key sizes results... ")
|
||||
results = parse_key_sizes(raw_results)
|
||||
results, failures = parse_key_sizes(raw_results)
|
||||
print("Parsing results done")
|
||||
|
||||
output_file = pathlib.Path(args.output_file)
|
||||
@@ -246,3 +344,10 @@ if __name__ == "__main__":
|
||||
dump_results(results, output_file, args)
|
||||
|
||||
print("Done")
|
||||
|
||||
if failures:
|
||||
print("\nParsing failed for some results")
|
||||
print("-------------------------------")
|
||||
for name, error in failures:
|
||||
print(f"[{name}] {error}")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -18,6 +18,11 @@ workflow = "aws_tfhe_integer_tests.yml"
|
||||
profile = "cpu-big"
|
||||
check_run_name = "CPU Integer AWS Tests"
|
||||
|
||||
[command.integer_bench]
|
||||
workflow = "integer_benchmark.yml"
|
||||
profile = "bench"
|
||||
check_run_name = "Integer CPU AWS Benchmarks"
|
||||
|
||||
[command.shortint_bench]
|
||||
workflow = "shortint_benchmark.yml"
|
||||
profile = "bench"
|
||||
|
||||
@@ -28,14 +28,12 @@ if [[ "${BIG_TESTS_INSTANCE}" != TRUE ]]; then
|
||||
# block pbs are too slow for high params
|
||||
# mul_crt_4_4 is extremely flaky (~80% failure)
|
||||
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
|
||||
# same for test_wopbs_bivariate_radix_wopbs_param_message_4_carry_4
|
||||
# test_integer_smart_mul_param_message_4_carry_4 is too slow
|
||||
filter_expression=''\
|
||||
'test(/^integer::.*$/)'\
|
||||
'and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]$/)'\
|
||||
'and not test(~mul_crt_param_message_4_carry_4)'\
|
||||
'and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]$/)'\
|
||||
'and not test(/.*test_wopbs_bivariate_radix_wopbs_param_message_4_carry_4$/)'\
|
||||
'and not test(/.*test_integer_smart_mul_param_message_4_carry_4$/)'
|
||||
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
@@ -57,14 +55,12 @@ else
|
||||
# block pbs are too slow for high params
|
||||
# mul_crt_4_4 is extremely flaky (~80% failure)
|
||||
# test_wopbs_bivariate_crt_wopbs_param_message generate tables that are too big at the moment
|
||||
# same for test_wopbs_bivariate_radix_wopbs_param_message_4_carry_4
|
||||
# test_integer_smart_mul_param_message_4_carry_4 is too slow
|
||||
filter_expression=''\
|
||||
'test(/^integer::.*$/)'\
|
||||
'and not test(/.*_block_pbs(_base)?_param_message_[34]_carry_[34]$/)'\
|
||||
'and not test(~mul_crt_param_message_4_carry_4)'\
|
||||
'and not test(/.*test_wopbs_bivariate_crt_wopbs_param_message_[34]_carry_[34]$/)'\
|
||||
'and not test(/.*test_wopbs_bivariate_radix_wopbs_param_message_4_carry_4$/)'\
|
||||
'and not test(/.*test_integer_smart_mul_param_message_4_carry_4$/)'
|
||||
|
||||
cargo ${1:+"${1}"} nextest run \
|
||||
|
||||
@@ -23,10 +23,12 @@ paste = "1.0.7"
|
||||
lazy_static = { version = "1.4.0" }
|
||||
criterion = "0.4.0"
|
||||
doc-comment = "0.3.3"
|
||||
serde_json = "1.0.94"
|
||||
# Used in user documentation
|
||||
bincode = "1.3.3"
|
||||
fs2 = { version = "0.4.3" }
|
||||
itertools = "0.10.5"
|
||||
num_cpus = "1.15"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = { version = "0.24.3", optional = true }
|
||||
@@ -74,8 +76,17 @@ experimental-force_fft_algo_dif4 = []
|
||||
__c_api = ["cbindgen", "bincode"]
|
||||
boolean-c-api = ["boolean", "__c_api"]
|
||||
shortint-c-api = ["shortint", "__c_api"]
|
||||
high-level-c-api = ["boolean", "shortint", "integer", "__c_api"]
|
||||
|
||||
__wasm_api = ["wasm-bindgen", "js-sys", "console_error_panic_hook", "serde-wasm-bindgen", "getrandom", "getrandom/js", "bincode"]
|
||||
__wasm_api = [
|
||||
"wasm-bindgen",
|
||||
"js-sys",
|
||||
"console_error_panic_hook",
|
||||
"serde-wasm-bindgen",
|
||||
"getrandom",
|
||||
"getrandom/js",
|
||||
"bincode",
|
||||
]
|
||||
boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
|
||||
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
|
||||
|
||||
@@ -157,6 +168,12 @@ path = "benches/keygen/bench.rs"
|
||||
harness = false
|
||||
required-features = ["shortint", "internal-keycache"]
|
||||
|
||||
[[bench]]
|
||||
name = "utilities"
|
||||
path = "benches/utilities.rs"
|
||||
harness = false
|
||||
required-features = ["boolean", "shortint", "integer", "internal-keycache"]
|
||||
|
||||
[[example]]
|
||||
name = "generates_test_keys"
|
||||
required-features = ["shortint", "internal-keycache"]
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use tfhe::boolean::client_key::ClientKey;
|
||||
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
|
||||
@@ -14,7 +18,9 @@ criterion_main!(gates_benches);
|
||||
|
||||
// Put all `bench_function` in one place
|
||||
// so the keygen is only run once per parameters saving time.
|
||||
fn bench_gates(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
let mut bench_group = c.benchmark_group("gates_benches");
|
||||
|
||||
let cks = ClientKey::new(¶ms);
|
||||
let sks = ServerKey::new(&cks);
|
||||
|
||||
@@ -22,32 +28,41 @@ fn bench_gates(c: &mut Criterion, params: BooleanParameters, parameter_name: &st
|
||||
let ct2 = cks.encrypt(false);
|
||||
let ct3 = cks.encrypt(true);
|
||||
|
||||
let id = format!("AND gate {parameter_name}");
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.and(&ct1, &ct2))));
|
||||
let operator = OperatorType::Atomic;
|
||||
|
||||
let id = format!("NAND gate {parameter_name}");
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.nand(&ct1, &ct2))));
|
||||
let id = format!("AND::{parameter_name}");
|
||||
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.and(&ct1, &ct2))));
|
||||
write_to_json(&id, params, parameter_name, "and", &operator);
|
||||
|
||||
let id = format!("OR gate {parameter_name}");
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.or(&ct1, &ct2))));
|
||||
let id = format!("NAND::{parameter_name}");
|
||||
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.nand(&ct1, &ct2))));
|
||||
write_to_json(&id, params, parameter_name, "nand", &operator);
|
||||
|
||||
let id = format!("XOR gate {parameter_name}");
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.xor(&ct1, &ct2))));
|
||||
let id = format!("OR::{parameter_name}");
|
||||
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.or(&ct1, &ct2))));
|
||||
write_to_json(&id, params, parameter_name, "or", &operator);
|
||||
|
||||
let id = format!("XNOR gate {parameter_name}");
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.xnor(&ct1, &ct2))));
|
||||
let id = format!("XOR::{parameter_name}");
|
||||
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.xor(&ct1, &ct2))));
|
||||
write_to_json(&id, params, parameter_name, "xor", &operator);
|
||||
|
||||
let id = format!("NOT gate {parameter_name}");
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.not(&ct1))));
|
||||
let id = format!("XNOR::{parameter_name}");
|
||||
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.xnor(&ct1, &ct2))));
|
||||
write_to_json(&id, params, parameter_name, "xnor", &operator);
|
||||
|
||||
let id = format!("MUX gate {parameter_name}");
|
||||
c.bench_function(&id, |b| b.iter(|| black_box(sks.mux(&ct1, &ct2, &ct3))));
|
||||
let id = format!("NOT::{parameter_name}");
|
||||
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.not(&ct1))));
|
||||
write_to_json(&id, params, parameter_name, "not", &operator);
|
||||
|
||||
let id = format!("MUX::{parameter_name}");
|
||||
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.mux(&ct1, &ct2, &ct3))));
|
||||
write_to_json(&id, params, parameter_name, "mux", &operator);
|
||||
}
|
||||
|
||||
fn bench_default_parameters(c: &mut Criterion) {
|
||||
bench_gates(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
|
||||
benchs(c, DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS");
|
||||
}
|
||||
|
||||
fn bench_tfhe_lib_parameters(c: &mut Criterion) {
|
||||
bench_gates(c, TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS");
|
||||
benchs(c, TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS");
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Syn
|
||||
thread_count,
|
||||
) = get_bench_params::<Scalar>();
|
||||
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
while input_lwe_dimension.0 % grouping_factor.0 != 0 {
|
||||
input_lwe_dimension = LweDimension(input_lwe_dimension.0 + 1);
|
||||
}
|
||||
@@ -109,16 +111,22 @@ fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Syn
|
||||
&input_lwe_secret_key,
|
||||
Plaintext(Scalar::ZERO),
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator =
|
||||
GlweCiphertext::new(Scalar::ZERO, glwe_dimension.to_glwe_size(), polynomial_size);
|
||||
let accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut out_pbs_ct = LweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let id = format!("Multi Bit PBS {}", Scalar::BITS);
|
||||
@@ -155,6 +163,8 @@ fn pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
|
||||
_,
|
||||
) = get_bench_params::<Scalar>();
|
||||
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
@@ -188,16 +198,22 @@ fn pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
|
||||
&input_lwe_secret_key,
|
||||
Plaintext(Scalar::ZERO),
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator =
|
||||
GlweCiphertext::new(Scalar::ZERO, glwe_dimension.to_glwe_size(), polynomial_size);
|
||||
let accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut out_pbs_ct = LweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let id = format!("PBS {}", Scalar::BITS);
|
||||
@@ -265,15 +281,21 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
|
||||
&input_lwe_secret_key,
|
||||
Plaintext(Scalar::ZERO),
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator =
|
||||
GlweCiphertext::new(Scalar::ZERO, glwe_dimension.to_glwe_size(), polynomial_size);
|
||||
let accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut out_pbs_ct = LweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
|
||||
@@ -7,10 +7,7 @@ fn sqr(x: f64) -> f64 {
|
||||
|
||||
fn criterion_bench(c: &mut Criterion) {
|
||||
{
|
||||
use tfhe::core_crypto::fft_impl::fft128::crypto::bootstrap::{
|
||||
bootstrap_scratch, Fourier128LweBootstrapKey,
|
||||
};
|
||||
use tfhe::core_crypto::fft_impl::fft128::math::fft::Fft128;
|
||||
use tfhe::core_crypto::fft_impl::fft128::crypto::bootstrap::bootstrap_scratch;
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
type Scalar = u128;
|
||||
|
||||
@@ -20,42 +17,32 @@ fn criterion_bench(c: &mut Criterion) {
|
||||
let lwe_modular_std_dev = StandardDev(sqr(0.000007069849454709433));
|
||||
let pbs_base_log = DecompositionBaseLog(23);
|
||||
let pbs_level = DecompositionLevelCount(1);
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
// Request the best seeder possible, starting with hardware entropy sources and falling back
|
||||
// to /dev/random on Unix systems if enabled via cargo features
|
||||
let mut boxed_seeder = new_seeder();
|
||||
// Get a mutable reference to the seeder as a trait object from the Box returned by
|
||||
// new_seeder
|
||||
let seeder = boxed_seeder.as_mut();
|
||||
|
||||
// Create a generator which uses a CSPRNG to generate secret keys
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
// Create a generator which uses two CSPRNGs to generate public masks and secret encryption
|
||||
// noise
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
|
||||
// Generate an LweSecretKey with binary coefficients
|
||||
let small_lwe_sk =
|
||||
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
|
||||
|
||||
// Generate a GlweSecretKey with binary coefficients
|
||||
let glwe_sk = GlweSecretKey::<Vec<Scalar>>::generate_new_binary(
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
|
||||
// Create a copy of the GlweSecretKey re-interpreted as an LweSecretKey
|
||||
let big_lwe_sk = glwe_sk.into_lwe_secret_key();
|
||||
|
||||
// Create the empty bootstrapping key in the Fourier domain
|
||||
let fourier_bsk = Fourier128LweBootstrapKey::new(
|
||||
small_lwe_dimension,
|
||||
polynomial_size,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
pbs_base_log,
|
||||
pbs_level,
|
||||
);
|
||||
@@ -63,34 +50,34 @@ fn criterion_bench(c: &mut Criterion) {
|
||||
let fft = Fft128::new(polynomial_size);
|
||||
let fft = fft.as_view();
|
||||
|
||||
// We don't need the standard bootstrapping key anymore
|
||||
|
||||
// Our 4 bits message space
|
||||
let message_modulus: Scalar = 1 << 4;
|
||||
|
||||
// Our input message
|
||||
let input_message: Scalar = 3;
|
||||
|
||||
// Delta used to encode 4 bits of message + a bit of padding on Scalar
|
||||
let delta: Scalar = (1 << (Scalar::BITS - 1)) / message_modulus;
|
||||
|
||||
// Apply our encoding
|
||||
let plaintext = Plaintext(input_message * delta);
|
||||
|
||||
// Allocate a new LweCiphertext and encrypt our plaintext
|
||||
let lwe_ciphertext_in: LweCiphertextOwned<Scalar> = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
&small_lwe_sk,
|
||||
plaintext,
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator: GlweCiphertextOwned<Scalar> =
|
||||
GlweCiphertextOwned::new(Scalar::ONE, glwe_dimension.to_glwe_size(), polynomial_size);
|
||||
let accumulator: GlweCiphertextOwned<Scalar> = GlweCiphertextOwned::new(
|
||||
Scalar::ONE,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut pbs_multiplication_ct: LweCiphertext<Vec<Scalar>> =
|
||||
LweCiphertext::new(0, big_lwe_sk.lwe_dimension().to_lwe_size());
|
||||
let mut pbs_out: LweCiphertext<Vec<Scalar>> = LweCiphertext::new(
|
||||
0,
|
||||
big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let mut buf = vec![
|
||||
0u8;
|
||||
@@ -106,7 +93,7 @@ fn criterion_bench(c: &mut Criterion) {
|
||||
c.bench_function("pbs128", |b| {
|
||||
b.iter(|| {
|
||||
fourier_bsk.bootstrap(
|
||||
&mut pbs_multiplication_ct,
|
||||
&mut pbs_out,
|
||||
&lwe_ciphertext_in,
|
||||
&accumulator,
|
||||
fft,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_PARAMETERS};
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
@@ -24,8 +28,8 @@ const SHORTINT_BENCH_PARAMS: [Parameters; 15] = [
|
||||
];
|
||||
|
||||
const BOOLEAN_BENCH_PARAMS: [(&str, BooleanParameters); 2] = [
|
||||
("boolean_default_params", DEFAULT_PARAMETERS),
|
||||
("boolean_tfhe_lib_params", TFHE_LIB_PARAMETERS),
|
||||
("BOOLEAN_DEFAULT_PARAMS", DEFAULT_PARAMETERS),
|
||||
("BOOLEAN_TFHE_LIB_PARAMS", TFHE_LIB_PARAMETERS),
|
||||
];
|
||||
|
||||
criterion_group!(
|
||||
@@ -34,53 +38,19 @@ criterion_group!(
|
||||
targets = mem_optimized_pbs::<u64>, mem_optimized_pbs::<u32>
|
||||
);
|
||||
|
||||
criterion_main!(pbs_group);
|
||||
criterion_group!(
|
||||
name = multi_bit_pbs_group;
|
||||
config = Criterion::default().sample_size(2000);
|
||||
targets = multi_bit_pbs::<u64>, multi_bit_pbs::<u32>
|
||||
);
|
||||
|
||||
struct BenchmarkPbsParameters {
|
||||
input_lwe_dimension: LweDimension,
|
||||
lwe_modular_std_dev: StandardDev,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
glwe_dimension: GlweDimension,
|
||||
polynomial_size: PolynomialSize,
|
||||
}
|
||||
criterion_main!(pbs_group, multi_bit_pbs_group);
|
||||
|
||||
impl From<BooleanParameters> for BenchmarkPbsParameters {
|
||||
fn from(params: BooleanParameters) -> Self {
|
||||
BenchmarkPbsParameters {
|
||||
input_lwe_dimension: params.lwe_dimension,
|
||||
lwe_modular_std_dev: params.lwe_modular_std_dev,
|
||||
decomp_base_log: params.pbs_base_log,
|
||||
decomp_level_count: params.pbs_level,
|
||||
glwe_dimension: params.glwe_dimension,
|
||||
polynomial_size: params.polynomial_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Parameters> for BenchmarkPbsParameters {
|
||||
fn from(params: Parameters) -> Self {
|
||||
BenchmarkPbsParameters {
|
||||
input_lwe_dimension: params.lwe_dimension,
|
||||
lwe_modular_std_dev: params.lwe_modular_std_dev,
|
||||
decomp_base_log: params.pbs_base_log,
|
||||
decomp_level_count: params.pbs_level,
|
||||
glwe_dimension: params.glwe_dimension,
|
||||
polynomial_size: params.polynomial_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn benchmark_parameters<Scalar: Numeric>() -> Vec<(String, BenchmarkPbsParameters)> {
|
||||
fn benchmark_parameters<Scalar: Numeric>() -> Vec<(String, CryptoParametersRecord)> {
|
||||
if Scalar::BITS == 64 {
|
||||
SHORTINT_BENCH_PARAMS
|
||||
.iter()
|
||||
.map(|params| {
|
||||
(
|
||||
format!("shortint_{}", params.name().to_lowercase()),
|
||||
params.to_owned().into(),
|
||||
)
|
||||
})
|
||||
.map(|params| (params.name(), params.to_owned().into()))
|
||||
.collect()
|
||||
} else if Scalar::BITS == 32 {
|
||||
BOOLEAN_BENCH_PARAMS
|
||||
@@ -92,7 +62,53 @@ fn benchmark_parameters<Scalar: Numeric>() -> Vec<(String, BenchmarkPbsParameter
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_bit_benchmark_parameters<Scalar: Numeric>(
|
||||
) -> Vec<(String, (CryptoParametersRecord, LweBskGroupingFactor))> {
|
||||
if Scalar::BITS == 64 {
|
||||
vec![
|
||||
(
|
||||
"4_bits_multi_bit_group_2".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(788)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.000003871078133364534)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(22)),
|
||||
pbs_level: Some(DecompositionLevelCount(1)),
|
||||
glwe_dimension: Some(GlweDimension(2)),
|
||||
glwe_modular_std_dev: Some(StandardDev(0.0000000000000003152931493498455)),
|
||||
polynomial_size: Some(PolynomialSize(1024)),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(2),
|
||||
),
|
||||
),
|
||||
(
|
||||
"4_bits_multi_bit_group_3".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(789)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.0000038003596741624174)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(22)),
|
||||
pbs_level: Some(DecompositionLevelCount(1)),
|
||||
glwe_dimension: Some(GlweDimension(2)),
|
||||
glwe_modular_std_dev: Some(StandardDev(0.0000000000000003152931493498455)),
|
||||
polynomial_size: Some(PolynomialSize(1024)),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(3),
|
||||
),
|
||||
),
|
||||
]
|
||||
} else {
|
||||
// For now there are no parameters available to test multi bit PBS on 32 bits.
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion) {
|
||||
let bench_name = "PBS_mem-optimized";
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
@@ -104,44 +120,47 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
|
||||
for (name, params) in benchmark_parameters::<Scalar>().iter() {
|
||||
// Create the LweSecretKey
|
||||
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
|
||||
params.input_lwe_dimension,
|
||||
params.lwe_dimension.unwrap(),
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
|
||||
allocate_and_generate_new_binary_glwe_secret_key(
|
||||
params.glwe_dimension,
|
||||
params.polynomial_size,
|
||||
params.glwe_dimension.unwrap(),
|
||||
params.polynomial_size.unwrap(),
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
|
||||
|
||||
// Create the empty bootstrapping key in the Fourier domain
|
||||
let fourier_bsk = FourierLweBootstrapKey::new(
|
||||
params.input_lwe_dimension,
|
||||
params.glwe_dimension.to_glwe_size(),
|
||||
params.polynomial_size,
|
||||
params.decomp_base_log,
|
||||
params.decomp_level_count,
|
||||
params.lwe_dimension.unwrap(),
|
||||
params.glwe_dimension.unwrap().to_glwe_size(),
|
||||
params.polynomial_size.unwrap(),
|
||||
params.pbs_base_log.unwrap(),
|
||||
params.pbs_level.unwrap(),
|
||||
);
|
||||
|
||||
// Allocate a new LweCiphertext and encrypt our plaintext
|
||||
let lwe_ciphertext_in: LweCiphertextOwned<Scalar> = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
&input_lwe_secret_key,
|
||||
Plaintext(Scalar::ZERO),
|
||||
params.lwe_modular_std_dev,
|
||||
params.lwe_modular_std_dev.unwrap(),
|
||||
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
params.glwe_dimension.to_glwe_size(),
|
||||
params.polynomial_size,
|
||||
params.glwe_dimension.unwrap().to_glwe_size(),
|
||||
params.polynomial_size.unwrap(),
|
||||
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut out_pbs_ct = LweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
@@ -159,9 +178,9 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
|
||||
let id = format!("PBS_mem-optimized_{name}");
|
||||
let id = format!("{bench_name}_{name}");
|
||||
{
|
||||
c.bench_function(&id, |b| {
|
||||
bench_group.bench_function(&id, |b| {
|
||||
b.iter(|| {
|
||||
programmable_bootstrap_lwe_ciphertext_mem_optimized(
|
||||
&lwe_ciphertext_in,
|
||||
@@ -175,5 +194,90 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
write_to_json(&id, *params, name, "pbs", &OperatorType::Atomic);
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Sync>(
|
||||
c: &mut Criterion,
|
||||
) {
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
// computations
|
||||
// Define parameters for LweBootstrapKey creation
|
||||
|
||||
let bench_name = "multi_bits_PBS";
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
for (name, (params, grouping_factor)) in multi_bit_benchmark_parameters::<Scalar>().iter() {
|
||||
// Create the LweSecretKey
|
||||
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
|
||||
params.lwe_dimension.unwrap(),
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_glwe_secret_key: GlweSecretKeyOwned<Scalar> =
|
||||
allocate_and_generate_new_binary_glwe_secret_key(
|
||||
params.glwe_dimension.unwrap(),
|
||||
params.polynomial_size.unwrap(),
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_lwe_secret_key = output_glwe_secret_key.into_lwe_secret_key();
|
||||
|
||||
let multi_bit_bsk = FourierLweMultiBitBootstrapKey::new(
|
||||
params.lwe_dimension.unwrap(),
|
||||
params.glwe_dimension.unwrap().to_glwe_size(),
|
||||
params.polynomial_size.unwrap(),
|
||||
params.pbs_base_log.unwrap(),
|
||||
params.pbs_level.unwrap(),
|
||||
*grouping_factor,
|
||||
);
|
||||
|
||||
// Allocate a new LweCiphertext and encrypt our plaintext
|
||||
let lwe_ciphertext_in = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
&input_lwe_secret_key,
|
||||
Plaintext(Scalar::ZERO),
|
||||
params.lwe_modular_std_dev.unwrap(),
|
||||
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
params.glwe_dimension.unwrap().to_glwe_size(),
|
||||
params.polynomial_size.unwrap(),
|
||||
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut out_pbs_ct = LweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
output_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
tfhe::core_crypto::prelude::CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
let id = format!("{bench_name}_{name}_parallelized");
|
||||
bench_group.bench_function(&id, |b| {
|
||||
b.iter(|| {
|
||||
multi_bit_programmable_bootstrap_lwe_ciphertext(
|
||||
&lwe_ciphertext_in,
|
||||
&mut out_pbs_ct,
|
||||
&accumulator.as_view(),
|
||||
&multi_bit_bsk,
|
||||
// Leave one thread to the OS and one for the ext product loop
|
||||
ThreadCount(2.max(num_cpus::get_physical() - 2)),
|
||||
);
|
||||
black_box(&mut out_pbs_ct);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(&id, *params, name, "pbs", &OperatorType::Atomic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use itertools::iproduct;
|
||||
use rand::Rng;
|
||||
@@ -19,15 +23,16 @@ use tfhe::shortint::parameters::{
|
||||
/// in radix decomposition
|
||||
struct ParamsAndNumBlocksIter {
|
||||
params_and_bit_sizes:
|
||||
itertools::Product<IntoIter<tfhe::shortint::Parameters, 3>, IntoIter<usize, 7>>,
|
||||
itertools::Product<IntoIter<tfhe::shortint::Parameters, 1>, IntoIter<usize, 7>>,
|
||||
}
|
||||
|
||||
impl Default for ParamsAndNumBlocksIter {
|
||||
fn default() -> Self {
|
||||
const PARAMS: [tfhe::shortint::Parameters; 3] = [
|
||||
// FIXME One set of parameter is tested since we want to benchmark only quickest operations.
|
||||
const PARAMS: [tfhe::shortint::Parameters; 1] = [
|
||||
PARAM_MESSAGE_2_CARRY_2,
|
||||
PARAM_MESSAGE_3_CARRY_3,
|
||||
PARAM_MESSAGE_4_CARRY_4,
|
||||
// PARAM_MESSAGE_3_CARRY_3,
|
||||
// PARAM_MESSAGE_4_CARRY_4,
|
||||
];
|
||||
const BIT_SIZES: [usize; 7] = [8, 16, 32, 40, 64, 128, 256];
|
||||
let params_and_bit_sizes = iproduct!(PARAMS, BIT_SIZES);
|
||||
@@ -48,9 +53,14 @@ impl Iterator for ParamsAndNumBlocksIter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a binary operation
|
||||
fn bench_server_key_binary_function<F>(c: &mut Criterion, bench_name: &str, binary_op: F)
|
||||
where
|
||||
/// Base function to bench a server key function that is a binary operation, input ciphertexts will
|
||||
/// contain non zero carries
|
||||
fn bench_server_key_binary_function_dirty_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, &mut RadixCiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
@@ -62,7 +72,7 @@ where
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{param_name}/{bit_size}_bits");
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
@@ -102,24 +112,98 @@ where
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a unary operation
|
||||
fn bench_server_key_unary_function<F>(c: &mut Criterion, group_name: &str, unary_fn: F)
|
||||
where
|
||||
/// Base function to bench a server key function that is a binary operation, input ciphertext will
|
||||
/// contain only zero carries
|
||||
fn bench_server_key_binary_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, &mut RadixCiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_two_values = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_0 = cks.encrypt_radix(clear_0, num_block);
|
||||
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_1 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_1 = cks.encrypt_radix(clear_1, num_block);
|
||||
|
||||
(ct_0, ct_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_two_values,
|
||||
|(mut ct_0, mut ct_1)| {
|
||||
binary_op(&sks, &mut ct_0, &mut ct_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
/// Base function to bench a server key function that is a unary operation, input ciphertexts will
|
||||
/// contain non zero carries
|
||||
fn bench_server_key_unary_function_dirty_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_fn: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(group_name);
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{param_name}/{bit_size}_bits");
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
@@ -155,22 +239,158 @@ where
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn bench_server_key_binary_scalar_function<F>(c: &mut Criterion, bench_name: &str, binary_op: F)
|
||||
where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, u64),
|
||||
/// Base function to bench a server key function that is a unary operation, input ciphertext will
|
||||
/// contain only zero carries
|
||||
fn bench_server_key_unary_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_fn: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{param_name}/{bit_size}_bits");
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_one_value = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
|
||||
cks.encrypt_radix(clear_0, num_block)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_one_value,
|
||||
|mut ct_0| {
|
||||
unary_fn(&sks, &mut ct_0);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn bench_server_key_binary_scalar_function_dirty_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, u64),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
let encrypt_one_value = || {
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
|
||||
let clear_0 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let mut ct_0 = cks.encrypt_radix(clear_0, num_block);
|
||||
|
||||
// Raise the degree, so as to ensure worst case path in operations
|
||||
let mut carry_mod = param.carry_modulus.0;
|
||||
while carry_mod > 0 {
|
||||
// Raise the degree, so as to ensure worst case path in operations
|
||||
let clearlow = rng.gen::<u128>();
|
||||
let clearhigh = rng.gen::<u128>();
|
||||
let clear_2 = tfhe::integer::U256::from((clearlow, clearhigh));
|
||||
let ct_2 = cks.encrypt_radix(clear_2, num_block);
|
||||
sks.unchecked_add_assign(&mut ct_0, &ct_2);
|
||||
|
||||
carry_mod -= 1;
|
||||
}
|
||||
|
||||
let clear_1 = rng.gen::<u64>();
|
||||
|
||||
(ct_0, clear_1)
|
||||
};
|
||||
|
||||
b.iter_batched(
|
||||
encrypt_one_value,
|
||||
|(mut ct_0, clear_1)| {
|
||||
binary_op(&sks, &mut ct_0, clear_1);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
fn bench_server_key_binary_scalar_function_clean_inputs<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut RadixCiphertextBig, u64),
|
||||
{
|
||||
let mut bench_group = c.benchmark_group(bench_name);
|
||||
bench_group
|
||||
.sample_size(15)
|
||||
.measurement_time(std::time::Duration::from_secs(60));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (param, num_block, bit_size) in ParamsAndNumBlocksIter::default() {
|
||||
let param_name = param.name();
|
||||
|
||||
let bench_id = format!("{bench_name}::{param_name}::{bit_size}_bits");
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
let (cks, sks) = KEY_CACHE.get_from_params(param);
|
||||
|
||||
@@ -194,30 +414,54 @@ where
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
}
|
||||
|
||||
macro_rules! define_server_key_bench_unary_fn (
|
||||
($server_key_method:ident) => {
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_unary_function(
|
||||
bench_server_key_unary_function_dirty_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs| {
|
||||
server_key.$server_key_method(lhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_unary_default_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_unary_function_clean_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs| {
|
||||
server_key.$server_key_method(lhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_fn (
|
||||
($server_key_method:ident) => {
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_function(
|
||||
bench_server_key_binary_function_dirty_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
@@ -225,12 +469,27 @@ macro_rules! define_server_key_bench_fn (
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_default_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_function_clean_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_scalar_fn (
|
||||
($server_key_method:ident) => {
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_function(
|
||||
bench_server_key_binary_scalar_function_dirty_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
@@ -238,79 +497,157 @@ macro_rules! define_server_key_bench_scalar_fn (
|
||||
}
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(smart_add);
|
||||
define_server_key_bench_fn!(smart_sub);
|
||||
define_server_key_bench_fn!(smart_mul);
|
||||
define_server_key_bench_fn!(smart_bitand);
|
||||
define_server_key_bench_fn!(smart_bitor);
|
||||
define_server_key_bench_fn!(smart_bitxor);
|
||||
macro_rules! define_server_key_bench_scalar_default_fn (
|
||||
(method_name: $server_key_method:ident, display_name:$name:ident) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_function_clean_inputs(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
server_key.$server_key_method(lhs, rhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(smart_add_parallelized);
|
||||
define_server_key_bench_fn!(smart_sub_parallelized);
|
||||
define_server_key_bench_fn!(smart_mul_parallelized);
|
||||
define_server_key_bench_fn!(smart_bitand_parallelized);
|
||||
define_server_key_bench_fn!(smart_bitxor_parallelized);
|
||||
define_server_key_bench_fn!(smart_bitor_parallelized);
|
||||
define_server_key_bench_fn!(method_name: smart_add, display_name: add);
|
||||
define_server_key_bench_fn!(method_name: smart_sub, display_name: sub);
|
||||
define_server_key_bench_fn!(method_name: smart_mul, display_name: mul);
|
||||
define_server_key_bench_fn!(method_name: smart_bitand, display_name: bitand);
|
||||
define_server_key_bench_fn!(method_name: smart_bitor, display_name: bitor);
|
||||
define_server_key_bench_fn!(method_name: smart_bitxor, display_name: bitxor);
|
||||
|
||||
define_server_key_bench_fn!(unchecked_add);
|
||||
define_server_key_bench_fn!(unchecked_sub);
|
||||
define_server_key_bench_fn!(unchecked_mul);
|
||||
define_server_key_bench_fn!(unchecked_bitand);
|
||||
define_server_key_bench_fn!(unchecked_bitor);
|
||||
define_server_key_bench_fn!(unchecked_bitxor);
|
||||
define_server_key_bench_fn!(method_name: smart_add_parallelized, display_name: add);
|
||||
define_server_key_bench_fn!(method_name: smart_sub_parallelized, display_name: sub);
|
||||
define_server_key_bench_fn!(method_name: smart_mul_parallelized, display_name: mul);
|
||||
define_server_key_bench_fn!(method_name: smart_bitand_parallelized, display_name: bitand);
|
||||
define_server_key_bench_fn!(method_name: smart_bitxor_parallelized, display_name: bitxor);
|
||||
define_server_key_bench_fn!(method_name: smart_bitor_parallelized, display_name: bitor);
|
||||
|
||||
define_server_key_bench_fn!(unchecked_mul_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_bitand_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_bitor_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_bitxor_parallelized);
|
||||
define_server_key_bench_default_fn!(method_name: add_parallelized, display_name: add);
|
||||
define_server_key_bench_default_fn!(method_name: sub_parallelized, display_name: sub);
|
||||
define_server_key_bench_default_fn!(method_name: mul_parallelized, display_name: mul);
|
||||
define_server_key_bench_default_fn!(method_name: bitand_parallelized, display_name: bitand);
|
||||
define_server_key_bench_default_fn!(method_name: bitxor_parallelized, display_name: bitxor);
|
||||
define_server_key_bench_default_fn!(method_name: bitor_parallelized, display_name: bitor);
|
||||
|
||||
define_server_key_bench_scalar_fn!(smart_scalar_add);
|
||||
define_server_key_bench_scalar_fn!(smart_scalar_sub);
|
||||
define_server_key_bench_scalar_fn!(smart_scalar_mul);
|
||||
define_server_key_bench_fn!(method_name: unchecked_add, display_name: add);
|
||||
define_server_key_bench_fn!(method_name: unchecked_sub, display_name: sub);
|
||||
define_server_key_bench_fn!(method_name: unchecked_mul, display_name: mul);
|
||||
define_server_key_bench_fn!(method_name: unchecked_bitand, display_name: bitand);
|
||||
define_server_key_bench_fn!(method_name: unchecked_bitor, display_name: bitor);
|
||||
define_server_key_bench_fn!(method_name: unchecked_bitxor, display_name: bitxor);
|
||||
|
||||
define_server_key_bench_scalar_fn!(smart_scalar_add_parallelized);
|
||||
define_server_key_bench_scalar_fn!(smart_scalar_sub_parallelized);
|
||||
define_server_key_bench_scalar_fn!(smart_scalar_mul_parallelized);
|
||||
define_server_key_bench_fn!(method_name: unchecked_mul_parallelized, display_name: mul);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_bitand_parallelized,
|
||||
display_name: bitand
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_bitor_parallelized,
|
||||
display_name: bitor
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_bitxor_parallelized,
|
||||
display_name: bitxor
|
||||
);
|
||||
|
||||
define_server_key_bench_scalar_fn!(unchecked_scalar_add);
|
||||
define_server_key_bench_scalar_fn!(unchecked_scalar_sub);
|
||||
define_server_key_bench_scalar_fn!(unchecked_small_scalar_mul);
|
||||
define_server_key_bench_scalar_fn!(method_name: smart_scalar_add, display_name: add);
|
||||
define_server_key_bench_scalar_fn!(method_name: smart_scalar_sub, display_name: sub);
|
||||
define_server_key_bench_scalar_fn!(method_name: smart_scalar_mul, display_name: mul);
|
||||
|
||||
define_server_key_bench_unary_fn!(smart_neg);
|
||||
define_server_key_bench_unary_fn!(full_propagate);
|
||||
define_server_key_bench_unary_fn!(full_propagate_parallelized);
|
||||
define_server_key_bench_scalar_fn!(
|
||||
method_name: smart_scalar_add_parallelized,
|
||||
display_name: add
|
||||
);
|
||||
define_server_key_bench_scalar_fn!(
|
||||
method_name: smart_scalar_sub_parallelized,
|
||||
display_name: sub
|
||||
);
|
||||
define_server_key_bench_scalar_fn!(
|
||||
method_name: smart_scalar_mul_parallelized,
|
||||
display_name: mul
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(unchecked_max);
|
||||
define_server_key_bench_fn!(unchecked_min);
|
||||
define_server_key_bench_fn!(unchecked_eq);
|
||||
define_server_key_bench_fn!(unchecked_lt);
|
||||
define_server_key_bench_fn!(unchecked_le);
|
||||
define_server_key_bench_fn!(unchecked_gt);
|
||||
define_server_key_bench_fn!(unchecked_ge);
|
||||
define_server_key_bench_scalar_default_fn!(method_name: scalar_add_parallelized, display_name: add);
|
||||
define_server_key_bench_scalar_default_fn!(method_name: scalar_sub_parallelized, display_name: sub);
|
||||
define_server_key_bench_scalar_default_fn!(method_name: scalar_mul_parallelized, display_name: mul);
|
||||
|
||||
define_server_key_bench_fn!(unchecked_max_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_min_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_eq_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_lt_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_le_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_gt_parallelized);
|
||||
define_server_key_bench_fn!(unchecked_ge_parallelized);
|
||||
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_add, display_name: add);
|
||||
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_sub, display_name: sub);
|
||||
define_server_key_bench_scalar_fn!(method_name: unchecked_small_scalar_mul, display_name: mul);
|
||||
|
||||
define_server_key_bench_fn!(smart_max);
|
||||
define_server_key_bench_fn!(smart_min);
|
||||
define_server_key_bench_fn!(smart_eq);
|
||||
define_server_key_bench_fn!(smart_lt);
|
||||
define_server_key_bench_fn!(smart_le);
|
||||
define_server_key_bench_fn!(smart_gt);
|
||||
define_server_key_bench_fn!(smart_ge);
|
||||
define_server_key_bench_unary_fn!(method_name: smart_neg, display_name: negation);
|
||||
define_server_key_bench_unary_fn!(method_name: smart_neg_parallelized, display_name: negation);
|
||||
define_server_key_bench_unary_default_fn!(method_name: neg_parallelized, display_name: negation);
|
||||
|
||||
define_server_key_bench_fn!(smart_max_parallelized);
|
||||
define_server_key_bench_fn!(smart_min_parallelized);
|
||||
define_server_key_bench_fn!(smart_eq_parallelized);
|
||||
define_server_key_bench_fn!(smart_lt_parallelized);
|
||||
define_server_key_bench_fn!(smart_le_parallelized);
|
||||
define_server_key_bench_fn!(smart_gt_parallelized);
|
||||
define_server_key_bench_fn!(smart_ge_parallelized);
|
||||
define_server_key_bench_unary_fn!(method_name: full_propagate, display_name: carry_propagation);
|
||||
define_server_key_bench_unary_fn!(
|
||||
method_name: full_propagate_parallelized,
|
||||
display_name: carry_propagation
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(method_name: unchecked_max, display_name: max);
|
||||
define_server_key_bench_fn!(method_name: unchecked_min, display_name: min);
|
||||
define_server_key_bench_fn!(method_name: unchecked_eq, display_name: equal);
|
||||
define_server_key_bench_fn!(method_name: unchecked_lt, display_name: less_than);
|
||||
define_server_key_bench_fn!(method_name: unchecked_le, display_name: less_or_equal);
|
||||
define_server_key_bench_fn!(method_name: unchecked_gt, display_name: greater_than);
|
||||
define_server_key_bench_fn!(method_name: unchecked_ge, display_name: greater_or_equal);
|
||||
|
||||
define_server_key_bench_fn!(method_name: unchecked_max_parallelized, display_name: max);
|
||||
define_server_key_bench_fn!(method_name: unchecked_min_parallelized, display_name: min);
|
||||
define_server_key_bench_fn!(method_name: unchecked_eq_parallelized, display_name: equal);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_lt_parallelized,
|
||||
display_name: less_than
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_le_parallelized,
|
||||
display_name: less_or_equal
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_gt_parallelized,
|
||||
display_name: greater_than
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_ge_parallelized,
|
||||
display_name: greater_or_equal
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(method_name: smart_max, display_name: max);
|
||||
define_server_key_bench_fn!(method_name: smart_min, display_name: min);
|
||||
define_server_key_bench_fn!(method_name: smart_eq, display_name: equal);
|
||||
define_server_key_bench_fn!(method_name: smart_lt, display_name: less_than);
|
||||
define_server_key_bench_fn!(method_name: smart_le, display_name: less_or_equal);
|
||||
define_server_key_bench_fn!(method_name: smart_gt, display_name: greater_than);
|
||||
define_server_key_bench_fn!(method_name: smart_ge, display_name: greater_or_equal);
|
||||
|
||||
define_server_key_bench_fn!(method_name: smart_max_parallelized, display_name: max);
|
||||
define_server_key_bench_fn!(method_name: smart_min_parallelized, display_name: min);
|
||||
define_server_key_bench_fn!(method_name: smart_eq_parallelized, display_name: equal);
|
||||
define_server_key_bench_fn!(method_name: smart_lt_parallelized, display_name: less_than);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_le_parallelized,
|
||||
display_name: less_or_equal
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_gt_parallelized,
|
||||
display_name: greater_than
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_ge_parallelized,
|
||||
display_name: greater_or_equal
|
||||
);
|
||||
|
||||
define_server_key_bench_default_fn!(method_name: max_parallelized, display_name: max);
|
||||
define_server_key_bench_default_fn!(method_name: min_parallelized, display_name: min);
|
||||
define_server_key_bench_default_fn!(method_name: eq_parallelized, display_name: equal);
|
||||
define_server_key_bench_default_fn!(method_name: lt_parallelized, display_name: less_than);
|
||||
define_server_key_bench_default_fn!(method_name: le_parallelized, display_name: less_or_equal);
|
||||
define_server_key_bench_default_fn!(method_name: gt_parallelized, display_name: greater_than);
|
||||
define_server_key_bench_default_fn!(method_name: ge_parallelized, display_name: greater_or_equal);
|
||||
|
||||
criterion_group!(
|
||||
smart_arithmetic_operation,
|
||||
@@ -346,6 +683,23 @@ criterion_group!(
|
||||
smart_ge_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
arithmetic_parallelized_operation,
|
||||
add_parallelized,
|
||||
sub_parallelized,
|
||||
mul_parallelized,
|
||||
bitand_parallelized,
|
||||
bitor_parallelized,
|
||||
bitxor_parallelized,
|
||||
max_parallelized,
|
||||
min_parallelized,
|
||||
eq_parallelized,
|
||||
lt_parallelized,
|
||||
le_parallelized,
|
||||
gt_parallelized,
|
||||
ge_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
smart_scalar_arithmetic_operation,
|
||||
smart_scalar_add,
|
||||
@@ -360,6 +714,13 @@ criterion_group!(
|
||||
smart_scalar_mul_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
scalar_arithmetic_parallel_operation,
|
||||
scalar_add_parallelized,
|
||||
scalar_sub_parallelized,
|
||||
scalar_mul_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
unchecked_arithmetic_operation,
|
||||
unchecked_add,
|
||||
@@ -396,12 +757,36 @@ criterion_group!(
|
||||
|
||||
criterion_group!(misc, full_propagate, full_propagate_parallelized);
|
||||
|
||||
criterion_main!(
|
||||
smart_arithmetic_operation,
|
||||
smart_arithmetic_parallelized_operation,
|
||||
smart_scalar_arithmetic_operation,
|
||||
smart_scalar_arithmetic_parallel_operation,
|
||||
unchecked_arithmetic_operation,
|
||||
unchecked_scalar_arithmetic_operation,
|
||||
misc,
|
||||
// User-oriented benchmark group.
|
||||
// This gather all the operations that a high-level user could use.
|
||||
criterion_group!(
|
||||
fast_integer_benchmarks,
|
||||
bitand_parallelized,
|
||||
bitor_parallelized,
|
||||
bitxor_parallelized,
|
||||
add_parallelized,
|
||||
sub_parallelized,
|
||||
mul_parallelized,
|
||||
neg_parallelized,
|
||||
min_parallelized,
|
||||
max_parallelized,
|
||||
eq_parallelized,
|
||||
lt_parallelized,
|
||||
le_parallelized,
|
||||
gt_parallelized,
|
||||
ge_parallelized,
|
||||
scalar_add_parallelized,
|
||||
scalar_sub_parallelized,
|
||||
scalar_mul_parallelized,
|
||||
);
|
||||
|
||||
criterion_main!(
|
||||
fast_integer_benchmarks,
|
||||
// smart_arithmetic_operation,
|
||||
// smart_arithmetic_parallelized_operation,
|
||||
// smart_scalar_arithmetic_operation,
|
||||
// smart_scalar_arithmetic_parallel_operation,
|
||||
// unchecked_arithmetic_operation,
|
||||
// unchecked_scalar_arithmetic_operation,
|
||||
// misc,
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ use criterion::*;
|
||||
use tfhe::core_crypto::commons::generators::DeterministicSeeder;
|
||||
use tfhe::core_crypto::prelude::{
|
||||
allocate_and_generate_new_binary_glwe_secret_key,
|
||||
par_allocate_and_generate_new_lwe_bootstrap_key, ActivatedRandomGenerator,
|
||||
par_allocate_and_generate_new_lwe_bootstrap_key, ActivatedRandomGenerator, CiphertextModulus,
|
||||
EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
};
|
||||
use tfhe::core_crypto::seeders::new_seeder;
|
||||
@@ -34,6 +34,7 @@ fn criterion_bench(c: &mut Criterion) {
|
||||
parameters.pbs_base_log,
|
||||
parameters.pbs_level,
|
||||
parameters.glwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut encryption_generator,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use tfhe::shortint::keycache::NamedParam;
|
||||
use tfhe::shortint::parameters::*;
|
||||
@@ -37,6 +41,7 @@ const SERVER_KEY_BENCH_PARAMS_EXTENDED: [Parameters; 15] = [
|
||||
fn bench_server_key_unary_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_op: F,
|
||||
params: &[Parameters],
|
||||
) where
|
||||
@@ -62,6 +67,14 @@ fn bench_server_key_unary_function<F>(
|
||||
unary_op(sks, &mut ct);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
@@ -70,6 +83,7 @@ fn bench_server_key_unary_function<F>(
|
||||
fn bench_server_key_binary_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
) where
|
||||
@@ -97,6 +111,14 @@ fn bench_server_key_binary_function<F>(
|
||||
binary_op(sks, &mut ct_0, &mut ct_1);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
@@ -105,6 +127,7 @@ fn bench_server_key_binary_function<F>(
|
||||
fn bench_server_key_binary_scalar_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
) where
|
||||
@@ -131,6 +154,14 @@ fn bench_server_key_binary_scalar_function<F>(
|
||||
binary_op(sks, &mut ct_0, clear_1 as u8);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
@@ -139,6 +170,7 @@ fn bench_server_key_binary_scalar_function<F>(
|
||||
fn bench_server_key_binary_scalar_division_function<F>(
|
||||
c: &mut Criterion,
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
) where
|
||||
@@ -169,6 +201,14 @@ fn bench_server_key_binary_scalar_division_function<F>(
|
||||
binary_op(sks, &mut ct_0, clear_1 as u8);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
*param,
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
@@ -195,6 +235,14 @@ fn carry_extract(c: &mut Criterion) {
|
||||
let _ = sks.carry_extract(&ct_0);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
"carry_extract",
|
||||
&OperatorType::Atomic,
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish()
|
||||
@@ -217,19 +265,21 @@ fn programmable_bootstrapping(c: &mut Criterion) {
|
||||
|
||||
let ctxt = cks.encrypt(clear_0);
|
||||
|
||||
let id = format!("ServerKey::programmable_bootstrap::{}", param.name());
|
||||
let bench_id = format!("ServerKey::programmable_bootstrap::{}", param.name());
|
||||
|
||||
bench_group.bench_function(&id, |b| {
|
||||
bench_group.bench_function(&bench_id, |b| {
|
||||
b.iter(|| {
|
||||
let _ = sks.apply_lookup_table(&ctxt, &acc);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(&bench_id, param, param.name(), "pbs", &OperatorType::Atomic);
|
||||
}
|
||||
|
||||
bench_group.finish();
|
||||
}
|
||||
|
||||
fn bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
|
||||
fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
|
||||
let mut bench_group = c.benchmark_group("programmable_bootstrap");
|
||||
|
||||
let param = WOPBS_PARAM_MESSAGE_4_NORM2_6;
|
||||
@@ -255,11 +305,12 @@ fn bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
macro_rules! define_server_key_unary_bench_fn (
|
||||
($server_key_method:ident, $params:expr) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_unary_function(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs| {
|
||||
let _ = server_key.$server_key_method(lhs);},
|
||||
$params)
|
||||
@@ -268,11 +319,12 @@ macro_rules! define_server_key_unary_bench_fn (
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_bench_fn (
|
||||
($server_key_method:ident, $params:expr) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_function(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
@@ -281,11 +333,12 @@ macro_rules! define_server_key_bench_fn (
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_scalar_bench_fn (
|
||||
($server_key_method:ident, $params:expr) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_function(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
@@ -294,11 +347,12 @@ macro_rules! define_server_key_scalar_bench_fn (
|
||||
);
|
||||
|
||||
macro_rules! define_server_key_scalar_div_bench_fn (
|
||||
($server_key_method:ident, $params:expr) => {
|
||||
(method_name:$server_key_method:ident, display_name:$name:ident, $params:expr) => {
|
||||
fn $server_key_method(c: &mut Criterion) {
|
||||
bench_server_key_binary_scalar_division_function(
|
||||
c,
|
||||
concat!("ServerKey::", stringify!($server_key_method)),
|
||||
stringify!($name),
|
||||
|server_key, lhs, rhs| {
|
||||
let _ = server_key.$server_key_method(lhs, rhs);},
|
||||
$params)
|
||||
@@ -306,31 +360,255 @@ macro_rules! define_server_key_scalar_div_bench_fn (
|
||||
}
|
||||
);
|
||||
|
||||
define_server_key_unary_bench_fn!(unchecked_neg, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_unary_bench_fn!(
|
||||
method_name: unchecked_neg,
|
||||
display_name: negation,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
define_server_key_bench_fn!(unchecked_add, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
|
||||
define_server_key_bench_fn!(unchecked_sub, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
|
||||
define_server_key_bench_fn!(unchecked_mul_lsb, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
|
||||
define_server_key_bench_fn!(unchecked_mul_msb, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(unchecked_div, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
|
||||
define_server_key_bench_fn!(smart_bitand, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(smart_bitor, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(smart_bitxor, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(smart_add, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(smart_sub, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(smart_mul_lsb, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(unchecked_greater, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(unchecked_less, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(unchecked_equal, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_mul_lsb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_mul_msb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitand,
|
||||
display_name: bitand,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitor,
|
||||
display_name: bitor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_bitxor,
|
||||
display_name: bitxor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: smart_mul_lsb,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitand,
|
||||
display_name: bitand,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitor,
|
||||
display_name: bitor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: bitxor,
|
||||
display_name: bitxor,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: greater,
|
||||
display_name: greater,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: greater_or_equal,
|
||||
display_name: greater_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: less,
|
||||
display_name: less,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: less_or_equal,
|
||||
display_name: less_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: not_equal,
|
||||
display_name: not_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_unary_bench_fn!(
|
||||
method_name: neg,
|
||||
display_name: negation,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_greater,
|
||||
display_name: greater_than,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_less,
|
||||
display_name: less_than,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_bench_fn!(
|
||||
method_name: unchecked_equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
define_server_key_scalar_bench_fn!(unchecked_scalar_add, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
|
||||
define_server_key_scalar_bench_fn!(unchecked_scalar_sub, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
|
||||
define_server_key_scalar_bench_fn!(unchecked_scalar_mul, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
|
||||
define_server_key_scalar_bench_fn!(unchecked_scalar_left_shift, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_scalar_bench_fn!(unchecked_scalar_right_shift, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_left_shift,
|
||||
display_name: left_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: unchecked_scalar_right_shift,
|
||||
display_name: right_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
define_server_key_scalar_div_bench_fn!(unchecked_scalar_div, &SERVER_KEY_BENCH_PARAMS_EXTENDED);
|
||||
define_server_key_scalar_div_bench_fn!(unchecked_scalar_mod, &SERVER_KEY_BENCH_PARAMS);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: unchecked_scalar_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS_EXTENDED
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: unchecked_scalar_mod,
|
||||
display_name: modulo,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_add,
|
||||
display_name: add,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_sub,
|
||||
display_name: sub,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_mul,
|
||||
display_name: mul,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_left_shift,
|
||||
display_name: left_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_right_shift,
|
||||
display_name: right_shift,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_div,
|
||||
display_name: div,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_mod,
|
||||
display_name: modulo,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_greater,
|
||||
display_name: greater,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_greater_or_equal,
|
||||
display_name: greater_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_less,
|
||||
display_name: less,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_bench_fn!(
|
||||
method_name: scalar_less_or_equal,
|
||||
display_name: less_or_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_equal,
|
||||
display_name: equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
define_server_key_scalar_div_bench_fn!(
|
||||
method_name: scalar_not_equal,
|
||||
display_name: not_equal,
|
||||
&SERVER_KEY_BENCH_PARAMS
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
arithmetic_operation,
|
||||
@@ -354,7 +632,7 @@ criterion_group!(
|
||||
// multivalue_programmable_bootstrapping
|
||||
//bench_two_block_pbs
|
||||
//wopbs_v0_norm2_2,
|
||||
bench_wopbs_param_message_8_norm2_5,
|
||||
//bench_wopbs_param_message_8_norm2_5,
|
||||
programmable_bootstrapping
|
||||
);
|
||||
|
||||
@@ -369,4 +647,44 @@ criterion_group!(
|
||||
unchecked_scalar_right_shift,
|
||||
);
|
||||
|
||||
criterion_main!(arithmetic_operation, arithmetic_scalar_operation);
|
||||
criterion_group!(
|
||||
default_ops,
|
||||
neg,
|
||||
bitand,
|
||||
bitor,
|
||||
bitxor,
|
||||
add,
|
||||
sub,
|
||||
div,
|
||||
mul,
|
||||
greater,
|
||||
greater_or_equal,
|
||||
less,
|
||||
less_or_equal,
|
||||
equal,
|
||||
not_equal
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
default_scalar_ops,
|
||||
scalar_add,
|
||||
scalar_sub,
|
||||
scalar_div,
|
||||
scalar_mul,
|
||||
scalar_mod,
|
||||
scalar_left_shift,
|
||||
scalar_right_shift,
|
||||
scalar_greater,
|
||||
scalar_greater_or_equal,
|
||||
scalar_less,
|
||||
scalar_less_or_equal,
|
||||
scalar_equal,
|
||||
scalar_not_equal
|
||||
);
|
||||
|
||||
criterion_main!(
|
||||
// arithmetic_operation,
|
||||
// arithmetic_scalar_operation,
|
||||
default_ops,
|
||||
default_scalar_ops,
|
||||
);
|
||||
|
||||
184
tfhe/benches/utilities.rs
Normal file
184
tfhe/benches/utilities.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use serde::Serialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(feature = "boolean")]
|
||||
use tfhe::boolean::parameters::BooleanParameters;
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
#[cfg(feature = "shortint")]
|
||||
use tfhe::shortint::Parameters;
|
||||
|
||||
#[derive(Clone, Copy, Default, Serialize)]
|
||||
pub struct CryptoParametersRecord {
|
||||
pub lwe_dimension: Option<LweDimension>,
|
||||
pub glwe_dimension: Option<GlweDimension>,
|
||||
pub polynomial_size: Option<PolynomialSize>,
|
||||
pub lwe_modular_std_dev: Option<StandardDev>,
|
||||
pub glwe_modular_std_dev: Option<StandardDev>,
|
||||
pub pbs_base_log: Option<DecompositionBaseLog>,
|
||||
pub pbs_level: Option<DecompositionLevelCount>,
|
||||
pub ks_base_log: Option<DecompositionBaseLog>,
|
||||
pub ks_level: Option<DecompositionLevelCount>,
|
||||
pub pfks_level: Option<DecompositionLevelCount>,
|
||||
pub pfks_base_log: Option<DecompositionBaseLog>,
|
||||
pub pfks_modular_std_dev: Option<StandardDev>,
|
||||
pub cbs_level: Option<DecompositionLevelCount>,
|
||||
pub cbs_base_log: Option<DecompositionBaseLog>,
|
||||
pub message_modulus: Option<usize>,
|
||||
pub carry_modulus: Option<usize>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "boolean")]
|
||||
impl From<BooleanParameters> for CryptoParametersRecord {
|
||||
fn from(params: BooleanParameters) -> Self {
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(params.lwe_dimension),
|
||||
glwe_dimension: Some(params.glwe_dimension),
|
||||
polynomial_size: Some(params.polynomial_size),
|
||||
lwe_modular_std_dev: Some(params.lwe_modular_std_dev),
|
||||
glwe_modular_std_dev: Some(params.glwe_modular_std_dev),
|
||||
pbs_base_log: Some(params.pbs_base_log),
|
||||
pbs_level: Some(params.pbs_level),
|
||||
ks_base_log: Some(params.ks_base_log),
|
||||
ks_level: Some(params.ks_level),
|
||||
pfks_level: None,
|
||||
pfks_base_log: None,
|
||||
pfks_modular_std_dev: None,
|
||||
cbs_level: None,
|
||||
cbs_base_log: None,
|
||||
message_modulus: None,
|
||||
carry_modulus: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "shortint")]
|
||||
impl From<Parameters> for CryptoParametersRecord {
|
||||
fn from(params: Parameters) -> Self {
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(params.lwe_dimension),
|
||||
glwe_dimension: Some(params.glwe_dimension),
|
||||
polynomial_size: Some(params.polynomial_size),
|
||||
lwe_modular_std_dev: Some(params.lwe_modular_std_dev),
|
||||
glwe_modular_std_dev: Some(params.glwe_modular_std_dev),
|
||||
pbs_base_log: Some(params.pbs_base_log),
|
||||
pbs_level: Some(params.pbs_level),
|
||||
ks_base_log: Some(params.ks_base_log),
|
||||
ks_level: Some(params.ks_level),
|
||||
pfks_level: Some(params.pfks_level),
|
||||
pfks_base_log: Some(params.pfks_base_log),
|
||||
pfks_modular_std_dev: Some(params.pfks_modular_std_dev),
|
||||
cbs_level: Some(params.cbs_level),
|
||||
cbs_base_log: Some(params.cbs_base_log),
|
||||
message_modulus: Some(params.message_modulus.0),
|
||||
carry_modulus: Some(params.carry_modulus.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum PolynomialMultiplication {
|
||||
Fft,
|
||||
// Ntt,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum IntegerRepresentation {
|
||||
Radix,
|
||||
// Crt,
|
||||
// Hybrid,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum ExecutionType {
|
||||
Sequential,
|
||||
Parallel,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum KeySetType {
|
||||
Single,
|
||||
// Multi,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum OperandType {
|
||||
CipherText,
|
||||
PlainText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum OperatorType {
|
||||
Atomic,
|
||||
// AtomicPattern,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BenchmarkParametersRecord {
|
||||
display_name: String,
|
||||
crypto_parameters_alias: String,
|
||||
crypto_parameters: CryptoParametersRecord,
|
||||
message_modulus: Option<usize>,
|
||||
carry_modulus: Option<usize>,
|
||||
ciphertext_modulus: usize,
|
||||
polynomial_multiplication: PolynomialMultiplication,
|
||||
precision: u32,
|
||||
error_probability: f64,
|
||||
integer_representation: IntegerRepresentation,
|
||||
decomposition_basis: u32,
|
||||
pbs_algorithm: Option<String>,
|
||||
execution_type: ExecutionType,
|
||||
key_set_type: KeySetType,
|
||||
operand_type: OperandType,
|
||||
operator_type: OperatorType,
|
||||
}
|
||||
|
||||
/// Writes benchmarks parameters to disk in JSON format.
|
||||
pub fn write_to_json<T: Into<CryptoParametersRecord>>(
|
||||
bench_id: &str,
|
||||
params: T,
|
||||
params_alias: impl Into<String>,
|
||||
display_name: impl Into<String>,
|
||||
operator_type: &OperatorType,
|
||||
) {
|
||||
let params = params.into();
|
||||
|
||||
let execution_type = match bench_id.contains("parallelized") {
|
||||
true => ExecutionType::Parallel,
|
||||
false => ExecutionType::Sequential,
|
||||
};
|
||||
let operand_type = match bench_id.contains("scalar") {
|
||||
true => OperandType::PlainText,
|
||||
false => OperandType::CipherText,
|
||||
};
|
||||
|
||||
let record = BenchmarkParametersRecord {
|
||||
display_name: display_name.into(),
|
||||
crypto_parameters_alias: params_alias.into(),
|
||||
crypto_parameters: params.to_owned(),
|
||||
message_modulus: params.message_modulus,
|
||||
carry_modulus: params.carry_modulus,
|
||||
ciphertext_modulus: 64,
|
||||
polynomial_multiplication: PolynomialMultiplication::Fft,
|
||||
precision: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
|
||||
error_probability: 2f64.powf(-41.0),
|
||||
integer_representation: IntegerRepresentation::Radix,
|
||||
decomposition_basis: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
|
||||
pbs_algorithm: None, // To be added in future version
|
||||
execution_type,
|
||||
key_set_type: KeySetType::Single,
|
||||
operand_type,
|
||||
operator_type: operator_type.to_owned(),
|
||||
};
|
||||
|
||||
let mut params_directory = ["benchmarks_parameters", bench_id]
|
||||
.iter()
|
||||
.collect::<PathBuf>();
|
||||
fs::create_dir_all(¶ms_directory).unwrap();
|
||||
params_directory.push("parameters.json");
|
||||
|
||||
fs::write(params_directory, serde_json::to_string(&record).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
// Empty main to please clippy.
|
||||
#[allow(dead_code)]
|
||||
pub fn main() {}
|
||||
@@ -1,10 +1,12 @@
|
||||
// tfhe/build.rs
|
||||
|
||||
#[cfg(feature = "__c_api")]
|
||||
fn gen_c_api() {
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
if std::env::var("_CBINDGEN_IS_RUNNING").is_ok() {
|
||||
return;
|
||||
}
|
||||
|
||||
/// Find the location of the `target/` directory. Note that this may be
|
||||
/// overridden by `cmake`, so we also need to check the `CARGO_TARGET_DIR`
|
||||
/// variable.
|
||||
@@ -24,7 +26,35 @@ fn gen_c_api() {
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
cbindgen::generate(crate_dir)
|
||||
let parse_expand_features_vec = vec![
|
||||
#[cfg(feature = "__c_api")]
|
||||
"__c_api",
|
||||
#[cfg(feature = "boolean-c-api")]
|
||||
"boolean-c-api",
|
||||
#[cfg(feature = "shortint-c-api")]
|
||||
"shortint-c-api",
|
||||
#[cfg(feature = "high-level-c-api")]
|
||||
"high-level-c-api",
|
||||
#[cfg(feature = "boolean")]
|
||||
"boolean",
|
||||
#[cfg(feature = "shortint")]
|
||||
"shortint",
|
||||
#[cfg(feature = "integer")]
|
||||
"integer",
|
||||
];
|
||||
|
||||
let parse_expand_vec = if parse_expand_features_vec.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![package_name.as_str()]
|
||||
};
|
||||
|
||||
cbindgen::Builder::new()
|
||||
.with_crate(crate_dir.clone())
|
||||
.with_config(cbindgen::Config::from_root_or_default(crate_dir))
|
||||
.with_parse_expand(&parse_expand_vec)
|
||||
.with_parse_expand_features(&parse_expand_features_vec)
|
||||
.generate()
|
||||
.unwrap()
|
||||
.write_to_file(output_file);
|
||||
}
|
||||
|
||||
88
tfhe/c_api_tests/test_high_level_128_bits.c
Normal file
88
tfhe/c_api_tests/test_high_level_128_bits.c
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <tfhe.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int uint128_client_key(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint128 *lhs = NULL;
|
||||
FheUint128 *rhs = NULL;
|
||||
FheUint128 *result = NULL;
|
||||
|
||||
ok = fhe_uint128_try_encrypt_with_client_key_u128(10, 20, client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_try_encrypt_with_client_key_u128(1, 2, client_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_sub(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint64_t w0, w1;
|
||||
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 9);
|
||||
assert(w1 == 18);
|
||||
|
||||
fhe_uint128_destroy(lhs);
|
||||
fhe_uint128_destroy(rhs);
|
||||
fhe_uint128_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint128_public_key(const ClientKey *client_key, const PublicKey *public_key) {
|
||||
int ok;
|
||||
FheUint128 *lhs = NULL;
|
||||
FheUint128 *rhs = NULL;
|
||||
FheUint128 *result = NULL;
|
||||
|
||||
ok = fhe_uint128_try_encrypt_with_public_key_u128(1, 2, public_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_try_encrypt_with_public_key_u128(10, 20, public_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_add(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint64_t w0, w1;
|
||||
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 11);
|
||||
assert(w1 == 22);
|
||||
|
||||
fhe_uint128_destroy(lhs);
|
||||
fhe_uint128_destroy(rhs);
|
||||
fhe_uint128_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int ok = 0;
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
config_builder_all_disabled(&builder);
|
||||
config_builder_enable_default_uint128_small(&builder);
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
generate_keys(config, &client_key, &server_key);
|
||||
public_key_new(client_key, &public_key);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
uint128_client_key(client_key);
|
||||
uint128_public_key(client_key, public_key);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
return ok;
|
||||
}
|
||||
121
tfhe/c_api_tests/test_high_level_256_bits.c
Normal file
121
tfhe/c_api_tests/test_high_level_256_bits.c
Normal file
@@ -0,0 +1,121 @@
|
||||
#include <tfhe.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
int uint256_client_key(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint256 *lhs = NULL;
|
||||
FheUint256 *rhs = NULL;
|
||||
FheUint256 *result = NULL;
|
||||
U256 *lhs_clear = NULL;
|
||||
U256 *rhs_clear = NULL;
|
||||
U256 *result_clear = NULL;
|
||||
|
||||
ok = u256_from_u64_words(1, 2, 3, 4, &lhs_clear);
|
||||
assert(ok == 0);
|
||||
ok = u256_from_u64_words(5, 6, 7, 8, &rhs_clear);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_try_encrypt_with_client_key_u256(lhs_clear, client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_try_encrypt_with_client_key_u256(rhs_clear, client_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_add(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
|
||||
assert(ok == 0);
|
||||
|
||||
uint64_t w0, w1, w2, w3;
|
||||
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 6);
|
||||
assert(w1 == 8);
|
||||
assert(w2 == 10);
|
||||
assert(w3 == 12);
|
||||
|
||||
u256_destroy(lhs_clear);
|
||||
u256_destroy(rhs_clear);
|
||||
u256_destroy(result_clear);
|
||||
fhe_uint256_destroy(lhs);
|
||||
fhe_uint256_destroy(rhs);
|
||||
fhe_uint256_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint256_public_key(const ClientKey *client_key, const PublicKey *public_key) {
|
||||
int ok;
|
||||
FheUint256 *lhs = NULL;
|
||||
FheUint256 *rhs = NULL;
|
||||
FheUint256 *result = NULL;
|
||||
U256 *lhs_clear = NULL;
|
||||
U256 *rhs_clear = NULL;
|
||||
U256 *result_clear = NULL;
|
||||
|
||||
ok = u256_from_u64_words(5, 6, 7, 8, &lhs_clear);
|
||||
assert(ok == 0);
|
||||
ok = u256_from_u64_words(1, 2, 3, 4, &rhs_clear);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_try_encrypt_with_public_key_u256(lhs_clear, public_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_try_encrypt_with_public_key_u256(rhs_clear, public_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_sub(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
|
||||
assert(ok == 0);
|
||||
|
||||
uint64_t w0, w1, w2, w3;
|
||||
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 4);
|
||||
assert(w1 == 4);
|
||||
assert(w2 == 4);
|
||||
assert(w3 == 4);
|
||||
|
||||
u256_destroy(lhs_clear);
|
||||
u256_destroy(rhs_clear);
|
||||
u256_destroy(result_clear);
|
||||
fhe_uint256_destroy(lhs);
|
||||
fhe_uint256_destroy(rhs);
|
||||
fhe_uint256_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int ok = 0;
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
config_builder_all_disabled(&builder);
|
||||
config_builder_enable_default_uint256_small(&builder);
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
generate_keys(config, &client_key, &server_key);
|
||||
public_key_new(client_key, &public_key);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
uint256_client_key(client_key);
|
||||
uint256_public_key(client_key, public_key);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
return ok;
|
||||
}
|
||||
97
tfhe/c_api_tests/test_high_level_boolean.c
Normal file
97
tfhe/c_api_tests/test_high_level_boolean.c
Normal file
@@ -0,0 +1,97 @@
|
||||
#include <tfhe.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
int client_key_test(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheBool *lhs = NULL;
|
||||
FheBool *rhs = NULL;
|
||||
FheBool *result = NULL;
|
||||
|
||||
bool lhs_clear = 0;
|
||||
bool rhs_clear = 1;
|
||||
|
||||
ok = fhe_bool_try_encrypt_with_client_key_bool(lhs_clear, client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_bool_try_encrypt_with_client_key_bool(rhs_clear, client_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_bool_bitand(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
bool clear;
|
||||
ok = fhe_bool_decrypt(result, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == (lhs_clear & rhs_clear));
|
||||
|
||||
fhe_bool_destroy(lhs);
|
||||
fhe_bool_destroy(rhs);
|
||||
fhe_bool_destroy(result);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
int public_key_test(const ClientKey *client_key, const PublicKey *public_key) {
|
||||
int ok;
|
||||
FheBool *lhs = NULL;
|
||||
FheBool *rhs = NULL;
|
||||
FheBool *result = NULL;
|
||||
|
||||
bool lhs_clear = 0;
|
||||
bool rhs_clear = 1;
|
||||
|
||||
ok = fhe_bool_try_encrypt_with_public_key_bool(lhs_clear, public_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_bool_try_encrypt_with_public_key_bool(rhs_clear, public_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_bool_bitand(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
bool clear;
|
||||
ok = fhe_bool_decrypt(result, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == (lhs_clear & rhs_clear));
|
||||
|
||||
fhe_bool_destroy(lhs);
|
||||
fhe_bool_destroy(rhs);
|
||||
fhe_bool_destroy(result);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
config_builder_all_disabled(&builder);
|
||||
config_builder_enable_default_bool(&builder);
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
generate_keys(config, &client_key, &server_key);
|
||||
public_key_new(client_key, &public_key);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
client_key_test(client_key);
|
||||
public_key_test(client_key, public_key);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
213
tfhe/c_api_tests/test_high_level_integers.c
Normal file
213
tfhe/c_api_tests/test_high_level_integers.c
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <tfhe.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
int uint8_client_key(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint8 *lhs = NULL;
|
||||
FheUint8 *rhs = NULL;
|
||||
FheUint8 *result = NULL;
|
||||
|
||||
uint8_t lhs_clear = 123;
|
||||
uint8_t rhs_clear = 14;
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_client_key_u8(rhs_clear, client_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_add(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint8_t clear;
|
||||
ok = fhe_uint8_decrypt(result, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == (lhs_clear + rhs_clear));
|
||||
|
||||
fhe_uint8_destroy(lhs);
|
||||
fhe_uint8_destroy(rhs);
|
||||
fhe_uint8_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint8_public_key(const ClientKey *client_key, const PublicKey *public_key) {
|
||||
int ok;
|
||||
FheUint8 *lhs = NULL;
|
||||
FheUint8 *rhs = NULL;
|
||||
FheUint8 *result = NULL;
|
||||
|
||||
uint8_t lhs_clear = 123;
|
||||
uint8_t rhs_clear = 14;
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_public_key_u8(lhs_clear, public_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_public_key_u8(rhs_clear, public_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_sub(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint8_t clear;
|
||||
ok = fhe_uint8_decrypt(result, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == (lhs_clear - rhs_clear));
|
||||
|
||||
fhe_uint8_destroy(lhs);
|
||||
fhe_uint8_destroy(rhs);
|
||||
fhe_uint8_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint8_serialization(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint8 *lhs = NULL;
|
||||
FheUint8 *deserialized_lhs = NULL;
|
||||
FheUint8 *result = NULL;
|
||||
Buffer value_buffer = {.pointer = NULL, .length = 0};
|
||||
Buffer cks_buffer = {.pointer = NULL, .length = 0};
|
||||
BufferView deser_view = {.pointer = NULL, .length = 0};
|
||||
ClientKey *deserialized_client_key = NULL;
|
||||
|
||||
uint8_t lhs_clear = 123;
|
||||
|
||||
ok = client_key_serialize(client_key, &cks_buffer);
|
||||
assert(ok == 0);
|
||||
|
||||
deser_view.pointer = cks_buffer.pointer;
|
||||
deser_view.length = cks_buffer.length;
|
||||
ok = client_key_deserialize(deser_view, &deserialized_client_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, deserialized_client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint8_serialize(lhs, &value_buffer);
|
||||
assert(ok == 0);
|
||||
|
||||
deser_view.pointer = value_buffer.pointer;
|
||||
deser_view.length = value_buffer.length;
|
||||
ok = fhe_uint8_deserialize(deser_view, &deserialized_lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
uint8_t clear;
|
||||
ok = fhe_uint8_decrypt(deserialized_lhs, deserialized_client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == lhs_clear);
|
||||
|
||||
if (value_buffer.pointer != NULL) {
|
||||
destroy_buffer(&value_buffer);
|
||||
}
|
||||
fhe_uint8_destroy(lhs);
|
||||
fhe_uint8_destroy(deserialized_lhs);
|
||||
fhe_uint8_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint8_compressed(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint8 *lhs = NULL;
|
||||
FheUint8 *result = NULL;
|
||||
CompressedFheUint8 *clhs = NULL;
|
||||
|
||||
uint8_t lhs_clear = 123;
|
||||
|
||||
ok = compressed_fhe_uint8_try_encrypt_with_client_key_u8(lhs_clear, client_key, &clhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = compressed_fhe_uint8_decompress(clhs, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
uint8_t clear;
|
||||
ok = fhe_uint8_decrypt(lhs, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == lhs_clear);
|
||||
|
||||
fhe_uint8_destroy(lhs);
|
||||
compressed_fhe_uint8_destroy(clhs);
|
||||
fhe_uint8_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int ok = 0;
|
||||
{
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
ok = config_builder_all_disabled(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_enable_default_uint8(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_build(builder, &config);
|
||||
assert(ok == 0);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
ok = generate_keys(config, &client_key, &server_key);
|
||||
assert(ok == 0);
|
||||
ok = public_key_new(client_key, &public_key);
|
||||
assert(ok == 0);
|
||||
ok = uint8_serialization(client_key);
|
||||
assert(ok == 0);
|
||||
ok = uint8_compressed(client_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = set_server_key(server_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = uint8_client_key(client_key);
|
||||
assert(ok == 0);
|
||||
ok = uint8_public_key(client_key, public_key);
|
||||
assert(ok == 0);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
}
|
||||
|
||||
{
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
ok = config_builder_all_disabled(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_enable_default_uint8_small(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_build(builder, &config);
|
||||
assert(ok == 0);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
PublicKey *public_key = NULL;
|
||||
|
||||
ok = generate_keys(config, &client_key, &server_key);
|
||||
assert(ok == 0);
|
||||
ok = public_key_new(client_key, &public_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = set_server_key(server_key);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = uint8_client_key(client_key);
|
||||
assert(ok == 0);
|
||||
ok = uint8_public_key(client_key, public_key);
|
||||
assert(ok == 0);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
public_key_destroy(public_key);
|
||||
server_key_destroy(server_key);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
@@ -109,7 +109,7 @@ void test_custom_keygen(void) {
|
||||
ShortintParameters *params = NULL;
|
||||
|
||||
int params_ok = shortint_create_parameters(10, 1, 1024, 10e-100, 10e-100, 2, 3, 2, 3, 2, 3,
|
||||
10e-100, 2, 3, 2, 2, ¶ms);
|
||||
10e-100, 2, 3, 2, 2, SHORTINT_NATIVE_MODULUS, ¶ms);
|
||||
assert(params_ok == 0);
|
||||
|
||||
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
|
||||
|
||||
@@ -107,7 +107,6 @@ allow_static_const = true
|
||||
allow_constexpr = false
|
||||
sort_by = "Name"
|
||||
|
||||
|
||||
[macro_expansion]
|
||||
bitflags = false
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Operations
|
||||
|
||||
In tfhe::boolean, the available operations are mainly related to their equivalent Boolean gates (i.e., AND, OR... etc). What follows 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`.
|
||||
In tfhe::boolean, the available operations are mainly related to their equivalent Boolean gates (i.e., AND, OR... etc). What follows are examples of a unary gate (NOT) and a binary gate (XOR). The last one is about the ternary MUX gate, which allows homomorphic computation of conditional statements of the form `If..Then..Else`.
|
||||
|
||||
## The NOT unary gate
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
## Default parameters
|
||||
|
||||
The TFHE cryptographic scheme relies on a variant of [Regev cryptosystem](https://cims.nyu.edu/\~regev/papers/lwesurvey.pdf), and is based on a problem so hard to solve that it is even post-quantum resistant.
|
||||
The TFHE cryptographic scheme relies on a variant of [Regev cryptosystem](https://cims.nyu.edu/\~regev/papers/lwesurvey.pdf) and is based on a problem so difficult that it is even post-quantum resistant.
|
||||
|
||||
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.
|
||||
Some cryptographic parameters will require tuning to ensure both the correctness of the result and the security of the computation.
|
||||
|
||||
To make it simpler, **we 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.
|
||||
To make it simpler, **we've provided two sets of parameters**, which ensure correct computations for a certain probability with the standard security of 128 bits. There exists an error probability due to the probabilistic nature of the encryption, which requires adding randomness (noise) following a Gaussian distribution. If this noise is too large, the decryption will not give a correct result. There is a trade-off between efficiency and correctness: generally, using a less efficient parameter set (in terms of computation time) leads to a smaller risk of having an error during homomorphic evaluation.
|
||||
|
||||
In the two proposed sets of parameters, the only difference lies in this probability error. The default parameter set ensures a probability error of at most $$2^{-40}$$ when computing a programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error probability claimed into the original [TFHE paper](https://eprint.iacr.org/2018/421), namely $$2^{-165}$$, but it is up-to-date regarding security requirements.
|
||||
In the two proposed sets of parameters, the only difference lies in this probability error. The default parameter set ensures a probability error of at most $$2^{-40}$$ when computing a programmable bootstrapping (i.e., any gates but the `not`). The other one is closer to the error probability claimed in the original [TFHE paper](https://eprint.iacr.org/2018/421), namely $$2^{-165}$$, but it is up-to-date regarding security requirements.
|
||||
|
||||
The following array summarizes this:
|
||||
|
||||
@@ -19,7 +19,7 @@ The following array summarizes this:
|
||||
|
||||
## 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 in an incorrect and/or insecure computation:
|
||||
You can also create your own set of parameters. This is an `unsafe` operation as failing to properly fix the parameters will result in an incorrect and/or insecure computation:
|
||||
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# Tutorial
|
||||
|
||||
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:
|
||||
This library is meant to be used both on the **server side** and the **client side**. The typical use case should follow the subsequent steps:
|
||||
|
||||
1. On the **client side**, generate the `client` and `server keys`.
|
||||
2. Send the `server key` to the **server**.
|
||||
3. Then any number of times:
|
||||
* On the **client side**, _encryption_ of the input data with the `client key`.
|
||||
* On the **client side**, _encrypt_ the input data with the `client key`.
|
||||
* Transmit the encrypted input to the **server**.
|
||||
* On the **server side**, _homomorphic computation_ with the `server key`.
|
||||
* On the **server side**, perform _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`.
|
||||
* On the **client side**, _decrypt_ the output data with the `client key`.
|
||||
|
||||
## Setup
|
||||
|
||||
In the first step, the client creates two keys: the `client key` and the `server key`, with the `concrete_boolean::gen_keys` function:
|
||||
In the first step, the client creates two keys, the `client key` and the `server key`, with the `concrete_boolean::gen_keys` function:
|
||||
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
@@ -26,10 +26,10 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
* The `client_key` is of type `ClientKey`. It is **secret**, and must **never** be transmitted. This key will only be used to encrypt and decrypt data.
|
||||
* The `server_key` is of type `ServerKey`. It is a **public key**, and can be shared with any party. This key has to be sent to the server because it is required for homomorphic computation.
|
||||
* The `client_key` is of type `ClientKey`. It is **secret** and must **never** be transmitted. This key will only be used to encrypt and decrypt data.
|
||||
* The `server_key` is of type `ServerKey`. It is a **public key** and can be shared with any party. This key has to be sent to the server because it is required for homomorphic computation.
|
||||
|
||||
Note that both the `client_key` and `server_key` implement the `Serialize` and `Deserialize` traits. This way you can use any compatible serializer to store/send the data. 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. To store the `server_key` in a binary file, you can use the `bincode` library:
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
@@ -72,9 +72,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Encrypting Inputs
|
||||
## Encrypting inputs
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client 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 needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be employed:
|
||||
|
||||
```rust
|
||||
use tfhe::boolean::prelude::*;
|
||||
@@ -99,7 +99,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Encrypting Inputs using a public key
|
||||
## Encrypting inputs using a public key
|
||||
|
||||
Once the server key is available on the **server side**, it is possible to perform some homomorphic computations. The client simply needs to encrypt some data and send it to the server. Again, the `Ciphertext` type implements the `Serialize` and the `Deserialize` traits, so that any serializer and communication tool suiting your use case can be utilized:
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 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)
|
||||
📁 [Github](https://github.com/zama-ai/tfhe-rs) | 💛 [Community support](https://zama.ai/community) | 🟨 [Zama Bounty Program](https://github.com/zama-ai/bounty-program)
|
||||
|
||||

|
||||
|
||||
TFHE-rs is a pure Rust implementation of TFHE for boolean and small integer arithmetics over encrypted data. It includes a Rust and C API, as well as a client-side WASM API.
|
||||
TFHE-rs is a pure Rust implementation of TFHE for Boolean and integer arithmetics over encrypted data. It includes a Rust and C API, as well as a client-side WASM API.
|
||||
|
||||
TFHE-rs is meant for developers and researchers who want full control over what they can do with TFHE, while not having to worry about the low level implementation.
|
||||
TFHE-rs is meant for developers and researchers who want full control over what they can do with TFHE, while not worrying about the low level implementation.
|
||||
|
||||
The goal is to have a stable, simple, high-performance, and production-ready library for all the advanced features of TFHE.
|
||||
|
||||
|
||||
@@ -11,12 +11,9 @@
|
||||
* [Security and Cryptography](getting\_started/security\_and\_cryptography.md)
|
||||
|
||||
## High Level API
|
||||
* [Tutorial](typed_api/tutorial.md)
|
||||
* [Operations](typed_api/operations.md)
|
||||
* [Serialization/Deserialization](typed_api/serialization.md)
|
||||
* Tutorials
|
||||
* [Parity Bit](typed_api/tutorials/parity_bit.md)
|
||||
* [Latin String](typed_api/tutorials/latin_string.md)
|
||||
* [Tutorial](high_level_api/tutorial.md)
|
||||
* [Operations](high_level_api/operations.md)
|
||||
* [Serialization/Deserialization](high_level_api/serialization.md)
|
||||
|
||||
## Boolean
|
||||
* [Tutorial](Boolean/tutorial.md)
|
||||
@@ -33,17 +30,12 @@
|
||||
## Integer
|
||||
* [Tutorial](integer/tutorial.md)
|
||||
* [Operations](integer/operations.md)
|
||||
* [Representations](integer/representations.md)
|
||||
* [Cryptographic Parameters](integer/parameters.md)
|
||||
* [Serialization/Deserialization](integer/serialization.md)
|
||||
* How To
|
||||
* [PBS](integer/how_to/pbs.md)
|
||||
* Tutorials
|
||||
* [Circuit Evaluation](integer/tutorials/circuit_evaluation.md)
|
||||
|
||||
|
||||
## C API
|
||||
* [Tutorial](c_api/tutorial.md)
|
||||
* [High-Level API](c_api/high-level-api.md)
|
||||
* [Shortint API](c_api/shortint-api.md)
|
||||
|
||||
## JS on WASM API
|
||||
* [Tutorial](js_on_wasm_api/tutorial.md)
|
||||
|
||||
BIN
tfhe/docs/_static/integer-ciphertext.png
vendored
Normal file
BIN
tfhe/docs/_static/integer-ciphertext.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
143
tfhe/docs/c_api/high-level-api.md
Normal file
143
tfhe/docs/c_api/high-level-api.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# High-Level API
|
||||
|
||||
\#Using the High-level C API
|
||||
|
||||
This library exposes a C binding to the high-level TFHE-rs primitives to implement _Fully Homomorphic Encryption_ (FHE) programs.
|
||||
|
||||
## First steps using TFHE-rs C API
|
||||
|
||||
### Setting-up TFHE-rs C API for use in a C program.
|
||||
|
||||
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
|
||||
|
||||
```shell
|
||||
RUSTFLAGS="-C target-cpu=native" cargo +nightly build --release --features=x86_64-unix,high-level-c-api -p tfhe
|
||||
```
|
||||
|
||||
or on a Unix aarch64 machine using the following command:
|
||||
|
||||
```shell
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build +nightly --release --features=aarch64-unix,high-level-c-api -p tfhe
|
||||
```
|
||||
|
||||
The `tfhe.h` header as well as the static (.a) and dynamic (.so) `libtfhe` binaries can then be found in "${REPO\_ROOT}/target/release/"
|
||||
|
||||
The build system needs to be set up so that the C or C++ program links against TFHE-rs C API binaries.
|
||||
|
||||
Here is a minimal CMakeLists.txt to do just that:
|
||||
|
||||
```cmake
|
||||
project(my-project)
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(TFHE_C_API "/path/to/tfhe-rs/binaries/and/header")
|
||||
|
||||
include_directories(${TFHE_C_API})
|
||||
add_library(tfhe STATIC IMPORTED)
|
||||
set_target_properties(tfhe PROPERTIES IMPORTED_LOCATION ${TFHE_C_API}/libtfhe.a)
|
||||
|
||||
if(APPLE)
|
||||
find_library(SECURITY_FRAMEWORK Security)
|
||||
if (NOT SECURITY_FRAMEWORK)
|
||||
message(FATAL_ERROR "Security framework not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(EXECUTABLE_NAME my-executable)
|
||||
add_executable(${EXECUTABLE_NAME} main.c)
|
||||
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC tfhe m pthread dl)
|
||||
if(APPLE)
|
||||
target_link_libraries(${EXECUTABLE_NAME} LINK_PUBLIC ${SECURITY_FRAMEWORK})
|
||||
endif()
|
||||
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
|
||||
```
|
||||
|
||||
### Commented code of a uint128 subtraction using `TFHE-rs C API`.
|
||||
|
||||
{% hint style="warning" %}
|
||||
WARNING: The following example does not have proper memory management in the error case to make it easier to fit the code on this page.
|
||||
{% endhint %}
|
||||
|
||||
To run the example below, the above CMakeLists.txt and main.c files need to be in the same directory. The commands to run are:
|
||||
|
||||
```shell
|
||||
# /!\ Be sure to update CMakeLists.txt to give the absolute path to the compiled tfhe library
|
||||
$ ls
|
||||
CMakeLists.txt main.c
|
||||
$ mkdir build && cd build
|
||||
$ cmake .. -DCMAKE_BUILD_TYPE=RELEASE
|
||||
...
|
||||
$ make
|
||||
...
|
||||
$ ./my-executable
|
||||
Result: 2
|
||||
$
|
||||
```
|
||||
|
||||
```c
|
||||
#include <tfhe.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int ok = 0;
|
||||
// Prepare the config builder for the high level API and choose which types to enable
|
||||
ConfigBuilder *builder;
|
||||
Config *config;
|
||||
|
||||
// Put the builder in a default state without any types enabled
|
||||
config_builder_all_disabled(&builder);
|
||||
// Enable the uint128 type using the small LWE key for encryption
|
||||
config_builder_enable_default_uint128_small(&builder);
|
||||
// Populate the config
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
ServerKey *server_key = NULL;
|
||||
|
||||
// Generate the keys using the config
|
||||
generate_keys(config, &client_key, &server_key);
|
||||
// Set the server key for the current thread
|
||||
set_server_key(server_key);
|
||||
|
||||
FheUint128 *lhs = NULL;
|
||||
FheUint128 *rhs = NULL;
|
||||
FheUint128 *result = NULL;
|
||||
|
||||
// Encrypt a u128 using 64 bits words, we encrypt 20 << 64 | 10
|
||||
ok = fhe_uint128_try_encrypt_with_client_key_u128(10, 20, client_key, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
// Encrypt a u128 using words, we encrypt 2 << 64 | 1
|
||||
ok = fhe_uint128_try_encrypt_with_client_key_u128(1, 2, client_key, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
// Compute the subtraction
|
||||
ok = fhe_uint128_sub(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint64_t w0, w1;
|
||||
// Decrypt
|
||||
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
|
||||
assert(ok == 0);
|
||||
|
||||
// Here the subtraction allows us to compare each word
|
||||
assert(w0 == 9);
|
||||
assert(w1 == 18);
|
||||
|
||||
// Destroy the ciphertexts
|
||||
fhe_uint128_destroy(lhs);
|
||||
fhe_uint128_destroy(rhs);
|
||||
fhe_uint128_destroy(result);
|
||||
|
||||
// Destroy the keys
|
||||
client_key_destroy(client_key);
|
||||
server_key_destroy(server_key);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
```
|
||||
@@ -1,34 +1,32 @@
|
||||
# Tutorial
|
||||
# Shortint API
|
||||
|
||||
## Using the C API
|
||||
## Using the shortint 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.
|
||||
This library exposes a C binding to the TFHE-rs shortint API to implement _Fully Homomorphic Encryption_ (FHE) programs.
|
||||
|
||||
## First steps using TFHE-rs C API
|
||||
|
||||
### Setting-up TFHE-rs C API for use in a C program.
|
||||
### Setting up TFHE-rs C API for use in a C program.
|
||||
|
||||
TFHE-rs C API can be built on a Unix x86\_64 machine using the following command:
|
||||
|
||||
```shell
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features=x86_64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
RUSTFLAGS="-C target-cpu=native" cargo +nightly build --release --features=x86_64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
```
|
||||
|
||||
or on a Unix aarch64 machine using the following command
|
||||
or on a Unix aarch64 machine using the following command:
|
||||
|
||||
```shell
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features=aarch64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build +nightly --release --features=aarch64-unix,boolean-c-api,shortint-c-api -p tfhe
|
||||
```
|
||||
|
||||
All features are opt-in, but for simplicity here, the C API is enabled for boolean and shortint.
|
||||
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:
|
||||
Here is a minimal CMakeLists.txt to do just that:
|
||||
|
||||
```cmake
|
||||
project(my-project)
|
||||
@@ -58,9 +56,9 @@ endif()
|
||||
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Werror)
|
||||
```
|
||||
|
||||
### Commented code of a PBS doubling a 2 bits encrypted message using `TFHE-rs C API`.
|
||||
### Commented code of a PBS doubling a 2-bits encrypted message using `TFHE-rs C API`.
|
||||
|
||||
The steps required to perform the multiplication by 2 of a 2 bits ciphertext using a PBS are detailed. This is NOT the most efficient way of doing this operation, but it can help to show the management required to run a PBS manually using the C API.
|
||||
The steps required to perform the multiplication by 2 of a 2-bits ciphertext using a PBS are detailed. This is NOT the most efficient way of doing this operation, but it can help to show the management required to run a PBS manually using the C API.
|
||||
|
||||
WARNING: The following example does not have proper memory management in the error case to make it easier to fit the code on this page.
|
||||
|
||||
@@ -172,7 +170,3 @@ int main(void)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
## Audience
|
||||
|
||||
Programmers wishing to use TFHE-rs but who are unable to use Rust (for various reasons) can use these bindings in their language of choice, as long as it can interface with C code to bring TFHE-rs functionalities to said language.
|
||||
@@ -1,15 +1,15 @@
|
||||
# Overview of the `core_crypto` Module
|
||||
# Quick Start
|
||||
|
||||
The `core_crypto` module from TFHE-rs is dedicated to the implementation of the cryptographic tools related to TFHE. To construct an FHE application, the [shortint](../shortint/tutorial.md) and/or [Boolean](../Boolean/tutorial.md) modules (based on this one) are recommended.
|
||||
|
||||
The `core_crypto` module offers an API to low-level cryptographic primitives and objects, like `lwe_encryption` or `rlwe_ciphertext`. Its goal is to propose an easy-to-use API for cryptographers.
|
||||
|
||||
The overall code architecture is split in two parts: one for the entity definitions, and another one focused on the algorithms. For instance, the entities contain the definition of useful types, like LWE ciphertext or bootstrapping keys. The algorithms are then naturally defined to work using these entities.
|
||||
|
||||
The API is convenient to easily add or modify existing algorithms or to have direct access to the raw data. For instance, even if the LWE ciphertext object is defined along with functions giving access to he body, this is also possible to bypass these to get directly the $$i^{th}$$ element of LWE mask.
|
||||
The overall code architecture is split in two parts: one for entity definitions and another focused on algorithms. The entities contain the definition of useful types, like LWE ciphertext or bootstrapping keys. The algorithms are then naturally defined to work using these entities.
|
||||
|
||||
The API is convenient to easily add or modify existing algorithms or to have direct access to the raw data. Even if the LWE ciphertext object is defined along with functions giving access to the body, this is also possible to bypass these to get directly the $$i^{th}$$ element of LWE mask.
|
||||
|
||||
For instance, the code to encrypt and then decrypt a message looks like:
|
||||
|
||||
```rust
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
|
||||
@@ -18,6 +18,7 @@ use tfhe::core_crypto::prelude::*;
|
||||
// Define parameters for LweCiphertext creation
|
||||
let lwe_dimension = LweDimension(742);
|
||||
let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
@@ -36,7 +37,7 @@ let msg = 3u64;
|
||||
let plaintext = Plaintext(msg << 60);
|
||||
|
||||
// Create a new LweCiphertext
|
||||
let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
|
||||
let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), ciphertext_modulus);
|
||||
|
||||
encrypt_lwe_ciphertext(
|
||||
&lwe_secret_key,
|
||||
@@ -59,4 +60,3 @@ let cleartext = rounded >> 60;
|
||||
// Check we recovered the original message
|
||||
assert_eq!(cleartext, msg);
|
||||
```
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
## Using the `core_crypto` primitives
|
||||
|
||||
Welcome to this tutorial about TFHE-rs `core_crypto` module!
|
||||
Welcome to this tutorial about TFHE-rs `core_crypto` module.
|
||||
|
||||
### Setting-up TFHE-rs to use the `core_crypto` module
|
||||
### Setting up TFHE-rs to use the `core_crypto` module
|
||||
|
||||
To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
|
||||
|
||||
@@ -12,12 +12,11 @@ To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
|
||||
tfhe = { version = "0.2.0", features = [ "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
Here, this enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is actived as a fallback if no hardware number generator is available, like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available. To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
|
||||
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available, like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available. To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
|
||||
|
||||
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. Note that `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs.
|
||||
For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `aarch64` is not supported on Windows as it's currently missing an entropy source required to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically\_secure\_pseudorandom\_number\_generator) used in TFHE-rs.
|
||||
|
||||
In short:
|
||||
For x86_64-based machines running Unix-like OSes:
|
||||
In short: For x86\_64-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = ["x86_64-unix"] }
|
||||
@@ -29,15 +28,15 @@ For Apple Silicon or aarch64-based machines running Unix-like OSes:
|
||||
tfhe = { version = "0.2.0", features = ["aarch64-unix"] }
|
||||
```
|
||||
|
||||
For x86_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
|
||||
For x86\_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = ["x86_64"] }
|
||||
```
|
||||
|
||||
### Commented code to double a 2 bits message in a leveled fashion and using a PBS with the `core_crypto` module.
|
||||
### Commented code to double a 2-bits message in a leveled fashion and using a PBS with the `core_crypto` module.
|
||||
|
||||
As a complete example showing the usage of some common primitives of the `core_crypto` APIs, the following Rust code homomorphically computes 2 * 3 using two different methods. First using a cleartext multiplication and second using a PBS.
|
||||
As a complete example showing the usage of some common primitives of the `core_crypto` APIs, the following Rust code homomorphically computes 2 \* 3 using two different methods. First using a cleartext multiplication and then using a PBS.
|
||||
|
||||
```rust
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
@@ -53,6 +52,7 @@ pub fn main() {
|
||||
let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
let pbs_base_log = DecompositionBaseLog(23);
|
||||
let pbs_level = DecompositionLevelCount(1);
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
// Request the best seeder possible, starting with hardware entropy sources and falling back to
|
||||
// /dev/random on Unix systems if enabled via cargo features
|
||||
@@ -89,6 +89,7 @@ pub fn main() {
|
||||
pbs_base_log,
|
||||
pbs_level,
|
||||
glwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
@@ -124,6 +125,7 @@ pub fn main() {
|
||||
&small_lwe_sk,
|
||||
plaintext,
|
||||
lwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
@@ -167,6 +169,7 @@ pub fn main() {
|
||||
polynomial_size: PolynomialSize,
|
||||
glwe_size: GlweSize,
|
||||
message_modulus: usize,
|
||||
ciphertext_modulus: CiphertextModulus<u64>,
|
||||
delta: u64,
|
||||
f: F,
|
||||
) -> GlweCiphertextOwned<u64>
|
||||
@@ -202,7 +205,11 @@ pub fn main() {
|
||||
let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
|
||||
|
||||
let accumulator =
|
||||
allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &accumulator_plaintext);
|
||||
allocate_and_trivially_encrypt_new_glwe_ciphertext(
|
||||
glwe_size,
|
||||
&accumulator_plaintext,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
accumulator
|
||||
}
|
||||
@@ -212,13 +219,17 @@ pub fn main() {
|
||||
polynomial_size,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
message_modulus as usize,
|
||||
ciphertext_modulus,
|
||||
delta,
|
||||
|x: u64| 2 * x,
|
||||
);
|
||||
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut pbs_multiplication_ct =
|
||||
LweCiphertext::new(0u64, big_lwe_sk.lwe_dimension().to_lwe_size());
|
||||
let mut pbs_multiplication_ct = LweCiphertext::new(
|
||||
0u64,
|
||||
big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
println!("Computing PBS...");
|
||||
programmable_bootstrap_lwe_ciphertext(
|
||||
&lwe_ciphertext_in,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contributing
|
||||
|
||||
There are two ways to contribute to **TFHE-rs**:
|
||||
There are two ways to contribute to **TFHE-rs**. You can:
|
||||
|
||||
* you can open issues to report bugs and typos and to suggest ideas
|
||||
* you can ask to become an official contributor by emailing hello@zama.ai. Only approved contributors can end pull requests, so please make sure to get in touch before you do!
|
||||
* open issues to report bugs and typos and to suggest ideas;
|
||||
* ask to become an official contributor by emailing hello@zama.ai. Only approved contributors can send pull requests, so get in touch before you do.
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Benchmarks
|
||||
|
||||
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.
|
||||
Due to their nature, homomorphic operations are naturally slower than their clear equivalent. Some timings are exposed for basic operations. For completeness, benchmarks for other libraries are also given.
|
||||
|
||||
All the benchmarks had been launched on an AWS m6i.metal with the following specifications: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
|
||||
All benchmarks were launched on an AWS m6i.metal with the following specifications: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz and 512GB of RAM.
|
||||
|
||||
## Boolean
|
||||
|
||||
This measures the execution time of a single binary boolean gate.
|
||||
This measures the execution time of a single binary Boolean gate.
|
||||
|
||||
### tfhe.rs::boolean.
|
||||
### tfhe-rs::boolean.
|
||||
|
||||
| Parameter set | concrete-fft | concrete-fft + avx512 |
|
||||
| Parameter set | Concrete FFT | Concrete FFT + avx512 |
|
||||
| --------------------- | ------------ | --------------------- |
|
||||
| DEFAULT\_PARAMETERS | 8.8ms | 6.8ms |
|
||||
| TFHE\_LIB\_PARAMETERS | 13.6ms | 10.9ms |
|
||||
@@ -28,17 +28,41 @@ This measures the execution time of a single binary boolean gate.
|
||||
| STD\_128 | 172ms | 78ms |
|
||||
| MEDIUM | 113ms | 50.2ms |
|
||||
|
||||
|
||||
## Shortint
|
||||
This measures the execution time for some operations and some parameter sets of tfhe-rs::shortint.
|
||||
|
||||
This measures the execution time for some operations and some parameter sets of shortint.
|
||||
|
||||
### tfhe.rs::shortint.
|
||||
|
||||
This uses the concrete-fft + avx512 configuration.
|
||||
This uses the Concrete FFT + avx512 configuration.
|
||||
|
||||
| Parameter set | unchecked\_add | unchecked\_mul\_lsb | keyswitch\_programmable\_bootstrap |
|
||||
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
|
||||
| PARAM\_MESSAGE\_1\_CARRY\_1 | 338 ns | 8.3 ms | 8.1 ms |
|
||||
| PARAM\_MESSAGE\_2\_CARRY\_2 | 406 ns | 18.4 ms | 18.4 ms |
|
||||
| PARAM\_MESSAGE\_3\_CARRY\_3 | 3.06 µs | 134 ms | 134 ms |
|
||||
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 945 ms |
|
||||
| PARAM\_MESSAGE\_3\_CARRY\_3 | 3.06 µs | 134 ms | 129.4 ms |
|
||||
| PARAM\_MESSAGE\_4\_CARRY\_4 | 11.7 µs | 854 ms | 828.1 ms |
|
||||
|
||||
Next, the timings for the operation flavor `default` are given. This flavor ensures predictable timings of an operation all along the circuit by clearing the carry space after each operation.
|
||||
|
||||
| Parameter set | add | mul\_lsb | keyswitch\_programmable\_bootstrap |
|
||||
| --------------------------- | -------------- | ------------------- | ---------------------------------- |
|
||||
| PARAM\_MESSAGE\_1\_CARRY\_1 | 7.90 ms | 8.00 ms | 8.10 ms |
|
||||
| PARAM\_MESSAGE\_2\_CARRY\_2 | 18.4 ms | 18.1 ms | 18.4 ms |
|
||||
| PARAM\_MESSAGE\_3\_CARRY\_3 | 131.5 ms | 129.5 ms | 129.4 ms |
|
||||
| PARAM\_MESSAGE\_4\_CARRY\_4 | 852.5 ms | 839.7 ms | 828.1 ms |
|
||||
|
||||
|
||||
## Integer
|
||||
This measures the execution time for some operation sets of tfhe-rs::integer.
|
||||
|
||||
All timings are related to parallelized Radix-based integer operations, where each block is encrypted using PARAM\_MESSAGE\_2\_CARRY\_2.
|
||||
To ensure predictable timings, the operation flavor is the `default` one: a carry propagation is computed after each operation. Operation cost could be reduced by using `unchecked`, `checked`, or `smart`.
|
||||
|
||||
| Plaintext size | add | mul | greater\_than (gt) | min |
|
||||
| -------------------| -------------- | ------------------- | --------- | ------- |
|
||||
| 8 bits | 129.0 ms | 178.2 ms | 111.9 ms | 287.7 ms |
|
||||
| 16 bits | 256.3 ms | 328.0 ms | 145.3 ms | 437.4 ms |
|
||||
| 32 bits | 469.4 ms | 645.5 ms | 192.0 ms | 776.4 ms |
|
||||
| 40 bits | 608.0 ms | 849.3 ms | 228.4 ms | 953.5 ms |
|
||||
| 64 bits | 959.9 ms | 1.49 s | 249.0 ms | 1.36 s |
|
||||
| 128 bits | 1.88 s | 3.25 s | 294.7 ms | 2.37 s |
|
||||
| 256 bits | 3.66 s | 8.38 s | 361.8 ms | 4.51 s |
|
||||
|
||||
@@ -9,9 +9,7 @@ tfhe = { version = "0.2.0", features = [ "boolean", "shortint", "integer", "x86_
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
When running code that uses `tfhe-rs`, it is highly recommended
|
||||
to run in release mode with cargo's `--release` flag to have the best performances possible,
|
||||
eg: `cargo run --release`.
|
||||
When running code that uses `tfhe-rs`, it is highly recommended to run in release mode with cargo's `--release` flag to have the best performances possible, eg: `cargo run --release`.
|
||||
{% endhint %}
|
||||
|
||||
## Choosing your features
|
||||
@@ -32,7 +30,7 @@ This crate exposes two kinds of data types. Each kind is enabled by activating i
|
||||
|
||||
The different data types and keys exposed by the crate can be serialized / deserialized.
|
||||
|
||||
More information can be found [here](../Boolean/serialization.md) for boolean and [here](../shortint/serialization.md) for shortint.
|
||||
More information can be found [here](../Boolean/serialization.md) for Boolean and [here](../shortint/serialization.md) for shortint.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
@@ -50,7 +48,7 @@ Users who have ARM devices can use `TFHE-rs` by compiling using the `nightly` to
|
||||
|
||||
### Using TFHE-rs with nightly toolchain.
|
||||
|
||||
First, install the needed Rust toolchain:
|
||||
Install the needed Rust toolchain:
|
||||
|
||||
```shell
|
||||
rustup toolchain install nightly
|
||||
@@ -60,8 +58,6 @@ Then, you can either:
|
||||
|
||||
* Manually specify the toolchain to use in each of the cargo commands:
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
cargo +nightly build
|
||||
cargo +nightly test
|
||||
|
||||
@@ -16,7 +16,7 @@ The list of supported operations by the homomorphic Booleans is:
|
||||
|
||||
A walk-through using homomorphic Booleans can be found [here](../Boolean/tutorial.md).
|
||||
|
||||
## ShortInt
|
||||
## Shortint
|
||||
|
||||
In TFHE-rs, shortint represents short unsigned integers encoded over a maximum of 8 bits. A complete homomorphic arithmetic is provided, along with the possibility to compute univariate and bi-variate functions. Some operations are only available for integers up to 4 bits. More technical details can be found [here](../shortint/operations.md).
|
||||
|
||||
@@ -38,7 +38,27 @@ The list of supported operations is:
|
||||
| Exact Function Evaluation | Unary/Binary |
|
||||
|
||||
{% hint style="info" %}
|
||||
\* The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. In this case, the division is tweaked so that dividing by 0 returns 0.
|
||||
The division operation implements a subtlety: since data is encrypted, it might be possible to compute a division by 0. The division is tweaked so that dividing by 0 returns 0.
|
||||
{% endhint %}
|
||||
|
||||
A walk-through example can be found [here](../shortint/tutorial.md), and more examples and explanations can be found [here](../shortint/operations.md).[ ](../shortint/operations.md)
|
||||
A walk-through example can be found [here](../shortint/tutorial.md), and more examples and explanations can be found [here](../shortint/operations.md).
|
||||
|
||||
## Integer
|
||||
|
||||
In TFHE-rs, integers represent unsigned integers up to 256 bits. They are encoded using Radix representations by default (more details [here](../integer/operations.md)).
|
||||
|
||||
The list of supported operations is:
|
||||
|
||||
| Operation name | Type |
|
||||
| ------------------------------ | ------ |
|
||||
| Negation | Unary |
|
||||
| Addition | Binary |
|
||||
| Subtraction | Binary |
|
||||
| Multiplication | Binary |
|
||||
| Bitwise OR, AND, XOR | Binary |
|
||||
| Equality | Binary |
|
||||
| Left/Right Shift | Binary |
|
||||
| Comparisons `<`,`<=`,`>`, `>=` | Binary |
|
||||
| Min, Max | Binary |
|
||||
|
||||
A walk-through example can be found [here](../integer/tutorial.md).
|
||||
|
||||
@@ -1,47 +1,40 @@
|
||||
# Quick Start
|
||||
|
||||
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.
|
||||
This library makes it possible to execute **homomorphic operations over encrypted data**, where the data are either Booleans, short integers (named shortint in the rest of this documentation), or integers up to 256 bits. It allows you to execute a circuit on an **untrusted server** because both circuit inputs and outputs are kept **private**. Data are indeed encrypted on the client side, before being sent to the server. On the server side, every computation is performed on ciphertexts.
|
||||
|
||||
The server, however, has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. She can then decrypt it with her `secret key`.
|
||||
The server, however, has to know the circuit to be evaluated. At the end of the computation, the server returns the encryption of the result to the user. Then the user can decrypt it with the `secret key`.
|
||||
|
||||
## General method to write an homomorphic circuit program
|
||||
|
||||
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 all types. The basic steps for using the TFHE-rs library are the following:
|
||||
|
||||
* Choose a data type (Boolean or shortint)
|
||||
* Import the library
|
||||
* Create client and server keys
|
||||
* Encrypt data with the client key
|
||||
* Compute over encrypted data using the server key
|
||||
* Decrypt data with the client key
|
||||
1. Choose a data type (Boolean, shortint, integer)
|
||||
2. Import the library
|
||||
3. Create client and server keys
|
||||
4. Encrypt data with the client key
|
||||
5. Compute over encrypted data using the server key
|
||||
6. Decrypt data with the client key
|
||||
|
||||
### API levels.
|
||||
|
||||
### API Levels
|
||||
This library has different modules, with different levels of abstraction.
|
||||
|
||||
This library has different modules, with different level of abstraction.
|
||||
There is the **core\_crypto** module, which is the lowest level API with the primitive functions and types of the TFHE scheme.
|
||||
|
||||
There is a the core_crypto module which is the lowest level API, with the primitive
|
||||
functions and types of the TFHE scheme.
|
||||
Above the core\_crypto module, there are the B**oolean**, **shortint**, and **integer** modules, which simply allow evaluation of Boolean, short integer, and integer circuits.
|
||||
|
||||
The are the boolean, shortint and integer modules which are based on the core_crypto,
|
||||
to allow construction of respectively, booleans, short integers, and integers circuits.
|
||||
Finally, there is the high-level module built on top of the Boolean, shortint, integer modules. This module is meant to abstract cryptographic complexities: no cryptographical knowledge is required to start developing an FHE application. Another benefit of the high-level module is the drastically simplified development process compared to lower level modules.
|
||||
|
||||
Then there is the high-level module built on top of the boolean, shortint, integer modules,
|
||||
this module is meant to abstract as much as possible the TFHE part and allow quick development of
|
||||
FHE applications.
|
||||
#### high-level API
|
||||
|
||||
#### High Level API
|
||||
TFHE-rs exposes a high-level API by default that includes datatypes that try to match Rust's native types by having overloaded operators (+, -, ...).
|
||||
|
||||
tfhe-rs by default exposes a High Level API, that manages the server_key and proposes datatypes
|
||||
that try to match Rust's native types by having overloaded operators (+, -, ...).
|
||||
Here is an example of how the high-level API is used:
|
||||
|
||||
Here is an example to illustrate how the high level API is used.
|
||||
|
||||
{% hint style="info" %}
|
||||
{% hint style="warning" %}
|
||||
Use the `--release` flag to run this example (eg: `cargo run --release`)
|
||||
{% endhint %}
|
||||
|
||||
|
||||
```rust
|
||||
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
|
||||
use tfhe::prelude::*;
|
||||
@@ -71,11 +64,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Boolean example.
|
||||
#### Boolean example
|
||||
|
||||
Here is an example to illustrate how the library can be used to evaluate a Boolean circuit:
|
||||
Here is an example of how the library can be used to evaluate a Boolean circuit:
|
||||
|
||||
{% hint style="info" %}
|
||||
{% hint style="warning" %}
|
||||
Use the `--release` flag to run this example (eg: `cargo run --release`)
|
||||
{% endhint %}
|
||||
|
||||
@@ -103,11 +96,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Shortint example.
|
||||
#### shortint example
|
||||
|
||||
And here is a full example using shortint:
|
||||
Here is a full example using shortint:
|
||||
|
||||
{% hint style="info" %}
|
||||
{% hint style="warning" %}
|
||||
Use the `--release` flag to run this example (eg: `cargo run --release`)
|
||||
{% endhint %}
|
||||
|
||||
@@ -137,9 +130,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Integer example.
|
||||
#### integer example
|
||||
|
||||
{% hint style="info" %}
|
||||
{% hint style="warning" %}
|
||||
Use the `--release` flag to run this example (eg: `cargo run --release`)
|
||||
{% endhint %}
|
||||
|
||||
@@ -165,4 +158,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)).
|
||||
The library is simple to use and can evaluate **homomorphic circuits of arbitrary length**. The description of the algorithms can be found in the [TFHE](https://doi.org/10.1007/s00145-019-09319-x) paper (also available as [ePrint 2018/421](https://ia.cr/2018/421)).
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
TFHE-rs is a cryptographic library dedicated to Fully Homomorphic Encryption. As its name suggests, it is based on the TFHE scheme.
|
||||
|
||||
It is 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).
|
||||
It is necessary to understand some basics about TFHE to comprehend where the limitations are coming from, both in terms of precision (number of bits used to represent plaintext values) and execution time (why TFHE operations are slower than native operations).
|
||||
|
||||
## LWE ciphertexts
|
||||
|
||||
@@ -24,7 +24,7 @@ To go from a **plaintext** to a **ciphertext,** one must encrypt the plaintext u
|
||||
|
||||
An LWE secret key is a list of `n` random integers: $$S = (s_0, ..., s_n)$$. $$n$$ is called the $$LweDimension$$
|
||||
|
||||
A LWE ciphertext, is composed of two parts:
|
||||
A LWE ciphertext is composed of two parts:
|
||||
|
||||
* The mask $$(a_0, ..., a_{n-1})$$
|
||||
* The body $$b$$
|
||||
@@ -35,15 +35,15 @@ The body is computed as follows:
|
||||
|
||||
$$b = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext$$
|
||||
|
||||
Now that the encryption scheme is defined, to illustrate why it is slower to compute over encrypted data, let us show the example of the addition between ciphertexts.
|
||||
Now that the encryption scheme is defined, let's review the example of the addition between ciphertexts to illustrate why it is slower to compute over encrypted data.
|
||||
|
||||
To add two ciphertexts, we must add their $mask$ and $body$, as is done below.
|
||||
To add two ciphertexts, we must add their $mask$ and $body$:
|
||||
|
||||
$$
|
||||
ct_0 = (a_{0}, ..., a_{n}, b) \\ ct_1 = (a_{1}^{'}, ..., a_{n}^{'}, b^{'}) \\ ct_{2} = ct_0 + ct_1 \\ ct_{2} = (a_{0} + a_{0}^{'}, ..., a_{n} + a_{n}^{'}, b + b^{'})\\ b + b^{'} = (\sum_{i = 0}^{n-1}{a_i * s_i}) + plaintext + (\sum_{i = 0}^{n-1}{a_i^{'} * s_i}) + plaintext^{'}\\ b + b^{'} = (\sum_{i = 0}^{n-1}{(a_i + a_i^{'})* s_i}) + \Delta m + \Delta m^{'} + e + e^{'}\\
|
||||
$$
|
||||
|
||||
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).
|
||||
To add ciphertexts, it is sufficient to add their masks and bodies. Instead of just adding two integers, one needs to add $$n + 1$$ elements. The addition is an intuitive example to show the slowdown of FHE computation compared to plaintext computation, but other operations are far more expensive (e.g., the computation of a lookup table using Programmable Bootstrapping).
|
||||
|
||||
## Understanding noise and padding
|
||||
|
||||
@@ -52,9 +52,9 @@ In FHE, there are two types of operations that can be applied to ciphertexts:
|
||||
* **leveled operations**, which increase the noise in the ciphertext
|
||||
* **bootstrapped operations**, which reduce the noise in the ciphertext
|
||||
|
||||
In FHE, the noise must be tracked and managed in order to guarantee the correctness of the computation.
|
||||
In FHE, noise must be tracked and managed to guarantee the correctness of the computation.
|
||||
|
||||
Bootstrapping operations are used across the computation to decrease the noise in the ciphertexts, preventing it from tampering the message. The rest of the operations are called leveled because they do not need bootstrapping operations and, thus, are usually really fast.
|
||||
Bootstrapping operations are used across the computation to decrease noise within the ciphertexts, preventing it from tampering the message. The rest of the operations are called leveled because they do not need bootstrapping operations and are usually really fast as a result.
|
||||
|
||||
The following sections explain the concept of noise and padding in ciphertexts.
|
||||
|
||||
@@ -62,19 +62,19 @@ The following sections explain the concept of noise and padding in ciphertexts.
|
||||
|
||||
For it to be secure, LWE requires random noise to be added to the message at encryption time.
|
||||
|
||||
In TFHE, this random noise is drawn from a Centered Normal Distribution parameterized by a standard deviation. This standard deviation is a security parameter. With all other security parameters set, the larger the standard deviation is, the more secure the encryption is.
|
||||
In TFHE, this random noise is drawn from a Centered Normal Distribution, parameterized by a standard deviation. This standard deviation is a security parameter. With all other security parameters set, the more secure the encryption, the larger the standard deviation.
|
||||
|
||||
In `TFHE-rs`, the noise is encoded in the least significant bits of the plaintexts. Each leveled computation will increase the noise. 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.
|
||||
In `TFHE-rs`, noise is encoded in the least significant bits of the plaintexts. Each leveled computation increases the noise. If too many computations are performed, the noise will eventually overflow onto the significant data bits of the message and lead to an incorrect result.
|
||||
|
||||
The figure below illustrates this problem in case of an addition, where an extra bit of noise is incurred as a result.
|
||||
|
||||

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

|
||||
|
||||
This crate implements two ways to represent an integer:
|
||||
|
||||
* the Radix representation
|
||||
* the CRT (Chinese Reminder Theorem) representation
|
||||
|
||||
### Radix-based integers.
|
||||
|
||||
The first possibility to represent a large integer is to use a Radix-based decomposition on the plaintexts. Let $$B \in \mathbb{N}$$ be a basis such that the size of $$B$$ is smaller than (or equal to) 4 bits. Then, an integer $$m \in \mathbb{N}$$ can be written as $$m = m_0 + m_1*B + m_2*B^2 + ...$$, where each $$m_i$$ is strictly smaller than $$B$$. Each $$m_i$$ is then independently encrypted. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
|
||||
|
||||
The definition of an integer requires a basis and a number of blocks. This is done at key generation. Below, the keys are dedicated to unsigned integers encrypting messages over 8 bits, using a basis over 2 bits (i.e., $$B=2^2$$) and 4 blocks.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
}
|
||||
```
|
||||
|
||||
In this representation, the correctness of operations requires to propagate the carries between the ciphertext. This operation is costly since it relies on the computation of many programmable bootstrapping operations over shortints.
|
||||
|
||||
### CRT-based integers.
|
||||
|
||||
The second approach to represent large integers is based on the Chinese Remainder Theorem. In this case, the basis $$B$$ is composed of several integers $$b_i$$, such that there are pairwise coprime, and each $$b\_i$$ has a size smaller than 4 bits. The CRT-based integer are defined modulus $$\prod b_i$$. For an integer $$m$$, its CRT decomposition is simply defined as $$m % b_0, m % b_1, ...$$. Each part is then encrypted as a shortint ciphertext. In the end, an Integer ciphertext is defined as a set of shortint ciphertexts.
|
||||
|
||||
In the following example, the chosen basis is $$B = [2, 3, 5]$$. The integer is defined modulus $$2*3*5 = 30$$. There is no need to pre-size the number of blocks since it is determined from the number of values composing the basis. Here, the integer is split over three blocks.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::CrtClientKey;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let basis = vec![2, 3, 5];
|
||||
let cks = CrtClientKey::new(PARAM_MESSAGE_2_CARRY_2, basis);
|
||||
}
|
||||
```
|
||||
|
||||
This representation has many advantages: no carry propagation is required, cleaning the carry buffer of each ciphertext block is enough. This implies that operations can easily be
|
||||
parallelized. It also allows the efficient computation of PBS in the case where the function is CRT-compliant.
|
||||
|
||||
A variant of the CRT is proposed, where each block might be associated to a different key couple. In the end, a keychain to the computations is required, but performance might be improved.
|
||||
|
||||
## List of available operations
|
||||
|
||||
The list of operations available in `integer` depends on the type of representation:
|
||||
|
||||
| Operation name | Radix-based | CRT-based |
|
||||
| ------------------------------ | -------------------- | -------------------------- |
|
||||
| Negation | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Addition | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Scalar Addition | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Subtraction | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Scalar Subtraction | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Multiplication | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Scalar Multiplication | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Bitwise OR, AND, XOR | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Equality | :heavy\_check\_mark: | :heavy\_check\_mark: |
|
||||
| Left/Right Shift | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
|
||||
| Comparisons `<`,`<=`,`>`, `>=` | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
|
||||
| Min, Max | :heavy\_check\_mark: | :heavy\_multiplication\_x: |
|
||||
|
||||
## Types of operations
|
||||
|
||||
Much like `shortint`, the operations available via a `ServerKey` may come in different variants:
|
||||
|
||||
- operations that take their inputs as encrypted values.
|
||||
- scalar operations take at least one non-encrypted value as input.
|
||||
* operations that take their inputs as encrypted values.
|
||||
* scalar operations take at least one non-encrypted value as input.
|
||||
|
||||
For example, the addition has both variants:
|
||||
|
||||
- `ServerKey::unchecked_add` which takes two encrypted values and adds them.
|
||||
- `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the
|
||||
so-called scalar) and adds them.
|
||||
* `ServerKey::unchecked_add`, which takes two encrypted values and adds them.
|
||||
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (the so-called scalar) and adds them.
|
||||
|
||||
Each operation may come in different 'flavors':
|
||||
|
||||
- `unchecked`: Always does the operation, without checking if the result may exceed the capacity of
|
||||
the plaintext space.
|
||||
- `checked`: Checks are done before computing the operation, returning an error if operation
|
||||
cannot be done safely.
|
||||
- `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation
|
||||
will propagate the carry buffer to make the operation possible.
|
||||
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space.
|
||||
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely.
|
||||
* `smart`: Always does the operation, if the operation cannot be computed safely, the smart operation will propagate the carry buffer to make the operation possible.
|
||||
* `default`: Always compute the operation and always clear the carry. Could be **slower** than smart, but ensure that the timings are consistent from one call to another.
|
||||
|
||||
Not all operations have these 3 flavors, as some of them are implemented in a way that the operation
|
||||
is always possible without ever exceeding the plaintext space capacity.
|
||||
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
|
||||
|
||||
## How to use each operation type
|
||||
|
||||
Let's try to do a circuit evaluation using the different flavors of already introduced operations. For a very small circuit, the `unchecked` flavor may be enough to do the computation correctly. Otherwise, `checked` and `smart` are the best options.
|
||||
|
||||
As an example, let's do a scalar multiplication, a subtraction, and an addition.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12u64;
|
||||
let msg2 = 11u64;
|
||||
let msg3 = 9u64;
|
||||
let scalar = 3u64;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
let ct_3 = client_key.encrypt(msg2);
|
||||
|
||||
server_key.unchecked_small_scalar_mul_assign(&mut ct_1, scalar);
|
||||
|
||||
server_key.unchecked_sub_assign(&mut ct_1, &ct_2);
|
||||
|
||||
server_key.unchecked_add_assign(&mut ct_1, &ct_3);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
// The carry buffer has been overflowed, the result is not correct
|
||||
assert_ne!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
During this computation the carry buffer has been overflowed, and the output may be incorrect as all the operations were `unchecked`.
|
||||
|
||||
If the same circuit is done but using the `checked` flavor, a panic will occur:
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 2;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12u64;
|
||||
let msg2 = 11u64;
|
||||
let msg3 = 9u64;
|
||||
let scalar = 3u64;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
let ct_3 = client_key.encrypt(msg3);
|
||||
|
||||
let result = server_key.checked_small_scalar_mul_assign(&mut ct_1, scalar);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
|
||||
assert!(result.is_err());
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
// Only the scalar multiplication could be done
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, (msg1 * scalar) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
The `checked` flavor permits the manual management of the overflow of the carry buffer by raising an error if correctness is not guaranteed.
|
||||
|
||||
Using the `smart` flavor will output the correct result all the time. However, the computation may be slower as the carry buffer may be propagated during the computations.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12;
|
||||
let msg2 = 11;
|
||||
let msg3 = 9;
|
||||
let scalar = 3;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let mut ct_2 = client_key.encrypt(msg2);
|
||||
let mut ct_3 = client_key.encrypt(msg3);
|
||||
|
||||
server_key.smart_scalar_mul_assign(&mut ct_1, scalar);
|
||||
|
||||
server_key.smart_sub_assign(&mut ct_1, &mut ct_2);
|
||||
|
||||
server_key.smart_add_assign(&mut ct_1, &mut ct_3);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
The main advantage of the default flavor is to ensure predictable timings, as long as only this kind of operation is used. Only the parallelized version of the operations is provided.
|
||||
|
||||
{% hint style="warning" %}
|
||||
Using `default` could **slow down** computations.
|
||||
{% endhint %}
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12;
|
||||
let msg2 = 11;
|
||||
let msg3 = 9;
|
||||
let scalar = 3;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let mut ct_2 = client_key.encrypt(msg2);
|
||||
let mut ct_3 = client_key.encrypt(msg3);
|
||||
|
||||
server_key.scalar_mul_assign_parallelized(&mut ct_1, scalar);
|
||||
|
||||
server_key.sub_assign_parallelized(&mut ct_1, &mut ct_2);
|
||||
|
||||
server_key.add_assign_parallelized(&mut ct_1, &mut ct_3);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# Use of parameters
|
||||
# Cryptographic Parameters
|
||||
|
||||
|
||||
`integer` does not come with its own set of parameters, instead it uses
|
||||
parameters from the `shortint` crate. Currently, only the parameters
|
||||
`PARAM_MESSAGE_{X}_CARRY_{X}` with `X` in [1,4] can be used in `integer`.
|
||||
`integer` does not come with its own set of parameters. Instead, it relies on parameters from `shortint`. Currently, parameter sets having the same space dedicated to the message and the carry (i.e. `PARAM_MESSAGE_{X}_CARRY_{X}` with `X` in \[1,4]) are recommended. See [here](../shortint/parameters.md) for more details about cryptographic parameters, and [here](operations.md) to see how to properly instantiate integers depending on the chosen representation.
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# How Integers are represented
|
||||
|
||||
|
||||
In `integer`, the encrypted data is split amongst many ciphertexts
|
||||
encrypted using the `shortint` library.
|
||||
|
||||
This crate implements two ways to represent an integer:
|
||||
- the Radix representation
|
||||
- the CRT (Chinese Reminder Theorem) representation
|
||||
|
||||
## Radix based Integers
|
||||
The first possibility to represent a large integer is to use a radix-based decomposition on the
|
||||
plaintexts. Let $$B \in \mathbb{N}$$ be a basis such that the size of $$B$$ is smaller (or equal)
|
||||
to four bits.
|
||||
Then, an integer $$m \in \mathbb{N}$$ can be written as $$m = m_0 + m_1*B + m_2*B^2 + ... $$, where
|
||||
each $$m_i$$ is strictly smaller than $$B$$. Each $$m_i$$ is then independently encrypted. In
|
||||
the end, an Integer ciphertext is defined as a set of Shortint ciphertexts.
|
||||
|
||||
In practice, the definition of an Integer requires the basis and the number of blocks. This is
|
||||
done at the key creation step.
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the keys are dedicated to Integers decomposed as four blocks using the basis
|
||||
$$B=2^2$$. Otherwise said, they allow to work on Integers modulus $$(2^2)^4 = 2^8$$.
|
||||
|
||||
|
||||
In this representation, the correctness of operations requires to propagate the carries
|
||||
between the ciphertext. This operation is costly since it relies on the computation of many
|
||||
programmable bootstrapping over Shortints.
|
||||
|
||||
|
||||
## CRT based Integers
|
||||
The second approach to represent large integers is based on the Chinese Remainder Theorem.
|
||||
In this cases, the basis $$B$$ is composed of several integers $$b_i$$, such that there are
|
||||
pairwise coprime, and each b_i has a size smaller than four bits. Then, the Integer will be
|
||||
defined modulus $$\prod b_i$$. For an integer $$m$$, its CRT decomposition is simply defined as
|
||||
$$m % b_0, m % b_1, ...$$. Each part is then encrypted as a Shortint ciphertext. In
|
||||
the end, an Integer ciphertext is defined as a set of Shortint ciphertexts.
|
||||
|
||||
An example of such a basis
|
||||
could be $$B = [2, 3, 5]$$. This means that the Integer is defined modulus $$2*3*5 = 30$$.
|
||||
|
||||
This representation has many advantages: no carry propagation is required, so that only cleaning
|
||||
the carry buffer of each ciphertexts is enough. This implies that operations can easily be
|
||||
parallelized. Moreover, it allows to efficiently compute PBS in the case where the function is
|
||||
CRT compliant.
|
||||
|
||||
A variant of the CRT is proposed, where each block might be associated to a different key couple.
|
||||
In the end, a keychain is required to the computations, but performance might be improved.
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +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 does the computations.
|
||||
|
||||
The easiest way to send these data to a server is to use the serialization and deserialization features.
|
||||
concrete-integer uses the serde framework, serde's Serialize and Deserialize are implemented.
|
||||
|
||||
To be able to serialize our data, we need to pick a [data format], for our use case,
|
||||
[bincode] is a good choice, mainly because it is binary format.
|
||||
The easiest way to send these data to a server is to use the serialization and deserialization features. TFHE-rs uses the serde framework, so serde's Serialize and Deserialize are implemented.
|
||||
|
||||
To be able to serialize our data, a [data format](https://serde.rs/#data-formats) needs to be picked. Here, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
@@ -18,7 +14,6 @@ To be able to serialize our data, we need to pick a [data format], for our use c
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
@@ -72,7 +67,3 @@ fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error
|
||||
Ok(serialized_result)
|
||||
}
|
||||
```
|
||||
|
||||
[serde]: https://crates.io/crates/serde
|
||||
[data format]: https://serde.rs/#data-formats
|
||||
[bincode]: https://crates.io/crates/bincode
|
||||
|
||||
@@ -1,38 +1,31 @@
|
||||
# Tutorial
|
||||
|
||||
The steps to homomorphically evaluate an integer circuit are described here.
|
||||
|
||||
## Key Types
|
||||
|
||||
`integer` provides 2 basic key types:
|
||||
- `ClientKey`
|
||||
- `ServerKey`
|
||||
`integer` provides 3 basic key types:
|
||||
|
||||
The `ClientKey` is the key that encrypts and decrypts messages,
|
||||
thus this key is meant to be kept private and should never be shared.
|
||||
This key is created from parameter values that will dictate both the security and efficiency
|
||||
of computations. The parameters also set the maximum number of bits of message encrypted
|
||||
in a ciphertext.
|
||||
* `ClientKey`
|
||||
* `ServerKey`
|
||||
* `PublicKey`
|
||||
|
||||
The `ServerKey` is the key that is used to actually do the FHE computations. It contains (among other things)
|
||||
a bootstrapping key and a keyswitching key.
|
||||
This key is created from a `ClientKey` that needs to be shared to the server, therefore it is not
|
||||
meant to be kept private.
|
||||
A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated
|
||||
`ClientKey`.
|
||||
The `ClientKey` is the key that encrypts and decrypts messages, thus this key is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
|
||||
|
||||
The `ServerKey` is the key that is used to actually do the FHE computations. It contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, so it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
|
||||
|
||||
To reflect that, computation/operation methods are tied to the `ServerKey` type.
|
||||
|
||||
The `PublicKey` is a key used to encrypt messages. It can be publicly shared to allow users to encrypt data such that only the `ClientKey` holder will be able to decrypt. Encrypting with the `PublicKey` does not alter the homomorphic capabilities associated to the `ServerKey`.
|
||||
|
||||
## 1. Key Generation
|
||||
|
||||
To generate the keys, a user needs two parameters:
|
||||
- A set of `shortint` cryptographic parameters.
|
||||
- The number of ciphertexts used to encrypt an integer (we call them "shortint blocks").
|
||||
|
||||
* A set of `shortint` cryptographic parameters.
|
||||
* The number of ciphertexts used to encrypt an integer (we call them "shortint blocks").
|
||||
|
||||
For this example we are going to build a pair of keys that can encrypt an **8-bit** integer
|
||||
by using **4** shortint blocks that store **2** bits of message each.
|
||||
|
||||
We are now going to build a pair of keys that can encrypt an **8-bit** integer by using **4** shortint blocks that store **2** bits of message each.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
@@ -45,12 +38,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 2. Encrypting values
|
||||
|
||||
|
||||
Once we have our keys we can encrypt values:
|
||||
Once we have our keys, we can encrypt values:
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
@@ -70,10 +60,36 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Computing and decrypting
|
||||
## 3. Encrypting values with the public key
|
||||
|
||||
With our `server_key`, and encrypted values, we can now do an addition
|
||||
and then decrypt the result.
|
||||
Once the client key is generated, the public key can be derived and used to encrypt data.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::integer::PublicKeyBig;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, _) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
//We generate the public key from the secret client key:
|
||||
let public_key = PublicKeyBig::new(&client_key);
|
||||
|
||||
//encryption
|
||||
let msg1 = 128u64;
|
||||
let msg2 = 13u64;
|
||||
|
||||
// We use the public key to encrypt two messages:
|
||||
let ct_1 = public_key.encrypt_radix(msg1, num_block);
|
||||
let ct_2 = public_key.encrypt_radix(msg2, num_block);
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Computing and decrypting
|
||||
|
||||
With our `server_key`, and encrypted values, we can now do an addition and then decrypt the result.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
# Circuit evaluation
|
||||
|
||||
Let's try to do a circuit evaluation using the different flavours of operations we already introduced.
|
||||
For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly.
|
||||
Otherwise, the `checked` and `smart` are the best options.
|
||||
|
||||
As an example, let's do a scalar multiplication, a subtraction and an addition.
|
||||
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12u64;
|
||||
let msg2 = 11u64;
|
||||
let msg3 = 9u64;
|
||||
let scalar = 3u64;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
let ct_3 = client_key.encrypt(msg2);
|
||||
|
||||
server_key.unchecked_small_scalar_mul_assign(&mut ct_1, scalar);
|
||||
|
||||
server_key.unchecked_sub_assign(&mut ct_1, &ct_2);
|
||||
|
||||
server_key.unchecked_add_assign(&mut ct_1, &ct_3);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
// The carry buffer has been overflowed, the result is not correct
|
||||
assert_ne!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
During this computation the carry buffer has been overflowed and as all the operations were `unchecked` the output
|
||||
may be incorrect.
|
||||
|
||||
If we redo this same circuit but using the `checked` flavour, a panic will occur.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 2;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12u64;
|
||||
let msg2 = 11u64;
|
||||
let msg3 = 9u64;
|
||||
let scalar = 3u64;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let ct_2 = client_key.encrypt(msg2);
|
||||
let ct_3 = client_key.encrypt(msg3);
|
||||
|
||||
let result = server_key.checked_small_scalar_mul_assign(&mut ct_1, scalar);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
|
||||
assert!(result.is_err());
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
// Only the scalar multiplication could be done
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, (msg1 * scalar) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
Therefore the `checked` flavour permits to manually manage the overflow of the carry buffer
|
||||
by raising an error if the correctness is not guaranteed.
|
||||
|
||||
Lastly, using the `smart` flavour will output the correct result all the time. However, the computation may be slower
|
||||
as the carry buffer may be propagated during the computations.
|
||||
|
||||
```rust
|
||||
use tfhe::integer::gen_keys_radix;
|
||||
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
fn main() {
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12;
|
||||
let msg2 = 11;
|
||||
let msg3 = 9;
|
||||
let scalar = 3;
|
||||
|
||||
// message_modulus^vec_length
|
||||
let modulus = client_key.parameters().message_modulus.0.pow(num_block as u32) as u64;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let mut ct_2 = client_key.encrypt(msg2);
|
||||
let mut ct_3 = client_key.encrypt(msg3);
|
||||
|
||||
server_key.smart_scalar_mul_assign(&mut ct_1, scalar);
|
||||
|
||||
server_key.smart_sub_assign(&mut ct_1, &mut ct_2);
|
||||
|
||||
server_key.smart_add_assign(&mut ct_1, &mut ct_3);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output: u64 = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) + msg3) % modulus as u64);
|
||||
}
|
||||
```
|
||||
@@ -2,24 +2,21 @@
|
||||
|
||||
## Using the JS on WASM API
|
||||
|
||||
Welcome to this TFHE-rs JS on WASM API tutorial!
|
||||
Welcome to this TFHE-rs JS on WASM API tutorial.
|
||||
|
||||
TFHE-rs uses WASM to expose a JS binding to the client-side primitives, like key generation and encryption, of the Boolean and shortint modules.
|
||||
|
||||
There are several limitations at this time. Due to a lack of threading support in WASM, key
|
||||
generation can be too slow to be practical for bigger parameter sets.
|
||||
There are several limitations at this time. Due to a lack of threading support in WASM, key generation can be too slow to be practical for bigger parameter sets.
|
||||
|
||||
Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory space of WASM.
|
||||
This means that some parameters sets are virtually unusable.
|
||||
Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory space of WASM. This means that some parameter sets are virtually unusable.
|
||||
|
||||
## First steps using TFHE-rs JS on WASM API
|
||||
|
||||
### Setting-up TFHE-rs JS on WASM API for use in nodejs programs.
|
||||
|
||||
To build the JS on WASM bindings for TFHE-rs, you will first need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.65) [`rust toolchain`](https://rustup.rs/).
|
||||
To build the JS on WASM bindings for TFHE-rs, you need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.65) [`rust toolchain`](https://rustup.rs/).
|
||||
|
||||
Then, in a shell run the following to clone the TFHE-rs repo (one may want to checkout a
|
||||
specific tag, here the default branch is used for the build):
|
||||
In a shell, then run the following to clone the TFHE-rs repo (one may want to checkout a specific tag, here the default branch is used for the build):
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/zama-ai/tfhe-rs.git
|
||||
@@ -34,12 +31,11 @@ $ rustup run wasm-pack build --release --target=nodejs --features=boolean-client
|
||||
[INFO]: :-) Your wasm pkg is ready to publish at ...
|
||||
```
|
||||
|
||||
The command above targets nodejs. A binding for a web browser can be generated as well using
|
||||
`--target=web`. This use case will not be discussed in this tutorial.
|
||||
The command above targets nodejs. A binding for a web browser can be generated as well using `--target=web`. This use case will not be discussed in this tutorial.
|
||||
|
||||
Both Boolean and shortint features are enabled here but it's possible to use one without the other.
|
||||
Both Boolean and shortint features are enabled here, but it's possible to use one without the other.
|
||||
|
||||
After the build, a new directory ***pkg*** is present in the `tfhe` directory.
|
||||
After the build, a new directory _**pkg**_ is present in the `tfhe` directory.
|
||||
|
||||
```shell
|
||||
$ ls pkg
|
||||
@@ -49,9 +45,8 @@ $
|
||||
|
||||
### Commented code to generate keys for shortint and encrypt a ciphertext
|
||||
|
||||
{% hint style=“warning” %}
|
||||
Be sure to update the path of the required clause in the example below for the TFHE
|
||||
package that was just built.
|
||||
{% hint style="info" %}
|
||||
Be sure to update the path of the required clause in the example below for the TFHE package that was just built.
|
||||
{% endhint %}
|
||||
|
||||
```javascript
|
||||
@@ -101,7 +96,7 @@ function shortint_example() {
|
||||
shortint_example();
|
||||
```
|
||||
|
||||
The `example.js` script can then be run using [`node`](https://nodejs.org/) like so:
|
||||
The `example.js` script can then be run using [`node`](https://nodejs.org/), like so:
|
||||
|
||||
```shell
|
||||
$ node example.js
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Operations
|
||||
|
||||
## How shortint is represented
|
||||
The structure and the operations related to the short integers are described in this section.
|
||||
|
||||
## How a shortint is represented
|
||||
|
||||
In `shortint`, the encrypted data is stored in an LWE ciphertext.
|
||||
|
||||
@@ -8,37 +10,38 @@ Conceptually, the message stored in an LWE ciphertext is divided into a **carry
|
||||
|
||||

|
||||
|
||||
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus: the exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
|
||||
The message buffer is the space where the actual message is stored. This represents the modulus of the input messages (denoted by `MessageModulus` in the code). When doing computations on a ciphertext, the encrypted message can overflow the message modulus. The exceeding information is stored in the carry buffer. The size of the carry buffer is defined by another modulus, called `CarryModulus`.
|
||||
|
||||
Together, the message modulus and the carry modulus form the plaintext space that is available in a ciphertext. This space cannot be overflowed, otherwise the computation may result in incorrect outputs.
|
||||
|
||||
In order to ensure the correctness of the computation, we 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.
|
||||
In order to ensure the correctness of the computation, we track the maximum value encrypted in a ciphertext via an associated attribute called the **degree**. When the degree reaches a defined threshold, the carry buffer may be emptied to safely resume the computations. In `shortint` the carry modulus is considered useful as a means to do more computations.
|
||||
|
||||
## Types of operations
|
||||
|
||||
The operations available via a `ServerKey` may come in different variants:
|
||||
|
||||
* operations that take their inputs as encrypted values.
|
||||
* scalar operations that take at least one non-encrypted value as input.
|
||||
* operations that take their inputs as encrypted values
|
||||
* scalar operations that take at least one non-encrypted value as input
|
||||
|
||||
For example, the addition has both variants:
|
||||
|
||||
* `ServerKey::unchecked_add` which takes two encrypted values and adds them.
|
||||
* `ServerKey::unchecked_scalar_add` which takes an encrypted value and a clear value (the so-called scalar) and adds them.
|
||||
* `ServerKey::unchecked_add`, which takes two encrypted values and adds them.
|
||||
* `ServerKey::unchecked_scalar_add`, which takes an encrypted value and a clear value (the so-called scalar) and adds them.
|
||||
|
||||
Each operation may come in different 'flavors':
|
||||
|
||||
* `unchecked`: Always does the operation, without checking if the result may exceed the capacity of the plaintext space. Using this operation might have an impact on the correctness of the following operations;
|
||||
* `checked`: Checks are done before computing the operation, returning an error if operation cannot be done safely;
|
||||
* `smart`: Always does the operation - if the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible.
|
||||
* `smart`: Always does the operation. If the operation cannot be computed safely, the smart operation will clear the carry modulus to make the operation possible;
|
||||
* `default`: Always does the operation and always clears the carry. Could be **slower** than smart, but it ensures that the timings are consistent from one call to another.
|
||||
|
||||
Not all operations have these 3 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
|
||||
Not all operations have these 4 flavors, as some of them are implemented in a way that the operation is always possible without ever exceeding the plaintext space capacity.
|
||||
|
||||
## How to use operation types
|
||||
|
||||
Let's try to do a circuit evaluation using the different flavours of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise, the `checked` and `smart` are the best options.
|
||||
Let's try to do a circuit evaluation using the different flavors of operations we already introduced. For a very small circuit, the `unchecked` flavour may be enough to do the computation correctly. Otherwise,`checked` and `smart` are the best options.
|
||||
|
||||
As an example, let's do a scalar multiplication, a subtraction, and a multiplication.
|
||||
Let's do a scalar multiplication, a subtraction, and a multiplication.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -70,7 +73,7 @@ fn main() {
|
||||
|
||||
During this computation, the carry buffer has been overflowed and, as all the operations were `unchecked`, the output may be incorrect.
|
||||
|
||||
If we redo this same circuit with the `checked` flavour, a panic will occur.
|
||||
If we redo this same circuit with the `checked` flavor, a panic will occur:
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -112,9 +115,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Therefore, the `checked` flavour permits manual management of the overflow of the carry buffer by raising an error if the correctness is not guaranteed.
|
||||
The `checked` flavor permits manual management of the overflow of the carry buffer by raising an error if correctness is not guaranteed.
|
||||
|
||||
Lastly, using the `smart` flavour will output the correct result all the time. However, the computation may be slower as the carry buffer may be cleaned during the computations.
|
||||
Using the `smart` flavor will output the correct result all the time. However, the computation may be slower as the carry buffer may be cleaned during the computations.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -144,17 +147,51 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
The main advantage of the default flavor is to ensure predictable timings as long as only this kind of operation is used.
|
||||
|
||||
{% hint style="warning" %}
|
||||
Using `default` could **slow-down** computations.
|
||||
{% endhint %}
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);
|
||||
|
||||
let msg1 = 3;
|
||||
let msg2 = 3;
|
||||
let scalar = 4;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
let mut ct_2 = client_key.encrypt(msg2);
|
||||
|
||||
server_key.scalar_mul_assign(&mut ct_1, scalar);
|
||||
server_key.sub_assign(&mut ct_1, &mut ct_2);
|
||||
server_key.mul_lsb_assign(&mut ct_1, &mut ct_2);
|
||||
|
||||
// We use the client key to decrypt the output of the circuit:
|
||||
let output = client_key.decrypt(&ct_1);
|
||||
assert_eq!(output, ((msg1 * scalar as u64 - msg2) * msg2) % modulus as u64);
|
||||
}
|
||||
```
|
||||
|
||||
\#List of available operations
|
||||
|
||||
{% hint style="warning" %}
|
||||
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 (\*).
|
||||
Certain operations can only be used if the parameter set chosen is compatible with the bivariate programmable bootstrapping, meaning the carry buffer is larger than or equal to the message buffer. These operations are marked with a star (\*).
|
||||
{% endhint %}
|
||||
|
||||
The list of implemented operations for shortint is:
|
||||
|
||||
* addition between two ciphertexts
|
||||
* addition between a ciphertext and an unencrypted scalar
|
||||
* comparisons `<`, `<=`, `>`, `>=`, `==` between a ciphertext and an unencrypted scalar
|
||||
* comparisons `<`, `<=`, `>`, `>=`, `==`, `!=` between a ciphertext and an unencrypted scalar
|
||||
* division of a ciphertext by an unencrypted scalar
|
||||
* LSB multiplication between two ciphertexts returning the result truncated to fit in the `message buffer`
|
||||
* multiplication of a ciphertext by an unencrypted scalar
|
||||
@@ -163,15 +200,13 @@ The list of implemented operations for shortint is:
|
||||
* subtraction of a ciphertext by an unencrypted scalar
|
||||
* negation of a ciphertext
|
||||
* bitwise and, or and xor (\*)
|
||||
* comparisons `<`, `<=`, `>`, `>=`, `==` between two ciphertexts (\*)
|
||||
* comparisons `<`, `<=`, `>`, `>=`, `==`, `!=` between two ciphertexts (\*)
|
||||
* division between two ciphertexts (\*)
|
||||
* MSB multiplication between two ciphertexts returning the part overflowing the `message buffer` (\*)
|
||||
|
||||
In what follows, some simple code examples are given.
|
||||
|
||||
### Public key encryption.
|
||||
|
||||
TFHE-rs supports both private and public key encryption methods. 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`.
|
||||
TFHE-rs supports both private and public key encryption methods. The only difference between both lies in the encryption step: in this case, the encryption method is called using `public_key` instead of `client_key`.
|
||||
|
||||
Here is a small example on how to use public encryption:
|
||||
|
||||
@@ -192,8 +227,6 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
In what follows, all examples are related to private key encryption.
|
||||
|
||||
### Arithmetic operations.
|
||||
|
||||
Classical arithmetic operations are supported by shortint:
|
||||
@@ -318,7 +351,7 @@ fn main() {
|
||||
|
||||
#### 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 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.
|
||||
Using the shortint types offers the possibility to evaluate bi-variate functions, or functions that take two ciphertexts as input. This requires choosing a parameter set such that the carry buffer size is at least as large as the message (i.e., PARAM\_MESSAGE\_X\_CARRY\_Y with X <= Y).
|
||||
|
||||
Here is a simple code example:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Cryptographic Parameters
|
||||
|
||||
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing 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).
|
||||
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator), with an error probability equal to $$2^{-40}$$ when computing using programmable bootstrapping. This error probability is due to the randomness added at each encryption (see [here](../getting\_started/security\_and\_cryptography.md) for more details about the encryption process).
|
||||
|
||||
## Parameters and message precision
|
||||
|
||||
@@ -10,9 +10,9 @@ The user is allowed to choose which set of parameters to use when creating the p
|
||||
|
||||
The difference between the parameter sets is the total amount of space dedicated to the plaintext and how it is split between the message buffer and the carry buffer. The syntax chosen for the name of a parameter is: `PARAM_MESSAGE_{number of message bits}_CARRY_{number of carry bits}`. For example, the set of parameters for a message buffer of 5 bits and a carry buffer of 2 bits is `PARAM_MESSAGE_5_CARRY_2`.
|
||||
|
||||
In what follows, there is an example where keys are generated to have messages encoded over 2 bits i.e., computations are done modulus $$2^2 = 4$$), with 2 bits of carry.
|
||||
This example contains keys that are generated to have messages encoded over 2 bits (i.e., computations are done modulus $$2^2 = 4$$) with 2 bits of carry.
|
||||
|
||||
Note that the `PARAM_MESSAGE_2_CARRY_2` parameter set is the default `shortint` parameter set that you can also use through the `tfhe::shortint::prelude::DEFAULT_PARAMETERS` constant.
|
||||
The `PARAM_MESSAGE_2_CARRY_2` parameter set is the default `shortint` parameter set that you can also use through the `tfhe::shortint::prelude::DEFAULT_PARAMETERS` constant.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -36,15 +36,15 @@ As shown [here](../getting\_started/benchmarks.md), the choice of the parameter
|
||||
|
||||
### Generic bi-variate functions.
|
||||
|
||||
The computations of bi-variate functions is based on a trick, _concatenating_ two ciphertexts into one. 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.
|
||||
The computations of bi-variate functions is based on a trick: _concatenating_ two ciphertexts into one. Where the carry buffer is not at least as large as the message one, this trick no longer works. Many bi-variate operations, such as comparisons, then cannot be correctly computed. The only exception concerns multiplication.
|
||||
|
||||
### Multiplication.
|
||||
|
||||
In the case of the multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication\_algorithm#Quarter\_square\_multiplication). In order to correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM\_MESSAGE\_X\_CARRY\_Y with Y>=1). This method is, in general, slower than using the other one. Note that using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
|
||||
In the case of multiplication, two algorithms are implemented: the first one relies on the bi-variate function trick, where the other one is based on the [quarter square method](https://en.wikipedia.org/wiki/Multiplication\_algorithm#Quarter\_square\_multiplication). To correctly compute a multiplication, the only requirement is to have at least one bit of carry (i.e., using parameter sets PARAM\_MESSAGE\_X\_CARRY\_Y with Y>=1). This method is slower than using the other one. Using the `smart` version of the multiplication automatically chooses which algorithm is used depending on the chosen parameters.
|
||||
|
||||
## User-defined parameter sets
|
||||
|
||||
Beyond the predefined parameter sets, it is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `Parameter` structure fields.
|
||||
It is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `Parameter` structure fields.
|
||||
|
||||
For instance:
|
||||
|
||||
@@ -70,6 +70,7 @@ fn main() {
|
||||
DecompositionBaseLog(0),
|
||||
MessageModulus(4),
|
||||
CarryModulus(1),
|
||||
CiphertextModulus::new_native(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ As explained in the introduction, some types (`Serverkey`, `Ciphertext`) are mea
|
||||
|
||||
The easiest way to send these data to a server is to use the serialization and deserialization features. tfhe::shortint uses the [serde](https://crates.io/crates/serde) framework. Serde's Serialize and Deserialize are then implemented on tfhe::shortint's types.
|
||||
|
||||
To 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.
|
||||
To serialize the data, we need to pick a [data format](https://serde.rs/#data-formats). For our use case, [bincode](https://crates.io/crates/bincode) is a good choice, mainly because it is binary format.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
# Tutorial
|
||||
|
||||
## Writing an homomorphic circuit using shortint
|
||||
The steps to homomorphically evaluate a circuit are described below.
|
||||
|
||||
## Key Generation
|
||||
## Key generation
|
||||
|
||||
`tfhe::shortint` provides 2 key types:
|
||||
`tfhe::shortint` provides 3 key types:
|
||||
|
||||
* `ClientKey`
|
||||
* `ServerKey`
|
||||
* `PublicKey`
|
||||
|
||||
The `ClientKey` is the key that encrypts and decrypts messages (integer values up to 8 bits here), thus this key is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
|
||||
The `ClientKey` is the key that encrypts and decrypts messages (integer values up to 8 bits here). It is meant to be kept private and should never be shared. This key is created from parameter values that will dictate both the security and efficiency of computations. The parameters also set the maximum number of bits of message encrypted in a ciphertext.
|
||||
|
||||
The `ServerKey` is the key that is used to actually do the FHE computations. It contains (among other things) a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, therefore it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
|
||||
The `ServerKey` is the key that is used to actually do the FHE computations. Most importantly, it contains a bootstrapping key and a keyswitching key. This key is created from a `ClientKey` that needs to be shared to the server, therefore it is not meant to be kept private. A user with a `ServerKey` can compute on the encrypted data sent by the owner of the associated `ClientKey`.
|
||||
|
||||
To reflect that, computation/operation methods are tied to the `ServerKey` type.
|
||||
Computation/operation methods are tied to the `ServerKey` type.
|
||||
|
||||
The `PublicKey` is the key used to encrypt messages. It can be publicly shared to allow users to encrypt data such that only the `ClientKey` holder will be able to decrypt. Encrypting with the `PublicKey` does not alter the homomorphic capabilities associated to the `ServerKey`.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
@@ -67,7 +70,7 @@ fn main() {
|
||||
|
||||
## Computing and decrypting
|
||||
|
||||
With our `server_key` and encrypted values, we can now do an addition and then decrypt the result.
|
||||
With the `server_key`, addition is now possible over encrypted values. The resulting plaintext is recovered after the decryption with the secret client key.
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::prelude::*;
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
# Quick Start
|
||||
|
||||
The basic steps for using the high level API of tfhe are the following:
|
||||
|
||||
- importing tfhe
|
||||
- configuring and creating keys
|
||||
- setting server key
|
||||
- encrypting data
|
||||
- computing over encrypted data
|
||||
- decrypting data
|
||||
|
||||
Here is the full example that we will walk through:
|
||||
|
||||
```rust
|
||||
use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let clear_a = 27u8;
|
||||
let clear_b = 128u8;
|
||||
|
||||
let a = FheUint8::encrypt(clear_a, &client_key);
|
||||
let b = FheUint8::encrypt(clear_b, &client_key);
|
||||
|
||||
let result = a + b;
|
||||
|
||||
let decrypted_result: u8 = result.decrypt(&client_key);
|
||||
|
||||
let clear_result = clear_a + clear_b;
|
||||
|
||||
assert_eq!(decrypted_result, clear_result);
|
||||
}
|
||||
```
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = ["integer"]}
|
||||
```
|
||||
|
||||
## Imports
|
||||
|
||||
`tfhe` uses `traits` to have a consistent API for creating FHE types and enable users to write generic functions. However, to be able to use associated functions and methods of a trait, the trait has to be in scope.
|
||||
|
||||
To make it easier for users, we use the `prelude` 'pattern'. That is, all `tfhe` important traits are in a `prelude` module that you **glob import**. With this, there is no need to remember or know the traits to import.
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
```
|
||||
|
||||
## 1. Configuring and creating keys
|
||||
|
||||
The first step in your Rust code building is the creation of the configuration.
|
||||
|
||||
The configuration is used to declare which type you will use or not use, as well as enabling you
|
||||
to use custom crypto-parameters for these types for more advanced usage / testing.
|
||||
|
||||
Creating a configuration is done using the ConfigBuilder type.
|
||||
|
||||
In our example, we are interested in using 8-bit unsigned integers with default parameters. As per the table on the Getting Started page, we need to enable the `integers` feature.
|
||||
|
||||
{% hint style="info" %}
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = ["integer"]}
|
||||
```
|
||||
{% endhint %}
|
||||
|
||||
Next in our code, we create a config by first creating a builder with all types deactivated. Then, we enable the `uint8` type with default parameters.
|
||||
|
||||
```rust
|
||||
use tfhe::{ConfigBuilder, generate_keys};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
}
|
||||
```
|
||||
|
||||
The `generate_keys` command returns a client key and a server key.
|
||||
|
||||
As the names try to convey, the `client_key` is meant to stay private and not leave the client whereas the `server_key` can be made public and sent to a server for it to enable FHE computations.
|
||||
|
||||
## 2. Setting the server key
|
||||
|
||||
The next step is to call set_server_key
|
||||
|
||||
This function will **move** the server key to an internal state of the crate, allowing us to manage the details and give you, the user, a simpler interface.
|
||||
|
||||
```rust
|
||||
use tfhe::{ConfigBuilder, generate_keys, set_server_key};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Encrypting Data
|
||||
|
||||
Encrypting data is done via the `encrypt` associated function of the [FheEncrypt] trait.
|
||||
|
||||
Types exposed by this crate will implement at least one of [FheEncrypt] or [FheTryEncrypt],
|
||||
to allow enryption.
|
||||
|
||||
```Rust
|
||||
let clear_a = 27u8;
|
||||
let clear_b = 128u8;
|
||||
|
||||
let a = FheUint8::encrypt(clear_a, &client_key);
|
||||
let b = FheUint8::encrypt(clear_b, &client_key);
|
||||
```
|
||||
|
||||
|
||||
## 4. Computation & decryption
|
||||
|
||||
Computations should be as easy as normal Rust to write, thanks to operator overloading.
|
||||
|
||||
```Rust
|
||||
let result = a + b;
|
||||
```
|
||||
|
||||
The decryption is done by using the `decrypt` method. (This method comes from the [FheDecrypt]
|
||||
trait).
|
||||
|
||||
```Rust
|
||||
let decrypted_result: u8 = result.decrypt(&client_key);
|
||||
|
||||
let clear_result = clear_a + clear_b;
|
||||
|
||||
assert_eq!(decrypted_result, clear_result);
|
||||
```
|
||||
@@ -1,154 +0,0 @@
|
||||
# FheLatinString (Integer)
|
||||
|
||||
In this tutorial, we are going to build a data type that represents a Latin string in FHE while implementing the `to_lower` and `to_upper` functions.
|
||||
|
||||
The allowed characters in a Latin string are:
|
||||
|
||||
* Uppercase letters: `A B C D E F G H I J K L M N O P Q R S T U V W X Y Z`
|
||||
* Lowercase letters: `a b c d e f g h i j k l m n o p q r s t u v w x y z`
|
||||
|
||||
For the code point of the letters, we will use the `ascii` codes. In ascii:
|
||||
|
||||
* The uppercase letters are in the range \[65, 90]
|
||||
* The lowercase letters are in the range \[97, 122]
|
||||
|
||||
`lower_case` = `upper_case` + 32 <=> `upper_case` = `lower_case` - 32
|
||||
|
||||
For this type, we will use the `FheUint8` type.
|
||||
|
||||
***
|
||||
|
||||
## Types and methods
|
||||
|
||||
Our type will hold the encrypted characters as a `Vec<FheUint8>`, as well as the encrypted constant `32` to implement our functions that change the case.
|
||||
|
||||
In the `FheLatinString::encrypt` function, we have to make a bit of data validation:
|
||||
|
||||
* The input string can only contain ascii letters (no digit, no special characters)
|
||||
* The input string cannot mix lower and upper case letters
|
||||
|
||||
These two points are to work around a limitation of FHE, which is that we cannot create branches, meaning our function cannot use conditional statements. For example, we can not check if the 'char' is a letter and uppercase to modify it to lowercase, like in the example below.
|
||||
|
||||
```rust
|
||||
fn to_lower(string: &String) -> String {
|
||||
let mut result = String::with_capacity(string.len());
|
||||
for char in string.chars() {
|
||||
if char.is_uppercase() {
|
||||
result.extend(char.to_lowercase().to_string().chars())
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
```
|
||||
|
||||
With these preconditions checked, implementing `to_lower` and `to_upper` is rather simple.
|
||||
|
||||
As we will be using the `FheUint8` type, the `integer` feature must be activated:
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.2.0", features = ["integer"]}
|
||||
```
|
||||
|
||||
```rust
|
||||
use tfhe::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
struct FheLatinString{
|
||||
bytes: Vec<FheUint8>,
|
||||
// Constant used to switch lower case <=> upper case
|
||||
cst: FheUint8,
|
||||
}
|
||||
|
||||
impl FheLatinString {
|
||||
fn encrypt(string: &str, client_key: &ClientKey) -> Self {
|
||||
assert!(
|
||||
string.chars().all(|char| char.is_ascii_alphabetic()),
|
||||
"The input string must only contain ascii letters"
|
||||
);
|
||||
|
||||
let has_mixed_case = string.as_bytes().windows(2).any(|window| {
|
||||
let first = char::from(*window.first().unwrap());
|
||||
let second = char::from(*window.last().unwrap());
|
||||
|
||||
(first.is_ascii_lowercase() && second.is_ascii_uppercase())
|
||||
|| (first.is_ascii_uppercase() && second.is_ascii_lowercase())
|
||||
});
|
||||
|
||||
assert!(
|
||||
!has_mixed_case,
|
||||
"The input string cannot mix lower case and upper case letters"
|
||||
);
|
||||
|
||||
let fhe_bytes = string
|
||||
.bytes()
|
||||
.map(|b| FheUint8::encrypt(b, client_key))
|
||||
.collect::<Vec<FheUint8>>();
|
||||
let cst = FheUint8::encrypt(32, client_key);
|
||||
|
||||
Self {
|
||||
bytes: fhe_bytes,
|
||||
cst,
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt(&self, client_key: &ClientKey) -> String {
|
||||
let ascii_bytes = self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|fhe_b| fhe_b.decrypt(client_key))
|
||||
.collect::<Vec<u8>>();
|
||||
String::from_utf8(ascii_bytes).unwrap()
|
||||
}
|
||||
|
||||
fn to_upper(&self) -> Self {
|
||||
Self {
|
||||
bytes: self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|b| b - &self.cst)
|
||||
.collect::<Vec<FheUint8>>(),
|
||||
cst: self.cst.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lower(&self) -> Self {
|
||||
Self {
|
||||
bytes: self
|
||||
.bytes
|
||||
.iter()
|
||||
.map(|b| b + &self.cst)
|
||||
.collect::<Vec<FheUint8>>(),
|
||||
cst: self.cst.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let my_string = FheLatinString::encrypt("zama", &client_key);
|
||||
let verif_string = my_string.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
|
||||
let my_string_upper = my_string.to_upper();
|
||||
let verif_string = my_string_upper.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
assert_eq!(verif_string, "ZAMA");
|
||||
|
||||
let my_string_lower = my_string_upper.to_lower();
|
||||
let verif_string = my_string_lower.decrypt(&client_key);
|
||||
println!("{}", verif_string);
|
||||
assert_eq!(verif_string, "zama");
|
||||
}
|
||||
```
|
||||
@@ -1,385 +0,0 @@
|
||||
# Parity Bit (Boolean)
|
||||
|
||||
In this example, we are going to build a small function that homomorphically computes a parity bit.
|
||||
|
||||
We will first write a non-generic function. Then, we will write it using generics to be able to use the function with both `FheBool`s and normal `bool`s.
|
||||
|
||||
Our function that computes the parity bit will take 2 parameters:
|
||||
|
||||
* A slice of boolean
|
||||
* A mode (`Odd` or `Even`)
|
||||
|
||||
This function will return a boolean that will be either `true` or `false` so that the sum of booleans (in the input + the returned one) is either an `Odd` or `Even` number depending on the requested mode.
|
||||
|
||||
***
|
||||
|
||||
## Non-generic version
|
||||
|
||||
Since we are going to use booleans, we must enable the `booleans` feature in our Cargo.toml.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.2.0", features = ["booleans"]}
|
||||
```
|
||||
|
||||
### Function definition.
|
||||
|
||||
First, we need to define the function as well as the verification function.
|
||||
|
||||
The way to find the parity bit is to first initialize it to `false, then` `XOR` it with all the bits, one after the other and add negation depending on the requested mode.
|
||||
|
||||
We also define a validation function, that simply sums together the number of the bit set within the input with the computed parity bit and checks that the sum is an even or odd number, depending on the mode.
|
||||
|
||||
```rust
|
||||
use tfhe::FheBool;
|
||||
use tfhe::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ParityMode {
|
||||
// The sum bits of message + parity bit must an odd number
|
||||
Odd,
|
||||
// The sum bits of message + parity bit must an even number
|
||||
Even,
|
||||
}
|
||||
|
||||
fn compute_parity_bit(fhe_bits: &[FheBool], mode: ParityMode) -> FheBool {
|
||||
let mut parity_bit = fhe_bits[0].clone();
|
||||
for fhe_bit in &fhe_bits[1..] {
|
||||
parity_bit = fhe_bit ^ parity_bit
|
||||
}
|
||||
|
||||
match mode {
|
||||
ParityMode::Odd => !parity_bit,
|
||||
ParityMode::Even => parity_bit,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_even(n: u8) -> bool {
|
||||
(n & 1) == 0
|
||||
}
|
||||
|
||||
fn is_odd(n: u8) -> bool {
|
||||
!is_even(n)
|
||||
}
|
||||
|
||||
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
|
||||
let num_bit_set = bits
|
||||
.iter()
|
||||
.map(|bit| *bit as u8)
|
||||
.fold(parity_bit as u8, |acc, bit| acc + bit);
|
||||
|
||||
match mode {
|
||||
ParityMode::Even => is_even(num_bit_set),
|
||||
ParityMode::Odd => is_odd(num_bit_set),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Final code.
|
||||
|
||||
We can now call it, but first we have to do the mandatory configuration steps:
|
||||
|
||||
```rust
|
||||
use tfhe::{FheBool, ConfigBuilder, generate_keys, set_server_key};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ParityMode {
|
||||
// The sum bits of message + parity bit must an odd number
|
||||
Odd,
|
||||
// The sum bits of message + parity bit must an even number
|
||||
Even,
|
||||
}
|
||||
|
||||
fn compute_parity_bit(fhe_bits: &[FheBool], mode: ParityMode) -> FheBool {
|
||||
let mut parity_bit = fhe_bits[0].clone();
|
||||
for fhe_bit in &fhe_bits[1..] {
|
||||
parity_bit = fhe_bit ^ parity_bit
|
||||
}
|
||||
|
||||
match mode {
|
||||
ParityMode::Odd => !parity_bit,
|
||||
ParityMode::Even => parity_bit,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_even(n: u8) -> bool {
|
||||
(n & 1) == 0
|
||||
}
|
||||
|
||||
fn is_odd(n: u8) -> bool {
|
||||
!is_even(n)
|
||||
}
|
||||
|
||||
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
|
||||
let num_bit_set = bits
|
||||
.iter()
|
||||
.map(|bit| *bit as u8)
|
||||
.fold(parity_bit as u8, |acc, bit| acc + bit);
|
||||
|
||||
match mode {
|
||||
ParityMode::Even => is_even(num_bit_set),
|
||||
ParityMode::Odd => is_odd(num_bit_set),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let clear_bits = [0, 1, 0, 0, 0, 1, 1].map(|b| (b != 0) as bool);
|
||||
|
||||
let fhe_bits = clear_bits
|
||||
.iter()
|
||||
.map(|bit| FheBool::encrypt(*bit, &client_key))
|
||||
.collect::<Vec<FheBool>>();
|
||||
|
||||
let mode = ParityMode::Odd;
|
||||
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
|
||||
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
|
||||
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
|
||||
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
|
||||
assert!(is_parity_bit_valid);
|
||||
|
||||
let mode = ParityMode::Even;
|
||||
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
|
||||
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
|
||||
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
|
||||
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
|
||||
assert!(is_parity_bit_valid);
|
||||
}
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## Generic version
|
||||
|
||||
Now we want to make our `compute_parity_bit` function generic so that we can use it with both `FheBool` and `bool`.
|
||||
|
||||
Writing a generic function that accepts `FHE` types as well as clear types can help test the function to see if it is correct.
|
||||
If the function is generic, that means we can run it with clear data,
|
||||
allowing the use of print-debugging or a debugger to spot errors.
|
||||
|
||||
However, writing generic functions that use operator overloading for our FHE types can be a bit trickier than normal,
|
||||
since, as explained in our [Generic Bounds How To](../how\_to/generic\_bounds.md), `FHE` types are not copy.
|
||||
Therefore, you will need to use the reference `&`, even though you wouldn't normally use it when using native types, which are all `Copy`.
|
||||
|
||||
This will make the generic bounds a bit trickier at first.
|
||||
|
||||
### Writing the correct trait bounds.
|
||||
|
||||
Our function has the following signature:
|
||||
|
||||
```text
|
||||
fn check_parity_bit_validity(
|
||||
fhe_bits: &[FheBool],
|
||||
mode: ParityMode,
|
||||
) -> bool
|
||||
```
|
||||
|
||||
To make it generic, we can start by doing:
|
||||
|
||||
```text
|
||||
fn compute_parity_bit<BoolType>(
|
||||
fhe_bits: &[BoolType],
|
||||
mode: ParityMode,
|
||||
) -> BoolType
|
||||
```
|
||||
|
||||
We now have to write the generic bounds: the `where` clause.
|
||||
|
||||
In our function, we use the following operators:
|
||||
|
||||
* `!` (trait: `Not`)
|
||||
* `^` (trait: `BitXor`)
|
||||
|
||||
We can add them to our `where`, which would look like:
|
||||
|
||||
```text
|
||||
where
|
||||
BoolType: Clone + Not<Output = BoolType>,
|
||||
BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
```
|
||||
|
||||
However, the compiler will complain:
|
||||
|
||||
```text
|
||||
---- src/user_doc_tests.rs - user_doc_tests (line 199) stdout ----
|
||||
error[E0369]: no implementation for `&BoolType ^ BoolType`
|
||||
--> src/user_doc_tests.rs:218:30
|
||||
|
|
||||
21 | parity_bit = fhe_bit ^ parity_bit
|
||||
| ------- ^ ---------- BoolType
|
||||
| |
|
||||
| &BoolType
|
||||
|
|
||||
help: consider extending the `where` bound, but there might be an alternative better way to express this requirement
|
||||
|
|
||||
17 | BoolType: BitXor<BoolType, Output=BoolType>, &BoolType: BitXor<BoolType>
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
error: aborting due to previous error
|
||||
```
|
||||
|
||||
`fhe_bit` is a reference to a `BoolType` (`&BoolType`) since it is borrowed from the `fhe_bits` slice when we iterate over its elements. We can try to change the `BitXor` bounds to what the compiler suggests by requiring `&BoolType` to implement `BitXor` and not `BoolType`.
|
||||
|
||||
```text
|
||||
where
|
||||
BoolType: Clone + Not<Output = BoolType>,
|
||||
&BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
```
|
||||
|
||||
The compiler is still not happy:
|
||||
|
||||
```text
|
||||
---- src/user_doc_tests.rs - user_doc_tests (line 236) stdout ----
|
||||
error[E0637]: `&` without an explicit lifetime name cannot be used here
|
||||
--> src/user_doc_tests.rs:251:5
|
||||
|
|
||||
17 | &BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
| ^ explicit lifetime name needed here
|
||||
|
||||
error[E0310]: the parameter type `BoolType` may not live long enough
|
||||
--> src/user_doc_tests.rs:251:16
|
||||
|
|
||||
17 | &BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the reference type `&'static BoolType` does not outlive the data it points at
|
||||
|
|
||||
help: consider adding an explicit lifetime bound...
|
||||
|
|
||||
15 | BoolType: Clone + Not<Output = BoolType> + 'static,
|
||||
|
|
||||
```
|
||||
|
||||
The way to fix this is to use `Higher-Rank Trait Bounds` as shown in the [Generic Bounds How To](../how\_to/generic\_bounds.md):
|
||||
|
||||
```text
|
||||
where
|
||||
BoolType: Clone + Not<Output = BoolType>,
|
||||
for<'a> &'a BoolType: BitXor<BoolType, Output = BoolType>,
|
||||
```
|
||||
|
||||
The final code will look like this:
|
||||
|
||||
```rust
|
||||
use std::ops::{Not, BitXor};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ParityMode {
|
||||
// The sum bits of message + parity bit must an odd number
|
||||
Odd,
|
||||
// The sum bits of message + parity bit must an even number
|
||||
Even,
|
||||
}
|
||||
|
||||
fn compute_parity_bit<BoolType>(fhe_bits: &[BoolType], mode: ParityMode) -> BoolType
|
||||
where
|
||||
BoolType: Clone + Not<Output = BoolType>,
|
||||
for<'a> &'a BoolType: BitXor<BoolType, Output = BoolType>,
|
||||
{
|
||||
let mut parity_bit = fhe_bits[0].clone();
|
||||
for fhe_bit in &fhe_bits[1..] {
|
||||
parity_bit = fhe_bit ^ parity_bit
|
||||
}
|
||||
|
||||
match mode {
|
||||
ParityMode::Odd => !parity_bit,
|
||||
ParityMode::Even => parity_bit,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
### Final code.
|
||||
|
||||
Here is a complete example that uses this function for both clear and FHE values:
|
||||
|
||||
```rust
|
||||
use tfhe::{FheBool, ConfigBuilder, generate_keys, set_server_key};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
use std::ops::{Not, BitXor};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ParityMode {
|
||||
// The sum bits of message + parity bit must an odd number
|
||||
Odd,
|
||||
// The sum bits of message + parity bit must an even number
|
||||
Even,
|
||||
}
|
||||
|
||||
fn compute_parity_bit<BoolType>(fhe_bits: &[BoolType], mode: ParityMode) -> BoolType
|
||||
where
|
||||
BoolType: Clone + Not<Output=BoolType>,
|
||||
for<'a> &'a BoolType: BitXor<BoolType, Output=BoolType>,
|
||||
{
|
||||
let mut parity_bit = fhe_bits[0].clone();
|
||||
for fhe_bit in &fhe_bits[1..] {
|
||||
parity_bit = fhe_bit ^ parity_bit
|
||||
}
|
||||
|
||||
match mode {
|
||||
ParityMode::Odd => !parity_bit,
|
||||
ParityMode::Even => parity_bit,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_even(n: u8) -> bool {
|
||||
(n & 1) == 0
|
||||
}
|
||||
|
||||
fn is_odd(n: u8) -> bool {
|
||||
!is_even(n)
|
||||
}
|
||||
|
||||
fn check_parity_bit_validity(bits: &[bool], mode: ParityMode, parity_bit: bool) -> bool {
|
||||
let num_bit_set = bits
|
||||
.iter()
|
||||
.map(|bit| *bit as u8)
|
||||
.fold(parity_bit as u8, |acc, bit| acc + bit);
|
||||
|
||||
match mode {
|
||||
ParityMode::Even => is_even(num_bit_set),
|
||||
ParityMode::Odd => is_odd(num_bit_set),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled().enable_default_bool().build();
|
||||
|
||||
let ( client_key, server_key) = generate_keys(config);
|
||||
|
||||
set_server_key(server_key);
|
||||
|
||||
let clear_bits = [0, 1, 0, 0, 0, 1, 1].map(|b| (b != 0) as bool);
|
||||
|
||||
let fhe_bits = clear_bits
|
||||
.iter()
|
||||
.map(|bit| FheBool::encrypt(*bit, &client_key))
|
||||
.collect::<Vec<FheBool>>();
|
||||
|
||||
let mode = ParityMode::Odd;
|
||||
let clear_parity_bit = compute_parity_bit(&clear_bits, mode);
|
||||
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
|
||||
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
|
||||
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
|
||||
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
|
||||
assert!(is_parity_bit_valid);
|
||||
assert_eq!(decrypted_parity_bit, clear_parity_bit);
|
||||
|
||||
let mode = ParityMode::Even;
|
||||
let clear_parity_bit = compute_parity_bit(&clear_bits, mode);
|
||||
let fhe_parity_bit = compute_parity_bit(&fhe_bits, mode);
|
||||
let decrypted_parity_bit = fhe_parity_bit.decrypt(&client_key);
|
||||
let is_parity_bit_valid = check_parity_bit_validity(&clear_bits, mode, decrypted_parity_bit);
|
||||
println!("Parity bit is set: {} for mode: {:?}", decrypted_parity_bit, mode);
|
||||
assert!(is_parity_bit_valid);
|
||||
assert_eq!(decrypted_parity_bit, clear_parity_bit);
|
||||
}
|
||||
```
|
||||
@@ -1,3 +1,7 @@
|
||||
#[path = "../benches/utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
@@ -12,8 +16,8 @@ fn write_result(file: &mut File, name: &str, value: usize) {
|
||||
|
||||
fn client_server_key_sizes(results_file: &Path) {
|
||||
let boolean_params_vec = vec![
|
||||
(DEFAULT_PARAMETERS, "default"),
|
||||
(TFHE_LIB_PARAMETERS, "tfhe_lib"),
|
||||
(DEFAULT_PARAMETERS, "DEFAULT_PARAMETERS"),
|
||||
(TFHE_LIB_PARAMETERS, "TFHE_LIB_PARAMETERS"),
|
||||
];
|
||||
File::create(results_file).expect("create results file failed");
|
||||
let mut file = OpenOptions::new()
|
||||
@@ -21,19 +25,25 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
.open(results_file)
|
||||
.expect("cannot open results file");
|
||||
|
||||
let operator = OperatorType::Atomic;
|
||||
|
||||
println!("Generating boolean (ClientKey, ServerKey)");
|
||||
for (i, (params, name)) in boolean_params_vec.iter().enumerate() {
|
||||
for (i, (params, params_name)) in boolean_params_vec.iter().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
boolean_params_vec.len(),
|
||||
name
|
||||
params_name.to_lowercase()
|
||||
);
|
||||
|
||||
let cks = client_key::ClientKey::new(params);
|
||||
let sks = server_key::ServerKey::new(&cks);
|
||||
let ksk_size = sks.key_switching_key_size_bytes();
|
||||
write_result(&mut file, &format!("boolean_{}_{}", name, "ksk"), ksk_size);
|
||||
let test_name = format!("boolean_key_sizes_{params_name}_ksk");
|
||||
|
||||
write_result(&mut file, &test_name, ksk_size);
|
||||
write_to_json(&test_name, *params, *params_name, "KSK", &operator);
|
||||
|
||||
println!(
|
||||
"Element in KSK: {}, size in bytes: {}",
|
||||
sks.key_switching_key_size_elements(),
|
||||
@@ -41,7 +51,11 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
);
|
||||
|
||||
let bsk_size = sks.bootstrapping_key_size_bytes();
|
||||
write_result(&mut file, &format!("boolean_{}_{}", name, "bsk"), bsk_size);
|
||||
let test_name = format!("boolean_key_sizes_{params_name}_bsk");
|
||||
|
||||
write_result(&mut file, &test_name, bsk_size);
|
||||
write_to_json(&test_name, *params, *params_name, "BSK", &operator);
|
||||
|
||||
println!(
|
||||
"Element in BSK: {}, size in bytes: {}",
|
||||
sks.bootstrapping_key_size_elements(),
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#[path = "../benches/utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
@@ -26,13 +30,15 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
.open(results_file)
|
||||
.expect("cannot open results file");
|
||||
|
||||
let operator = OperatorType::Atomic;
|
||||
|
||||
println!("Generating shortint (ClientKey, ServerKey)");
|
||||
for (i, params) in shortint_params_vec.iter().enumerate() {
|
||||
println!(
|
||||
"Generating [{} / {}] : {}",
|
||||
i + 1,
|
||||
shortint_params_vec.len(),
|
||||
params.name()
|
||||
params.name().to_lowercase()
|
||||
);
|
||||
|
||||
let keys = KEY_CACHE.get_from_param(*params);
|
||||
@@ -41,11 +47,11 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
// let cks = keys.client_key();
|
||||
let sks = keys.server_key();
|
||||
let ksk_size = sks.key_switching_key_size_bytes();
|
||||
write_result(
|
||||
&mut file,
|
||||
&format!("shortint_{}_ksk", params.name().to_lowercase()),
|
||||
ksk_size,
|
||||
);
|
||||
let test_name = format!("shortint_key_sizes_{}_ksk", params.name());
|
||||
|
||||
write_result(&mut file, &test_name, ksk_size);
|
||||
write_to_json(&test_name, *params, params.name(), "KSK", &operator);
|
||||
|
||||
println!(
|
||||
"Element in KSK: {}, size in bytes: {}",
|
||||
sks.key_switching_key_size_elements(),
|
||||
@@ -53,11 +59,11 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
);
|
||||
|
||||
let bsk_size = sks.bootstrapping_key_size_bytes();
|
||||
write_result(
|
||||
&mut file,
|
||||
&format!("shortint_{}_bsk", params.name().to_lowercase()),
|
||||
bsk_size,
|
||||
);
|
||||
let test_name = format!("shortint_key_sizes_{}_bsk", params.name());
|
||||
|
||||
write_result(&mut file, &test_name, bsk_size);
|
||||
write_to_json(&test_name, *params, params.name(), "BSK", &operator);
|
||||
|
||||
println!(
|
||||
"Element in BSK: {}, size in bytes: {}",
|
||||
sks.bootstrapping_key_size_elements(),
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
|
||||
use crate::core_crypto::commons::generators::{DeterministicSeeder, EncryptionRandomGenerator};
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder};
|
||||
use crate::core_crypto::commons::parameters::CiphertextModulus;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::fft_impl::fft64::math::fft::Fft;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -51,6 +52,7 @@ impl Memory {
|
||||
let mut accumulator = GlweCiphertextMutView::from_container(
|
||||
accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
accumulator.get_mut_mask().as_mut().fill(0u32);
|
||||
@@ -60,9 +62,11 @@ impl Memory {
|
||||
let accumulator = GlweCiphertextView::from_container(
|
||||
accumulator_elements,
|
||||
server_key.bootstrapping_key.polynomial_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
let lwe = LweCiphertextMutView::from_container(lwe_elements);
|
||||
let lwe =
|
||||
LweCiphertextMutView::from_container(lwe_elements, CiphertextModulus::new_native());
|
||||
|
||||
(accumulator, lwe)
|
||||
}
|
||||
@@ -153,6 +157,7 @@ impl Bootstrapper {
|
||||
cks.parameters.pbs_base_log,
|
||||
cks.parameters.pbs_level,
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
@@ -192,6 +197,7 @@ impl Bootstrapper {
|
||||
cks.parameters.ks_base_log,
|
||||
cks.parameters.ks_level,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
@@ -212,6 +218,7 @@ impl Bootstrapper {
|
||||
cks.parameters.pbs_base_log,
|
||||
cks.parameters.pbs_level,
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.seeder,
|
||||
);
|
||||
|
||||
@@ -222,6 +229,7 @@ impl Bootstrapper {
|
||||
cks.parameters.pbs_base_log,
|
||||
cks.parameters.pbs_level,
|
||||
cks.parameters.glwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.seeder,
|
||||
);
|
||||
|
||||
@@ -234,6 +242,7 @@ impl Bootstrapper {
|
||||
cks.parameters.ks_base_log,
|
||||
cks.parameters.ks_level,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.seeder,
|
||||
);
|
||||
|
||||
@@ -277,6 +286,7 @@ impl Bootstrapper {
|
||||
|
||||
Ok(LweCiphertext::from_container(
|
||||
buffer_after_pbs.as_ref().to_owned(),
|
||||
input.ciphertext_modulus(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -292,6 +302,7 @@ impl Bootstrapper {
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
input.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
keyswitch_lwe_ciphertext(&server_key.key_switching_key, input, &mut output);
|
||||
|
||||
@@ -122,6 +122,7 @@ impl BooleanEngine {
|
||||
&client_key.lwe_secret_key,
|
||||
zero_encryption_count,
|
||||
client_key.parameters.lwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
@@ -130,6 +131,7 @@ impl BooleanEngine {
|
||||
&client_key.lwe_secret_key,
|
||||
zero_encryption_count,
|
||||
client_key.parameters.lwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
@@ -156,6 +158,7 @@ impl BooleanEngine {
|
||||
&cks.lwe_secret_key,
|
||||
plain,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.encryption_generator,
|
||||
);
|
||||
|
||||
@@ -175,6 +178,7 @@ impl BooleanEngine {
|
||||
&cks.lwe_secret_key,
|
||||
plain,
|
||||
cks.parameters.lwe_modular_std_dev,
|
||||
CiphertextModulus::new_native(),
|
||||
&mut self.bootstrapper.seeder,
|
||||
);
|
||||
|
||||
@@ -189,7 +193,11 @@ impl BooleanEngine {
|
||||
Plaintext(PLAINTEXT_FALSE)
|
||||
};
|
||||
|
||||
let mut output = LweCiphertext::new(0u32, pks.parameters.lwe_dimension.to_lwe_size());
|
||||
let mut output = LweCiphertext::new(
|
||||
0u32,
|
||||
pks.parameters.lwe_dimension.to_lwe_size(),
|
||||
CiphertextModulus::new_native(),
|
||||
);
|
||||
|
||||
// encryption
|
||||
encrypt_lwe_ciphertext_with_public_key(
|
||||
@@ -292,6 +300,7 @@ impl BooleanEngine {
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
plain,
|
||||
CiphertextModulus::new_native(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -338,7 +347,7 @@ impl BooleanEngine {
|
||||
};
|
||||
}
|
||||
|
||||
// convert inputs into LweCiphertext32
|
||||
// convert inputs into LweCiphertextOwned<u32>
|
||||
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);
|
||||
|
||||
@@ -348,6 +357,7 @@ impl BooleanEngine {
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_condition_ct.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let buffer_lwe_before_pbs = &mut buffer_lwe_before_pbs_o;
|
||||
@@ -413,6 +423,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
@@ -455,6 +466,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
@@ -497,6 +509,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
@@ -540,6 +553,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
@@ -581,6 +595,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
@@ -625,6 +640,7 @@ impl BinaryGatesEngine<&Ciphertext, &Ciphertext, ServerKey> for BooleanEngine {
|
||||
.bootstrapping_key
|
||||
.input_lwe_dimension()
|
||||
.to_lwe_size(),
|
||||
ct_left_ct.ciphertext_modulus(),
|
||||
);
|
||||
let bootstrapper = &mut self.bootstrapper;
|
||||
|
||||
|
||||
34
tfhe/src/c_api/high_level_api/booleans.rs
Normal file
34
tfhe/src/c_api/high_level_api/booleans.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::high_level_api::prelude::*;
|
||||
|
||||
use std::ops::{BitAnd, BitOr, BitXor, Not};
|
||||
|
||||
pub struct FheBool(crate::high_level_api::FheBool);
|
||||
|
||||
impl_destroy_on_type!(FheBool);
|
||||
impl_clone_on_type!(FheBool);
|
||||
|
||||
impl_binary_fn_on_type!(FheBool => bitand, bitor, bitxor);
|
||||
impl_unary_fn_on_type!(FheBool => not);
|
||||
|
||||
impl_decrypt_on_type!(FheBool, bool);
|
||||
impl_try_encrypt_with_client_key_on_type!(FheBool{crate::high_level_api::FheBool}, bool);
|
||||
impl_try_encrypt_with_public_key_on_type!(FheBool{crate::high_level_api::FheBool}, bool);
|
||||
|
||||
pub struct CompressedFheBool(crate::high_level_api::CompressedFheBool);
|
||||
|
||||
impl_destroy_on_type!(CompressedFheBool);
|
||||
impl_clone_on_type!(CompressedFheBool);
|
||||
impl_serialize_deserialize_on_type!(CompressedFheBool);
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn compressed_fhe_bool_decompress(
|
||||
sself: *const CompressedFheBool,
|
||||
result: *mut *mut FheBool,
|
||||
) -> ::std::os::raw::c_int {
|
||||
crate::c_api::utils::catch_panic(|| {
|
||||
let compressed = crate::c_api::utils::get_ref_checked(sself).unwrap();
|
||||
|
||||
let decompressed_inner = compressed.0.clone().into();
|
||||
*result = Box::into_raw(Box::new(FheBool(decompressed_inner)));
|
||||
})
|
||||
}
|
||||
120
tfhe/src/c_api/high_level_api/config.rs
Normal file
120
tfhe/src/c_api/high_level_api/config.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use crate::c_api::utils::*;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub struct ConfigBuilder(pub(in crate::c_api) crate::high_level_api::ConfigBuilder);
|
||||
pub struct Config(pub(in crate::c_api) crate::high_level_api::Config);
|
||||
|
||||
impl_destroy_on_type!(ConfigBuilder);
|
||||
impl_destroy_on_type!(Config);
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn config_builder_all_disabled(result: *mut *mut ConfigBuilder) -> c_int {
|
||||
catch_panic(|| {
|
||||
check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
|
||||
let inner_builder = crate::high_level_api::ConfigBuilder::all_disabled();
|
||||
|
||||
*result = Box::into_raw(Box::new(ConfigBuilder(inner_builder)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn config_builder_clone(
|
||||
input: *const ConfigBuilder,
|
||||
result: *mut *mut ConfigBuilder,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
|
||||
let cloned = get_ref_checked(input).unwrap().0.clone();
|
||||
|
||||
*result = Box::into_raw(Box::new(ConfigBuilder(cloned)));
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! define_enable_default_fn(
|
||||
($type_name:ident) => {
|
||||
::paste::paste!{
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<config_builder_enable_default_ $type_name>](
|
||||
builder: *mut *mut ConfigBuilder,
|
||||
) -> ::std::os::raw::c_int {
|
||||
catch_panic(|| {
|
||||
check_ptr_is_non_null_and_aligned(builder).unwrap();
|
||||
|
||||
let inner = Box::from_raw(*builder).0.[<enable_default_ $type_name>]();
|
||||
*builder = Box::into_raw(Box::new(ConfigBuilder(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
($type_name:ident @small) => {
|
||||
::paste::paste!{
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<config_builder_enable_default_ $type_name _small>](
|
||||
builder: *mut *mut ConfigBuilder,
|
||||
) -> ::std::os::raw::c_int {
|
||||
catch_panic(|| {
|
||||
check_ptr_is_non_null_and_aligned(builder).unwrap();
|
||||
|
||||
let inner = Box::from_raw(*builder).0.[<enable_default_ $type_name _small>]();
|
||||
*builder = Box::into_raw(Box::new(ConfigBuilder(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[cfg(feature = "boolean")]
|
||||
define_enable_default_fn!(bool);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint8);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint8 @small);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint10);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint10 @small);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint12);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint12 @small);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint14);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint14 @small);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint16);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint16 @small);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint32);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint32 @small);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint64);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint64 @small);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint128);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint128 @small);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint256);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint256 @small);
|
||||
|
||||
/// Takes ownership of the builder
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn config_builder_build(
|
||||
builder: *mut ConfigBuilder,
|
||||
result: *mut *mut Config,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
|
||||
let config = Box::from_raw(builder).0.build();
|
||||
|
||||
*result = Box::into_raw(Box::new(Config(config)));
|
||||
})
|
||||
}
|
||||
254
tfhe/src/c_api/high_level_api/integers.rs
Normal file
254
tfhe/src/c_api/high_level_api/integers.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
use crate::c_api::high_level_api::keys::{ClientKey, PublicKey};
|
||||
use crate::high_level_api::prelude::*;
|
||||
use std::ops::{
|
||||
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Mul, MulAssign,
|
||||
Neg, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
|
||||
};
|
||||
|
||||
use crate::c_api::high_level_api::u256::U256;
|
||||
use crate::c_api::utils::*;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
/// Implement C functions for all the operations supported by a integer type,
|
||||
/// which should also be accessible from C API
|
||||
macro_rules! impl_operations_for_integer_type {
|
||||
(
|
||||
name: $name:ident,
|
||||
clear_scalar_type: $clear_scalar_type:ty
|
||||
) => {
|
||||
impl_binary_fn_on_type_mut!($name => add, sub, mul, bitand, bitor, bitxor, eq, ge, gt, le, lt, min, max);
|
||||
impl_binary_assign_fn_on_type_mut!($name => add_assign, sub_assign, mul_assign, bitand_assign, bitor_assign, bitxor_assign);
|
||||
impl_scalar_binary_fn_on_type_mut!($name, $clear_scalar_type => add, sub, mul, shl, shr);
|
||||
impl_scalar_binary_assign_fn_on_type_mut!($name, $clear_scalar_type => add_assign, sub_assign, mul_assign, shl_assign, shr_assign);
|
||||
|
||||
impl_unary_fn_on_type_mut!($name => neg);
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a type that will act as an opaque wrapper
|
||||
/// aroung a tfhe integer.
|
||||
///
|
||||
/// It also implements binary operations for this wrapper type
|
||||
macro_rules! create_integer_wrapper_type {
|
||||
(
|
||||
name: $name:ident,
|
||||
clear_scalar_type: $clear_scalar_type:ty
|
||||
) => {
|
||||
pub struct $name($crate::high_level_api::$name);
|
||||
|
||||
impl_destroy_on_type!($name);
|
||||
|
||||
impl_operations_for_integer_type!(name: $name, clear_scalar_type: $clear_scalar_type);
|
||||
|
||||
impl_serialize_deserialize_on_type!($name);
|
||||
|
||||
impl_clone_on_type!($name);
|
||||
|
||||
// The compressed version of the ciphertext type
|
||||
::paste::paste! {
|
||||
pub struct [<Compressed $name>]($crate::high_level_api::[<Compressed $name>]);
|
||||
|
||||
impl_destroy_on_type!([<Compressed $name>]);
|
||||
|
||||
impl_clone_on_type!([<Compressed $name>]);
|
||||
|
||||
impl_serialize_deserialize_on_type!([<Compressed $name>]);
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<compressed_ $name:snake _decompress>](
|
||||
sself: *const [<Compressed $name>],
|
||||
result: *mut *mut $name,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let compressed = $crate::c_api::utils::get_ref_checked(sself).unwrap();
|
||||
|
||||
let decompressed_inner = compressed.0.clone().into();
|
||||
*result = Box::into_raw(Box::new($name(decompressed_inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
create_integer_wrapper_type!(name: FheUint8, clear_scalar_type: u8);
|
||||
create_integer_wrapper_type!(name: FheUint10, clear_scalar_type: u16);
|
||||
create_integer_wrapper_type!(name: FheUint12, clear_scalar_type: u16);
|
||||
create_integer_wrapper_type!(name: FheUint14, clear_scalar_type: u16);
|
||||
create_integer_wrapper_type!(name: FheUint16, clear_scalar_type: u16);
|
||||
create_integer_wrapper_type!(name: FheUint32, clear_scalar_type: u32);
|
||||
create_integer_wrapper_type!(name: FheUint64, clear_scalar_type: u64);
|
||||
create_integer_wrapper_type!(name: FheUint128, clear_scalar_type: u64);
|
||||
create_integer_wrapper_type!(name: FheUint256, clear_scalar_type: u64);
|
||||
|
||||
impl_decrypt_on_type!(FheUint8, u8);
|
||||
impl_try_encrypt_with_client_key_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
|
||||
impl_try_encrypt_with_public_key_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
|
||||
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint8{crate::high_level_api::CompressedFheUint8}, u8);
|
||||
|
||||
impl_decrypt_on_type!(FheUint10, u16);
|
||||
impl_try_encrypt_with_client_key_on_type!(FheUint10{crate::high_level_api::FheUint10}, u16);
|
||||
impl_try_encrypt_with_public_key_on_type!(FheUint10{crate::high_level_api::FheUint10}, u16);
|
||||
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint10{crate::high_level_api::CompressedFheUint10}, u16);
|
||||
|
||||
impl_decrypt_on_type!(FheUint12, u16);
|
||||
impl_try_encrypt_with_client_key_on_type!(FheUint12{crate::high_level_api::FheUint12}, u16);
|
||||
impl_try_encrypt_with_public_key_on_type!(FheUint12{crate::high_level_api::FheUint12}, u16);
|
||||
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint12{crate::high_level_api::CompressedFheUint12}, u16);
|
||||
|
||||
impl_decrypt_on_type!(FheUint14, u16);
|
||||
impl_try_encrypt_with_client_key_on_type!(FheUint14{crate::high_level_api::FheUint14}, u16);
|
||||
impl_try_encrypt_with_public_key_on_type!(FheUint14{crate::high_level_api::FheUint14}, u16);
|
||||
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint14{crate::high_level_api::CompressedFheUint14}, u16);
|
||||
|
||||
impl_decrypt_on_type!(FheUint16, u16);
|
||||
impl_try_encrypt_with_client_key_on_type!(FheUint16{crate::high_level_api::FheUint16}, u16);
|
||||
impl_try_encrypt_with_public_key_on_type!(FheUint16{crate::high_level_api::FheUint16}, u16);
|
||||
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint16{crate::high_level_api::CompressedFheUint16}, u16);
|
||||
|
||||
impl_decrypt_on_type!(FheUint32, u32);
|
||||
impl_try_encrypt_with_client_key_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
|
||||
impl_try_encrypt_with_public_key_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
|
||||
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint32{crate::high_level_api::CompressedFheUint32}, u32);
|
||||
|
||||
impl_decrypt_on_type!(FheUint64, u64);
|
||||
impl_try_encrypt_with_client_key_on_type!(FheUint64{crate::high_level_api::FheUint64}, u64);
|
||||
impl_try_encrypt_with_public_key_on_type!(FheUint64{crate::high_level_api::FheUint64}, u64);
|
||||
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint64{crate::high_level_api::CompressedFheUint64}, u64);
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_client_key_u128(
|
||||
low_word: u64,
|
||||
high_word: u64,
|
||||
client_key: *const ClientKey,
|
||||
result: *mut *mut FheUint128,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let client_key = get_ref_checked(client_key).unwrap();
|
||||
|
||||
let value = ((high_word as u128) << 64u128) | low_word as u128;
|
||||
|
||||
let inner = <crate::high_level_api::FheUint128>::try_encrypt(value, &client_key.0).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new(FheUint128(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn compressed_fhe_uint128_try_encrypt_with_client_key_u128(
|
||||
low_word: u64,
|
||||
high_word: u64,
|
||||
client_key: *const ClientKey,
|
||||
result: *mut *mut CompressedFheUint128,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let client_key = get_ref_checked(client_key).unwrap();
|
||||
|
||||
let value = ((high_word as u128) << 64u128) | low_word as u128;
|
||||
|
||||
let inner =
|
||||
<crate::high_level_api::CompressedFheUint128>::try_encrypt(value, &client_key.0)
|
||||
.unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new(CompressedFheUint128(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_public_key_u128(
|
||||
low_word: u64,
|
||||
high_word: u64,
|
||||
public_key: *const PublicKey,
|
||||
result: *mut *mut FheUint128,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let public_key = get_ref_checked(public_key).unwrap();
|
||||
|
||||
let value = ((high_word as u128) << 64u128) | low_word as u128;
|
||||
|
||||
let inner = <crate::high_level_api::FheUint128>::try_encrypt(value, &public_key.0).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new(FheUint128(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint128_decrypt(
|
||||
encrypted_value: *const FheUint128,
|
||||
client_key: *const ClientKey,
|
||||
low_word: *mut u64,
|
||||
high_word: *mut u64,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let client_key = get_ref_checked(client_key).unwrap();
|
||||
let encrypted_value = get_ref_checked(encrypted_value).unwrap();
|
||||
|
||||
let inner: u128 = encrypted_value.0.decrypt(&client_key.0);
|
||||
|
||||
*low_word = (inner & (u64::MAX as u128)) as u64;
|
||||
*high_word = (inner >> 64) as u64;
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_client_key_u256(
|
||||
value: *const U256,
|
||||
client_key: *const ClientKey,
|
||||
result: *mut *mut FheUint256,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let client_key = get_ref_checked(client_key).unwrap();
|
||||
|
||||
let inner =
|
||||
<crate::high_level_api::FheUint256>::try_encrypt((*value).0, &client_key.0).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new(FheUint256(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn compressed_fhe_uint256_try_encrypt_with_client_key_u256(
|
||||
value: *const U256,
|
||||
client_key: *const ClientKey,
|
||||
result: *mut *mut CompressedFheUint256,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let client_key = get_ref_checked(client_key).unwrap();
|
||||
|
||||
let inner =
|
||||
<crate::high_level_api::CompressedFheUint256>::try_encrypt((*value).0, &client_key.0)
|
||||
.unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new(CompressedFheUint256(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_public_key_u256(
|
||||
value: *const U256,
|
||||
public_key: *const PublicKey,
|
||||
result: *mut *mut FheUint256,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let public_key = get_ref_checked(public_key).unwrap();
|
||||
|
||||
let inner =
|
||||
<crate::high_level_api::FheUint256>::try_encrypt((*value).0, &public_key.0).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new(FheUint256(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint256_decrypt(
|
||||
encrypted_value: *const FheUint256,
|
||||
client_key: *const ClientKey,
|
||||
result: *mut *mut U256,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let client_key = get_ref_checked(client_key).unwrap();
|
||||
let encrypted_value = get_ref_checked(encrypted_value).unwrap();
|
||||
|
||||
let inner: crate::integer::U256 = encrypted_value.0.decrypt(&client_key.0);
|
||||
*result = Box::into_raw(Box::new(U256(inner)));
|
||||
})
|
||||
}
|
||||
71
tfhe/src/c_api/high_level_api/keys.rs
Normal file
71
tfhe/src/c_api/high_level_api/keys.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use crate::c_api::utils::*;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub struct ClientKey(pub(crate) crate::high_level_api::ClientKey);
|
||||
pub struct PublicKey(pub(crate) crate::high_level_api::PublicKey);
|
||||
pub struct ServerKey(pub(crate) crate::high_level_api::ServerKey);
|
||||
|
||||
impl_destroy_on_type!(ClientKey);
|
||||
impl_destroy_on_type!(PublicKey);
|
||||
impl_destroy_on_type!(ServerKey);
|
||||
|
||||
impl_serialize_deserialize_on_type!(ClientKey);
|
||||
impl_serialize_deserialize_on_type!(PublicKey);
|
||||
impl_serialize_deserialize_on_type!(ServerKey);
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn generate_keys(
|
||||
config: *mut super::config::Config,
|
||||
result_client_key: *mut *mut ClientKey,
|
||||
result_server_key: *mut *mut ServerKey,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
check_ptr_is_non_null_and_aligned(result_client_key).unwrap();
|
||||
check_ptr_is_non_null_and_aligned(result_server_key).unwrap();
|
||||
|
||||
*result_client_key = std::ptr::null_mut();
|
||||
*result_server_key = std::ptr::null_mut();
|
||||
|
||||
let config = Box::from_raw(config);
|
||||
|
||||
let (cks, sks) = crate::high_level_api::generate_keys(config.0);
|
||||
|
||||
*result_client_key = Box::into_raw(Box::new(ClientKey(cks)));
|
||||
*result_server_key = Box::into_raw(Box::new(ServerKey(sks)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_server_key(server_key: *const ServerKey) -> c_int {
|
||||
catch_panic(|| {
|
||||
let server_key = get_ref_checked(server_key).unwrap();
|
||||
|
||||
let cloned = server_key.0.clone();
|
||||
crate::high_level_api::set_server_key(cloned);
|
||||
})
|
||||
}
|
||||
|
||||
/// result can be null
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn unset_server_key(result: *mut *mut ServerKey) -> c_int {
|
||||
catch_panic(|| {
|
||||
let previous_key = crate::high_level_api::unset_server_key();
|
||||
|
||||
if !result.is_null() {
|
||||
*result = Box::into_raw(Box::new(ServerKey(previous_key)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn public_key_new(
|
||||
client_key: *const ClientKey,
|
||||
result_public_key: *mut *mut PublicKey,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let client_key = get_ref_checked(client_key).unwrap();
|
||||
let inner = crate::high_level_api::PublicKey::new(&client_key.0);
|
||||
|
||||
*result_public_key = Box::into_raw(Box::new(PublicKey(inner)));
|
||||
})
|
||||
}
|
||||
10
tfhe/src/c_api/high_level_api/mod.rs
Normal file
10
tfhe/src/c_api/high_level_api/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
#[cfg(feature = "boolean")]
|
||||
pub mod booleans;
|
||||
pub mod config;
|
||||
#[cfg(feature = "integer")]
|
||||
pub mod integers;
|
||||
pub mod keys;
|
||||
#[cfg(feature = "integer")]
|
||||
pub mod u256;
|
||||
115
tfhe/src/c_api/high_level_api/u256.rs
Normal file
115
tfhe/src/c_api/high_level_api/u256.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use crate::c_api::utils::*;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
pub struct U256(pub(in crate::c_api) crate::integer::U256);
|
||||
|
||||
impl_destroy_on_type!(U256);
|
||||
|
||||
/// w0 is the least significant, w4 is the most significant
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn u256_from_u64_words(
|
||||
w0: u64,
|
||||
w1: u64,
|
||||
w2: u64,
|
||||
w3: u64,
|
||||
result: *mut *mut U256,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let inner = crate::integer::U256::from((w0, w1, w2, w3));
|
||||
*result = Box::into_raw(Box::new(U256(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
/// w0 is the least significant, w4 is the most significant
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn u256_to_u64_words(
|
||||
input: *const U256,
|
||||
w0: *mut u64,
|
||||
w1: *mut u64,
|
||||
w2: *mut u64,
|
||||
w3: *mut u64,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let input = get_ref_checked(input).unwrap();
|
||||
|
||||
check_ptr_is_non_null_and_aligned(w0).unwrap();
|
||||
check_ptr_is_non_null_and_aligned(w1).unwrap();
|
||||
check_ptr_is_non_null_and_aligned(w2).unwrap();
|
||||
check_ptr_is_non_null_and_aligned(w3).unwrap();
|
||||
|
||||
*w0 = input.0 .0[0];
|
||||
*w1 = input.0 .0[1];
|
||||
*w2 = input.0 .0[2];
|
||||
*w3 = input.0 .0[3];
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a U256 from little endian bytes
|
||||
///
|
||||
/// len must be 32
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn u256_from_little_endian_bytes(
|
||||
input: *const u8,
|
||||
len: usize,
|
||||
result: *mut *mut U256,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let mut inner = crate::integer::U256::default();
|
||||
|
||||
let input = std::slice::from_raw_parts(input, len);
|
||||
inner.copy_from_le_byte_slice(input);
|
||||
|
||||
*result = Box::into_raw(Box::new(U256(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a U256 from big endian bytes
|
||||
///
|
||||
/// len must be 32
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn u256_from_big_endian_bytes(
|
||||
input: *const u8,
|
||||
len: usize,
|
||||
result: *mut *mut U256,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let mut inner = crate::integer::U256::default();
|
||||
|
||||
let input = std::slice::from_raw_parts(input, len);
|
||||
inner.copy_from_be_byte_slice(input);
|
||||
|
||||
*result = Box::into_raw(Box::new(U256(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
/// len must be 32
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn u256_little_endian_bytes(
|
||||
input: *const U256,
|
||||
result: *mut u8,
|
||||
len: usize,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
let input = get_ref_checked(input).unwrap();
|
||||
|
||||
let bytes = std::slice::from_raw_parts_mut(result, len);
|
||||
input.0.copy_to_le_byte_slice(bytes);
|
||||
})
|
||||
}
|
||||
|
||||
/// len must be 32
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn u256_big_endian_bytes(
|
||||
input: *const U256,
|
||||
result: *mut u8,
|
||||
len: usize,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
let input = get_ref_checked(input).unwrap();
|
||||
|
||||
let bytes = std::slice::from_raw_parts_mut(result, len);
|
||||
input.0.copy_to_be_byte_slice(bytes);
|
||||
})
|
||||
}
|
||||
312
tfhe/src/c_api/high_level_api/utils.rs
Normal file
312
tfhe/src/c_api/high_level_api/utils.rs
Normal file
@@ -0,0 +1,312 @@
|
||||
macro_rules! impl_destroy_on_type {
|
||||
($wrapper_type:ty) => {
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
#[doc = "ptr can be null (no-op in that case)"]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _destroy>](
|
||||
ptr: *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
if (!ptr.is_null()) {
|
||||
$crate::c_api::utils::check_ptr_is_non_null_and_aligned(ptr).unwrap();
|
||||
drop(Box::from_raw(ptr));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_try_encrypt_with_client_key_on_type {
|
||||
($wrapper_type:ty{$wrapped_type:ty}, $input_type:ty) => {
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _try_encrypt_with_client_key_ $input_type:snake>](
|
||||
value: $input_type,
|
||||
client_key: *const $crate::c_api::high_level_api::keys::ClientKey,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let client_key = $crate::c_api::utils::get_ref_checked(client_key).unwrap();
|
||||
|
||||
let inner = <$wrapped_type>::try_encrypt(value, &client_key.0).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new($wrapper_type(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_try_encrypt_with_public_key_on_type {
|
||||
($wrapper_type:ty{$wrapped_type:ty}, $input_type:ty) => {
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _try_encrypt_with_public_key_ $input_type:snake>](
|
||||
value: $input_type,
|
||||
public_key: *const $crate::c_api::high_level_api::keys::PublicKey,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let public_key = $crate::c_api::utils::get_ref_checked(public_key).unwrap();
|
||||
|
||||
let inner = <$wrapped_type>::try_encrypt(value, &public_key.0).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new($wrapper_type(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_decrypt_on_type {
|
||||
($wrapper_type:ty, $output_type:ty) => {
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _decrypt>](
|
||||
encrypted_value: *const $wrapper_type,
|
||||
client_key: *const $crate::c_api::high_level_api::keys::ClientKey,
|
||||
result: *mut $output_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let client_key = $crate::c_api::utils::get_ref_checked(client_key).unwrap();
|
||||
let encrypted_value = $crate::c_api::utils::get_ref_checked(encrypted_value).unwrap();
|
||||
|
||||
*result = encrypted_value.0.decrypt(&client_key.0);
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_clone_on_type {
|
||||
($wrapper_type:ty) => {
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _clone>](
|
||||
sself: *const $wrapper_type,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
$crate::c_api::utils::check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
|
||||
let wrapper = $crate::c_api::utils::get_ref_checked(sself).unwrap();
|
||||
|
||||
let heap_allocated_object = Box::new($wrapper_type(wrapper.0.clone()));
|
||||
|
||||
*result = Box::into_raw(heap_allocated_object);
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_serialize_deserialize_on_type {
|
||||
($wrapper_type:ty) => {
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _serialize>](
|
||||
sself: *const $wrapper_type,
|
||||
result: *mut $crate::c_api::buffer::Buffer,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
$crate::c_api::utils::check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
|
||||
let wrapper = $crate::c_api::utils::get_ref_checked(sself).unwrap();
|
||||
|
||||
let buffer: $crate::c_api::buffer::Buffer = bincode::serialize(&wrapper.0)
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
*result = buffer;
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _deserialize>](
|
||||
buffer_view: $crate::c_api::buffer::BufferView,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
$crate::c_api::utils::check_ptr_is_non_null_and_aligned(result).unwrap();
|
||||
|
||||
// First fill the result with a null ptr so that if we fail and the return code is not
|
||||
// checked, then any access to the result pointer will segfault (mimics malloc on failure)
|
||||
*result = std::ptr::null_mut();
|
||||
|
||||
let object = bincode::deserialize(buffer_view.into()).unwrap();
|
||||
|
||||
let heap_allocated_object = Box::new($wrapper_type(object));
|
||||
|
||||
*result = Box::into_raw(heap_allocated_object);
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_binary_fn_on_type {
|
||||
($wrapper_type:ty => $($binary_fn_name:ident),* $(,)?) => {
|
||||
$(
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _ $binary_fn_name>](
|
||||
lhs: *const $wrapper_type,
|
||||
rhs: *const $wrapper_type,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
|
||||
let rhs = $crate::c_api::utils::get_ref_checked(rhs).unwrap();
|
||||
|
||||
let inner = (&lhs.0).$binary_fn_name(&rhs.0);
|
||||
|
||||
*result = Box::into_raw(Box::new($wrapper_type(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_unary_fn_on_type {
|
||||
($wrapper_type:ty => $($unary_fn_name:ident),* $(,)?) => {
|
||||
$(
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _ $unary_fn_name>](
|
||||
lhs: *const $wrapper_type,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
|
||||
|
||||
let inner = (&lhs.0).$unary_fn_name();
|
||||
|
||||
*result = Box::into_raw(Box::new($wrapper_type(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Meant for types on which makes use of interior mutability
|
||||
#[cfg(feature = "integer")]
|
||||
macro_rules! impl_unary_fn_on_type_mut {
|
||||
($wrapper_type:ty => $($unary_fn_name:ident),* $(,)?) => {
|
||||
$(
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _ $unary_fn_name>](
|
||||
lhs: *mut $wrapper_type,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
|
||||
|
||||
let inner = (&lhs.0).$unary_fn_name();
|
||||
|
||||
*result = Box::into_raw(Box::new($wrapper_type(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Meant for types on which makes use of interior mutability
|
||||
#[cfg(feature = "integer")]
|
||||
macro_rules! impl_binary_fn_on_type_mut {
|
||||
($wrapper_type:ty => $($binary_fn_name:ident),* $(,)?) => {
|
||||
$(
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _ $binary_fn_name>](
|
||||
lhs: *mut $wrapper_type,
|
||||
rhs: *mut $wrapper_type,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
|
||||
let rhs = $crate::c_api::utils::get_mut_checked(rhs).unwrap();
|
||||
|
||||
let inner = (&lhs.0).$binary_fn_name(&rhs.0);
|
||||
|
||||
*result = Box::into_raw(Box::new($wrapper_type(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Meant for types on which makes use of interior mutability
|
||||
#[cfg(feature = "integer")]
|
||||
macro_rules! impl_binary_assign_fn_on_type_mut {
|
||||
($wrapper_type:ty => $($binary_assign_fn_name:ident),* $(,)?) => {
|
||||
$(
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _ $binary_assign_fn_name>](
|
||||
lhs: *mut $wrapper_type,
|
||||
rhs: *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
|
||||
let rhs = $crate::c_api::utils::get_mut_checked(rhs).unwrap();
|
||||
|
||||
lhs.0.$binary_assign_fn_name(&rhs.0);
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Meant for types on which makes use of interior mutability
|
||||
#[cfg(feature = "integer")]
|
||||
macro_rules! impl_scalar_binary_fn_on_type_mut {
|
||||
($wrapper_type:ty, $scalar_type:ty => $($binary_fn_name:ident),* $(,)?) => {
|
||||
$(
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _scalar_ $binary_fn_name>](
|
||||
lhs: *mut $wrapper_type,
|
||||
rhs: $scalar_type,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
|
||||
|
||||
let inner = (&lhs.0).$binary_fn_name(rhs);
|
||||
|
||||
*result = Box::into_raw(Box::new($wrapper_type(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Meant for types on which makes use of interior mutability
|
||||
#[cfg(feature = "integer")]
|
||||
macro_rules! impl_scalar_binary_assign_fn_on_type_mut {
|
||||
($wrapper_type:ty, $scalar_type:ty => $($binary_assign_fn_name:ident),* $(,)?) => {
|
||||
$(
|
||||
::paste::paste! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$wrapper_type:snake _scalar_ $binary_assign_fn_name>](
|
||||
lhs: *mut $wrapper_type,
|
||||
rhs: $scalar_type,
|
||||
) -> c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
|
||||
|
||||
lhs.0.$binary_assign_fn_name(rhs);
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
#[cfg(feature = "boolean-c-api")]
|
||||
pub mod boolean;
|
||||
pub mod buffer;
|
||||
#[cfg(feature = "high-level-c-api")]
|
||||
pub mod high_level_api;
|
||||
#[cfg(feature = "shortint-c-api")]
|
||||
pub mod shortint;
|
||||
pub(crate) mod utils;
|
||||
|
||||
@@ -7,6 +7,8 @@ use std::os::raw::c_int;
|
||||
|
||||
use crate::shortint;
|
||||
|
||||
pub const SHORTINT_NATIVE_MODULUS: u64 = 0;
|
||||
|
||||
pub struct ShortintParameters(pub(in crate::c_api) shortint::parameters::Parameters);
|
||||
|
||||
#[no_mangle]
|
||||
@@ -111,6 +113,7 @@ pub unsafe extern "C" fn shortint_create_parameters(
|
||||
cbs_base_log: usize,
|
||||
message_modulus: usize,
|
||||
carry_modulus: usize,
|
||||
modulus_power_of_2_exponent: usize,
|
||||
result: *mut *mut ShortintParameters,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
@@ -138,6 +141,11 @@ pub unsafe extern "C" fn shortint_create_parameters(
|
||||
cbs_base_log: DecompositionBaseLog(cbs_base_log),
|
||||
message_modulus: crate::shortint::parameters::MessageModulus(message_modulus),
|
||||
carry_modulus: crate::shortint::parameters::CarryModulus(carry_modulus),
|
||||
ciphertext_modulus:
|
||||
crate::shortint::parameters::CiphertextModulus::try_new_power_of_2(
|
||||
modulus_power_of_2_exponent,
|
||||
)
|
||||
.unwrap(),
|
||||
}));
|
||||
|
||||
*result = Box::into_raw(heap_allocated_parameters);
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
|
||||
use crate::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
use crate::core_crypto::commons::parameters::{DecompositionLevelCount, PlaintextCount};
|
||||
use crate::core_crypto::commons::parameters::PlaintextCount;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use rayon::prelude::*;
|
||||
@@ -28,6 +28,7 @@ use rayon::prelude::*;
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -54,6 +55,7 @@ use rayon::prelude::*;
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_constant_ggsw_ciphertext(
|
||||
@@ -107,15 +109,19 @@ pub fn encrypt_constant_ggsw_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
let output_glwe_size = output.glwe_size();
|
||||
let output_polynomial_size = output.polynomial_size();
|
||||
let decomp_base_log = output.decomposition_base_log();
|
||||
let ciphertext_modulus = output.ciphertext_modulus();
|
||||
|
||||
for (level_index, (mut level_matrix, mut generator)) in
|
||||
output.iter_mut().zip(gen_iter).enumerate()
|
||||
{
|
||||
let decomp_level = DecompositionLevel(level_index + 1);
|
||||
// We scale the factor down from the native torus to whatever our torus is, the
|
||||
// encryption process will scale it back up
|
||||
let factor = encoded
|
||||
.0
|
||||
.wrapping_neg()
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)))
|
||||
.wrapping_div(ciphertext_modulus.get_scaling_to_native_torus());
|
||||
|
||||
// We iterate over the rows of the level matrix, the last row needs special treatment
|
||||
let gen_iter = generator
|
||||
@@ -160,6 +166,7 @@ pub fn encrypt_constant_ggsw_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -186,6 +193,7 @@ pub fn encrypt_constant_ggsw_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_encrypt_constant_ggsw_ciphertext(
|
||||
@@ -239,16 +247,21 @@ pub fn par_encrypt_constant_ggsw_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
let output_glwe_size = output.glwe_size();
|
||||
let output_polynomial_size = output.polynomial_size();
|
||||
let decomp_base_log = output.decomposition_base_log();
|
||||
let ciphertext_modulus = output.ciphertext_modulus();
|
||||
|
||||
output.par_iter_mut().zip(gen_iter).enumerate().for_each(
|
||||
|(level_index, (mut level_matrix, mut generator))| {
|
||||
let decomp_level = DecompositionLevel(level_index + 1);
|
||||
// We scale the factor down from the native torus to whatever our torus is, the
|
||||
// encryption process will scale it back up
|
||||
let factor = encoded
|
||||
.0
|
||||
.wrapping_neg()
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)))
|
||||
.wrapping_div(ciphertext_modulus.get_scaling_to_native_torus());
|
||||
|
||||
// We iterate over the rows of the level matrix, the last row needs special treatment
|
||||
// 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");
|
||||
@@ -352,15 +365,19 @@ pub fn encrypt_constant_seeded_ggsw_ciphertext_with_existing_generator<
|
||||
let output_glwe_size = output.glwe_size();
|
||||
let output_polynomial_size = output.polynomial_size();
|
||||
let decomp_base_log = output.decomposition_base_log();
|
||||
let ciphertext_modulus = output.ciphertext_modulus();
|
||||
|
||||
for (level_index, (mut level_matrix, mut loop_generator)) in
|
||||
output.iter_mut().zip(gen_iter).enumerate()
|
||||
{
|
||||
let decomp_level = DecompositionLevel(level_index + 1);
|
||||
// We scale the factor down from the native torus to whatever our torus is, the
|
||||
// encryption process will scale it back up
|
||||
let factor = encoded
|
||||
.0
|
||||
.wrapping_neg()
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)))
|
||||
.wrapping_div(ciphertext_modulus.get_scaling_to_native_torus());
|
||||
|
||||
// We iterate over the rows of the level matrix, the last row needs special treatment
|
||||
let gen_iter = loop_generator
|
||||
@@ -404,6 +421,7 @@ pub fn encrypt_constant_seeded_ggsw_ciphertext_with_existing_generator<
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -432,6 +450,7 @@ pub fn encrypt_constant_seeded_ggsw_ciphertext_with_existing_generator<
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// seeder.seed().into(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_constant_seeded_ggsw_ciphertext(
|
||||
@@ -521,14 +540,18 @@ pub fn par_encrypt_constant_seeded_ggsw_ciphertext_with_existing_generator<
|
||||
let output_glwe_size = output.glwe_size();
|
||||
let output_polynomial_size = output.polynomial_size();
|
||||
let decomp_base_log = output.decomposition_base_log();
|
||||
let ciphertext_modulus = output.ciphertext_modulus();
|
||||
|
||||
output.par_iter_mut().zip(gen_iter).enumerate().for_each(
|
||||
|(level_index, (mut level_matrix, mut generator))| {
|
||||
let decomp_level = DecompositionLevel(level_index + 1);
|
||||
// We scale the factor down from the native torus to whatever our torus is, the
|
||||
// encryption process will scale it back up
|
||||
let factor = encoded
|
||||
.0
|
||||
.wrapping_neg()
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
|
||||
.wrapping_mul(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)))
|
||||
.wrapping_div(ciphertext_modulus.get_scaling_to_native_torus());
|
||||
|
||||
// We iterate over the rows of the level matrix, the last row needs special treatment
|
||||
let gen_iter = generator
|
||||
@@ -556,7 +579,7 @@ pub fn par_encrypt_constant_seeded_ggsw_ciphertext_with_existing_generator<
|
||||
);
|
||||
}
|
||||
|
||||
/// Parallel variant of [`encrypt_constant_ggsw_ciphertext`].
|
||||
/// Parallel variant of [`encrypt_constant_seeded_ggsw_ciphertext`].
|
||||
///
|
||||
/// See the [`formal definition`](`GgswCiphertext#ggsw-encryption`) for the definition of the
|
||||
/// encryption algorithm.
|
||||
@@ -574,6 +597,7 @@ pub fn par_encrypt_constant_seeded_ggsw_ciphertext_with_existing_generator<
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -602,6 +626,7 @@ pub fn par_encrypt_constant_seeded_ggsw_ciphertext_with_existing_generator<
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// seeder.seed().into(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_encrypt_constant_seeded_ggsw_ciphertext(
|
||||
@@ -723,6 +748,7 @@ fn encrypt_constant_seeded_ggsw_level_matrix_row<Scalar, KeyCont, OutputCont, Ge
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -749,6 +775,7 @@ fn encrypt_constant_seeded_ggsw_level_matrix_row<Scalar, KeyCont, OutputCont, Ge
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_encrypt_constant_ggsw_ciphertext(
|
||||
@@ -787,11 +814,10 @@ where
|
||||
glwe_secret_key.glwe_dimension()
|
||||
);
|
||||
|
||||
let level_matrix = ggsw_ciphertext.iter().next().unwrap();
|
||||
let level_matrix = ggsw_ciphertext.last().unwrap();
|
||||
let level_matrix_as_glwe_list = level_matrix.as_glwe_list();
|
||||
let last_row = level_matrix_as_glwe_list.last().unwrap();
|
||||
// The first level matrix is associated to DecompositionLevel #1
|
||||
let decomp_level = DecompositionLevelCount(1);
|
||||
let decomp_level = ggsw_ciphertext.decomposition_level_count();
|
||||
|
||||
let mut decrypted_plaintext_list = PlaintextList::new(
|
||||
Scalar::ZERO,
|
||||
@@ -806,167 +832,16 @@ where
|
||||
|
||||
let plaintext_ref = decrypted_plaintext_list.get(0);
|
||||
|
||||
let rounded = decomposer.closest_representable(*plaintext_ref.0);
|
||||
// Glwe decryption maps to a smaller torus potentially, map back to the native torus
|
||||
let rounded = decomposer.closest_representable(
|
||||
(*plaintext_ref.0).wrapping_mul(
|
||||
ggsw_ciphertext
|
||||
.ciphertext_modulus()
|
||||
.get_scaling_to_native_torus(),
|
||||
),
|
||||
);
|
||||
let decoded =
|
||||
rounded.wrapping_div(Scalar::ONE << (Scalar::BITS - (decomp_base_log.0 * decomp_level.0)));
|
||||
|
||||
Plaintext(decoded)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::core_crypto::commons::generators::{
|
||||
DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
};
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, CompressionSeed};
|
||||
use crate::core_crypto::commons::test_tools;
|
||||
use crate::core_crypto::prelude::*;
|
||||
|
||||
fn test_parallel_and_seeded_ggsw_encryption_equivalence<Scalar>()
|
||||
where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
{
|
||||
// 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 main_seed = seeder.seed();
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
const NB_TESTS: usize = 10;
|
||||
|
||||
for _ in 0..NB_TESTS {
|
||||
// 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: Scalar =
|
||||
test_tools::random_uint_between(Scalar::ZERO..Scalar::TWO.shl(2));
|
||||
let plaintext = Plaintext(encoded_msg);
|
||||
|
||||
let compression_seed: CompressionSeed = seeder.seed().into();
|
||||
|
||||
let mut ser_ggsw = GgswCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
);
|
||||
|
||||
let mut deterministic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
|
||||
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
|
||||
compression_seed.seed,
|
||||
&mut deterministic_seeder,
|
||||
);
|
||||
|
||||
encrypt_constant_ggsw_ciphertext(
|
||||
&glwe_secret_key,
|
||||
&mut ser_ggsw,
|
||||
plaintext,
|
||||
glwe_modular_std_dev,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let mut par_ggsw = GgswCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
);
|
||||
|
||||
let mut deterministic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
|
||||
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
|
||||
compression_seed.seed,
|
||||
&mut deterministic_seeder,
|
||||
);
|
||||
|
||||
par_encrypt_constant_ggsw_ciphertext(
|
||||
&glwe_secret_key,
|
||||
&mut par_ggsw,
|
||||
plaintext,
|
||||
glwe_modular_std_dev,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
assert_eq!(ser_ggsw, par_ggsw);
|
||||
|
||||
// Create a new GgswCiphertext
|
||||
let mut ser_seeded_ggsw = SeededGgswCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
compression_seed,
|
||||
);
|
||||
|
||||
let mut deterministic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
|
||||
|
||||
encrypt_constant_seeded_ggsw_ciphertext(
|
||||
&glwe_secret_key,
|
||||
&mut ser_seeded_ggsw,
|
||||
plaintext,
|
||||
glwe_modular_std_dev,
|
||||
&mut deterministic_seeder,
|
||||
);
|
||||
|
||||
let mut par_seeded_ggsw = SeededGgswCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
compression_seed,
|
||||
);
|
||||
|
||||
let mut deterministic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
|
||||
|
||||
par_encrypt_constant_seeded_ggsw_ciphertext(
|
||||
&glwe_secret_key,
|
||||
&mut par_seeded_ggsw,
|
||||
plaintext,
|
||||
glwe_modular_std_dev,
|
||||
&mut deterministic_seeder,
|
||||
);
|
||||
|
||||
assert_eq!(ser_seeded_ggsw, par_seeded_ggsw);
|
||||
|
||||
let decompressed_ggsw = par_seeded_ggsw.decompress_into_ggsw_ciphertext();
|
||||
|
||||
assert_eq!(ser_ggsw, decompressed_ggsw);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parallel_and_seeded_ggsw_encryption_equivalence_u32() {
|
||||
test_parallel_and_seeded_ggsw_encryption_equivalence::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parallel_and_seeded_ggsw_encryption_equivalence_u64() {
|
||||
test_parallel_and_seeded_ggsw_encryption_equivalence::<u64>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
//! encryption`](`GlweCiphertext#glwe-encryption`).
|
||||
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::algorithms::slice_algorithms::{
|
||||
slice_wrapping_scalar_div_assign, slice_wrapping_scalar_mul_assign,
|
||||
};
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
@@ -24,12 +27,28 @@ pub fn fill_glwe_mask_and_body_for_encryption_assign<KeyCont, BodyCont, MaskCont
|
||||
MaskCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
generator.unsigned_torus_slice_wrapping_add_random_noise_assign(
|
||||
output_body.as_mut(),
|
||||
noise_parameters,
|
||||
assert_eq!(
|
||||
output_mask.ciphertext_modulus(),
|
||||
output_body.ciphertext_modulus(),
|
||||
"Mismatched moduli between output_mask ({:?}) and output_body ({:?})",
|
||||
output_mask.ciphertext_modulus(),
|
||||
output_body.ciphertext_modulus()
|
||||
);
|
||||
|
||||
generator.fill_slice_with_random_mask(output_mask.as_mut());
|
||||
let ciphertext_modulus = output_body.ciphertext_modulus();
|
||||
|
||||
generator.fill_slice_with_random_mask_custom_mod(output_mask.as_mut(), ciphertext_modulus);
|
||||
generator.unsigned_torus_slice_wrapping_add_random_noise_custom_mod_assign(
|
||||
output_body.as_mut(),
|
||||
noise_parameters,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
let torus_scaling = ciphertext_modulus.get_scaling_to_native_torus();
|
||||
slice_wrapping_scalar_mul_assign(output_mask.as_mut(), torus_scaling);
|
||||
slice_wrapping_scalar_mul_assign(output_body.as_mut(), torus_scaling);
|
||||
}
|
||||
|
||||
polynomial_wrapping_add_multisum_assign(
|
||||
&mut output_body.as_mut_polynomial(),
|
||||
@@ -56,6 +75,7 @@ pub fn fill_glwe_mask_and_body_for_encryption_assign<KeyCont, BodyCont, MaskCont
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -77,7 +97,7 @@ pub fn fill_glwe_mask_and_body_for_encryption_assign<KeyCont, BodyCont, MaskCont
|
||||
/// let encoded_msg = msg << 60;
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// // Manually fill the body with the encoded message
|
||||
/// glwe.get_mut_body().as_mut().fill(encoded_msg);
|
||||
@@ -193,6 +213,7 @@ pub fn encrypt_seeded_glwe_ciphertext_assign_with_existing_generator<
|
||||
)
|
||||
],
|
||||
output.polynomial_size(),
|
||||
output.ciphertext_modulus(),
|
||||
);
|
||||
let mut body = output.get_mut_body();
|
||||
|
||||
@@ -222,15 +243,31 @@ pub fn fill_glwe_mask_and_body_for_encryption<KeyCont, InputCont, BodyCont, Mask
|
||||
MaskCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
generator.fill_slice_with_random_noise(output_body.as_mut(), noise_parameters);
|
||||
assert_eq!(
|
||||
output_mask.ciphertext_modulus(),
|
||||
output_body.ciphertext_modulus()
|
||||
);
|
||||
|
||||
generator.fill_slice_with_random_mask(output_mask.as_mut());
|
||||
let ciphertext_modulus = output_body.ciphertext_modulus();
|
||||
|
||||
generator.fill_slice_with_random_mask_custom_mod(output_mask.as_mut(), ciphertext_modulus);
|
||||
generator.fill_slice_with_random_noise_custom_mod(
|
||||
output_body.as_mut(),
|
||||
noise_parameters,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
polynomial_wrapping_add_assign(
|
||||
&mut output_body.as_mut_polynomial(),
|
||||
&encoded.as_polynomial(),
|
||||
);
|
||||
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
let torus_scaling = ciphertext_modulus.get_scaling_to_native_torus();
|
||||
slice_wrapping_scalar_mul_assign(output_mask.as_mut(), torus_scaling);
|
||||
slice_wrapping_scalar_mul_assign(output_body.as_mut(), torus_scaling);
|
||||
}
|
||||
|
||||
polynomial_wrapping_add_multisum_assign(
|
||||
&mut output_body.as_mut_polynomial(),
|
||||
&output_mask.as_polynomial_list(),
|
||||
@@ -256,6 +293,7 @@ pub fn fill_glwe_mask_and_body_for_encryption<KeyCont, InputCont, BodyCont, Mask
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -278,7 +316,7 @@ pub fn fill_glwe_mask_and_body_for_encryption<KeyCont, InputCont, BodyCont, Mask
|
||||
/// 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);
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
@@ -375,6 +413,7 @@ pub fn encrypt_glwe_ciphertext<Scalar, KeyCont, InputCont, OutputCont, Gen>(
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let glwe_count = GlweCiphertextCount(2);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -400,7 +439,13 @@ pub fn encrypt_glwe_ciphertext<Scalar, KeyCont, InputCont, OutputCont, Gen>(
|
||||
/// );
|
||||
///
|
||||
/// // Create a new GlweCiphertextList
|
||||
/// let mut glwe_list = GlweCiphertextList::new(0u64, glwe_size, polynomial_size, glwe_count);
|
||||
/// let mut glwe_list = GlweCiphertextList::new(
|
||||
/// 0u64,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// glwe_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_glwe_ciphertext_list(
|
||||
/// &glwe_secret_key,
|
||||
@@ -525,6 +570,8 @@ pub fn decrypt_glwe_ciphertext<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
input_glwe_ciphertext.polynomial_size()
|
||||
);
|
||||
|
||||
let ciphertext_modulus = input_glwe_ciphertext.ciphertext_modulus();
|
||||
|
||||
let (mask, body) = input_glwe_ciphertext.get_mask_and_body();
|
||||
output_plaintext_list
|
||||
.as_mut()
|
||||
@@ -534,6 +581,13 @@ pub fn decrypt_glwe_ciphertext<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
&mask.as_polynomial_list(),
|
||||
&glwe_secret_key.as_polynomial_list(),
|
||||
);
|
||||
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
slice_wrapping_scalar_div_assign(
|
||||
output_plaintext_list.as_mut(),
|
||||
ciphertext_modulus.get_scaling_to_native_torus(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypt a [`GLWE ciphertext list`](`GlweCiphertextList`) in a (scalar) plaintext list.
|
||||
@@ -604,6 +658,7 @@ pub fn decrypt_glwe_ciphertext_list<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -617,7 +672,7 @@ pub fn decrypt_glwe_ciphertext_list<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
/// 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);
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// trivially_encrypt_glwe_ciphertext(&mut glwe, &plaintext_list);
|
||||
///
|
||||
@@ -662,6 +717,15 @@ pub fn trivially_encrypt_glwe_ciphertext<Scalar, InputCont, OutputCont>(
|
||||
|
||||
mask.as_mut().fill(Scalar::ZERO);
|
||||
body.as_mut().copy_from_slice(encoded.as_ref());
|
||||
|
||||
let ciphertext_modulus = body.ciphertext_modulus();
|
||||
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
slice_wrapping_scalar_mul_assign(
|
||||
body.as_mut(),
|
||||
ciphertext_modulus.get_scaling_to_native_torus(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A trivial encryption uses a zero mask and no noise.
|
||||
@@ -686,6 +750,7 @@ pub fn trivially_encrypt_glwe_ciphertext<Scalar, InputCont, OutputCont>(
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -699,7 +764,11 @@ pub fn trivially_encrypt_glwe_ciphertext<Scalar, InputCont, OutputCont>(
|
||||
/// 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);
|
||||
/// let mut glwe = allocate_and_trivially_encrypt_new_glwe_ciphertext(
|
||||
/// glwe_size,
|
||||
/// &plaintext_list,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// // Here we show the content of the trivial encryption is actually the input data in clear and
|
||||
/// // that the mask is full of 0s
|
||||
@@ -726,6 +795,7 @@ pub fn trivially_encrypt_glwe_ciphertext<Scalar, InputCont, OutputCont>(
|
||||
pub fn allocate_and_trivially_encrypt_new_glwe_ciphertext<Scalar, InputCont>(
|
||||
glwe_size: GlweSize,
|
||||
encoded: &PlaintextList<InputCont>,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) -> GlweCiphertextOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
@@ -733,11 +803,19 @@ where
|
||||
{
|
||||
let polynomial_size = PolynomialSize(encoded.plaintext_count().0);
|
||||
|
||||
let mut new_ct = GlweCiphertextOwned::new(Scalar::ZERO, glwe_size, polynomial_size);
|
||||
let mut new_ct =
|
||||
GlweCiphertextOwned::new(Scalar::ZERO, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
|
||||
let mut body = new_ct.get_mut_body();
|
||||
body.as_mut().copy_from_slice(encoded.as_ref());
|
||||
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
slice_wrapping_scalar_mul_assign(
|
||||
body.as_mut(),
|
||||
ciphertext_modulus.get_scaling_to_native_torus(),
|
||||
);
|
||||
}
|
||||
|
||||
new_ct
|
||||
}
|
||||
|
||||
@@ -786,12 +864,14 @@ pub fn encrypt_seeded_glwe_ciphertext_with_exsiting_generator<
|
||||
|
||||
let glwe_dimension = output_glwe_ciphertext.glwe_size().to_glwe_dimension();
|
||||
let polynomial_size = output_glwe_ciphertext.polynomial_size();
|
||||
let ciphertext_modulus = output_glwe_ciphertext.ciphertext_modulus();
|
||||
|
||||
let mut body = output_glwe_ciphertext.get_mut_body();
|
||||
|
||||
let mut tmp_mask = GlweMask::from_container(
|
||||
vec![Scalar::ZERO; glwe_ciphertext_mask_size(glwe_dimension, polynomial_size)],
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
fill_glwe_mask_and_body_for_encryption(
|
||||
@@ -816,6 +896,7 @@ pub fn encrypt_seeded_glwe_ciphertext_with_exsiting_generator<
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -838,8 +919,13 @@ pub fn encrypt_seeded_glwe_ciphertext_with_exsiting_generator<
|
||||
/// let plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut glwe =
|
||||
/// SeededGlweCiphertext::new(0u64, glwe_size, polynomial_size, seeder.seed().into());
|
||||
/// let mut glwe = SeededGlweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// seeder.seed().into(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_seeded_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
@@ -953,6 +1039,7 @@ pub fn encrypt_seeded_glwe_ciphertext_list_with_existing_generator<
|
||||
)
|
||||
],
|
||||
output.polynomial_size(),
|
||||
output.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
// TODO: use forking generators (for later parallel implementation).
|
||||
@@ -987,6 +1074,7 @@ pub fn encrypt_seeded_glwe_ciphertext_list_with_existing_generator<
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let glwe_count = GlweCiphertextCount(2);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -1018,6 +1106,7 @@ pub fn encrypt_seeded_glwe_ciphertext_list_with_existing_generator<
|
||||
/// polynomial_size,
|
||||
/// glwe_count,
|
||||
/// seeder.seed().into(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_seeded_glwe_ciphertext_list(
|
||||
|
||||
@@ -26,6 +26,7 @@ use crate::core_crypto::entities::*;
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -51,7 +52,7 @@ use crate::core_crypto::entities::*;
|
||||
/// *plaintext_list.get_mut(42).0 = 15 << 60;
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
/// let mut glwe = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
@@ -64,8 +65,11 @@ use crate::core_crypto::entities::*;
|
||||
/// // 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());
|
||||
/// let mut extracted_sample = LweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// equivalent_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// // 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));
|
||||
@@ -100,12 +104,20 @@ pub fn extract_lwe_sample_from_glwe_ciphertext<Scalar, InputCont, OutputCont>(
|
||||
output_lwe.lwe_size().to_lwe_dimension(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_glwe.ciphertext_modulus(),
|
||||
output_lwe.ciphertext_modulus(),
|
||||
"Mismatched moduli between input_glwe ({:?}) and output_lwe ({:?})",
|
||||
input_glwe.ciphertext_modulus(),
|
||||
output_lwe.ciphertext_modulus()
|
||||
);
|
||||
|
||||
// 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];
|
||||
*lwe_body.data = 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());
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
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::fft128::crypto::bootstrap::Fourier128LweBootstrapKey;
|
||||
use crate::core_crypto::fft_impl::fft128::math::fft::Fft128;
|
||||
use crate::core_crypto::fft_impl::fft64::crypto::bootstrap::{
|
||||
fill_with_forward_fourier_scratch, FourierLweBootstrapKey,
|
||||
};
|
||||
@@ -37,9 +39,7 @@ pub fn convert_standard_lwe_bootstrap_key_to_fourier<Scalar, InputCont, OutputCo
|
||||
|
||||
let stack = buffers.stack();
|
||||
|
||||
output_bsk
|
||||
.as_mut_view()
|
||||
.fill_with_forward_fourier(input_bsk.as_view(), fft, stack);
|
||||
convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized(input_bsk, output_bsk, fft, stack);
|
||||
}
|
||||
|
||||
/// Memory optimized version of [`convert_standard_lwe_bootstrap_key_to_fourier`].
|
||||
@@ -53,6 +53,44 @@ pub fn convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized<Scalar, Input
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = c64>,
|
||||
{
|
||||
assert_eq!(
|
||||
input_bsk.polynomial_size(),
|
||||
output_bsk.polynomial_size(),
|
||||
"Mismatched PolynomialSize between input_bsk {:?} and output_bsk {:?}",
|
||||
input_bsk.polynomial_size(),
|
||||
output_bsk.polynomial_size(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_bsk.glwe_size(),
|
||||
output_bsk.glwe_size(),
|
||||
"Mismatched GlweSize"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_bsk.decomposition_base_log(),
|
||||
output_bsk.decomposition_base_log(),
|
||||
"Mismatched DecompositionBaseLog between input_bsk {:?} and output_bsk {:?}",
|
||||
input_bsk.glwe_size(),
|
||||
output_bsk.glwe_size(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_bsk.decomposition_level_count(),
|
||||
output_bsk.decomposition_level_count(),
|
||||
"Mismatched DecompositionLevelCount between input_bsk {:?} and output_bsk {:?}",
|
||||
input_bsk.decomposition_level_count(),
|
||||
output_bsk.decomposition_level_count(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_bsk.input_lwe_dimension(),
|
||||
output_bsk.input_lwe_dimension(),
|
||||
"Mismatched input LweDimension between input_bsk {:?} and output_bsk {:?}",
|
||||
input_bsk.input_lwe_dimension(),
|
||||
output_bsk.input_lwe_dimension(),
|
||||
);
|
||||
|
||||
output_bsk
|
||||
.as_mut_view()
|
||||
.fill_with_forward_fourier(input_bsk.as_view(), fft, stack);
|
||||
@@ -64,3 +102,59 @@ pub fn convert_standard_lwe_bootstrap_key_to_fourier_mem_optimized_requirement(
|
||||
) -> Result<StackReq, SizeOverflow> {
|
||||
fill_with_forward_fourier_scratch(fft)
|
||||
}
|
||||
|
||||
/// Convert an [`LWE bootstrap key`](`LweBootstrapKey`) with standard coefficients to the Fourier
|
||||
/// domain.
|
||||
///
|
||||
/// See [`programmable_bootstrap_f128_lwe_ciphertext`](`crate::core_crypto::algorithms::programmable_bootstrap_f128_lwe_ciphertext`) for usage.
|
||||
pub fn convert_standard_lwe_bootstrap_key_to_fourier_128<Scalar, InputCont, OutputCont>(
|
||||
input_bsk: &LweBootstrapKey<InputCont>,
|
||||
output_bsk: &mut Fourier128LweBootstrapKey<OutputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = f64>,
|
||||
{
|
||||
assert_eq!(
|
||||
input_bsk.polynomial_size(),
|
||||
output_bsk.polynomial_size(),
|
||||
"Mismatched PolynomialSize between input_bsk {:?} and output_bsk {:?}",
|
||||
input_bsk.polynomial_size(),
|
||||
output_bsk.polynomial_size(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_bsk.glwe_size(),
|
||||
output_bsk.glwe_size(),
|
||||
"Mismatched GlweSize"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_bsk.decomposition_base_log(),
|
||||
output_bsk.decomposition_base_log(),
|
||||
"Mismatched DecompositionBaseLog between input_bsk {:?} and output_bsk {:?}",
|
||||
input_bsk.glwe_size(),
|
||||
output_bsk.glwe_size(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_bsk.decomposition_level_count(),
|
||||
output_bsk.decomposition_level_count(),
|
||||
"Mismatched DecompositionLevelCount between input_bsk {:?} and output_bsk {:?}",
|
||||
input_bsk.decomposition_level_count(),
|
||||
output_bsk.decomposition_level_count(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_bsk.input_lwe_dimension(),
|
||||
output_bsk.input_lwe_dimension(),
|
||||
"Mismatched input LweDimension between input_bsk {:?} and output_bsk {:?}",
|
||||
input_bsk.input_lwe_dimension(),
|
||||
output_bsk.input_lwe_dimension(),
|
||||
);
|
||||
|
||||
let fft = Fft128::new(output_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
output_bsk.fill_with_forward_fourier(input_bsk, fft);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use rayon::prelude::*;
|
||||
/// let glwe_dimension = GlweDimension(1);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -56,6 +57,7 @@ use rayon::prelude::*;
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// generate_lwe_bootstrap_key(
|
||||
@@ -144,6 +146,7 @@ pub fn allocate_and_generate_new_lwe_bootstrap_key<Scalar, InputKeyCont, OutputK
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweBootstrapKeyOwned<Scalar>
|
||||
where
|
||||
@@ -159,6 +162,7 @@ where
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_lwe_bootstrap_key(
|
||||
@@ -174,6 +178,62 @@ where
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweBootstrapKey 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);
|
||||
/// let glwe_dimension = GlweDimension(1);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let input_lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(input_lwe_dimension, &mut secret_generator);
|
||||
/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_dimension,
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut bsk = LweBootstrapKey::new(
|
||||
/// 0u64,
|
||||
/// glwe_dimension.to_glwe_size(),
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_generate_lwe_bootstrap_key(
|
||||
/// &input_lwe_secret_key,
|
||||
/// &output_glwe_secret_key,
|
||||
/// &mut bsk,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// assert!(bsk.as_ref().iter().all(|&x| x == 0) == false);
|
||||
/// ```
|
||||
pub fn par_generate_lwe_bootstrap_key<Scalar, InputKeyCont, OutputKeyCont, OutputCont, Gen>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
@@ -232,7 +292,7 @@ pub fn par_generate_lwe_bootstrap_key<Scalar, InputKeyCont, OutputKeyCont, Outpu
|
||||
noise_parameters,
|
||||
&mut generator,
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Parallel variant of [`allocate_and_generate_new_lwe_bootstrap_key`], it is recommended to use
|
||||
@@ -245,6 +305,7 @@ pub fn par_allocate_and_generate_new_lwe_bootstrap_key<Scalar, InputKeyCont, Out
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweBootstrapKeyOwned<Scalar>
|
||||
where
|
||||
@@ -260,6 +321,7 @@ where
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
par_generate_lwe_bootstrap_key(
|
||||
@@ -368,6 +430,7 @@ pub fn allocate_and_generate_new_seeded_lwe_bootstrap_key<
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
noise_seeder: &mut NoiseSeeder,
|
||||
) -> SeededLweBootstrapKeyOwned<Scalar>
|
||||
where
|
||||
@@ -385,6 +448,7 @@ where
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
noise_seeder.seed().into(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_seeded_lwe_bootstrap_key(
|
||||
@@ -486,6 +550,7 @@ pub fn par_allocate_and_generate_new_seeded_lwe_bootstrap_key<
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
noise_seeder: &mut NoiseSeeder,
|
||||
) -> SeededLweBootstrapKeyOwned<Scalar>
|
||||
where
|
||||
@@ -503,6 +568,7 @@ where
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
noise_seeder.seed().into(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
par_generate_seeded_lwe_bootstrap_key(
|
||||
@@ -515,155 +581,3 @@ where
|
||||
|
||||
bsk
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod parallel_test {
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::StandardDev;
|
||||
use crate::core_crypto::commons::generators::{DeterministicSeeder, EncryptionRandomGenerator};
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, 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::*;
|
||||
|
||||
fn test_parallel_and_seeded_bsk_gen_equivalence<T: UnsignedTorus + Sync + Send>() {
|
||||
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::<ActivatedRandomGenerator>::new(
|
||||
mask_seed,
|
||||
&mut DeterministicSeeder::<ActivatedRandomGenerator>::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::<ActivatedRandomGenerator>::new(
|
||||
mask_seed,
|
||||
&mut DeterministicSeeder::<ActivatedRandomGenerator>::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, sequential_bsk);
|
||||
|
||||
let mut sequential_seeded_bsk = SeededLweBootstrapKey::new(
|
||||
T::ZERO,
|
||||
glwe_dim.to_glwe_size(),
|
||||
poly_size,
|
||||
base_log,
|
||||
level,
|
||||
lwe_dim,
|
||||
mask_seed.into(),
|
||||
);
|
||||
|
||||
generate_seeded_lwe_bootstrap_key(
|
||||
&lwe_sk,
|
||||
&glwe_sk,
|
||||
&mut sequential_seeded_bsk,
|
||||
StandardDev::from_standard_dev(10.),
|
||||
&mut DeterministicSeeder::<ActivatedRandomGenerator>::new(
|
||||
deterministic_seeder_seed,
|
||||
),
|
||||
);
|
||||
|
||||
let mut parallel_seeded_bsk = SeededLweBootstrapKey::new(
|
||||
T::ZERO,
|
||||
glwe_dim.to_glwe_size(),
|
||||
poly_size,
|
||||
base_log,
|
||||
level,
|
||||
lwe_dim,
|
||||
mask_seed.into(),
|
||||
);
|
||||
|
||||
par_generate_seeded_lwe_bootstrap_key(
|
||||
&lwe_sk,
|
||||
&glwe_sk,
|
||||
&mut parallel_seeded_bsk,
|
||||
StandardDev::from_standard_dev(10.),
|
||||
&mut DeterministicSeeder::<ActivatedRandomGenerator>::new(
|
||||
deterministic_seeder_seed,
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(sequential_seeded_bsk, parallel_seeded_bsk);
|
||||
|
||||
let decompressed_bsk = sequential_seeded_bsk.decompress_into_lwe_bootstrap_key();
|
||||
|
||||
assert_eq!(decompressed_bsk, sequential_bsk);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parallel_and_seeded_bsk_gen_equivalence_u32() {
|
||||
test_parallel_and_seeded_bsk_gen_equivalence::<u32>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parallel_and_seeded_bsk_gen_equivalence_u64() {
|
||||
test_parallel_and_seeded_bsk_gen_equivalence::<u64>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use rayon::prelude::*;
|
||||
pub fn fill_lwe_mask_and_body_for_encryption<Scalar, KeyCont, OutputCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
output_mask: &mut LweMask<OutputCont>,
|
||||
output_body: LweBody<&mut Scalar>,
|
||||
output_body: LweBodyRefMut<Scalar>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
@@ -26,18 +26,33 @@ pub fn fill_lwe_mask_and_body_for_encryption<Scalar, KeyCont, OutputCont, Gen>(
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
generator.fill_slice_with_random_mask(output_mask.as_mut());
|
||||
assert_eq!(
|
||||
output_mask.ciphertext_modulus(),
|
||||
output_body.ciphertext_modulus(),
|
||||
"Mismatched moduli between mask ({:?}) and body ({:?})",
|
||||
output_mask.ciphertext_modulus(),
|
||||
output_body.ciphertext_modulus()
|
||||
);
|
||||
|
||||
let ciphertext_modulus = output_mask.ciphertext_modulus();
|
||||
|
||||
generator.fill_slice_with_random_mask_custom_mod(output_mask.as_mut(), ciphertext_modulus);
|
||||
|
||||
// generate an error from the normal distribution described by std_dev
|
||||
*output_body.0 = generator.random_noise(noise_parameters);
|
||||
*output_body.data = generator.random_noise_custom_mod(noise_parameters, ciphertext_modulus);
|
||||
*output_body.data = (*output_body.data).wrapping_add(encoded.0);
|
||||
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
let torus_scaling = ciphertext_modulus.get_scaling_to_native_torus();
|
||||
slice_wrapping_scalar_mul_assign(output_mask.as_mut(), torus_scaling);
|
||||
*output_body.data = (*output_body.data).wrapping_mul(torus_scaling);
|
||||
}
|
||||
|
||||
// compute the multisum between the secret key and the mask
|
||||
*output_body.0 = (*output_body.0).wrapping_add(slice_wrapping_dot_product(
|
||||
*output_body.data = (*output_body.data).wrapping_add(slice_wrapping_dot_product(
|
||||
output_mask.as_ref(),
|
||||
lwe_secret_key.as_ref(),
|
||||
));
|
||||
|
||||
*output_body.0 = (*output_body.0).wrapping_add(encoded.0);
|
||||
}
|
||||
|
||||
/// Encrypt an input plaintext in an output [`LWE ciphertext`](`LweCiphertext`).
|
||||
@@ -55,6 +70,7 @@ pub fn fill_lwe_mask_and_body_for_encryption<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -73,7 +89,7 @@ pub fn fill_lwe_mask_and_body_for_encryption<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_lwe_ciphertext(
|
||||
/// &lwe_secret_key,
|
||||
@@ -144,6 +160,7 @@ pub fn encrypt_lwe_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -166,6 +183,7 @@ pub fn encrypt_lwe_ciphertext<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -187,6 +205,7 @@ pub fn allocate_and_encrypt_new_lwe_ciphertext<Scalar, KeyCont, Gen>(
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweCiphertextOwned<Scalar>
|
||||
where
|
||||
@@ -194,8 +213,11 @@ where
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut new_ct =
|
||||
LweCiphertextOwned::new(Scalar::ZERO, lwe_secret_key.lwe_dimension().to_lwe_size());
|
||||
let mut new_ct = LweCiphertextOwned::new(
|
||||
Scalar::ZERO,
|
||||
lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
encrypt_lwe_ciphertext(
|
||||
lwe_secret_key,
|
||||
@@ -226,6 +248,7 @@ where
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -238,13 +261,13 @@ where
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), ciphertext_modulus);
|
||||
///
|
||||
/// 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);
|
||||
/// assert_eq!(*lwe.get_body().data, plaintext.0);
|
||||
/// lwe.get_mask()
|
||||
/// .as_ref()
|
||||
/// .iter()
|
||||
@@ -257,7 +280,7 @@ where
|
||||
/// 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);
|
||||
/// assert_eq!(decrypted_plaintext.0, *lwe.get_body().data);
|
||||
/// ```
|
||||
pub fn trivially_encrypt_lwe_ciphertext<Scalar, OutputCont>(
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
@@ -266,13 +289,17 @@ pub fn trivially_encrypt_lwe_ciphertext<Scalar, OutputCont>(
|
||||
Scalar: UnsignedTorus,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
output
|
||||
.get_mut_mask()
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.for_each(|elt| *elt = Scalar::ZERO);
|
||||
output.get_mut_mask().as_mut().fill(Scalar::ZERO);
|
||||
|
||||
*output.get_mut_body().0 = encoded.0
|
||||
let output_body = output.get_mut_body();
|
||||
|
||||
*output_body.data = encoded.0;
|
||||
|
||||
let ciphertext_modulus = output_body.ciphertext_modulus();
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
*output_body.data =
|
||||
(*output_body.data).wrapping_mul(ciphertext_modulus.get_scaling_to_native_torus());
|
||||
}
|
||||
}
|
||||
|
||||
/// A trivial encryption uses a zero mask and no noise.
|
||||
@@ -294,6 +321,7 @@ pub fn trivially_encrypt_lwe_ciphertext<Scalar, OutputCont>(
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -306,12 +334,15 @@ pub fn trivially_encrypt_lwe_ciphertext<Scalar, OutputCont>(
|
||||
/// 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);
|
||||
/// let mut lwe = allocate_and_trivially_encrypt_new_lwe_ciphertext(
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// plaintext,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// // 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);
|
||||
/// assert_eq!(*lwe.get_body().data, plaintext.0);
|
||||
/// lwe.get_mask()
|
||||
/// .as_ref()
|
||||
/// .iter()
|
||||
@@ -324,18 +355,29 @@ pub fn trivially_encrypt_lwe_ciphertext<Scalar, OutputCont>(
|
||||
/// 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);
|
||||
/// assert_eq!(decrypted_plaintext.0, *lwe.get_body().data);
|
||||
/// ```
|
||||
pub fn allocate_and_trivially_encrypt_new_lwe_ciphertext<Scalar>(
|
||||
lwe_size: LweSize,
|
||||
encoded: Plaintext<Scalar>,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) -> LweCiphertextOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
{
|
||||
let mut new_ct = LweCiphertextOwned::new(Scalar::ZERO, lwe_size);
|
||||
let mut new_ct = LweCiphertextOwned::new(Scalar::ZERO, lwe_size, ciphertext_modulus);
|
||||
|
||||
*new_ct.get_mut_body().0 = encoded.0;
|
||||
*new_ct.get_mut_body().data = encoded.0;
|
||||
|
||||
let output_body = new_ct.get_mut_body();
|
||||
|
||||
*output_body.data = encoded.0;
|
||||
|
||||
let ciphertext_modulus = output_body.ciphertext_modulus();
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
*output_body.data =
|
||||
(*output_body.data).wrapping_mul(ciphertext_modulus.get_scaling_to_native_torus());
|
||||
}
|
||||
|
||||
new_ct
|
||||
}
|
||||
@@ -365,12 +407,25 @@ where
|
||||
lwe_secret_key.lwe_dimension()
|
||||
);
|
||||
|
||||
let ciphertext_modulus = lwe_ciphertext.ciphertext_modulus();
|
||||
|
||||
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(),
|
||||
)))
|
||||
if ciphertext_modulus.is_native_modulus() {
|
||||
Plaintext((*body.data).wrapping_sub(slice_wrapping_dot_product(
|
||||
mask.as_ref(),
|
||||
lwe_secret_key.as_ref(),
|
||||
)))
|
||||
} else {
|
||||
Plaintext(
|
||||
(*body.data)
|
||||
.wrapping_sub(slice_wrapping_dot_product(
|
||||
mask.as_ref(),
|
||||
lwe_secret_key.as_ref(),
|
||||
))
|
||||
.wrapping_div(ciphertext_modulus.get_scaling_to_native_torus()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypt an input plaintext list in an output [`LWE ciphertext list`](`LweCiphertextList`).
|
||||
@@ -389,6 +444,7 @@ where
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_ciphertext_count = LweCiphertextCount(2);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -408,8 +464,12 @@ where
|
||||
/// let plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(lwe_ciphertext_count.0));
|
||||
///
|
||||
/// // Create a new LweCiphertextList
|
||||
/// let mut lwe_list =
|
||||
/// LweCiphertextList::new(0u64, lwe_dimension.to_lwe_size(), lwe_ciphertext_count);
|
||||
/// let mut lwe_list = LweCiphertextList::new(
|
||||
/// 0u64,
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// lwe_ciphertext_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_lwe_ciphertext_list(
|
||||
/// &lwe_secret_key,
|
||||
@@ -493,6 +553,7 @@ pub fn encrypt_lwe_ciphertext_list<Scalar, KeyCont, OutputCont, InputCont, Gen>(
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_ciphertext_count = LweCiphertextCount(2);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -512,8 +573,12 @@ pub fn encrypt_lwe_ciphertext_list<Scalar, KeyCont, OutputCont, InputCont, Gen>(
|
||||
/// let plaintext_list = PlaintextList::new(encoded_msg, PlaintextCount(lwe_ciphertext_count.0));
|
||||
///
|
||||
/// // Create a new LweCiphertextList
|
||||
/// let mut lwe_list =
|
||||
/// LweCiphertextList::new(0u64, lwe_dimension.to_lwe_size(), lwe_ciphertext_count);
|
||||
/// let mut lwe_list = LweCiphertextList::new(
|
||||
/// 0u64,
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// lwe_ciphertext_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_encrypt_lwe_ciphertext_list(
|
||||
/// &lwe_secret_key,
|
||||
@@ -633,6 +698,7 @@ pub fn decrypt_lwe_ciphertext_list<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let zero_encryption_count =
|
||||
/// LwePublicKeyZeroEncryptionCount(lwe_dimension.to_lwe_size().0 * 64 + 128);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -650,6 +716,7 @@ pub fn decrypt_lwe_ciphertext_list<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
/// &lwe_secret_key,
|
||||
/// zero_encryption_count,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -658,7 +725,7 @@ pub fn decrypt_lwe_ciphertext_list<Scalar, KeyCont, InputCont, OutputCont>(
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_lwe_ciphertext_with_public_key(
|
||||
/// &lwe_public_key,
|
||||
@@ -692,6 +759,14 @@ pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
lwe_public_key.ciphertext_modulus(),
|
||||
output.ciphertext_modulus(),
|
||||
"Mismatched moduli between lwe_public_key ({:?}) and output ({:?})",
|
||||
lwe_public_key.ciphertext_modulus(),
|
||||
output.ciphertext_modulus()
|
||||
);
|
||||
|
||||
assert!(
|
||||
output.lwe_size().to_lwe_dimension() == lwe_public_key.lwe_size().to_lwe_dimension(),
|
||||
"Mismatch between LweDimension of output ciphertext and input public key. \
|
||||
@@ -700,6 +775,8 @@ pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
lwe_public_key.lwe_size().to_lwe_dimension()
|
||||
);
|
||||
|
||||
let ciphertext_modulus = output.ciphertext_modulus();
|
||||
|
||||
output.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
let mut ct_choice = vec![Scalar::ZERO; lwe_public_key.zero_encryption_count().0];
|
||||
@@ -708,14 +785,23 @@ pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
|
||||
// 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()) {
|
||||
// TODO: can leak choice of ciphertexts
|
||||
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);
|
||||
|
||||
if ciphertext_modulus.is_native_modulus() {
|
||||
*body.data = (*body.data).wrapping_add(encoded.0);
|
||||
} else {
|
||||
*body.data = (*body.data).wrapping_add(
|
||||
encoded
|
||||
.0
|
||||
.wrapping_mul(ciphertext_modulus.get_scaling_to_native_torus()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypt an input plaintext in an output [`LWE ciphertext`](`LweCiphertext`) using a
|
||||
@@ -734,6 +820,7 @@ pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let zero_encryption_count =
|
||||
/// LwePublicKeyZeroEncryptionCount(lwe_dimension.to_lwe_size().0 * 64 + 128);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -751,6 +838,7 @@ pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// &lwe_secret_key,
|
||||
/// zero_encryption_count,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// seeder,
|
||||
/// );
|
||||
///
|
||||
@@ -759,7 +847,7 @@ pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size());
|
||||
/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_lwe_ciphertext_with_seeded_public_key(
|
||||
/// &lwe_public_key,
|
||||
@@ -793,6 +881,14 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
lwe_public_key.ciphertext_modulus(),
|
||||
output.ciphertext_modulus(),
|
||||
"Mismatched moduli between lwe_public_key ({:?}) and output ({:?})",
|
||||
lwe_public_key.ciphertext_modulus(),
|
||||
output.ciphertext_modulus()
|
||||
);
|
||||
|
||||
assert!(
|
||||
output.lwe_size().to_lwe_dimension() == lwe_public_key.lwe_size().to_lwe_dimension(),
|
||||
"Mismatch between LweDimension of output ciphertext and input public key. \
|
||||
@@ -807,7 +903,10 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
|
||||
|
||||
generator.fill_slice_with_random_uniform_binary(&mut ct_choice);
|
||||
|
||||
let mut tmp_ciphertext = LweCiphertext::new(Scalar::ZERO, lwe_public_key.lwe_size());
|
||||
let ciphertext_modulus = output.ciphertext_modulus();
|
||||
|
||||
let mut tmp_ciphertext =
|
||||
LweCiphertext::new(Scalar::ZERO, lwe_public_key.lwe_size(), ciphertext_modulus);
|
||||
|
||||
let mut random_generator =
|
||||
RandomGenerator::<ActivatedRandomGenerator>::new(lwe_public_key.compression_seed().seed);
|
||||
@@ -815,9 +914,17 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
|
||||
// Add the public encryption of zeros to get the zero encryption
|
||||
for (&chosen, public_encryption_of_zero_body) in ct_choice.iter().zip(lwe_public_key.iter()) {
|
||||
let (mut mask, body) = tmp_ciphertext.get_mut_mask_and_body();
|
||||
random_generator.fill_slice_with_random_uniform(mask.as_mut());
|
||||
*body.0 = *public_encryption_of_zero_body.0;
|
||||
random_generator
|
||||
.fill_slice_with_random_uniform_custom_mod(mask.as_mut(), ciphertext_modulus);
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
slice_wrapping_scalar_mul_assign(
|
||||
mask.as_mut(),
|
||||
ciphertext_modulus.get_scaling_to_native_torus(),
|
||||
);
|
||||
}
|
||||
*body.data = *public_encryption_of_zero_body.data;
|
||||
|
||||
// TODO: can leak choice of ciphertexts
|
||||
if chosen == Scalar::ONE {
|
||||
lwe_ciphertext_add_assign(output, &tmp_ciphertext);
|
||||
}
|
||||
@@ -825,7 +932,15 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
|
||||
|
||||
// Add encoded plaintext
|
||||
let body = output.get_mut_body();
|
||||
*body.0 = (*body.0).wrapping_add(encoded.0);
|
||||
if ciphertext_modulus.is_native_modulus() {
|
||||
*body.data = (*body.data).wrapping_add(encoded.0);
|
||||
} else {
|
||||
*body.data = (*body.data).wrapping_add(
|
||||
encoded
|
||||
.0
|
||||
.wrapping_mul(ciphertext_modulus.get_scaling_to_native_torus()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function to share the core logic of the seeded LWE encryption between all functions
|
||||
@@ -864,8 +979,10 @@ pub fn encrypt_seeded_lwe_ciphertext_list_with_existing_generator<
|
||||
output.lwe_ciphertext_count()
|
||||
);
|
||||
|
||||
let mut output_mask =
|
||||
LweMask::from_container(vec![Scalar::ZERO; output.lwe_size().to_lwe_dimension().0]);
|
||||
let mut output_mask = LweMask::from_container(
|
||||
vec![Scalar::ZERO; output.lwe_size().to_lwe_dimension().0],
|
||||
output.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let gen_iter = generator
|
||||
.fork_lwe_list_to_lwe::<Scalar>(output.lwe_ciphertext_count(), output.lwe_size())
|
||||
@@ -897,6 +1014,7 @@ pub fn encrypt_seeded_lwe_ciphertext_list_with_existing_generator<
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_ciphertext_count = LweCiphertextCount(2);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -921,6 +1039,7 @@ pub fn encrypt_seeded_lwe_ciphertext_list_with_existing_generator<
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// lwe_ciphertext_count,
|
||||
/// seeder.seed().into(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_seeded_lwe_ciphertext_list(
|
||||
@@ -1024,13 +1143,15 @@ pub fn par_encrypt_seeded_lwe_ciphertext_list_with_existing_generator<
|
||||
.unwrap();
|
||||
|
||||
let lwe_dimension = output.lwe_size().to_lwe_dimension();
|
||||
let ciphertext_modulus = output.ciphertext_modulus();
|
||||
|
||||
output
|
||||
.par_iter_mut()
|
||||
.zip(encoded.par_iter())
|
||||
.zip(gen_iter)
|
||||
.for_each(|((output_body, plaintext), mut loop_generator)| {
|
||||
let mut output_mask = LweMask::from_container(vec![Scalar::ZERO; lwe_dimension.0]);
|
||||
let mut output_mask =
|
||||
LweMask::from_container(vec![Scalar::ZERO; lwe_dimension.0], ciphertext_modulus);
|
||||
fill_lwe_mask_and_body_for_encryption(
|
||||
lwe_secret_key,
|
||||
&mut output_mask,
|
||||
@@ -1053,6 +1174,7 @@ pub fn par_encrypt_seeded_lwe_ciphertext_list_with_existing_generator<
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_ciphertext_count = LweCiphertextCount(2);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -1077,6 +1199,7 @@ pub fn par_encrypt_seeded_lwe_ciphertext_list_with_existing_generator<
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// lwe_ciphertext_count,
|
||||
/// seeder.seed().into(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_encrypt_seeded_lwe_ciphertext_list(
|
||||
@@ -1152,7 +1275,10 @@ pub fn encrypt_seeded_lwe_ciphertext_with_existing_generator<Scalar, KeyCont, Ge
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut mask = LweMask::from_container(vec![Scalar::ZERO; lwe_secret_key.lwe_dimension().0]);
|
||||
let mut mask = LweMask::from_container(
|
||||
vec![Scalar::ZERO; lwe_secret_key.lwe_dimension().0],
|
||||
output.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
fill_lwe_mask_and_body_for_encryption(
|
||||
lwe_secret_key,
|
||||
@@ -1179,6 +1305,7 @@ pub fn encrypt_seeded_lwe_ciphertext_with_existing_generator<Scalar, KeyCont, Ge
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -1195,7 +1322,12 @@ pub fn encrypt_seeded_lwe_ciphertext_with_existing_generator<Scalar, KeyCont, Ge
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
///
|
||||
/// // Create a new SeededLweCiphertext
|
||||
/// let mut lwe = SeededLweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), seeder.seed().into());
|
||||
/// let mut lwe = SeededLweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// seeder.seed().into(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_seeded_lwe_ciphertext(
|
||||
/// &lwe_secret_key,
|
||||
@@ -1263,6 +1395,7 @@ pub fn encrypt_seeded_lwe_ciphertext<Scalar, KeyCont, NoiseSeeder>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -1283,6 +1416,7 @@ pub fn encrypt_seeded_lwe_ciphertext<Scalar, KeyCont, NoiseSeeder>(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// seeder,
|
||||
/// );
|
||||
///
|
||||
@@ -1306,6 +1440,7 @@ pub fn allocate_and_encrypt_new_seeded_lwe_ciphertext<Scalar, KeyCont, NoiseSeed
|
||||
lwe_secret_key: &LweSecretKey<KeyCont>,
|
||||
encoded: Plaintext<Scalar>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
noise_seeder: &mut NoiseSeeder,
|
||||
) -> SeededLweCiphertext<Scalar>
|
||||
where
|
||||
@@ -1318,6 +1453,7 @@ where
|
||||
Scalar::ZERO,
|
||||
lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
noise_seeder.seed().into(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
encrypt_seeded_lwe_ciphertext(
|
||||
@@ -1330,204 +1466,3 @@ where
|
||||
|
||||
seeded_ct
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::core_crypto::commons::generators::{
|
||||
DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
};
|
||||
use crate::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
use crate::core_crypto::commons::test_tools;
|
||||
use crate::core_crypto::prelude::*;
|
||||
|
||||
fn test_parallel_and_seeded_lwe_list_encryption_equivalence<
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
>() {
|
||||
// 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(10);
|
||||
let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
|
||||
let main_seed = seeder.seed();
|
||||
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
const NB_TESTS: usize = 10;
|
||||
|
||||
for _ in 0..NB_TESTS {
|
||||
// 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: Scalar = test_tools::random_uint_between(Scalar::ZERO..Scalar::TWO.shl(2));
|
||||
let encoded_msg = msg << (Scalar::BITS - 5);
|
||||
let plaintext_list =
|
||||
PlaintextList::new(encoded_msg, PlaintextCount(lwe_ciphertext_count.0));
|
||||
// Create a new LweCiphertextList
|
||||
let mut par_lwe_list = LweCiphertextList::new(
|
||||
Scalar::ZERO,
|
||||
lwe_dimension.to_lwe_size(),
|
||||
lwe_ciphertext_count,
|
||||
);
|
||||
|
||||
let mut determinisitic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
|
||||
determinisitic_seeder.seed(),
|
||||
&mut determinisitic_seeder,
|
||||
);
|
||||
par_encrypt_lwe_ciphertext_list(
|
||||
&lwe_secret_key,
|
||||
&mut par_lwe_list,
|
||||
&plaintext_list,
|
||||
lwe_modular_std_dev,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let mut ser_lwe_list = LweCiphertextList::new(
|
||||
Scalar::ZERO,
|
||||
lwe_dimension.to_lwe_size(),
|
||||
lwe_ciphertext_count,
|
||||
);
|
||||
|
||||
let mut determinisitic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
|
||||
determinisitic_seeder.seed(),
|
||||
&mut determinisitic_seeder,
|
||||
);
|
||||
encrypt_lwe_ciphertext_list(
|
||||
&lwe_secret_key,
|
||||
&mut ser_lwe_list,
|
||||
&plaintext_list,
|
||||
lwe_modular_std_dev,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
assert_eq!(par_lwe_list, ser_lwe_list);
|
||||
|
||||
let mut determinisitic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
|
||||
// Create a new LweCiphertextList
|
||||
let mut par_seeded_lwe_list = SeededLweCiphertextList::new(
|
||||
Scalar::ZERO,
|
||||
lwe_dimension.to_lwe_size(),
|
||||
lwe_ciphertext_count,
|
||||
determinisitic_seeder.seed().into(),
|
||||
);
|
||||
|
||||
par_encrypt_seeded_lwe_ciphertext_list(
|
||||
&lwe_secret_key,
|
||||
&mut par_seeded_lwe_list,
|
||||
&plaintext_list,
|
||||
lwe_modular_std_dev,
|
||||
&mut determinisitic_seeder,
|
||||
);
|
||||
|
||||
let mut determinisitic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(main_seed);
|
||||
|
||||
let mut ser_seeded_lwe_list = SeededLweCiphertextList::new(
|
||||
Scalar::ZERO,
|
||||
lwe_dimension.to_lwe_size(),
|
||||
lwe_ciphertext_count,
|
||||
determinisitic_seeder.seed().into(),
|
||||
);
|
||||
|
||||
encrypt_seeded_lwe_ciphertext_list(
|
||||
&lwe_secret_key,
|
||||
&mut ser_seeded_lwe_list,
|
||||
&plaintext_list,
|
||||
lwe_modular_std_dev,
|
||||
&mut determinisitic_seeder,
|
||||
);
|
||||
|
||||
assert_eq!(par_seeded_lwe_list, ser_seeded_lwe_list);
|
||||
|
||||
let decompressed_lwe_list = ser_seeded_lwe_list.decompress_into_lwe_ciphertext_list();
|
||||
|
||||
assert_eq!(decompressed_lwe_list, ser_lwe_list);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parallel_and_seeded_lwe_list_encryption_equivalence_u32() {
|
||||
test_parallel_and_seeded_lwe_list_encryption_equivalence::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parallel_and_seeded_lwe_list_encryption_equivalence_u64() {
|
||||
test_parallel_and_seeded_lwe_list_encryption_equivalence::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_u128_encryption() {
|
||||
// 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(4.998_277_131_225_527e-11);
|
||||
|
||||
// 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());
|
||||
|
||||
const NB_TESTS: usize = 10;
|
||||
const MSG_BITS: u32 = 4;
|
||||
|
||||
for _ in 0..NB_TESTS {
|
||||
for msg in 0..2u128.pow(MSG_BITS) {
|
||||
// Create the LweSecretKey
|
||||
let lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
|
||||
lwe_dimension,
|
||||
&mut secret_generator,
|
||||
);
|
||||
|
||||
// Create the plaintext
|
||||
const ENCODING: u32 = u128::BITS - MSG_BITS;
|
||||
let plaintext = Plaintext(msg << ENCODING);
|
||||
|
||||
// Create a new LweCiphertext
|
||||
let mut lwe = LweCiphertext::new(0u128, 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 >> ENCODING;
|
||||
|
||||
// Check we recovered the original message
|
||||
assert_eq!(cleartext, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use crate::core_crypto::entities::*;
|
||||
/// let output_lwe_dimension = LweDimension(2048);
|
||||
/// let decomp_base_log = DecompositionBaseLog(3);
|
||||
/// let decomp_level_count = DecompositionLevelCount(5);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -50,6 +51,7 @@ use crate::core_crypto::entities::*;
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -62,10 +64,15 @@ use crate::core_crypto::entities::*;
|
||||
/// &input_lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_lwe = LweCiphertext::new(0, output_lwe_secret_key.lwe_dimension().to_lwe_size());
|
||||
/// let mut output_lwe = LweCiphertext::new(
|
||||
/// 0,
|
||||
/// output_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// keyswitch_lwe_ciphertext(&ksk, &input_lwe, &mut output_lwe);
|
||||
///
|
||||
@@ -114,7 +121,7 @@ pub fn keyswitch_lwe_ciphertext<Scalar, KSKCont, InputCont, OutputCont>(
|
||||
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;
|
||||
*output_lwe_ciphertext.get_mut_body().data = *input_lwe_ciphertext.get_body().data;
|
||||
|
||||
// We instantiate a decomposer
|
||||
let decomposer = SignedDecomposer::new(
|
||||
|
||||
@@ -25,6 +25,7 @@ use crate::core_crypto::entities::*;
|
||||
/// let output_lwe_dimension = LweDimension(2048);
|
||||
/// let decomp_base_log = DecompositionBaseLog(3);
|
||||
/// let decomp_level_count = DecompositionLevelCount(5);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -48,6 +49,7 @@ use crate::core_crypto::entities::*;
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// output_lwe_dimension,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// generate_lwe_keyswitch_key(
|
||||
@@ -90,6 +92,7 @@ pub fn generate_lwe_keyswitch_key<Scalar, InputKeyCont, OutputKeyCont, KSKeyCont
|
||||
|
||||
let decomp_base_log = lwe_keyswitch_key.decomposition_base_log();
|
||||
let decomp_level_count = lwe_keyswitch_key.decomposition_level_count();
|
||||
let ciphertext_modulus = lwe_keyswitch_key.ciphertext_modulus();
|
||||
|
||||
// The plaintexts used to encrypt a key element will be stored in this buffer
|
||||
let mut decomposition_plaintexts_buffer =
|
||||
@@ -107,8 +110,12 @@ pub fn generate_lwe_keyswitch_key<Scalar, InputKeyCont, OutputKeyCont, KSKeyCont
|
||||
.map(DecompositionLevel)
|
||||
.zip(decomposition_plaintexts_buffer.iter_mut())
|
||||
{
|
||||
// Here we take the decomposition term from the native torus, bring it to the torus we
|
||||
// are working with by dividing by the scaling factor and the encryption will take care
|
||||
// of mapping that back to the native torus
|
||||
*message.0 = DecompositionTerm::new(level, decomp_base_log, *input_key_element)
|
||||
.to_recomposition_summand();
|
||||
.to_recomposition_summand()
|
||||
.wrapping_div(ciphertext_modulus.get_scaling_to_native_torus());
|
||||
}
|
||||
|
||||
encrypt_lwe_ciphertext_list(
|
||||
@@ -131,6 +138,7 @@ pub fn allocate_and_generate_new_lwe_keyswitch_key<Scalar, InputKeyCont, OutputK
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweKeyswitchKeyOwned<Scalar>
|
||||
where
|
||||
@@ -145,6 +153,7 @@ where
|
||||
decomp_level_count,
|
||||
input_lwe_sk.lwe_dimension(),
|
||||
output_lwe_sk.lwe_dimension(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_lwe_keyswitch_key(
|
||||
@@ -172,6 +181,7 @@ where
|
||||
/// let output_lwe_dimension = LweDimension(2048);
|
||||
/// let decomp_base_log = DecompositionBaseLog(3);
|
||||
/// let decomp_level_count = DecompositionLevelCount(5);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -194,6 +204,7 @@ where
|
||||
/// input_lwe_dimension,
|
||||
/// output_lwe_dimension,
|
||||
/// seeder.seed().into(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// generate_seeded_lwe_keyswitch_key(
|
||||
@@ -243,6 +254,7 @@ pub fn generate_seeded_lwe_keyswitch_key<
|
||||
|
||||
let decomp_base_log = lwe_keyswitch_key.decomposition_base_log();
|
||||
let decomp_level_count = lwe_keyswitch_key.decomposition_level_count();
|
||||
let ciphertext_modulus = lwe_keyswitch_key.ciphertext_modulus();
|
||||
|
||||
// The plaintexts used to encrypt a key element will be stored in this buffer
|
||||
let mut decomposition_plaintexts_buffer =
|
||||
@@ -265,8 +277,12 @@ pub fn generate_seeded_lwe_keyswitch_key<
|
||||
.map(DecompositionLevel)
|
||||
.zip(decomposition_plaintexts_buffer.iter_mut())
|
||||
{
|
||||
// Here we take the decomposition term from the native torus, bring it to the torus we
|
||||
// are working with by dividing by the scaling factor and the encryption will take care
|
||||
// of mapping that back to the native torus
|
||||
*message.0 = DecompositionTerm::new(level, decomp_base_log, *input_key_element)
|
||||
.to_recomposition_summand();
|
||||
.to_recomposition_summand()
|
||||
.wrapping_div(ciphertext_modulus.get_scaling_to_native_torus());
|
||||
}
|
||||
|
||||
encrypt_seeded_lwe_ciphertext_list_with_existing_generator(
|
||||
@@ -293,6 +309,7 @@ pub fn allocate_and_generate_new_seeded_lwe_keyswitch_key<
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
noise_seeder: &mut NoiseSeeder,
|
||||
) -> SeededLweKeyswitchKeyOwned<Scalar>
|
||||
where
|
||||
@@ -309,6 +326,7 @@ where
|
||||
input_lwe_sk.lwe_dimension(),
|
||||
output_lwe_sk.lwe_dimension(),
|
||||
noise_seeder.seed().into(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_seeded_lwe_keyswitch_key(
|
||||
@@ -321,103 +339,3 @@ where
|
||||
|
||||
new_lwe_keyswitch_key
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::core_crypto::commons::generators::{
|
||||
DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
};
|
||||
use crate::core_crypto::commons::math::random::ActivatedRandomGenerator;
|
||||
use crate::core_crypto::prelude::*;
|
||||
|
||||
fn test_seeded_lwe_ksk_gen_equivalence<Scalar: UnsignedTorus>() {
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
// computations
|
||||
// Define parameters for LweKeyswitchKey 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 mask_seed = seeder.seed();
|
||||
let deterministic_seeder_seed = seeder.seed();
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
const NB_TEST: usize = 10;
|
||||
|
||||
for _ in 0..NB_TEST {
|
||||
// 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(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_dimension,
|
||||
output_lwe_dimension,
|
||||
);
|
||||
|
||||
let mut deterministic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(deterministic_seeder_seed);
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
|
||||
mask_seed,
|
||||
&mut deterministic_seeder,
|
||||
);
|
||||
|
||||
generate_lwe_keyswitch_key(
|
||||
&input_lwe_secret_key,
|
||||
&output_lwe_secret_key,
|
||||
&mut ksk,
|
||||
lwe_modular_std_dev,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let mut seeded_ksk = SeededLweKeyswitchKey::new(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_dimension,
|
||||
output_lwe_dimension,
|
||||
mask_seed.into(),
|
||||
);
|
||||
|
||||
let mut deterministic_seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(deterministic_seeder_seed);
|
||||
|
||||
generate_seeded_lwe_keyswitch_key(
|
||||
&input_lwe_secret_key,
|
||||
&output_lwe_secret_key,
|
||||
&mut seeded_ksk,
|
||||
lwe_modular_std_dev,
|
||||
&mut deterministic_seeder,
|
||||
);
|
||||
|
||||
let decompressed_ksk = seeded_ksk.decompress_into_lwe_keyswitch_key();
|
||||
|
||||
assert_eq!(ksk, decompressed_ksk);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seeded_lwe_ksk_gen_equivalence_u32() {
|
||||
test_seeded_lwe_ksk_gen_equivalence::<u32>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seeded_lwe_ksk_gen_equivalence_u64() {
|
||||
test_seeded_lwe_ksk_gen_equivalence::<u64>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::core_crypto::entities::*;
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -41,6 +42,7 @@ use crate::core_crypto::entities::*;
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -70,6 +72,14 @@ pub fn lwe_ciphertext_add_assign<Scalar, LhsCont, RhsCont>(
|
||||
LhsCont: ContainerMut<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(
|
||||
lhs.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus(),
|
||||
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) LweCiphertext",
|
||||
lhs.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus()
|
||||
);
|
||||
|
||||
slice_wrapping_add_assign(lhs.as_mut(), rhs.as_ref());
|
||||
}
|
||||
|
||||
@@ -87,6 +97,7 @@ pub fn lwe_ciphertext_add_assign<Scalar, LhsCont, RhsCont>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -109,6 +120,7 @@ pub fn lwe_ciphertext_add_assign<Scalar, LhsCont, RhsCont>(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -142,6 +154,22 @@ pub fn lwe_ciphertext_add<Scalar, OutputCont, LhsCont, RhsCont>(
|
||||
LhsCont: Container<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(
|
||||
lhs.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus(),
|
||||
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) LweCiphertext",
|
||||
lhs.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
output.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus(),
|
||||
"Mismatched moduli between output ({:?}) and rhs ({:?}) LweCiphertext",
|
||||
output.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus()
|
||||
);
|
||||
|
||||
slice_wrapping_add(output.as_mut(), lhs.as_ref(), rhs.as_ref());
|
||||
}
|
||||
|
||||
@@ -158,6 +186,7 @@ pub fn lwe_ciphertext_add<Scalar, OutputCont, LhsCont, RhsCont>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -180,6 +209,7 @@ pub fn lwe_ciphertext_add<Scalar, OutputCont, LhsCont, RhsCont>(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -207,8 +237,15 @@ pub fn lwe_ciphertext_plaintext_add_assign<Scalar, InCont>(
|
||||
InCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
let body = lhs.get_mut_body();
|
||||
|
||||
*body.0 = (*body.0).wrapping_add(rhs.0);
|
||||
let ciphertext_modulus = body.ciphertext_modulus();
|
||||
if ciphertext_modulus.is_native_modulus() {
|
||||
*body.data = (*body.data).wrapping_add(rhs.0);
|
||||
} else {
|
||||
*body.data = (*body.data).wrapping_add(
|
||||
rhs.0
|
||||
.wrapping_mul(ciphertext_modulus.get_scaling_to_native_torus()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the right-hand side encoded [`Plaintext`] to the left-hand side [`LWE
|
||||
@@ -224,6 +261,7 @@ pub fn lwe_ciphertext_plaintext_add_assign<Scalar, InCont>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -246,6 +284,7 @@ pub fn lwe_ciphertext_plaintext_add_assign<Scalar, InCont>(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -273,8 +312,15 @@ pub fn lwe_ciphertext_plaintext_sub_assign<Scalar, InCont>(
|
||||
InCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
let body = lhs.get_mut_body();
|
||||
|
||||
*body.0 = (*body.0).wrapping_sub(rhs.0);
|
||||
let ciphertext_modulus = body.ciphertext_modulus();
|
||||
if ciphertext_modulus.is_native_modulus() {
|
||||
*body.data = (*body.data).wrapping_sub(rhs.0);
|
||||
} else {
|
||||
*body.data = (*body.data).wrapping_sub(
|
||||
rhs.0
|
||||
.wrapping_mul(ciphertext_modulus.get_scaling_to_native_torus()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the opposite of the input [`LWE ciphertext`](`LweCiphertext`) and update it in place.
|
||||
@@ -289,6 +335,7 @@ pub fn lwe_ciphertext_plaintext_sub_assign<Scalar, InCont>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -311,6 +358,7 @@ pub fn lwe_ciphertext_plaintext_sub_assign<Scalar, InCont>(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -351,6 +399,7 @@ where
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -374,6 +423,7 @@ where
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -403,6 +453,85 @@ pub fn lwe_ciphertext_cleartext_mul_assign<Scalar, InCont>(
|
||||
slice_wrapping_scalar_mul_assign(lhs.as_mut(), rhs.0);
|
||||
}
|
||||
|
||||
/// Mulitply the left-hand side [`LWE ciphertext`](`LweCiphertext`) by the right-hand side cleartext
|
||||
/// writing the result in the output [`LWE ciphertext`](`LweCiphertext`).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the LweSecretKey
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let plaintext = Plaintext(msg << 60);
|
||||
/// let mul_cleartext = 2;
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let lwe = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output = lwe.clone();
|
||||
///
|
||||
/// lwe_ciphertext_cleartext_mul(&mut output, &lwe, Cleartext(mul_cleartext));
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &output);
|
||||
///
|
||||
/// // 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 expected result
|
||||
/// assert_eq!(cleartext, msg * mul_cleartext);
|
||||
/// ```
|
||||
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>,
|
||||
{
|
||||
assert_eq!(
|
||||
output.ciphertext_modulus(),
|
||||
lhs.ciphertext_modulus(),
|
||||
"Mismatched moduli between output ({:?}) and lhs ({:?}) LweCiphertext",
|
||||
output.ciphertext_modulus(),
|
||||
lhs.ciphertext_modulus()
|
||||
);
|
||||
output.as_mut().copy_from_slice(lhs.as_ref());
|
||||
lwe_ciphertext_cleartext_mul_assign(output, rhs);
|
||||
}
|
||||
|
||||
/// Subtract the right-hand side [`LWE ciphertext`](`LweCiphertext`) to the left-hand side [`LWE
|
||||
/// ciphertext`](`LweCiphertext`) updating it in-place.
|
||||
///
|
||||
@@ -416,6 +545,7 @@ pub fn lwe_ciphertext_cleartext_mul_assign<Scalar, InCont>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -438,6 +568,7 @@ pub fn lwe_ciphertext_cleartext_mul_assign<Scalar, InCont>(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -467,6 +598,14 @@ pub fn lwe_ciphertext_sub_assign<Scalar, LhsCont, RhsCont>(
|
||||
LhsCont: ContainerMut<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(
|
||||
lhs.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus(),
|
||||
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) LweCiphertext",
|
||||
lhs.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus()
|
||||
);
|
||||
|
||||
slice_wrapping_sub_assign(lhs.as_mut(), rhs.as_ref());
|
||||
}
|
||||
|
||||
@@ -484,6 +623,7 @@ pub fn lwe_ciphertext_sub_assign<Scalar, LhsCont, RhsCont>(
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -508,12 +648,14 @@ pub fn lwe_ciphertext_sub_assign<Scalar, LhsCont, RhsCont>(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext1,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
/// let lwe2 = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext2,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -545,75 +687,22 @@ pub fn lwe_ciphertext_sub<Scalar, OutputCont, LhsCont, RhsCont>(
|
||||
LhsCont: Container<Element = Scalar>,
|
||||
RhsCont: Container<Element = Scalar>,
|
||||
{
|
||||
slice_wrapping_sub(output.as_mut(), lhs.as_ref(), rhs.as_ref());
|
||||
}
|
||||
assert_eq!(
|
||||
lhs.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus(),
|
||||
"Mismatched moduli between lhs ({:?}) and rhs ({:?}) LweCiphertext",
|
||||
lhs.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
output.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus(),
|
||||
"Mismatched moduli between output ({:?}) and rhs ({:?}) LweCiphertext",
|
||||
output.ciphertext_modulus(),
|
||||
rhs.ciphertext_modulus()
|
||||
);
|
||||
|
||||
/// Mulitply the left-hand side [`LWE ciphertext`](`LweCiphertext`) by the right-hand side cleartext
|
||||
/// writing the result in the output [`LWE ciphertext`](`LweCiphertext`).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweCiphertext creation
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
///
|
||||
/// // 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);
|
||||
/// let mul_cleartext = 2;
|
||||
///
|
||||
/// // Create a new LweCiphertext
|
||||
/// let lwe = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
/// &lwe_secret_key,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output = lwe.clone();
|
||||
///
|
||||
/// lwe_ciphertext_cleartext_mul(&mut output, &lwe, Cleartext(mul_cleartext));
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &output);
|
||||
///
|
||||
/// // 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 expected result
|
||||
/// assert_eq!(cleartext, msg * mul_cleartext);
|
||||
/// ```
|
||||
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);
|
||||
lwe_ciphertext_sub_assign(output, rhs);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use rayon::prelude::*;
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let grouping_factor = LweBskGroupingFactor(2);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -50,6 +51,7 @@ use rayon::prelude::*;
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// grouping_factor,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// generate_lwe_multi_bit_bootstrap_key(
|
||||
@@ -158,6 +160,7 @@ pub fn generate_lwe_multi_bit_bootstrap_key<Scalar, InputKeyCont, OutputKeyCont,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn allocate_and_generate_new_lwe_multi_bit_bootstrap_key<
|
||||
Scalar,
|
||||
InputKeyCont,
|
||||
@@ -170,6 +173,7 @@ pub fn allocate_and_generate_new_lwe_multi_bit_bootstrap_key<
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
grouping_factor: LweBskGroupingFactor,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweMultiBitBootstrapKeyOwned<Scalar>
|
||||
where
|
||||
@@ -186,6 +190,7 @@ where
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
grouping_factor,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_lwe_multi_bit_bootstrap_key(
|
||||
@@ -214,6 +219,7 @@ where
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let grouping_factor = LweBskGroupingFactor(2);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -240,6 +246,7 @@ where
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// grouping_factor,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_generate_lwe_multi_bit_bootstrap_key(
|
||||
@@ -400,6 +407,7 @@ where
|
||||
key_bits_plaintext
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn par_allocate_and_generate_new_lwe_multi_bit_bootstrap_key<
|
||||
Scalar,
|
||||
InputKeyCont,
|
||||
@@ -412,6 +420,7 @@ pub fn par_allocate_and_generate_new_lwe_multi_bit_bootstrap_key<
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
grouping_factor: LweBskGroupingFactor,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LweMultiBitBootstrapKeyOwned<Scalar>
|
||||
where
|
||||
@@ -428,6 +437,7 @@ where
|
||||
decomp_level_count,
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
grouping_factor,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
par_generate_lwe_multi_bit_bootstrap_key(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::core_crypto::algorithms::extract_lwe_sample_from_glwe_ciphertext;
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
|
||||
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::*;
|
||||
@@ -33,6 +34,7 @@ use std::thread;
|
||||
/// let pbs_base_log = DecompositionBaseLog(23);
|
||||
/// let pbs_level = DecompositionLevelCount(1);
|
||||
/// let grouping_factor = LweBskGroupingFactor(2); // Group bits in pairs
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Request the best seeder possible, starting with hardware entropy sources and falling back to
|
||||
/// // /dev/random on Unix systems if enabled via cargo features
|
||||
@@ -70,6 +72,7 @@ use std::thread;
|
||||
/// pbs_level,
|
||||
/// small_lwe_dimension,
|
||||
/// grouping_factor,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_generate_lwe_multi_bit_bootstrap_key(
|
||||
@@ -111,6 +114,7 @@ use std::thread;
|
||||
/// &small_lwe_sk,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -124,6 +128,7 @@ use std::thread;
|
||||
/// polynomial_size: PolynomialSize,
|
||||
/// glwe_size: GlweSize,
|
||||
/// message_modulus: usize,
|
||||
/// ciphertext_modulus: CiphertextModulus<u64>,
|
||||
/// delta: u64,
|
||||
/// f: F,
|
||||
/// ) -> GlweCiphertextOwned<u64>
|
||||
@@ -158,8 +163,11 @@ use std::thread;
|
||||
///
|
||||
/// let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
|
||||
///
|
||||
/// let accumulator =
|
||||
/// allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &accumulator_plaintext);
|
||||
/// let accumulator = allocate_and_trivially_encrypt_new_glwe_ciphertext(
|
||||
/// glwe_size,
|
||||
/// &accumulator_plaintext,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// accumulator
|
||||
/// }
|
||||
@@ -169,13 +177,17 @@ use std::thread;
|
||||
/// polynomial_size,
|
||||
/// glwe_dimension.to_glwe_size(),
|
||||
/// message_modulus as usize,
|
||||
/// ciphertext_modulus,
|
||||
/// delta,
|
||||
/// |x: u64| 2 * x,
|
||||
/// );
|
||||
///
|
||||
/// // Allocate the LweCiphertext to store the result of the PBS
|
||||
/// let mut pbs_multiplication_ct =
|
||||
/// LweCiphertext::new(0u64, big_lwe_sk.lwe_dimension().to_lwe_size());
|
||||
/// let mut pbs_multiplication_ct = LweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
/// println!("Performing blind rotation...");
|
||||
/// // Use 4 threads for the multi-bit blind rotation for example
|
||||
/// multi_bit_blind_rotate_assign(
|
||||
@@ -250,6 +262,14 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
multi_bit_bsk.polynomial_size(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input.ciphertext_modulus(),
|
||||
accumulator.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus between input ({:?}) and accumulator ({:?})",
|
||||
input.ciphertext_modulus(),
|
||||
accumulator.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let (lwe_mask, lwe_body) = input.get_mask_and_body();
|
||||
|
||||
// No way to chunk the result of ggsw_iter at the moment
|
||||
@@ -276,7 +296,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
|
||||
let lut_poly_size = accumulator.polynomial_size();
|
||||
let monomial_degree = pbs_modulus_switch(
|
||||
*lwe_body.0,
|
||||
*lwe_body.data,
|
||||
lut_poly_size,
|
||||
ModulusSwitchOffset(0),
|
||||
LutCountLog(0),
|
||||
@@ -426,7 +446,12 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
|
||||
// We initialize ct0 for the successive external products
|
||||
let ct0 = accumulator;
|
||||
let mut ct1 = GlweCiphertext::new(Scalar::ZERO, ct0.glwe_size(), ct0.polynomial_size());
|
||||
let mut ct1 = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
ct0.glwe_size(),
|
||||
ct0.polynomial_size(),
|
||||
ct0.ciphertext_modulus(),
|
||||
);
|
||||
let ct1 = &mut ct1;
|
||||
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
@@ -480,11 +505,26 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
ct0.as_mut().copy_from_slice(ct1.as_ref());
|
||||
}
|
||||
|
||||
let ciphertext_modulus = ct0.ciphertext_modulus();
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
// When we convert back from the fourier domain, integer values will contain up to 53
|
||||
// MSBs with information. In our representation of power of 2 moduli < native modulus we
|
||||
// fill the MSBs and leave the LSBs empty, this usage of the signed decomposer allows to
|
||||
// round while keeping the data in the MSBs
|
||||
let signed_decomposer = SignedDecomposer::new(
|
||||
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
ct0.as_mut()
|
||||
.iter_mut()
|
||||
.for_each(|x| *x = signed_decomposer.closest_representable(*x));
|
||||
}
|
||||
|
||||
threads.into_iter().for_each(|t| t.join().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
/// Perform a programmable bootsrap with given an input [`LWE ciphertext`](`LweCiphertext`), a
|
||||
/// Perform a programmable bootstrap with given an input [`LWE ciphertext`](`LweCiphertext`), a
|
||||
/// look-up table passed as a [`GLWE ciphertext`](`GlweCiphertext`) and an [`LWE multi-bit bootstrap
|
||||
/// key`](`LweMultiBitBootstrapKey`) in the fourier domain. The result is written in the provided
|
||||
/// output [`LWE ciphertext`](`LweCiphertext`).
|
||||
@@ -505,6 +545,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
/// let pbs_base_log = DecompositionBaseLog(23);
|
||||
/// let pbs_level = DecompositionLevelCount(1);
|
||||
/// let grouping_factor = LweBskGroupingFactor(2); // Group bits in pairs
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Request the best seeder possible, starting with hardware entropy sources and falling back to
|
||||
/// // /dev/random on Unix systems if enabled via cargo features
|
||||
@@ -542,6 +583,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
/// pbs_level,
|
||||
/// small_lwe_dimension,
|
||||
/// grouping_factor,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// par_generate_lwe_multi_bit_bootstrap_key(
|
||||
@@ -583,6 +625,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
/// &small_lwe_sk,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -596,6 +639,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
/// polynomial_size: PolynomialSize,
|
||||
/// glwe_size: GlweSize,
|
||||
/// message_modulus: usize,
|
||||
/// ciphertext_modulus: CiphertextModulus<u64>,
|
||||
/// delta: u64,
|
||||
/// f: F,
|
||||
/// ) -> GlweCiphertextOwned<u64>
|
||||
@@ -630,8 +674,11 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
///
|
||||
/// let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
|
||||
///
|
||||
/// let accumulator =
|
||||
/// allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &accumulator_plaintext);
|
||||
/// let accumulator = allocate_and_trivially_encrypt_new_glwe_ciphertext(
|
||||
/// glwe_size,
|
||||
/// &accumulator_plaintext,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// accumulator
|
||||
/// }
|
||||
@@ -641,13 +688,17 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
/// polynomial_size,
|
||||
/// glwe_dimension.to_glwe_size(),
|
||||
/// message_modulus as usize,
|
||||
/// ciphertext_modulus,
|
||||
/// delta,
|
||||
/// |x: u64| 2 * x,
|
||||
/// );
|
||||
///
|
||||
/// // Allocate the LweCiphertext to store the result of the PBS
|
||||
/// let mut pbs_multiplication_ct =
|
||||
/// LweCiphertext::new(0u64, big_lwe_sk.lwe_dimension().to_lwe_size());
|
||||
/// let mut pbs_multiplication_ct = LweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
/// println!("Computing PBS...");
|
||||
/// // Use 4 threads to compute the multi-bit PBS
|
||||
/// multi_bit_programmable_bootstrap_lwe_ciphertext(
|
||||
@@ -734,10 +785,27 @@ pub fn multi_bit_programmable_bootstrap_lwe_ciphertext<
|
||||
multi_bit_bsk.polynomial_size(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input.ciphertext_modulus(),
|
||||
output.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus between input ({:?}) and output ({:?})",
|
||||
input.ciphertext_modulus(),
|
||||
output.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input.ciphertext_modulus(),
|
||||
accumulator.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus between input ({:?}) and accumulator ({:?})",
|
||||
input.ciphertext_modulus(),
|
||||
accumulator.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let mut local_accumulator = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
accumulator.glwe_size(),
|
||||
accumulator.polynomial_size(),
|
||||
accumulator.ciphertext_modulus(),
|
||||
);
|
||||
local_accumulator
|
||||
.as_mut()
|
||||
@@ -747,236 +815,3 @@ pub fn multi_bit_programmable_bootstrap_lwe_ciphertext<
|
||||
|
||||
extract_lwe_sample_from_glwe_ciphertext(&local_accumulator, output, MonomialDegree(0));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::core_crypto::prelude::*;
|
||||
|
||||
fn multi_bit_pbs(
|
||||
multi_bit_params: MultiBitParams,
|
||||
grouping_factor: LweBskGroupingFactor,
|
||||
thread_count: ThreadCount,
|
||||
) {
|
||||
let MultiBitParams {
|
||||
mut input_lwe_dimension,
|
||||
lwe_modular_std_dev,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
glwe_modular_std_dev,
|
||||
} = multi_bit_params;
|
||||
|
||||
while input_lwe_dimension.0 % grouping_factor.0 != 0 {
|
||||
input_lwe_dimension = LweDimension(input_lwe_dimension.0 + 1);
|
||||
}
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let seeder = seeder.as_mut();
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
// Create the LweSecretKey
|
||||
let input_lwe_secret_key = allocate_and_generate_new_binary_lwe_secret_key(
|
||||
input_lwe_dimension,
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
let output_lwe_secret_key = output_glwe_secret_key.clone().into_lwe_secret_key();
|
||||
|
||||
let mut bsk = LweMultiBitBootstrapKey::new(
|
||||
0u64,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_dimension,
|
||||
grouping_factor,
|
||||
);
|
||||
|
||||
par_generate_lwe_multi_bit_bootstrap_key(
|
||||
&input_lwe_secret_key,
|
||||
&output_glwe_secret_key,
|
||||
&mut bsk,
|
||||
glwe_modular_std_dev,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let mut multi_bit_bsk = FourierLweMultiBitBootstrapKey::new(
|
||||
input_lwe_dimension,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
grouping_factor,
|
||||
);
|
||||
|
||||
convert_standard_lwe_multi_bit_bootstrap_key_to_fourier(&bsk, &mut multi_bit_bsk);
|
||||
|
||||
// Here we will define a helper function to generate an accumulator for a PBS
|
||||
fn generate_accumulator<F>(
|
||||
polynomial_size: PolynomialSize,
|
||||
glwe_size: GlweSize,
|
||||
message_modulus: usize,
|
||||
delta: u64,
|
||||
f: F,
|
||||
) -> GlweCiphertextOwned<u64>
|
||||
where
|
||||
F: Fn(u64) -> u64,
|
||||
{
|
||||
// N/(p/2) = size of each block, to correct noise from the input we introduce the
|
||||
// notion of box, which manages redundancy to yield a denoised value
|
||||
// for several noisy values around a true input value.
|
||||
let box_size = polynomial_size.0 / message_modulus;
|
||||
|
||||
// Create the accumulator
|
||||
let mut accumulator_u64 = vec![0_u64; polynomial_size.0];
|
||||
|
||||
// Fill each box with the encoded denoised value
|
||||
for i in 0..message_modulus {
|
||||
let index = i * box_size;
|
||||
accumulator_u64[index..index + box_size]
|
||||
.iter_mut()
|
||||
.for_each(|a| *a = f(i as u64) * delta);
|
||||
}
|
||||
|
||||
let half_box_size = box_size / 2;
|
||||
|
||||
// Negate the first half_box_size coefficients to manage negacyclicity and rotate
|
||||
for a_i in accumulator_u64[0..half_box_size].iter_mut() {
|
||||
*a_i = (*a_i).wrapping_neg();
|
||||
}
|
||||
|
||||
// Rotate the accumulator
|
||||
accumulator_u64.rotate_left(half_box_size);
|
||||
|
||||
let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
|
||||
|
||||
allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &accumulator_plaintext)
|
||||
}
|
||||
|
||||
// Our 4 bits message space
|
||||
let message_modulus = 1u64 << 4;
|
||||
|
||||
let f = |x: u64| (3 * x) % message_modulus;
|
||||
|
||||
// Delta used to encode 4 bits of message + a bit of padding on u64
|
||||
let delta = (1_u64 << 63) / message_modulus;
|
||||
|
||||
const NB_TESTS: usize = 10;
|
||||
|
||||
for input_message in 0..message_modulus {
|
||||
for _ in 0..NB_TESTS {
|
||||
// Apply our encoding
|
||||
let plaintext = Plaintext(input_message * delta);
|
||||
|
||||
// Allocate a new LweCiphertext and encrypt our plaintext
|
||||
let lwe_ciphertext_in: LweCiphertextOwned<u64> =
|
||||
allocate_and_encrypt_new_lwe_ciphertext(
|
||||
&input_lwe_secret_key,
|
||||
plaintext,
|
||||
lwe_modular_std_dev,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let accumulator: GlweCiphertextOwned<u64> = generate_accumulator(
|
||||
polynomial_size,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
message_modulus as usize,
|
||||
delta,
|
||||
f,
|
||||
);
|
||||
|
||||
// Allocate the LweCiphertext to store the result of the PBS
|
||||
let mut out_pbs_ct =
|
||||
LweCiphertext::new(0u64, output_lwe_secret_key.lwe_dimension().to_lwe_size());
|
||||
println!("Computing PBS...");
|
||||
multi_bit_programmable_bootstrap_lwe_ciphertext(
|
||||
&lwe_ciphertext_in,
|
||||
&mut out_pbs_ct,
|
||||
&accumulator.as_view(),
|
||||
&multi_bit_bsk,
|
||||
thread_count,
|
||||
);
|
||||
|
||||
// Decrypt the PBS result
|
||||
let result_plaintext: Plaintext<u64> =
|
||||
decrypt_lwe_ciphertext(&output_lwe_secret_key, &out_pbs_ct);
|
||||
|
||||
// Create a SignedDecomposer to perform the rounding of the decrypted plaintext
|
||||
// We pass a DecompositionBaseLog of 5 and a DecompositionLevelCount of 1 indicating
|
||||
// we want to round the 5 MSB, 1 bit of padding plus our 4 bits of
|
||||
// message
|
||||
let signed_decomposer =
|
||||
SignedDecomposer::new(DecompositionBaseLog(5), DecompositionLevelCount(1));
|
||||
|
||||
// Round and remove our encoding
|
||||
let result_cleartext: u64 =
|
||||
signed_decomposer.closest_representable(result_plaintext.0) / delta;
|
||||
|
||||
println!("Checking result...");
|
||||
assert_eq!(
|
||||
f(input_message),
|
||||
result_cleartext,
|
||||
"in: {input_message}, expected: {}, out: {result_cleartext}",
|
||||
f(input_message)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MultiBitParams {
|
||||
pub input_lwe_dimension: LweDimension,
|
||||
pub lwe_modular_std_dev: StandardDev,
|
||||
pub decomp_base_log: DecompositionBaseLog,
|
||||
pub decomp_level_count: DecompositionLevelCount,
|
||||
pub glwe_dimension: GlweDimension,
|
||||
pub polynomial_size: PolynomialSize,
|
||||
pub glwe_modular_std_dev: StandardDev,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_bit_pbs_test_factor_2_thread_5() {
|
||||
multi_bit_pbs(
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield
|
||||
// correct computations
|
||||
MultiBitParams {
|
||||
input_lwe_dimension: LweDimension(724),
|
||||
lwe_modular_std_dev: StandardDev(1.58705e-10),
|
||||
decomp_base_log: DecompositionBaseLog(1),
|
||||
decomp_level_count: DecompositionLevelCount(18),
|
||||
glwe_dimension: GlweDimension(3),
|
||||
polynomial_size: PolynomialSize(512),
|
||||
glwe_modular_std_dev: StandardDev(1.5734e-23),
|
||||
},
|
||||
LweBskGroupingFactor(2),
|
||||
ThreadCount(5),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_bit_pbs_test_factor_3_thread_12() {
|
||||
multi_bit_pbs(
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield
|
||||
// correct computations
|
||||
MultiBitParams {
|
||||
input_lwe_dimension: LweDimension(732),
|
||||
lwe_modular_std_dev: StandardDev(1.18161e-10),
|
||||
decomp_base_log: DecompositionBaseLog(1),
|
||||
decomp_level_count: DecompositionLevelCount(18),
|
||||
glwe_dimension: GlweDimension(3),
|
||||
polynomial_size: PolynomialSize(512),
|
||||
glwe_modular_std_dev: StandardDev(1.5734e-23),
|
||||
},
|
||||
LweBskGroupingFactor(3),
|
||||
ThreadCount(12),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,13 +31,30 @@ pub fn private_functional_keyswitch_lwe_ciphertext_into_glwe_ciphertext<
|
||||
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_eq!(
|
||||
lwe_pfpksk.input_lwe_key_dimension().0,
|
||||
input_lwe_ciphertext.lwe_size().to_lwe_dimension().0
|
||||
);
|
||||
assert_eq!(
|
||||
lwe_pfpksk.output_glwe_key_dimension().0,
|
||||
output_glwe_ciphertext.glwe_size().to_glwe_dimension().0
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
lwe_pfpksk.ciphertext_modulus(),
|
||||
output_glwe_ciphertext.ciphertext_modulus()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
output_glwe_ciphertext.ciphertext_modulus(),
|
||||
input_lwe_ciphertext.ciphertext_modulus()
|
||||
);
|
||||
|
||||
assert!(
|
||||
lwe_pfpksk.output_glwe_key_dimension().0
|
||||
== output_glwe_ciphertext.glwe_size().to_glwe_dimension().0
|
||||
input_lwe_ciphertext
|
||||
.ciphertext_modulus()
|
||||
.is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
// We reset the output
|
||||
@@ -87,10 +104,21 @@ pub fn private_functional_keyswitch_lwe_ciphertext_list_and_pack_in_glwe_ciphert
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(lwe_pfpksk.ciphertext_modulus(), output.ciphertext_modulus());
|
||||
assert_eq!(output.ciphertext_modulus(), input.ciphertext_modulus());
|
||||
assert!(
|
||||
input.ciphertext_modulus().is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
assert!(input.lwe_ciphertext_count().0 <= output.polynomial_size().0);
|
||||
output.as_mut().fill(Scalar::ZERO);
|
||||
let mut buffer =
|
||||
GlweCiphertext::new(Scalar::ZERO, output.glwe_size(), output.polynomial_size());
|
||||
let mut buffer = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
output.glwe_size(),
|
||||
output.polynomial_size(),
|
||||
output.ciphertext_modulus(),
|
||||
);
|
||||
// for each ciphertext, call mono_key_switch
|
||||
for (degree, input_ciphertext) in input.iter().enumerate() {
|
||||
private_functional_keyswitch_lwe_ciphertext_into_glwe_ciphertext(
|
||||
|
||||
@@ -62,6 +62,10 @@ pub fn generate_lwe_private_functional_packing_keyswitch_key<
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
lwe_pfpksk.output_polynomial_size()
|
||||
);
|
||||
assert!(
|
||||
lwe_pfpksk.ciphertext_modulus().is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
// We instantiate a buffer
|
||||
let mut messages = PlaintextListOwned::new(
|
||||
@@ -176,6 +180,10 @@ pub fn par_generate_lwe_private_functional_packing_keyswitch_key<
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
lwe_pfpksk.output_polynomial_size()
|
||||
);
|
||||
assert!(
|
||||
lwe_pfpksk.ciphertext_modulus().is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
// We retrieve decomposition arguments
|
||||
let decomp_level_count = lwe_pfpksk.decomposition_level_count();
|
||||
@@ -271,6 +279,8 @@ mod test {
|
||||
|
||||
let var_small = Variance::from_variance(2f64.powf(-80.0));
|
||||
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let mut secret_generator =
|
||||
@@ -297,6 +307,7 @@ mod test {
|
||||
pfpksk_base_log,
|
||||
pfpksk_level_count,
|
||||
var_small,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
@@ -314,6 +325,7 @@ mod test {
|
||||
pfpksk_base_log,
|
||||
pfpksk_level_count,
|
||||
var_small,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
//! bootstrap`](`LweBootstrapKey#programmable-bootstrapping`).
|
||||
|
||||
use crate::core_crypto::commons::computation_buffers::ComputationBuffers;
|
||||
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::*;
|
||||
use crate::core_crypto::fft_impl::fft128::crypto::bootstrap::{
|
||||
bootstrap_scratch as bootstrap_scratch_f128, Fourier128LweBootstrapKey,
|
||||
};
|
||||
use crate::core_crypto::fft_impl::fft128::math::fft::{Fft128, Fft128View};
|
||||
use crate::core_crypto::fft_impl::fft64::crypto::bootstrap::{
|
||||
bootstrap_scratch, FourierLweBootstrapKey,
|
||||
};
|
||||
@@ -42,6 +47,7 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let pbs_base_log = DecompositionBaseLog(23);
|
||||
/// let pbs_level = DecompositionLevelCount(1);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Request the best seeder possible, starting with hardware entropy sources and falling back to
|
||||
/// // /dev/random on Unix systems if enabled via cargo features
|
||||
@@ -79,6 +85,7 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
|
||||
/// pbs_base_log,
|
||||
/// pbs_level,
|
||||
/// glwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// seeder,
|
||||
/// );
|
||||
///
|
||||
@@ -118,6 +125,7 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
|
||||
/// &small_lwe_sk,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -131,6 +139,7 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
|
||||
/// polynomial_size: PolynomialSize,
|
||||
/// glwe_size: GlweSize,
|
||||
/// message_modulus: usize,
|
||||
/// ciphertext_modulus: CiphertextModulus<u64>,
|
||||
/// delta: u64,
|
||||
/// f: F,
|
||||
/// ) -> GlweCiphertextOwned<u64>
|
||||
@@ -165,8 +174,11 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
|
||||
///
|
||||
/// let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
|
||||
///
|
||||
/// let accumulator =
|
||||
/// allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &accumulator_plaintext);
|
||||
/// let accumulator = allocate_and_trivially_encrypt_new_glwe_ciphertext(
|
||||
/// glwe_size,
|
||||
/// &accumulator_plaintext,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// accumulator
|
||||
/// }
|
||||
@@ -176,13 +188,17 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
|
||||
/// polynomial_size,
|
||||
/// glwe_dimension.to_glwe_size(),
|
||||
/// message_modulus as usize,
|
||||
/// ciphertext_modulus,
|
||||
/// delta,
|
||||
/// |x: u64| 2 * x,
|
||||
/// );
|
||||
///
|
||||
/// // Allocate the LweCiphertext to store the result of the PBS
|
||||
/// let mut pbs_multiplication_ct =
|
||||
/// LweCiphertext::new(0u64, big_lwe_sk.lwe_dimension().to_lwe_size());
|
||||
/// let mut pbs_multiplication_ct = LweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
/// println!("Performing blind rotation...");
|
||||
/// blind_rotate_assign(&lwe_ciphertext_in, &mut accumulator, &fourier_bsk);
|
||||
/// println!("Performing sample extraction...");
|
||||
@@ -223,6 +239,8 @@ pub fn blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
KeyCont: Container<Element = c64>,
|
||||
{
|
||||
assert_eq!(input.ciphertext_modulus(), lut.ciphertext_modulus());
|
||||
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
@@ -259,6 +277,10 @@ pub fn blind_rotate_assign_mem_optimized<Scalar, InputCont, OutputCont, KeyCont>
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
KeyCont: Container<Element = c64>,
|
||||
{
|
||||
assert_eq!(input.ciphertext_modulus(), lut.ciphertext_modulus());
|
||||
|
||||
// Blind rotate assign manages the rounding to go back to the proper torus if the ciphertext
|
||||
// modulus is not the native one
|
||||
fourier_bsk
|
||||
.as_view()
|
||||
.blind_rotate_assign(lut.as_mut_view(), input.as_ref(), fft, stack);
|
||||
@@ -293,6 +315,8 @@ pub fn add_external_product_assign<Scalar, OutputGlweCont, InputGlweCont, GgswCo
|
||||
GgswCont: Container<Element = c64>,
|
||||
InputGlweCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(out.ciphertext_modulus(), glwe.ciphertext_modulus());
|
||||
|
||||
let fft = Fft::new(ggsw.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
@@ -336,6 +360,7 @@ pub fn add_external_product_assign<Scalar, OutputGlweCont, InputGlweCont, GgswCo
|
||||
/// let decomp_base_log = DecompositionBaseLog(23);
|
||||
/// let decomp_level_count = DecompositionLevelCount(1);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -362,6 +387,7 @@ pub fn add_external_product_assign<Scalar, OutputGlweCont, InputGlweCont, GgswCo
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_constant_ggsw_ciphertext(
|
||||
@@ -376,7 +402,7 @@ pub fn add_external_product_assign<Scalar, OutputGlweCont, InputGlweCont, GgswCo
|
||||
///
|
||||
/// let ct_plaintexts = PlaintextList::new(ct_plaintext.0, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// let mut ct = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
/// let mut ct = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
@@ -459,13 +485,30 @@ pub fn add_external_product_assign_mem_optimized<Scalar, OutputGlweCont, InputGl
|
||||
GgswCont: Container<Element = c64>,
|
||||
InputGlweCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(out.ciphertext_modulus(), glwe.ciphertext_modulus());
|
||||
|
||||
impl_add_external_product_assign(
|
||||
out.as_mut_view(),
|
||||
ggsw.as_view(),
|
||||
glwe.as_view(),
|
||||
fft,
|
||||
stack,
|
||||
)
|
||||
);
|
||||
|
||||
let ciphertext_modulus = out.ciphertext_modulus();
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
// When we convert back from the fourier domain, integer values will contain up to 53
|
||||
// MSBs with information. In our representation of power of 2 moduli < native modulus we
|
||||
// fill the MSBs and leave the LSBs empty, this usage of the signed decomposer allows to
|
||||
// round while keeping the data in the MSBs
|
||||
let signed_decomposer = SignedDecomposer::new(
|
||||
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
out.as_mut()
|
||||
.iter_mut()
|
||||
.for_each(|x| *x = signed_decomposer.closest_representable(*x));
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the required memory for [`add_external_product_assign_mem_optimized`].
|
||||
@@ -516,6 +559,8 @@ pub fn cmux_assign<Scalar, Cont0, Cont1, GgswCont>(
|
||||
Cont1: ContainerMut<Element = Scalar>,
|
||||
GgswCont: Container<Element = c64>,
|
||||
{
|
||||
assert_eq!(ct0.ciphertext_modulus(), ct1.ciphertext_modulus());
|
||||
|
||||
let fft = Fft::new(ggsw.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
@@ -549,6 +594,7 @@ pub fn cmux_assign<Scalar, Cont0, Cont1, GgswCont>(
|
||||
/// let decomp_base_log = DecompositionBaseLog(23);
|
||||
/// let decomp_level_count = DecompositionLevelCount(1);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
@@ -575,6 +621,7 @@ pub fn cmux_assign<Scalar, Cont0, Cont1, GgswCont>(
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_constant_ggsw_ciphertext(
|
||||
@@ -595,6 +642,7 @@ pub fn cmux_assign<Scalar, Cont0, Cont1, GgswCont>(
|
||||
/// polynomial_size,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_constant_ggsw_ciphertext(
|
||||
@@ -611,8 +659,8 @@ pub fn cmux_assign<Scalar, Cont0, Cont1, GgswCont>(
|
||||
/// let ct0_plaintexts = PlaintextList::new(ct0_plaintext.0, PlaintextCount(polynomial_size.0));
|
||||
/// let ct1_plaintexts = PlaintextList::new(ct1_plaintext.0, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// let mut ct0 = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
/// let mut ct1 = GlweCiphertext::new(0u64, glwe_size, polynomial_size);
|
||||
/// let mut ct0 = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
/// let mut ct1 = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
@@ -726,6 +774,8 @@ pub fn cmux_assign_mem_optimized<Scalar, Cont0, Cont1, GgswCont>(
|
||||
Cont1: ContainerMut<Element = Scalar>,
|
||||
GgswCont: Container<Element = c64>,
|
||||
{
|
||||
assert_eq!(ct0.ciphertext_modulus(), ct1.ciphertext_modulus());
|
||||
|
||||
cmux(
|
||||
ct0.as_mut_view(),
|
||||
ct1.as_mut_view(),
|
||||
@@ -733,6 +783,21 @@ pub fn cmux_assign_mem_optimized<Scalar, Cont0, Cont1, GgswCont>(
|
||||
fft,
|
||||
stack,
|
||||
);
|
||||
|
||||
let ciphertext_modulus = ct0.ciphertext_modulus();
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
// When we convert back from the fourier domain, integer values will contain up to 53
|
||||
// MSBs with information. In our representation of power of 2 moduli < native modulus we
|
||||
// fill the MSBs and leave the LSBs empty, this usage of the signed decomposer allows to
|
||||
// round while keeping the data in the MSBs
|
||||
let signed_decomposer = SignedDecomposer::new(
|
||||
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
ct0.as_mut()
|
||||
.iter_mut()
|
||||
.for_each(|x| *x = signed_decomposer.closest_representable(*x));
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the required memory for [`cmux_assign_mem_optimized`].
|
||||
@@ -744,7 +809,7 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
|
||||
cmux_scratch::<Scalar>(glwe_size, polynomial_size, fft)
|
||||
}
|
||||
|
||||
/// Perform a programmable bootsrap given an input [`LWE ciphertext`](`LweCiphertext`), a
|
||||
/// Perform a programmable bootstrap given an input [`LWE ciphertext`](`LweCiphertext`), a
|
||||
/// look-up table passed as a [`GLWE ciphertext`](`GlweCiphertext`) and an [`LWE bootstrap
|
||||
/// key`](`LweBootstrapKey`) in the fourier domain. The result is written in the provided output
|
||||
/// [`LWE ciphertext`](`LweCiphertext`).
|
||||
@@ -767,6 +832,7 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let pbs_base_log = DecompositionBaseLog(23);
|
||||
/// let pbs_level = DecompositionLevelCount(1);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Request the best seeder possible, starting with hardware entropy sources and falling back to
|
||||
/// // /dev/random on Unix systems if enabled via cargo features
|
||||
@@ -804,6 +870,7 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
|
||||
/// pbs_base_log,
|
||||
/// pbs_level,
|
||||
/// glwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// seeder,
|
||||
/// );
|
||||
///
|
||||
@@ -843,6 +910,7 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
|
||||
/// &small_lwe_sk,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -856,6 +924,7 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
|
||||
/// polynomial_size: PolynomialSize,
|
||||
/// glwe_size: GlweSize,
|
||||
/// message_modulus: usize,
|
||||
/// ciphertext_modulus: CiphertextModulus<u64>,
|
||||
/// delta: u64,
|
||||
/// f: F,
|
||||
/// ) -> GlweCiphertextOwned<u64>
|
||||
@@ -890,8 +959,11 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
|
||||
///
|
||||
/// let accumulator_plaintext = PlaintextList::from_container(accumulator_u64);
|
||||
///
|
||||
/// let accumulator =
|
||||
/// allocate_and_trivially_encrypt_new_glwe_ciphertext(glwe_size, &accumulator_plaintext);
|
||||
/// let accumulator = allocate_and_trivially_encrypt_new_glwe_ciphertext(
|
||||
/// glwe_size,
|
||||
/// &accumulator_plaintext,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// accumulator
|
||||
/// }
|
||||
@@ -901,13 +973,17 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
|
||||
/// polynomial_size,
|
||||
/// glwe_dimension.to_glwe_size(),
|
||||
/// message_modulus as usize,
|
||||
/// ciphertext_modulus,
|
||||
/// delta,
|
||||
/// |x: u64| 2 * x,
|
||||
/// );
|
||||
///
|
||||
/// // Allocate the LweCiphertext to store the result of the PBS
|
||||
/// let mut pbs_multiplication_ct =
|
||||
/// LweCiphertext::new(0u64, big_lwe_sk.lwe_dimension().to_lwe_size());
|
||||
/// let mut pbs_multiplication_ct = LweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
/// println!("Computing PBS...");
|
||||
/// programmable_bootstrap_lwe_ciphertext(
|
||||
/// &lwe_ciphertext_in,
|
||||
@@ -949,6 +1025,12 @@ pub fn programmable_bootstrap_lwe_ciphertext<Scalar, InputCont, OutputCont, AccC
|
||||
AccCont: Container<Element = Scalar>,
|
||||
KeyCont: Container<Element = c64>,
|
||||
{
|
||||
assert_eq!(input.ciphertext_modulus(), output.ciphertext_modulus());
|
||||
assert_eq!(
|
||||
output.ciphertext_modulus(),
|
||||
accumulator.ciphertext_modulus()
|
||||
);
|
||||
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
|
||||
let fft = Fft::new(fourier_bsk.polynomial_size());
|
||||
@@ -1001,9 +1083,25 @@ pub fn programmable_bootstrap_lwe_ciphertext_mem_optimized<
|
||||
AccCont: Container<Element = Scalar>,
|
||||
KeyCont: Container<Element = c64>,
|
||||
{
|
||||
assert_eq!(
|
||||
input.ciphertext_modulus(),
|
||||
output.ciphertext_modulus(),
|
||||
"Mismatched moduli between input ({:?}) and output ({:?})",
|
||||
input.ciphertext_modulus(),
|
||||
output.ciphertext_modulus()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
accumulator.ciphertext_modulus(),
|
||||
output.ciphertext_modulus(),
|
||||
"Mismatched moduli between accumulator ({:?}) and output ({:?})",
|
||||
accumulator.ciphertext_modulus(),
|
||||
output.ciphertext_modulus()
|
||||
);
|
||||
|
||||
fourier_bsk.as_view().bootstrap(
|
||||
output.as_mut(),
|
||||
input.as_ref(),
|
||||
output.as_mut_view(),
|
||||
input.as_view(),
|
||||
accumulator.as_view(),
|
||||
fft,
|
||||
stack,
|
||||
@@ -1018,3 +1116,290 @@ pub fn programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
) -> Result<StackReq, SizeOverflow> {
|
||||
bootstrap_scratch::<Scalar>(glwe_size, polynomial_size, fft)
|
||||
}
|
||||
|
||||
/// Perform a programmable bootstrap given an input [`LWE ciphertext`](`LweCiphertext`), a
|
||||
/// look-up table passed as a [`GLWE ciphertext`](`GlweCiphertext`) and an [`LWE bootstrap
|
||||
/// key`](`LweBootstrapKey`) in the fourier domain using f128. The result is written in the provided
|
||||
/// output [`LWE ciphertext`](`LweCiphertext`).
|
||||
///
|
||||
/// If you want to manage the computation memory manually you can use
|
||||
/// [`programmable_bootstrap_lwe_ciphertext_mem_optimized`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define the parameters for a 4 bits message able to hold the doubled 2 bits message
|
||||
/// let small_lwe_dimension = LweDimension(742);
|
||||
/// let glwe_dimension = GlweDimension(1);
|
||||
/// let polynomial_size = PolynomialSize(2048);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.000007069849454709433 * 0.000007069849454709433);
|
||||
/// let glwe_modular_std_dev =
|
||||
/// StandardDev(0.00000000000000029403601535432533 * 0.00000000000000029403601535432533);
|
||||
/// let pbs_base_log = DecompositionBaseLog(23);
|
||||
/// let pbs_level = DecompositionLevelCount(1);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Request the best seeder possible, starting with hardware entropy sources and falling back to
|
||||
/// // /dev/random on Unix systems if enabled via cargo features
|
||||
/// let mut boxed_seeder = new_seeder();
|
||||
/// // Get a mutable reference to the seeder as a trait object from the Box returned by new_seeder
|
||||
/// let seeder = boxed_seeder.as_mut();
|
||||
///
|
||||
/// // Create a generator which uses a CSPRNG to generate secret keys
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create a generator which uses two CSPRNGs to generate public masks and secret encryption
|
||||
/// // noise
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
///
|
||||
/// println!("Generating keys...");
|
||||
///
|
||||
/// // Generate an LweSecretKey with binary coefficients
|
||||
/// let small_lwe_sk =
|
||||
/// LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// // Generate a GlweSecretKey with binary coefficients
|
||||
/// let glwe_sk =
|
||||
/// GlweSecretKey::generate_new_binary(glwe_dimension, polynomial_size, &mut secret_generator);
|
||||
///
|
||||
/// // Create a copy of the GlweSecretKey re-interpreted as an LweSecretKey
|
||||
/// let big_lwe_sk = glwe_sk.clone().into_lwe_secret_key();
|
||||
///
|
||||
/// // Generate the seeded bootstrapping key to show how to handle entity decompression,
|
||||
/// // we use the parallel variant for performance reason
|
||||
/// let std_bootstrapping_key = par_allocate_and_generate_new_seeded_lwe_bootstrap_key(
|
||||
/// &small_lwe_sk,
|
||||
/// &glwe_sk,
|
||||
/// pbs_base_log,
|
||||
/// pbs_level,
|
||||
/// glwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// seeder,
|
||||
/// );
|
||||
///
|
||||
/// // We decompress the bootstrapping key
|
||||
/// let std_bootstrapping_key: LweBootstrapKeyOwned<u128> =
|
||||
/// std_bootstrapping_key.decompress_into_lwe_bootstrap_key();
|
||||
///
|
||||
/// // Create the empty bootstrapping key in the Fourier domain
|
||||
/// let mut fourier_bsk = Fourier128LweBootstrapKey::new(
|
||||
/// std_bootstrapping_key.input_lwe_dimension(),
|
||||
/// std_bootstrapping_key.glwe_size(),
|
||||
/// std_bootstrapping_key.polynomial_size(),
|
||||
/// std_bootstrapping_key.decomposition_base_log(),
|
||||
/// std_bootstrapping_key.decomposition_level_count(),
|
||||
/// );
|
||||
///
|
||||
/// // Use the conversion function (a memory optimized version also exists but is more complicated
|
||||
/// // to use) to convert the standard bootstrapping key to the Fourier domain
|
||||
/// convert_standard_lwe_bootstrap_key_to_fourier_128(&std_bootstrapping_key, &mut fourier_bsk);
|
||||
/// // We don't need the standard bootstrapping key anymore
|
||||
/// drop(std_bootstrapping_key);
|
||||
///
|
||||
/// // Our 4 bits message space
|
||||
/// let message_modulus = 1u128 << 4;
|
||||
///
|
||||
/// // Our input message
|
||||
/// let input_message = 3u128;
|
||||
///
|
||||
/// // Delta used to encode 4 bits of message + a bit of padding on u128
|
||||
/// let delta = (1_u128 << 127) / message_modulus;
|
||||
///
|
||||
/// // Apply our encoding
|
||||
/// let plaintext = Plaintext(input_message * delta);
|
||||
///
|
||||
/// // Allocate a new LweCiphertext and encrypt our plaintext
|
||||
/// let lwe_ciphertext_in: LweCiphertextOwned<u128> = allocate_and_encrypt_new_lwe_ciphertext(
|
||||
/// &small_lwe_sk,
|
||||
/// plaintext,
|
||||
/// lwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Now we will use a PBS to compute a multiplication by 2, it is NOT the recommended way of
|
||||
/// // doing this operation in terms of performance as it's much more costly than a multiplication
|
||||
/// // with a cleartext, however it resets the noise in a ciphertext to a nominal level and allows
|
||||
/// // to evaluate arbitrary functions so depending on your use case it can be a better fit.
|
||||
///
|
||||
/// // Here we will define a helper function to generate an accumulator for a PBS
|
||||
/// fn generate_accumulator<F>(
|
||||
/// polynomial_size: PolynomialSize,
|
||||
/// glwe_size: GlweSize,
|
||||
/// message_modulus: usize,
|
||||
/// ciphertext_modulus: CiphertextModulus<u128>,
|
||||
/// delta: u128,
|
||||
/// f: F,
|
||||
/// ) -> GlweCiphertextOwned<u128>
|
||||
/// where
|
||||
/// F: Fn(u128) -> u128,
|
||||
/// {
|
||||
/// // N/(p/2) = size of each block, to correct noise from the input we introduce the notion of
|
||||
/// // box, which manages redundancy to yield a denoised value for several noisy values around
|
||||
/// // a true input value.
|
||||
/// let box_size = polynomial_size.0 / message_modulus;
|
||||
///
|
||||
/// // Create the accumulator
|
||||
/// let mut accumulator_u128 = vec![0_u128; polynomial_size.0];
|
||||
///
|
||||
/// // Fill each box with the encoded denoised value
|
||||
/// for i in 0..message_modulus {
|
||||
/// let index = i * box_size;
|
||||
/// accumulator_u128[index..index + box_size]
|
||||
/// .iter_mut()
|
||||
/// .for_each(|a| *a = f(i as u128) * delta);
|
||||
/// }
|
||||
///
|
||||
/// let half_box_size = box_size / 2;
|
||||
///
|
||||
/// // Negate the first half_box_size coefficients to manage negacyclicity and rotate
|
||||
/// for a_i in accumulator_u128[0..half_box_size].iter_mut() {
|
||||
/// *a_i = (*a_i).wrapping_neg();
|
||||
/// }
|
||||
///
|
||||
/// // Rotate the accumulator
|
||||
/// accumulator_u128.rotate_left(half_box_size);
|
||||
///
|
||||
/// let accumulator_plaintext = PlaintextList::from_container(accumulator_u128);
|
||||
///
|
||||
/// let accumulator = allocate_and_trivially_encrypt_new_glwe_ciphertext(
|
||||
/// glwe_size,
|
||||
/// &accumulator_plaintext,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// accumulator
|
||||
/// }
|
||||
///
|
||||
/// // Generate the accumulator for our multiplication by 2 using a simple closure
|
||||
/// let accumulator: GlweCiphertextOwned<u128> = generate_accumulator(
|
||||
/// polynomial_size,
|
||||
/// glwe_dimension.to_glwe_size(),
|
||||
/// message_modulus as usize,
|
||||
/// ciphertext_modulus,
|
||||
/// delta,
|
||||
/// |x: u128| 2 * x,
|
||||
/// );
|
||||
///
|
||||
/// // Allocate the LweCiphertext to store the result of the PBS
|
||||
/// let mut pbs_multiplication_ct = LweCiphertext::new(
|
||||
/// 0u128,
|
||||
/// big_lwe_sk.lwe_dimension().to_lwe_size(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
/// println!("Computing PBS...");
|
||||
/// programmable_bootstrap_f128_lwe_ciphertext(
|
||||
/// &lwe_ciphertext_in,
|
||||
/// &mut pbs_multiplication_ct,
|
||||
/// &accumulator,
|
||||
/// &fourier_bsk,
|
||||
/// );
|
||||
///
|
||||
/// // Decrypt the PBS multiplication result
|
||||
/// let pbs_multipliation_plaintext: Plaintext<u128> =
|
||||
/// decrypt_lwe_ciphertext(&big_lwe_sk, &pbs_multiplication_ct);
|
||||
///
|
||||
/// // Create a SignedDecomposer to perform the rounding of the decrypted plaintext
|
||||
/// // We pass a DecompositionBaseLog of 5 and a DecompositionLevelCount of 1 indicating we want to
|
||||
/// // round the 5 MSB, 1 bit of padding plus our 4 bits of message
|
||||
/// let signed_decomposer =
|
||||
/// SignedDecomposer::new(DecompositionBaseLog(5), DecompositionLevelCount(1));
|
||||
///
|
||||
/// // Round and remove our encoding
|
||||
/// let pbs_multiplication_result: u128 =
|
||||
/// signed_decomposer.closest_representable(pbs_multipliation_plaintext.0) / delta;
|
||||
///
|
||||
/// println!("Checking result...");
|
||||
/// assert_eq!(6, pbs_multiplication_result);
|
||||
/// println!(
|
||||
/// "Mulitplication via PBS result is correct! Expected 6, got {pbs_multiplication_result}"
|
||||
/// );
|
||||
/// ```
|
||||
pub fn programmable_bootstrap_f128_lwe_ciphertext<Scalar, InputCont, OutputCont, AccCont, KeyCont>(
|
||||
input: &LweCiphertext<InputCont>,
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
accumulator: &GlweCiphertext<AccCont>,
|
||||
fourier_bsk: &Fourier128LweBootstrapKey<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 = f64>,
|
||||
{
|
||||
assert_eq!(input.ciphertext_modulus(), output.ciphertext_modulus());
|
||||
assert_eq!(
|
||||
output.ciphertext_modulus(),
|
||||
accumulator.ciphertext_modulus()
|
||||
);
|
||||
|
||||
let mut buffers = ComputationBuffers::new();
|
||||
|
||||
let fft = Fft128::new(fourier_bsk.polynomial_size());
|
||||
let fft = fft.as_view();
|
||||
|
||||
buffers.resize(
|
||||
programmable_bootstrap_f128_lwe_ciphertext_mem_optimized_requirement::<Scalar>(
|
||||
fourier_bsk.glwe_size(),
|
||||
fourier_bsk.polynomial_size(),
|
||||
fft,
|
||||
)
|
||||
.unwrap()
|
||||
.unaligned_bytes_required(),
|
||||
);
|
||||
|
||||
let stack = buffers.stack();
|
||||
|
||||
programmable_bootstrap_f128_lwe_ciphertext_mem_optimized(
|
||||
input,
|
||||
output,
|
||||
accumulator,
|
||||
fourier_bsk,
|
||||
fft,
|
||||
stack,
|
||||
)
|
||||
}
|
||||
|
||||
/// Memory optimized version of [`programmable_bootstrap_f128_lwe_ciphertext`], the caller must
|
||||
/// provide a properly configured [`Fft128View`] object and a `PodStack` used as a memory buffer
|
||||
/// having a capacity at least as large as the result of
|
||||
/// [`programmable_bootstrap_f128_lwe_ciphertext_mem_optimized_requirement`].
|
||||
pub fn programmable_bootstrap_f128_lwe_ciphertext_mem_optimized<
|
||||
Scalar,
|
||||
InputCont,
|
||||
OutputCont,
|
||||
AccCont,
|
||||
KeyCont,
|
||||
>(
|
||||
input: &LweCiphertext<InputCont>,
|
||||
output: &mut LweCiphertext<OutputCont>,
|
||||
accumulator: &GlweCiphertext<AccCont>,
|
||||
fourier_bsk: &Fourier128LweBootstrapKey<KeyCont>,
|
||||
fft: Fft128View<'_>,
|
||||
stack: PodStack<'_>,
|
||||
) 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 = f64>,
|
||||
{
|
||||
fourier_bsk.bootstrap(output, input, accumulator, fft, stack);
|
||||
}
|
||||
|
||||
/// Return the required memory for [`programmable_bootstrap_f128_lwe_ciphertext_mem_optimized`].
|
||||
pub fn programmable_bootstrap_f128_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
fft: Fft128View<'_>,
|
||||
) -> Result<StackReq, SizeOverflow> {
|
||||
bootstrap_scratch_f128::<Scalar>(glwe_size, polynomial_size, fft)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ 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,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LwePublicKeyOwned<Scalar>
|
||||
where
|
||||
@@ -61,6 +62,7 @@ where
|
||||
Scalar::ZERO,
|
||||
lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
zero_encryption_count,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_lwe_public_key(lwe_secret_key, &mut pk, noise_parameters, generator);
|
||||
@@ -102,6 +104,7 @@ 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,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LwePublicKeyOwned<Scalar>
|
||||
where
|
||||
@@ -113,6 +116,7 @@ where
|
||||
Scalar::ZERO,
|
||||
lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
zero_encryption_count,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
par_generate_lwe_public_key(lwe_secret_key, &mut pk, noise_parameters, generator);
|
||||
@@ -167,6 +171,7 @@ pub fn allocate_and_generate_new_seeded_lwe_public_key<Scalar, InputKeyCont, Noi
|
||||
lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
zero_encryption_count: LwePublicKeyZeroEncryptionCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
noise_seeder: &mut NoiseSeeder,
|
||||
) -> SeededLwePublicKeyOwned<Scalar>
|
||||
where
|
||||
@@ -182,6 +187,7 @@ where
|
||||
CompressionSeed {
|
||||
seed: noise_seeder.seed(),
|
||||
},
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_seeded_lwe_public_key(lwe_secret_key, &mut pk, noise_parameters, noise_seeder);
|
||||
@@ -230,6 +236,7 @@ pub fn par_allocate_and_generate_new_seeded_lwe_public_key<Scalar, InputKeyCont,
|
||||
lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
zero_encryption_count: LwePublicKeyZeroEncryptionCount,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
noise_seeder: &mut NoiseSeeder,
|
||||
) -> SeededLwePublicKeyOwned<Scalar>
|
||||
where
|
||||
@@ -245,6 +252,7 @@ where
|
||||
CompressionSeed {
|
||||
seed: noise_seeder.seed(),
|
||||
},
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
par_generate_seeded_lwe_public_key(lwe_secret_key, &mut pk, noise_parameters, noise_seeder);
|
||||
|
||||
@@ -33,6 +33,7 @@ pub fn allocate_and_generate_new_circuit_bootstrap_lwe_pfpksk_list<
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LwePrivateFunctionalPackingKeyswitchKeyListOwned<Scalar>
|
||||
where
|
||||
@@ -41,6 +42,11 @@ where
|
||||
GlweKeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
ciphertext_modulus.is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
let mut cbs_pfpksk_list = LwePrivateFunctionalPackingKeyswitchKeyListOwned::new(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
@@ -51,6 +57,7 @@ where
|
||||
FunctionalPackingKeyswitchKeyCount(
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size().0,
|
||||
),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_circuit_bootstrap_lwe_pfpksk_list(
|
||||
@@ -98,6 +105,13 @@ pub fn generate_circuit_bootstrap_lwe_pfpksk_list<
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size().0
|
||||
);
|
||||
|
||||
assert!(
|
||||
output_cbs_pfpksk_list
|
||||
.ciphertext_modulus()
|
||||
.is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
let decomp_level_count = output_cbs_pfpksk_list.decomposition_level_count();
|
||||
|
||||
let gen_iter = generator
|
||||
@@ -158,6 +172,7 @@ pub fn par_allocate_and_generate_new_circuit_bootstrap_lwe_pfpksk_list<
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> LwePrivateFunctionalPackingKeyswitchKeyListOwned<Scalar>
|
||||
where
|
||||
@@ -166,6 +181,11 @@ where
|
||||
GlweKeyCont: Container<Element = Scalar> + Sync,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
ciphertext_modulus.is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
let mut cbs_pfpksk_list = LwePrivateFunctionalPackingKeyswitchKeyListOwned::new(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
@@ -176,6 +196,7 @@ where
|
||||
FunctionalPackingKeyswitchKeyCount(
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size().0,
|
||||
),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
par_generate_circuit_bootstrap_lwe_pfpksk_list(
|
||||
@@ -219,6 +240,13 @@ pub fn par_generate_circuit_bootstrap_lwe_pfpksk_list<
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size().0
|
||||
);
|
||||
|
||||
assert!(
|
||||
output_cbs_pfpksk_list
|
||||
.ciphertext_modulus()
|
||||
.is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
let decomp_level_count = output_cbs_pfpksk_list.decomposition_level_count();
|
||||
|
||||
let gen_iter = generator
|
||||
@@ -311,6 +339,16 @@ pub fn extract_bits_from_lwe_ciphertext_mem_optimized<
|
||||
BskCont: Container<Element = c64>,
|
||||
KSKCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(
|
||||
lwe_list_out.ciphertext_modulus(),
|
||||
lwe_in.ciphertext_modulus()
|
||||
);
|
||||
assert_eq!(lwe_in.ciphertext_modulus(), ksk.ciphertext_modulus());
|
||||
assert!(
|
||||
ksk.ciphertext_modulus().is_native_modulus(),
|
||||
"This operation only supports native moduli"
|
||||
);
|
||||
|
||||
extract_bits(
|
||||
lwe_list_out.as_mut_view(),
|
||||
lwe_in.as_view(),
|
||||
@@ -391,6 +429,7 @@ pub fn extract_bits_from_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_dimension = GlweDimension(1);
|
||||
/// let lwe_dimension = LweDimension(481);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// let var_small = Variance::from_variance(2f64.powf(-80.0));
|
||||
/// let var_big = Variance::from_variance(2f64.powf(-70.0));
|
||||
@@ -421,6 +460,7 @@ pub fn extract_bits_from_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
/// bsk_base_log,
|
||||
/// bsk_level_count,
|
||||
/// var_small,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -441,6 +481,7 @@ pub fn extract_bits_from_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
/// ksk_base_log,
|
||||
/// ksk_level_count,
|
||||
/// var_big,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -453,6 +494,7 @@ pub fn extract_bits_from_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
/// pfpksk_base_log,
|
||||
/// pfpksk_level_count,
|
||||
/// var_small,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -525,6 +567,7 @@ pub fn extract_bits_from_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
/// &lwe_big_sk,
|
||||
/// encoded_message,
|
||||
/// var_big,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
@@ -533,6 +576,7 @@ pub fn extract_bits_from_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
/// 0u64,
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// LweCiphertextCount(bits_to_extract.0),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// extract_bits_from_lwe_ciphertext_mem_optimized(
|
||||
@@ -568,6 +612,7 @@ pub fn extract_bits_from_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
/// 0u64,
|
||||
/// lwe_big_sk.lwe_dimension().to_lwe_size(),
|
||||
/// number_of_luts_and_output_vp_ciphertexts,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// circuit_bootstrap_boolean_vertical_packing_lwe_ciphertext_list_mem_optimized(
|
||||
@@ -627,6 +672,19 @@ pub fn circuit_bootstrap_boolean_vertical_packing_lwe_ciphertext_list_mem_optimi
|
||||
BskCont: Container<Element = c64>,
|
||||
PFPKSKCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(
|
||||
lwe_list_out.ciphertext_modulus(),
|
||||
lwe_list_in.ciphertext_modulus()
|
||||
);
|
||||
assert_eq!(
|
||||
lwe_list_in.ciphertext_modulus(),
|
||||
pfpksk_list.ciphertext_modulus()
|
||||
);
|
||||
assert!(
|
||||
pfpksk_list.ciphertext_modulus().is_native_modulus(),
|
||||
"This operation currently only supports native moduli"
|
||||
);
|
||||
|
||||
circuit_bootstrap_boolean_vertical_packing(
|
||||
big_lut_as_polynomial_list.as_view(),
|
||||
fourier_bsk.as_view(),
|
||||
|
||||
@@ -34,6 +34,9 @@ pub mod seeded_lwe_keyswitch_key_decompression;
|
||||
pub mod seeded_lwe_public_key_decompression;
|
||||
pub mod slice_algorithms;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
// 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_conversion::*;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Module with primitives pertaining to [`SeededGlweCiphertext`] decompression.
|
||||
|
||||
use crate::core_crypto::algorithms::slice_algorithms::slice_wrapping_scalar_mul_assign;
|
||||
use crate::core_crypto::commons::math::random::RandomGenerator;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
@@ -21,10 +22,27 @@ pub fn decompress_seeded_glwe_ciphertext_with_existing_generator<
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_glwe.ciphertext_modulus(),
|
||||
input_seeded_glwe.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededGlweCiphertext ({:?}) and output GlweCiphertext ({:?})",
|
||||
input_seeded_glwe.ciphertext_modulus(),
|
||||
output_glwe.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let (mut output_mask, mut output_body) = output_glwe.get_mut_mask_and_body();
|
||||
|
||||
let ciphertext_modulus = output_mask.ciphertext_modulus();
|
||||
|
||||
// generate a uniformly random mask
|
||||
generator.fill_slice_with_random_uniform(output_mask.as_mut());
|
||||
generator.fill_slice_with_random_uniform_custom_mod(output_mask.as_mut(), ciphertext_modulus);
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
slice_wrapping_scalar_mul_assign(
|
||||
output_mask.as_mut(),
|
||||
ciphertext_modulus.get_scaling_to_native_torus(),
|
||||
);
|
||||
}
|
||||
output_body
|
||||
.as_mut()
|
||||
.copy_from_slice(input_seeded_glwe.get_body().as_ref());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Module with primitives pertaining to [`SeededGlweCiphertextList`] decompression.
|
||||
|
||||
use crate::core_crypto::algorithms::slice_algorithms::slice_wrapping_scalar_mul_assign;
|
||||
use crate::core_crypto::commons::math::random::RandomGenerator;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
@@ -21,11 +22,29 @@ pub fn decompress_seeded_glwe_ciphertext_list_with_existing_generator<
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_list.ciphertext_modulus(),
|
||||
input_seeded_list.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededGlweCiphertextList ({:?}) and output GlweCiphertextList ({:?})",
|
||||
input_seeded_list.ciphertext_modulus(),
|
||||
output_list.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let ciphertext_modulus = output_list.ciphertext_modulus();
|
||||
|
||||
for (mut glwe_out, body_in) in output_list.iter_mut().zip(input_seeded_list.iter()) {
|
||||
let (mut output_mask, mut output_body) = glwe_out.get_mut_mask_and_body();
|
||||
|
||||
// generate a uniformly random mask
|
||||
generator.fill_slice_with_random_uniform(output_mask.as_mut());
|
||||
generator
|
||||
.fill_slice_with_random_uniform_custom_mod(output_mask.as_mut(), ciphertext_modulus);
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
slice_wrapping_scalar_mul_assign(
|
||||
output_mask.as_mut(),
|
||||
ciphertext_modulus.get_scaling_to_native_torus(),
|
||||
);
|
||||
}
|
||||
output_body.as_mut().copy_from_slice(body_in.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,15 @@ pub fn decompress_seeded_lwe_bootstrap_key_with_existing_generator<
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_bsk.ciphertext_modulus(),
|
||||
input_bsk.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededLweBootstrapKey ({:?}) and output LweBootstrapKey ({:?})",
|
||||
input_bsk.ciphertext_modulus(),
|
||||
output_bsk.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
decompress_seeded_ggsw_ciphertext_list_with_existing_generator(output_bsk, input_bsk, generator)
|
||||
}
|
||||
|
||||
@@ -36,6 +45,15 @@ pub fn decompress_seeded_lwe_bootstrap_key<Scalar, InputCont, OutputCont, Gen>(
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_bsk.ciphertext_modulus(),
|
||||
input_bsk.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededLweBootstrapKey ({:?}) and output LweBootstrapKey ({:?})",
|
||||
input_bsk.ciphertext_modulus(),
|
||||
output_bsk.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let mut generator = RandomGenerator::<Gen>::new(input_bsk.compression_seed().seed);
|
||||
decompress_seeded_lwe_bootstrap_key_with_existing_generator::<_, _, _, Gen>(
|
||||
output_bsk,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Module with primitives pertaining to [`SeededLweCiphertext`] decompression.
|
||||
|
||||
use crate::core_crypto::algorithms::slice_algorithms::slice_wrapping_scalar_mul_assign;
|
||||
use crate::core_crypto::commons::math::random::RandomGenerator;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
@@ -15,11 +16,27 @@ pub fn decompress_seeded_lwe_ciphertext_with_existing_generator<Scalar, OutputCo
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_lwe.ciphertext_modulus(),
|
||||
input_seeded_lwe.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededLweCiphertext ({:?}) and output LweCiphertext ({:?})",
|
||||
input_seeded_lwe.ciphertext_modulus(),
|
||||
output_lwe.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let ciphertext_modulus = output_lwe.ciphertext_modulus();
|
||||
let (mut output_mask, output_body) = output_lwe.get_mut_mask_and_body();
|
||||
|
||||
// generate a uniformly random mask
|
||||
generator.fill_slice_with_random_uniform(output_mask.as_mut());
|
||||
*output_body.0 = *input_seeded_lwe.get_body().0
|
||||
generator.fill_slice_with_random_uniform_custom_mod(output_mask.as_mut(), ciphertext_modulus);
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
slice_wrapping_scalar_mul_assign(
|
||||
output_mask.as_mut(),
|
||||
ciphertext_modulus.get_scaling_to_native_torus(),
|
||||
);
|
||||
}
|
||||
*output_body.data = *input_seeded_lwe.get_body().data;
|
||||
}
|
||||
|
||||
/// Decompress a [`SeededLweCiphertext`], without consuming it, into a standard
|
||||
@@ -32,6 +49,15 @@ pub fn decompress_seeded_lwe_ciphertext<Scalar, OutputCont, Gen>(
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_lwe.ciphertext_modulus(),
|
||||
input_seeded_lwe.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededLweCiphertext ({:?}) and output LweCiphertext ({:?})",
|
||||
input_seeded_lwe.ciphertext_modulus(),
|
||||
output_lwe.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let mut generator = RandomGenerator::<Gen>::new(input_seeded_lwe.compression_seed().seed);
|
||||
decompress_seeded_lwe_ciphertext_with_existing_generator::<_, _, Gen>(
|
||||
output_lwe,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Module with primitives pertaining to [`SeededLweCiphertextList`] decompression.
|
||||
|
||||
use crate::core_crypto::algorithms::slice_algorithms::slice_wrapping_scalar_mul_assign;
|
||||
use crate::core_crypto::commons::math::random::RandomGenerator;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
@@ -21,12 +22,30 @@ pub fn decompress_seeded_lwe_ciphertext_list_with_existing_generator<
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_list.ciphertext_modulus(),
|
||||
input_seeded_list.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededLweCiphertextList ({:?}) and output LweCiphertextList ({:?})",
|
||||
input_seeded_list.ciphertext_modulus(),
|
||||
output_list.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let ciphertext_modulus = output_list.ciphertext_modulus();
|
||||
|
||||
for (mut lwe_out, body_in) in output_list.iter_mut().zip(input_seeded_list.iter()) {
|
||||
let (mut output_mask, output_body) = lwe_out.get_mut_mask_and_body();
|
||||
|
||||
// generate a uniformly random mask
|
||||
generator.fill_slice_with_random_uniform(output_mask.as_mut());
|
||||
*output_body.0 = *body_in.0;
|
||||
generator
|
||||
.fill_slice_with_random_uniform_custom_mod(output_mask.as_mut(), ciphertext_modulus);
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
slice_wrapping_scalar_mul_assign(
|
||||
output_mask.as_mut(),
|
||||
ciphertext_modulus.get_scaling_to_native_torus(),
|
||||
);
|
||||
}
|
||||
*output_body.data = *body_in.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +60,15 @@ pub fn decompress_seeded_lwe_ciphertext_list<Scalar, InputCont, OutputCont, Gen>
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_list.ciphertext_modulus(),
|
||||
input_seeded_list.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededLweCiphertextList ({:?}) and output LweCiphertextList ({:?})",
|
||||
input_seeded_list.ciphertext_modulus(),
|
||||
output_list.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let mut generator = RandomGenerator::<Gen>::new(input_seeded_list.compression_seed().seed);
|
||||
decompress_seeded_lwe_ciphertext_list_with_existing_generator::<_, _, _, Gen>(
|
||||
output_list,
|
||||
|
||||
@@ -16,6 +16,15 @@ pub fn decompress_seeded_lwe_public_key<Scalar, InputCont, OutputCont, Gen>(
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
output_pk.ciphertext_modulus(),
|
||||
input_pk.ciphertext_modulus(),
|
||||
"Mismatched CiphertextModulus \
|
||||
between input SeededLwePublicKey ({:?}) and output LwePublicKey ({:?})",
|
||||
output_pk.ciphertext_modulus(),
|
||||
input_pk.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let mut generator = RandomGenerator::<Gen>::new(input_pk.compression_seed().seed);
|
||||
decompress_seeded_lwe_ciphertext_list_with_existing_generator::<_, _, _, Gen>(
|
||||
output_pk,
|
||||
|
||||
@@ -301,3 +301,11 @@ where
|
||||
lhs.iter_mut()
|
||||
.for_each(|lhs| *lhs = (*lhs).wrapping_mul(rhs));
|
||||
}
|
||||
|
||||
pub fn slice_wrapping_scalar_div_assign<Scalar>(lhs: &mut [Scalar], rhs: Scalar)
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
lhs.iter_mut()
|
||||
.for_each(|lhs| *lhs = (*lhs).wrapping_div(rhs));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user