mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-11 07:38:08 -05:00
Compare commits
58 Commits
0.2.0
...
trace_pack
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d07cef91b | ||
|
|
1c837fa6f0 | ||
|
|
1ec7e4762a | ||
|
|
20fb697d57 | ||
|
|
0429d56cf3 | ||
|
|
509bf3e284 | ||
|
|
b2fc1d5266 | ||
|
|
62d94dbee8 | ||
|
|
fbe911d7db | ||
|
|
ba72faf828 | ||
|
|
c387b9340f | ||
|
|
cbb7d30fb8 | ||
|
|
6e4a707eff | ||
|
|
06b700f904 | ||
|
|
cfbabf7480 | ||
|
|
291ed9026f | ||
|
|
610f0010b8 | ||
|
|
8b3d31ae8a | ||
|
|
98539aaa61 | ||
|
|
9a80a01dc3 | ||
|
|
ecf9d50058 | ||
|
|
65e4aab38d | ||
|
|
ac348870ba | ||
|
|
6adfcaa5f7 | ||
|
|
bc6bbe66d9 | ||
|
|
871d4aea17 | ||
|
|
f81376b762 | ||
|
|
64813bae18 | ||
|
|
16ce2a8a3f | ||
|
|
f018987eac | ||
|
|
20f6c5419b | ||
|
|
58b530f40b | ||
|
|
689ad195f3 | ||
|
|
4a1eda25d3 | ||
|
|
af936df064 | ||
|
|
0233a69ea6 | ||
|
|
f72a6ec835 | ||
|
|
25a2586eae | ||
|
|
c112a43a63 | ||
|
|
2813812380 | ||
|
|
84a6036789 | ||
|
|
658368d0b6 | ||
|
|
9368049adf | ||
|
|
5e8ca0b52c | ||
|
|
605cd5b3b0 | ||
|
|
4bfe9c22d4 | ||
|
|
1c0b36c672 | ||
|
|
7dccb01a8d | ||
|
|
7bff348367 | ||
|
|
74a5a278b6 | ||
|
|
426ced3295 | ||
|
|
7af5fcc7eb | ||
|
|
12d9947149 | ||
|
|
7c54896e68 | ||
|
|
04533dedfe | ||
|
|
d01be35557 | ||
|
|
9fc32e2f52 | ||
|
|
e1e78b8b9d |
2
.github/workflows/aws_tfhe_integer_tests.yml
vendored
2
.github/workflows/aws_tfhe_integer_tests.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
echo "Type: ${{ github.event.inputs.instance_type }}"
|
||||
echo "Request ID: ${{ github.event.inputs.request_id }}"
|
||||
|
||||
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
- name: Set up home
|
||||
run: |
|
||||
|
||||
2
.github/workflows/aws_tfhe_tests.yml
vendored
2
.github/workflows/aws_tfhe_tests.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
echo "Type: ${{ github.event.inputs.instance_type }}"
|
||||
echo "Request ID: ${{ github.event.inputs.request_id }}"
|
||||
|
||||
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
- name: Set up home
|
||||
run: |
|
||||
|
||||
40
.github/workflows/boolean_benchmark.yml
vendored
40
.github/workflows/boolean_benchmark.yml
vendored
@@ -5,19 +5,19 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: 'Instance ID'
|
||||
description: "Instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: 'Instance AMI ID'
|
||||
description: "Instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: 'Instance product type'
|
||||
description: "Instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: 'Action runner name'
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
description: "Slab request ID"
|
||||
type: string
|
||||
|
||||
env:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -57,9 +57,9 @@ jobs:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
make bench_boolean
|
||||
make AVX512_SUPPORT=ON bench_boolean
|
||||
|
||||
- name: Parse results
|
||||
run: |
|
||||
@@ -73,24 +73,8 @@ jobs:
|
||||
--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_boolean
|
||||
|
||||
- name: Parse AVX512 results
|
||||
run: |
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--name-suffix avx512 \
|
||||
--walk-subdirs \
|
||||
--throughput \
|
||||
--append-results
|
||||
--throughput
|
||||
|
||||
- name: Measure key sizes
|
||||
run: |
|
||||
@@ -101,7 +85,7 @@ jobs:
|
||||
python3 ./ci/benchmark_parser.py tfhe/boolean_key_sizes.csv ${{ env.RESULTS_FILENAME }} \
|
||||
--key-sizes \
|
||||
--append-results
|
||||
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
with:
|
||||
@@ -109,7 +93,7 @@ jobs:
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
@@ -117,8 +101,6 @@ jobs:
|
||||
|
||||
- name: Send data to Slab
|
||||
shell: bash
|
||||
env:
|
||||
COMPRESSED_RESULTS : ${{ env.RESULTS_FILENAME }}.gz
|
||||
run: |
|
||||
echo "Computing HMac on results file"
|
||||
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
|
||||
|
||||
2
.github/workflows/cargo_build.yml
vendored
2
.github/workflows/cargo_build.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
- name: Run pcc checks
|
||||
run: |
|
||||
|
||||
36
.github/workflows/integer_benchmark.yml
vendored
36
.github/workflows/integer_benchmark.yml
vendored
@@ -5,19 +5,19 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: 'Instance ID'
|
||||
description: "Instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: 'Instance AMI ID'
|
||||
description: "Instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: 'Instance product type'
|
||||
description: "Instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: 'Action runner name'
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
description: "Slab request ID"
|
||||
type: string
|
||||
|
||||
env:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -57,9 +57,9 @@ jobs:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
make bench_integer
|
||||
make AVX512_SUPPORT=ON bench_integer
|
||||
|
||||
- name: Parse results
|
||||
run: |
|
||||
@@ -73,24 +73,8 @@ jobs:
|
||||
--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
|
||||
--throughput
|
||||
|
||||
- name: Upload parsed results artifact
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||
@@ -99,7 +83,7 @@ jobs:
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
2
.github/workflows/m1_tests.yml
vendored
2
.github/workflows/m1_tests.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ["self-hosted", "m1mac"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
- name: Install latest stable
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af
|
||||
|
||||
52
.github/workflows/make_release.yml
vendored
Normal file
52
.github/workflows/make_release.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# Publish new release of tfhe-rs on various platform.
|
||||
name: Publish release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: "Dry-run"
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
publish_release:
|
||||
name: Publish Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Publish crate.io package
|
||||
env:
|
||||
CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
DRY_RUN: ${{ inputs.dry_run && '--dry-run' || '' }}
|
||||
run: |
|
||||
cargo publish -p tfhe --token ${{ env.CRATES_TOKEN }} ${{ env.DRY_RUN }}
|
||||
|
||||
- name: Build web package
|
||||
run: |
|
||||
make build_web_js_api
|
||||
|
||||
- name: Publish web package
|
||||
uses: JS-DevTools/npm-publish@541aa6b21b4a1e9990c95a92c21adc16b35e9551
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: tfhe/pkg/package.json
|
||||
dry-run: ${{ inputs.dry_run }}
|
||||
|
||||
- name: Build Node package
|
||||
run: |
|
||||
rm -rf tfhe/pkg
|
||||
|
||||
make build_node_js_api
|
||||
sed -i 's/"tfhe"/"node-tfhe"/g' tfhe/pkg/package.json
|
||||
|
||||
- name: Publish Node package
|
||||
uses: JS-DevTools/npm-publish@541aa6b21b4a1e9990c95a92c21adc16b35e9551
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: tfhe/pkg/package.json
|
||||
dry-run: ${{ inputs.dry_run }}
|
||||
15
.github/workflows/pbs_benchmark.yml
vendored
15
.github/workflows/pbs_benchmark.yml
vendored
@@ -5,22 +5,21 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: 'Instance ID'
|
||||
description: "Instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: 'Instance AMI ID'
|
||||
description: "Instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: 'Instance product type'
|
||||
description: "Instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: 'Action runner name'
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
description: "Slab request ID"
|
||||
type: string
|
||||
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
|
||||
@@ -43,7 +42,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -84,7 +83,7 @@ jobs:
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
37
.github/workflows/shortint_benchmark.yml
vendored
37
.github/workflows/shortint_benchmark.yml
vendored
@@ -5,22 +5,21 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
instance_id:
|
||||
description: 'Instance ID'
|
||||
description: "Instance ID"
|
||||
type: string
|
||||
instance_image_id:
|
||||
description: 'Instance AMI ID'
|
||||
description: "Instance AMI ID"
|
||||
type: string
|
||||
instance_type:
|
||||
description: 'Instance product type'
|
||||
description: "Instance product type"
|
||||
type: string
|
||||
runner_name:
|
||||
description: 'Action runner name'
|
||||
description: "Action runner name"
|
||||
type: string
|
||||
request_id:
|
||||
description: 'Slab request ID'
|
||||
description: "Slab request ID"
|
||||
type: string
|
||||
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
|
||||
@@ -43,7 +42,7 @@ jobs:
|
||||
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout tfhe-rs repo with tags
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -58,9 +57,9 @@ jobs:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Run benchmarks
|
||||
- name: Run benchmarks with AVX512
|
||||
run: |
|
||||
make bench_shortint
|
||||
make AVX512_SUPPORT=ON bench_shortint
|
||||
|
||||
- name: Parse results
|
||||
run: |
|
||||
@@ -74,24 +73,8 @@ jobs:
|
||||
--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_shortint
|
||||
|
||||
- name: Parse AVX512 results
|
||||
run: |
|
||||
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
|
||||
--hardware ${{ inputs.instance_type }} \
|
||||
--walk-subdirs \
|
||||
--name-suffix avx512 \
|
||||
--throughput \
|
||||
--append-results
|
||||
--throughput
|
||||
|
||||
- name: Measure key sizes
|
||||
run: |
|
||||
@@ -110,7 +93,7 @@ jobs:
|
||||
path: ${{ env.RESULTS_FILENAME }}
|
||||
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
5
.github/workflows/start_benchmarks.yml
vendored
5
.github/workflows/start_benchmarks.yml
vendored
@@ -4,18 +4,19 @@ name: Start all benchmarks
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- "main"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
start-benchmarks:
|
||||
if: ${{ (github.event_name == 'push' && github.repository == 'zama-ai/tfhe-rs') || github.event_name == 'workflow_dispatch' }}
|
||||
strategy:
|
||||
matrix:
|
||||
command: [boolean_bench, shortint_bench, integer_bench, pbs_bench]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Slab repo
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
repository: zama-ai/slab
|
||||
path: slab
|
||||
|
||||
2
.github/workflows/sync_on_push.yml
vendored
2
.github/workflows/sync_on_push.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Save repo
|
||||
|
||||
20
Makefile
20
Makefile
@@ -7,7 +7,7 @@ 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
|
||||
MIN_RUST_VERSION:=$(shell grep rust-version tfhe/Cargo.toml | cut -d '=' -f 2 | xargs)
|
||||
AVX512_SUPPORT?=OFF
|
||||
WASM_RUSTFLAGS:=
|
||||
BIG_TESTS_INSTANCE?=FALSE
|
||||
@@ -52,6 +52,12 @@ install_cargo_nextest: install_rs_build_toolchain
|
||||
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-nextest --locked || \
|
||||
( echo "Unable to install cargo nextest, unknown error." && exit 1 )
|
||||
|
||||
.PHONY: install_wasm_pack # Install wasm-pack to build JS packages
|
||||
install_wasm_pack: install_rs_build_toolchain
|
||||
@wasm-pack --version > /dev/null 2>&1 || \
|
||||
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install wasm-pack || \
|
||||
( echo "Unable to install cargo wasm-pack, unknown error." && exit 1 )
|
||||
|
||||
.PHONY: fmt # Format rust code
|
||||
fmt: install_rs_check_toolchain
|
||||
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt
|
||||
@@ -99,10 +105,10 @@ clippy_c_api: install_rs_check_toolchain
|
||||
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint and the js wasm API
|
||||
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint, integer and the js wasm API
|
||||
clippy_js_wasm_api: install_rs_check_toolchain
|
||||
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
|
||||
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api \
|
||||
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api \
|
||||
-p tfhe -- --no-deps -D warnings
|
||||
|
||||
.PHONY: clippy_tasks # Run clippy lints on helper tasks crate.
|
||||
@@ -174,18 +180,18 @@ build_c_api: install_rs_check_toolchain
|
||||
-p tfhe
|
||||
|
||||
.PHONY: build_web_js_api # Build the js API targeting the web browser
|
||||
build_web_js_api: install_rs_build_toolchain
|
||||
build_web_js_api: install_rs_build_toolchain install_wasm_pack
|
||||
cd tfhe && \
|
||||
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
|
||||
wasm-pack build --release --target=web \
|
||||
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
|
||||
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api
|
||||
|
||||
.PHONY: build_node_js_api # Build the js API targeting nodejs
|
||||
build_node_js_api: install_rs_build_toolchain
|
||||
build_node_js_api: install_rs_build_toolchain install_wasm_pack
|
||||
cd tfhe && \
|
||||
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
|
||||
wasm-pack build --release --target=nodejs \
|
||||
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
|
||||
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api
|
||||
|
||||
.PHONY: test_core_crypto # Run the tests of the core_crypto module including experimental ones
|
||||
test_core_crypto: install_rs_build_toolchain install_rs_check_toolchain
|
||||
|
||||
@@ -58,7 +58,7 @@ tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64"]
|
||||
Note: aarch64-based machines are not yet supported for Windows as it's currently missing an entropy source to be able to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs
|
||||
|
||||
Note that when running code that uses `tfhe-rs`, it is highly recommended
|
||||
to run in release mode with cargo`s `--release` flag to have the best performances possible,
|
||||
to run in release mode with cargo's `--release` flag to have the best performances possible,
|
||||
eg: `cargo run --release`.
|
||||
|
||||
Here is a full example evaluating a Boolean circuit:
|
||||
@@ -68,7 +68,7 @@ use tfhe::boolean::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let (mut client_key, mut server_key) = gen_keys();
|
||||
let (client_key, server_key) = gen_keys();
|
||||
|
||||
// We use the client secret key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(true);
|
||||
@@ -132,7 +132,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
fn main() {
|
||||
// We create keys to create 16 bits integers
|
||||
// using 8 blocks of 2 bits
|
||||
let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, 8);
|
||||
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, 8);
|
||||
|
||||
let clear_a = 2382u16;
|
||||
let clear_b = 29374u16;
|
||||
|
||||
@@ -40,7 +40,7 @@ parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true',
|
||||
parser.add_argument('--key-sizes', dest='key_sizes', action='store_true',
|
||||
help='Parse only the results regarding keys size measurements')
|
||||
parser.add_argument('--throughput', dest='throughput', action='store_true',
|
||||
help='Compute and append number of operations per millisecond and'
|
||||
help='Compute and append number of operations per second and'
|
||||
'operations per dollar')
|
||||
parser.add_argument('--backend', dest='backend', default='cpu',
|
||||
help='Backend on which benchmarks have run')
|
||||
@@ -55,7 +55,7 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
|
||||
:param directory: path to directory that contains raw results as :class:`pathlib.Path`
|
||||
:param walk_subdirs: traverse results subdirectories if parameters changes for benchmark case.
|
||||
:param name_suffix: a :class:`str` suffix to apply to each test name found
|
||||
:param compute_throughput: compute number of operations per millisecond and operations per
|
||||
:param compute_throughput: compute number of operations per second and operations per
|
||||
dollar
|
||||
:param hardware_hourly_cost: hourly cost of the hardware used in dollar
|
||||
|
||||
@@ -106,11 +106,11 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
|
||||
)
|
||||
|
||||
if stat_name == "mean" and compute_throughput:
|
||||
test_suffix = "ops-per-ms"
|
||||
test_suffix = "ops-per-sec"
|
||||
test_name_parts.append(test_suffix)
|
||||
result_values.append(
|
||||
_create_point(
|
||||
compute_ops_per_millisecond(value),
|
||||
compute_ops_per_second(value),
|
||||
"_".join(test_name_parts),
|
||||
bench_class,
|
||||
"throughput",
|
||||
@@ -242,15 +242,15 @@ def compute_ops_per_dollar(data_point, product_hourly_cost):
|
||||
return ONE_HOUR_IN_NANOSECONDS / (product_hourly_cost * data_point)
|
||||
|
||||
|
||||
def compute_ops_per_millisecond(data_point):
|
||||
def compute_ops_per_second(data_point):
|
||||
"""
|
||||
Compute numbers of operations per millisecond for a given ``data_point``.
|
||||
Compute numbers of operations per second for a given ``data_point``.
|
||||
|
||||
:param data_point: timing value measured during benchmark in nanoseconds
|
||||
|
||||
:return: number of operations per millisecond
|
||||
:return: number of operations per second
|
||||
"""
|
||||
return 1E6 / data_point
|
||||
return 1E9 / data_point
|
||||
|
||||
|
||||
def _parse_file_to_json(directory, filename):
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
|
||||
set -e
|
||||
|
||||
CURR_DIR="$(dirname "$0")"
|
||||
REL_CARGO_TOML_PATH="${CURR_DIR}/../tfhe/Cargo.toml"
|
||||
MIN_RUST_VERSION="$(grep rust-version "${REL_CARGO_TOML_PATH}" | cut -d '=' -f 2 | xargs)"
|
||||
|
||||
function usage() {
|
||||
echo "$0: check minimum cargo version"
|
||||
echo
|
||||
echo "--help Print this message"
|
||||
echo "--rust-toolchain The toolchain to check the version for with leading"
|
||||
echo "--min-rust-version Check toolchain version is >= to this version, default is 1.65"
|
||||
echo "--min-rust-version Check toolchain version is >= to this version, default is ${MIN_RUST_VERSION}"
|
||||
echo
|
||||
}
|
||||
|
||||
RUST_TOOLCHAIN=""
|
||||
# We set the default rust version 1.65 which is the minimum version required for stable GATs
|
||||
MIN_RUST_VERSION="1.65"
|
||||
|
||||
while [ -n "$1" ]
|
||||
do
|
||||
|
||||
@@ -6,7 +6,7 @@ THIS_SCRIPT_NAME="$(basename "$0")"
|
||||
|
||||
TMP_FILE="$(mktemp)"
|
||||
|
||||
COUNT="$(git grep -rniI "thfe" . | grep -v "${THIS_SCRIPT_NAME}" | \
|
||||
COUNT="$(git grep -rniI "thfe\|tfhr\|thfr" . | grep -v "${THIS_SCRIPT_NAME}" | \
|
||||
tee "${TMP_FILE}" | wc -l | tr -d '[:space:]')"
|
||||
|
||||
cat "${TMP_FILE}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tfhe"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
|
||||
@@ -11,14 +11,13 @@ license = "BSD-3-Clause-Clear"
|
||||
description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
|
||||
build = "build.rs"
|
||||
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt", "/js_on_wasm_tests/"]
|
||||
rust-version = "1.65"
|
||||
rust-version = "1.67"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
kolmogorov_smirnov = "1.1.0"
|
||||
paste = "1.0.7"
|
||||
lazy_static = { version = "1.4.0" }
|
||||
criterion = "0.4.0"
|
||||
@@ -29,6 +28,8 @@ bincode = "1.3.3"
|
||||
fs2 = { version = "0.4.3" }
|
||||
itertools = "0.10.5"
|
||||
num_cpus = "1.15"
|
||||
# For erf and normality test
|
||||
libm = "0.2.6"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = { version = "0.24.3", optional = true }
|
||||
@@ -89,6 +90,7 @@ __wasm_api = [
|
||||
]
|
||||
boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
|
||||
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
|
||||
integer-client-js-wasm-api = ["integer", "__wasm_api"]
|
||||
|
||||
nightly-avx512 = ["concrete-fft/nightly", "pulp/nightly"]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#[path = "../utilities.rs"]
|
||||
mod utilities;
|
||||
use crate::utilities::{write_to_json, OperatorType};
|
||||
use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use tfhe::boolean::client_key::ClientKey;
|
||||
@@ -16,6 +16,24 @@ criterion_group!(
|
||||
|
||||
criterion_main!(gates_benches);
|
||||
|
||||
/// Helper function to write boolean benchmarks parameters to disk in JSON format.
|
||||
pub fn write_to_json_boolean<T: Into<CryptoParametersRecord>>(
|
||||
bench_id: &str,
|
||||
params: T,
|
||||
params_alias: impl Into<String>,
|
||||
display_name: impl Into<String>,
|
||||
) {
|
||||
write_to_json(
|
||||
bench_id,
|
||||
params,
|
||||
params_alias,
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
1,
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
|
||||
// Put all `bench_function` in one place
|
||||
// so the keygen is only run once per parameters saving time.
|
||||
fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
@@ -28,35 +46,33 @@ fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
|
||||
let ct2 = cks.encrypt(false);
|
||||
let ct3 = cks.encrypt(true);
|
||||
|
||||
let operator = OperatorType::Atomic;
|
||||
|
||||
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);
|
||||
write_to_json_boolean(&id, params, parameter_name, "and");
|
||||
|
||||
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);
|
||||
write_to_json_boolean(&id, params, parameter_name, "nand");
|
||||
|
||||
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);
|
||||
write_to_json_boolean(&id, params, parameter_name, "or");
|
||||
|
||||
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);
|
||||
write_to_json_boolean(&id, params, parameter_name, "xor");
|
||||
|
||||
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);
|
||||
write_to_json_boolean(&id, params, parameter_name, "xnor");
|
||||
|
||||
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);
|
||||
write_to_json_boolean(&id, params, parameter_name, "not");
|
||||
|
||||
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);
|
||||
write_to_json_boolean(&id, params, parameter_name, "mux");
|
||||
}
|
||||
|
||||
fn bench_default_parameters(c: &mut Criterion) {
|
||||
|
||||
@@ -7,9 +7,9 @@ use tfhe::boolean::parameters::{BooleanParameters, DEFAULT_PARAMETERS, TFHE_LIB_
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
use tfhe::shortint::keycache::NamedParam;
|
||||
use tfhe::shortint::parameters::*;
|
||||
use tfhe::shortint::Parameters;
|
||||
use tfhe::shortint::PBSParameters;
|
||||
|
||||
const SHORTINT_BENCH_PARAMS: [Parameters; 15] = [
|
||||
const SHORTINT_BENCH_PARAMS: [PBSParameters; 15] = [
|
||||
PARAM_MESSAGE_1_CARRY_0,
|
||||
PARAM_MESSAGE_1_CARRY_1,
|
||||
PARAM_MESSAGE_2_CARRY_0,
|
||||
@@ -66,17 +66,55 @@ fn multi_bit_benchmark_parameters<Scalar: Numeric>(
|
||||
) -> Vec<(String, (CryptoParametersRecord, LweBskGroupingFactor))> {
|
||||
if Scalar::BITS == 64 {
|
||||
vec![
|
||||
(
|
||||
"2_bits_multi_bit_group_2".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(764)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.000006025673585415336)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(18)),
|
||||
pbs_level: Some(DecompositionLevelCount(1)),
|
||||
glwe_dimension: Some(GlweDimension(3)),
|
||||
glwe_modular_std_dev: Some(StandardDev(0.0000000000039666089171633006)),
|
||||
polynomial_size: Some(PolynomialSize(1 << 9)),
|
||||
message_modulus: Some(1),
|
||||
carry_modulus: Some(1),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(2),
|
||||
),
|
||||
),
|
||||
(
|
||||
"2_bits_multi_bit_group_3".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(765)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.000005915594083804978)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(18)),
|
||||
pbs_level: Some(DecompositionLevelCount(1)),
|
||||
glwe_dimension: Some(GlweDimension(3)),
|
||||
glwe_modular_std_dev: Some(StandardDev(0.0000000000039666089171633006)),
|
||||
polynomial_size: Some(PolynomialSize(1 << 9)),
|
||||
message_modulus: Some(1),
|
||||
carry_modulus: Some(1),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(3),
|
||||
),
|
||||
),
|
||||
(
|
||||
"4_bits_multi_bit_group_2".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(788)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.000003871078133364534)),
|
||||
lwe_dimension: Some(LweDimension(818)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.000002226459789930014)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(22)),
|
||||
pbs_level: Some(DecompositionLevelCount(1)),
|
||||
glwe_dimension: Some(GlweDimension(2)),
|
||||
glwe_dimension: Some(GlweDimension(1)),
|
||||
glwe_modular_std_dev: Some(StandardDev(0.0000000000000003152931493498455)),
|
||||
polynomial_size: Some(PolynomialSize(1024)),
|
||||
polynomial_size: Some(PolynomialSize(1 << 11)),
|
||||
message_modulus: Some(2),
|
||||
carry_modulus: Some(2),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(2),
|
||||
@@ -86,13 +124,95 @@ fn multi_bit_benchmark_parameters<Scalar: Numeric>(
|
||||
"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)),
|
||||
lwe_dimension: Some(LweDimension(888)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.000002226459789930014)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(21)),
|
||||
pbs_level: Some(DecompositionLevelCount(1)),
|
||||
glwe_dimension: Some(GlweDimension(2)),
|
||||
glwe_dimension: Some(GlweDimension(1)),
|
||||
glwe_modular_std_dev: Some(StandardDev(0.0000000000000003152931493498455)),
|
||||
polynomial_size: Some(PolynomialSize(1024)),
|
||||
polynomial_size: Some(PolynomialSize(1 << 11)),
|
||||
message_modulus: Some(2),
|
||||
carry_modulus: Some(2),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(3),
|
||||
),
|
||||
),
|
||||
(
|
||||
"6_bits_multi_bit_group_2".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(922)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.0000003272369292345697)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(14)),
|
||||
pbs_level: Some(DecompositionLevelCount(2)),
|
||||
glwe_dimension: Some(GlweDimension(1)),
|
||||
glwe_modular_std_dev: Some(StandardDev(
|
||||
0.0000000000000000002168404344971009,
|
||||
)),
|
||||
polynomial_size: Some(PolynomialSize(1 << 13)),
|
||||
message_modulus: Some(3),
|
||||
carry_modulus: Some(3),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(2),
|
||||
),
|
||||
),
|
||||
(
|
||||
"6_bits_multi_bit_group_3".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(972)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.00000013016688349592805)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(14)),
|
||||
pbs_level: Some(DecompositionLevelCount(2)),
|
||||
glwe_dimension: Some(GlweDimension(1)),
|
||||
glwe_modular_std_dev: Some(StandardDev(
|
||||
0.0000000000000000002168404344971009,
|
||||
)),
|
||||
polynomial_size: Some(PolynomialSize(1 << 13)),
|
||||
message_modulus: Some(3),
|
||||
carry_modulus: Some(3),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(3),
|
||||
),
|
||||
),
|
||||
(
|
||||
"8_bits_multi_bit_group_2".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(1052)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.000000029779789543501806)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(14)),
|
||||
pbs_level: Some(DecompositionLevelCount(2)),
|
||||
glwe_dimension: Some(GlweDimension(1)),
|
||||
glwe_modular_std_dev: Some(StandardDev(
|
||||
0.0000000000000000002168404344971009,
|
||||
)),
|
||||
polynomial_size: Some(PolynomialSize(1 << 15)),
|
||||
message_modulus: Some(4),
|
||||
carry_modulus: Some(4),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(2),
|
||||
),
|
||||
),
|
||||
(
|
||||
"8_bits_multi_bit_group_3".to_string(),
|
||||
(
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(LweDimension(1098)),
|
||||
lwe_modular_std_dev: Some(StandardDev(0.000000012752307213087621)),
|
||||
pbs_base_log: Some(DecompositionBaseLog(14)),
|
||||
pbs_level: Some(DecompositionLevelCount(2)),
|
||||
glwe_dimension: Some(GlweDimension(1)),
|
||||
glwe_modular_std_dev: Some(StandardDev(
|
||||
0.0000000000000000002168404344971009,
|
||||
)),
|
||||
polynomial_size: Some(PolynomialSize(1 << 15)),
|
||||
message_modulus: Some(4),
|
||||
carry_modulus: Some(4),
|
||||
..Default::default()
|
||||
},
|
||||
LweBskGroupingFactor(3),
|
||||
@@ -195,7 +315,16 @@ fn mem_optimized_pbs<Scalar: UnsignedTorus + CastInto<usize>>(c: &mut Criterion)
|
||||
});
|
||||
}
|
||||
|
||||
write_to_json(&id, *params, name, "pbs", &OperatorType::Atomic);
|
||||
let bit_size = (params.message_modulus.unwrap_or(2) as u32).ilog2();
|
||||
write_to_json(
|
||||
&id,
|
||||
*params,
|
||||
name,
|
||||
"pbs",
|
||||
&OperatorType::Atomic,
|
||||
bit_size,
|
||||
vec![bit_size],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,13 +400,21 @@ fn multi_bit_pbs<Scalar: UnsignedTorus + CastInto<usize> + CastFrom<usize> + Syn
|
||||
&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)),
|
||||
ThreadCount(10),
|
||||
);
|
||||
black_box(&mut out_pbs_ct);
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(&id, *params, name, "pbs", &OperatorType::Atomic);
|
||||
let bit_size = params.message_modulus.unwrap().ilog2();
|
||||
write_to_json(
|
||||
&id,
|
||||
*params,
|
||||
name,
|
||||
"pbs",
|
||||
&OperatorType::Atomic,
|
||||
bit_size,
|
||||
vec![bit_size],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ use tfhe::shortint::parameters::{
|
||||
/// in radix decomposition
|
||||
struct ParamsAndNumBlocksIter {
|
||||
params_and_bit_sizes:
|
||||
itertools::Product<IntoIter<tfhe::shortint::Parameters, 1>, IntoIter<usize, 7>>,
|
||||
itertools::Product<IntoIter<tfhe::shortint::PBSParameters, 1>, IntoIter<usize, 7>>,
|
||||
}
|
||||
|
||||
impl Default for ParamsAndNumBlocksIter {
|
||||
fn default() -> Self {
|
||||
// FIXME One set of parameter is tested since we want to benchmark only quickest operations.
|
||||
const PARAMS: [tfhe::shortint::Parameters; 1] = [
|
||||
const PARAMS: [tfhe::shortint::PBSParameters; 1] = [
|
||||
PARAM_MESSAGE_2_CARRY_2,
|
||||
// PARAM_MESSAGE_3_CARRY_3,
|
||||
// PARAM_MESSAGE_4_CARRY_4,
|
||||
@@ -42,7 +42,7 @@ impl Default for ParamsAndNumBlocksIter {
|
||||
}
|
||||
}
|
||||
impl Iterator for ParamsAndNumBlocksIter {
|
||||
type Item = (tfhe::shortint::Parameters, usize, usize);
|
||||
type Item = (tfhe::shortint::PBSParameters, usize, usize);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (param, bit_size) = self.params_and_bit_sizes.next()?;
|
||||
@@ -119,6 +119,8 @@ fn bench_server_key_binary_function_dirty_inputs<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus.0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -177,6 +179,8 @@ fn bench_server_key_binary_function_clean_inputs<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus.0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -246,6 +250,8 @@ fn bench_server_key_unary_function_dirty_inputs<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus.0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -300,6 +306,8 @@ fn bench_server_key_unary_function_clean_inputs<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus.0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -367,6 +375,8 @@ fn bench_server_key_binary_scalar_function_dirty_inputs<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus.0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -421,6 +431,8 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
bit_size as u32,
|
||||
vec![param.message_modulus.0.ilog2(); num_block],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -573,6 +585,14 @@ define_server_key_bench_scalar_fn!(
|
||||
define_server_key_bench_scalar_default_fn!(method_name: scalar_add_parallelized, display_name: add);
|
||||
define_server_key_bench_scalar_default_fn!(method_name: scalar_sub_parallelized, display_name: sub);
|
||||
define_server_key_bench_scalar_default_fn!(method_name: scalar_mul_parallelized, display_name: mul);
|
||||
define_server_key_bench_scalar_default_fn!(
|
||||
method_name: scalar_left_shift_parallelized,
|
||||
display_name: left_shift
|
||||
);
|
||||
define_server_key_bench_scalar_default_fn!(
|
||||
method_name: scalar_right_shift_parallelized,
|
||||
display_name: right_shift
|
||||
);
|
||||
|
||||
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_add, display_name: add);
|
||||
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_sub, display_name: sub);
|
||||
@@ -719,6 +739,8 @@ criterion_group!(
|
||||
scalar_add_parallelized,
|
||||
scalar_sub_parallelized,
|
||||
scalar_mul_parallelized,
|
||||
scalar_left_shift_parallelized,
|
||||
scalar_right_shift_parallelized,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
@@ -778,6 +800,8 @@ criterion_group!(
|
||||
scalar_add_parallelized,
|
||||
scalar_sub_parallelized,
|
||||
scalar_mul_parallelized,
|
||||
scalar_left_shift_parallelized,
|
||||
scalar_right_shift_parallelized,
|
||||
);
|
||||
|
||||
criterion_main!(
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::utilities::{write_to_json, OperatorType};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use tfhe::shortint::keycache::NamedParam;
|
||||
use tfhe::shortint::parameters::*;
|
||||
use tfhe::shortint::{CiphertextBig, Parameters, ServerKey};
|
||||
use tfhe::shortint::{CiphertextBig, PBSParameters, ServerKey, ShortintParameterSet};
|
||||
|
||||
use rand::Rng;
|
||||
use tfhe::shortint::keycache::KEY_CACHE;
|
||||
@@ -13,14 +13,14 @@ use tfhe::shortint::keycache::KEY_CACHE;
|
||||
use tfhe::shortint::keycache::KEY_CACHE_WOPBS;
|
||||
use tfhe::shortint::parameters::parameters_wopbs::WOPBS_PARAM_MESSAGE_4_NORM2_6;
|
||||
|
||||
const SERVER_KEY_BENCH_PARAMS: [Parameters; 4] = [
|
||||
const SERVER_KEY_BENCH_PARAMS: [PBSParameters; 4] = [
|
||||
PARAM_MESSAGE_1_CARRY_1,
|
||||
PARAM_MESSAGE_2_CARRY_2,
|
||||
PARAM_MESSAGE_3_CARRY_3,
|
||||
PARAM_MESSAGE_4_CARRY_4,
|
||||
];
|
||||
|
||||
const SERVER_KEY_BENCH_PARAMS_EXTENDED: [Parameters; 15] = [
|
||||
const SERVER_KEY_BENCH_PARAMS_EXTENDED: [PBSParameters; 15] = [
|
||||
PARAM_MESSAGE_1_CARRY_0,
|
||||
PARAM_MESSAGE_1_CARRY_1,
|
||||
PARAM_MESSAGE_2_CARRY_0,
|
||||
@@ -43,7 +43,7 @@ fn bench_server_key_unary_function<F>(
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
unary_op: F,
|
||||
params: &[Parameters],
|
||||
params: &[PBSParameters],
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut CiphertextBig),
|
||||
{
|
||||
@@ -55,7 +55,7 @@ fn bench_server_key_unary_function<F>(
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
let modulus = cks.parameters.message_modulus().0 as u64;
|
||||
|
||||
let clear_text = rng.gen::<u64>() % modulus;
|
||||
|
||||
@@ -74,6 +74,8 @@ fn bench_server_key_unary_function<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
param.message_modulus.0.ilog2(),
|
||||
vec![param.message_modulus.0.ilog2()],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,7 +87,7 @@ fn bench_server_key_binary_function<F>(
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
params: &[PBSParameters],
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut CiphertextBig, &mut CiphertextBig),
|
||||
{
|
||||
@@ -97,7 +99,7 @@ fn bench_server_key_binary_function<F>(
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
let modulus = cks.parameters.message_modulus().0 as u64;
|
||||
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
let clear_1 = rng.gen::<u64>() % modulus;
|
||||
@@ -118,6 +120,8 @@ fn bench_server_key_binary_function<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
param.message_modulus.0.ilog2(),
|
||||
vec![param.message_modulus.0.ilog2()],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,7 +133,7 @@ fn bench_server_key_binary_scalar_function<F>(
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
params: &[PBSParameters],
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut CiphertextBig, u8),
|
||||
{
|
||||
@@ -141,7 +145,7 @@ fn bench_server_key_binary_scalar_function<F>(
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
let modulus = cks.parameters.message_modulus().0 as u64;
|
||||
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
let clear_1 = rng.gen::<u64>() % modulus;
|
||||
@@ -161,6 +165,8 @@ fn bench_server_key_binary_scalar_function<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
param.message_modulus.0.ilog2(),
|
||||
vec![param.message_modulus.0.ilog2()],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -172,7 +178,7 @@ fn bench_server_key_binary_scalar_division_function<F>(
|
||||
bench_name: &str,
|
||||
display_name: &str,
|
||||
binary_op: F,
|
||||
params: &[Parameters],
|
||||
params: &[PBSParameters],
|
||||
) where
|
||||
F: Fn(&ServerKey, &mut CiphertextBig, u8),
|
||||
{
|
||||
@@ -184,7 +190,7 @@ fn bench_server_key_binary_scalar_division_function<F>(
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
let modulus = cks.parameters.message_modulus().0 as u64;
|
||||
assert_ne!(modulus, 1);
|
||||
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
@@ -208,6 +214,8 @@ fn bench_server_key_binary_scalar_division_function<F>(
|
||||
param.name(),
|
||||
display_name,
|
||||
&OperatorType::Atomic,
|
||||
param.message_modulus.0.ilog2(),
|
||||
vec![param.message_modulus.0.ilog2()],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,7 +231,7 @@ fn carry_extract(c: &mut Criterion) {
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
let modulus = cks.parameters.message_modulus().0 as u64;
|
||||
|
||||
let clear_0 = rng.gen::<u64>() % modulus;
|
||||
|
||||
@@ -242,6 +250,8 @@ fn carry_extract(c: &mut Criterion) {
|
||||
param.name(),
|
||||
"carry_extract",
|
||||
&OperatorType::Atomic,
|
||||
param.message_modulus.0.ilog2(),
|
||||
vec![param.message_modulus.0.ilog2()],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -257,7 +267,7 @@ fn programmable_bootstrapping(c: &mut Criterion) {
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let modulus = cks.parameters.message_modulus.0 as u64;
|
||||
let modulus = cks.parameters.message_modulus().0 as u64;
|
||||
|
||||
let acc = sks.generate_accumulator(|x| x);
|
||||
|
||||
@@ -273,18 +283,29 @@ fn programmable_bootstrapping(c: &mut Criterion) {
|
||||
})
|
||||
});
|
||||
|
||||
write_to_json(&bench_id, param, param.name(), "pbs", &OperatorType::Atomic);
|
||||
write_to_json(
|
||||
&bench_id,
|
||||
param,
|
||||
param.name(),
|
||||
"pbs",
|
||||
&OperatorType::Atomic,
|
||||
param.message_modulus.0.ilog2(),
|
||||
vec![param.message_modulus.0.ilog2()],
|
||||
);
|
||||
}
|
||||
|
||||
bench_group.finish();
|
||||
}
|
||||
|
||||
// TODO: remove?
|
||||
fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
|
||||
let mut bench_group = c.benchmark_group("programmable_bootstrap");
|
||||
|
||||
let param = WOPBS_PARAM_MESSAGE_4_NORM2_6;
|
||||
let param_set: ShortintParameterSet = param.try_into().unwrap();
|
||||
let pbs_params = param_set.pbs_parameters().unwrap();
|
||||
|
||||
let keys = KEY_CACHE_WOPBS.get_from_param((param, param));
|
||||
let keys = KEY_CACHE_WOPBS.get_from_param((pbs_params, param));
|
||||
let (cks, _, wopbs_key) = (keys.client_key(), keys.server_key(), keys.wopbs_key());
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::PathBuf;
|
||||
use tfhe::boolean::parameters::BooleanParameters;
|
||||
use tfhe::core_crypto::prelude::*;
|
||||
#[cfg(feature = "shortint")]
|
||||
use tfhe::shortint::Parameters;
|
||||
use tfhe::shortint::PBSParameters;
|
||||
|
||||
#[derive(Clone, Copy, Default, Serialize)]
|
||||
pub struct CryptoParametersRecord {
|
||||
@@ -52,8 +52,8 @@ impl From<BooleanParameters> for CryptoParametersRecord {
|
||||
}
|
||||
|
||||
#[cfg(feature = "shortint")]
|
||||
impl From<Parameters> for CryptoParametersRecord {
|
||||
fn from(params: Parameters) -> Self {
|
||||
impl From<PBSParameters> for CryptoParametersRecord {
|
||||
fn from(params: PBSParameters) -> Self {
|
||||
CryptoParametersRecord {
|
||||
lwe_dimension: Some(params.lwe_dimension),
|
||||
glwe_dimension: Some(params.glwe_dimension),
|
||||
@@ -64,11 +64,11 @@ impl From<Parameters> for CryptoParametersRecord {
|
||||
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),
|
||||
pfks_level: None,
|
||||
pfks_base_log: None,
|
||||
pfks_modular_std_dev: None,
|
||||
cbs_level: None,
|
||||
cbs_base_log: None,
|
||||
message_modulus: Some(params.message_modulus.0),
|
||||
carry_modulus: Some(params.carry_modulus.0),
|
||||
}
|
||||
@@ -120,11 +120,12 @@ struct BenchmarkParametersRecord {
|
||||
message_modulus: Option<usize>,
|
||||
carry_modulus: Option<usize>,
|
||||
ciphertext_modulus: usize,
|
||||
bit_size: u32,
|
||||
polynomial_multiplication: PolynomialMultiplication,
|
||||
precision: u32,
|
||||
error_probability: f64,
|
||||
integer_representation: IntegerRepresentation,
|
||||
decomposition_basis: u32,
|
||||
decomposition_basis: Vec<u32>,
|
||||
pbs_algorithm: Option<String>,
|
||||
execution_type: ExecutionType,
|
||||
key_set_type: KeySetType,
|
||||
@@ -139,6 +140,8 @@ pub fn write_to_json<T: Into<CryptoParametersRecord>>(
|
||||
params_alias: impl Into<String>,
|
||||
display_name: impl Into<String>,
|
||||
operator_type: &OperatorType,
|
||||
bit_size: u32,
|
||||
decomposition_basis: Vec<u32>,
|
||||
) {
|
||||
let params = params.into();
|
||||
|
||||
@@ -158,11 +161,12 @@ pub fn write_to_json<T: Into<CryptoParametersRecord>>(
|
||||
message_modulus: params.message_modulus,
|
||||
carry_modulus: params.carry_modulus,
|
||||
ciphertext_modulus: 64,
|
||||
bit_size,
|
||||
polynomial_multiplication: PolynomialMultiplication::Fft,
|
||||
precision: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
|
||||
error_probability: 2f64.powf(-41.0),
|
||||
integer_representation: IntegerRepresentation::Radix,
|
||||
decomposition_basis: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
|
||||
decomposition_basis,
|
||||
pbs_algorithm: None, // To be added in future version
|
||||
execution_type,
|
||||
key_set_type: KeySetType::Single,
|
||||
|
||||
@@ -32,6 +32,34 @@ int uint128_client_key(const ClientKey *client_key) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint128_encrypt_trivial(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint128 *lhs = NULL;
|
||||
FheUint128 *rhs = NULL;
|
||||
FheUint128 *result = NULL;
|
||||
|
||||
ok = fhe_uint128_try_encrypt_trivial_u128(10, 20, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_try_encrypt_trivial_u128(1, 2, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint128_sub(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
uint64_t w0, w1;
|
||||
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 9);
|
||||
assert(w1 == 18);
|
||||
|
||||
fhe_uint128_destroy(lhs);
|
||||
fhe_uint128_destroy(rhs);
|
||||
fhe_uint128_destroy(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint128_public_key(const ClientKey *client_key, const PublicKey *public_key) {
|
||||
int ok;
|
||||
FheUint128 *lhs = NULL;
|
||||
@@ -66,7 +94,7 @@ int main(void) {
|
||||
Config *config;
|
||||
|
||||
config_builder_all_disabled(&builder);
|
||||
config_builder_enable_default_uint128_small(&builder);
|
||||
config_builder_enable_default_integers_small(&builder);
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
@@ -79,6 +107,7 @@ int main(void) {
|
||||
set_server_key(server_key);
|
||||
|
||||
uint128_client_key(client_key);
|
||||
uint128_encrypt_trivial(client_key);
|
||||
uint128_public_key(client_key, public_key);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
int uint256_client_key(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint256 *lhs = NULL;
|
||||
FheUint256 *rhs = NULL;
|
||||
FheUint256 *result = NULL;
|
||||
FheUint64 *cast_result = NULL;
|
||||
U256 *lhs_clear = NULL;
|
||||
U256 *rhs_clear = NULL;
|
||||
U256 *result_clear = NULL;
|
||||
@@ -40,6 +40,59 @@ int uint256_client_key(const ClientKey *client_key) {
|
||||
assert(w2 == 10);
|
||||
assert(w3 == 12);
|
||||
|
||||
// try some casting
|
||||
ok = fhe_uint256_cast_into_fhe_uint64(result, &cast_result);
|
||||
assert(ok == 0);
|
||||
uint64_t u64_clear;
|
||||
ok = fhe_uint64_decrypt(cast_result, client_key, &u64_clear);
|
||||
assert(ok == 0);
|
||||
assert(u64_clear == 6);
|
||||
|
||||
u256_destroy(lhs_clear);
|
||||
u256_destroy(rhs_clear);
|
||||
u256_destroy(result_clear);
|
||||
fhe_uint256_destroy(lhs);
|
||||
fhe_uint256_destroy(rhs);
|
||||
fhe_uint256_destroy(result);
|
||||
fhe_uint64_destroy(cast_result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int uint256_encrypt_trivial(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheUint256 *lhs = NULL;
|
||||
FheUint256 *rhs = NULL;
|
||||
FheUint256 *result = NULL;
|
||||
U256 *lhs_clear = NULL;
|
||||
U256 *rhs_clear = NULL;
|
||||
U256 *result_clear = NULL;
|
||||
|
||||
ok = u256_from_u64_words(1, 2, 3, 4, &lhs_clear);
|
||||
assert(ok == 0);
|
||||
ok = u256_from_u64_words(5, 6, 7, 8, &rhs_clear);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_try_encrypt_trivial_u256(lhs_clear, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_try_encrypt_trivial_u256(rhs_clear, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_add(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
|
||||
assert(ok == 0);
|
||||
|
||||
uint64_t w0, w1, w2, w3;
|
||||
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(w0 == 6);
|
||||
assert(w1 == 8);
|
||||
assert(w2 == 10);
|
||||
assert(w3 == 12);
|
||||
|
||||
u256_destroy(lhs_clear);
|
||||
u256_destroy(rhs_clear);
|
||||
u256_destroy(result_clear);
|
||||
@@ -99,7 +152,7 @@ int main(void) {
|
||||
Config *config;
|
||||
|
||||
config_builder_all_disabled(&builder);
|
||||
config_builder_enable_default_uint256_small(&builder);
|
||||
config_builder_enable_default_integers_small(&builder);
|
||||
config_builder_build(builder, &config);
|
||||
|
||||
ClientKey *client_key = NULL;
|
||||
@@ -112,6 +165,7 @@ int main(void) {
|
||||
set_server_key(server_key);
|
||||
|
||||
uint256_client_key(client_key);
|
||||
uint256_encrypt_trivial(client_key);
|
||||
uint256_public_key(client_key, public_key);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
|
||||
@@ -67,6 +67,37 @@ int public_key_test(const ClientKey *client_key, const PublicKey *public_key) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
int trivial_encrypt_test(const ClientKey *client_key) {
|
||||
int ok;
|
||||
FheBool *lhs = NULL;
|
||||
FheBool *rhs = NULL;
|
||||
FheBool *result = NULL;
|
||||
|
||||
bool lhs_clear = 0;
|
||||
bool rhs_clear = 1;
|
||||
|
||||
ok = fhe_bool_try_encrypt_trivial_bool(lhs_clear, &lhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_bool_try_encrypt_trivial_bool(rhs_clear, &rhs);
|
||||
assert(ok == 0);
|
||||
|
||||
ok = fhe_bool_bitand(lhs, rhs, &result);
|
||||
assert(ok == 0);
|
||||
|
||||
bool clear;
|
||||
ok = fhe_bool_decrypt(result, client_key, &clear);
|
||||
assert(ok == 0);
|
||||
|
||||
assert(clear == (lhs_clear & rhs_clear));
|
||||
|
||||
fhe_bool_destroy(lhs);
|
||||
fhe_bool_destroy(rhs);
|
||||
fhe_bool_destroy(result);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
|
||||
@@ -88,6 +119,7 @@ int main(void)
|
||||
|
||||
client_key_test(client_key);
|
||||
public_key_test(client_key, public_key);
|
||||
trivial_encrypt_test(client_key);
|
||||
|
||||
client_key_destroy(client_key);
|
||||
public_key_destroy(public_key);
|
||||
|
||||
@@ -145,7 +145,7 @@ int main(void) {
|
||||
|
||||
ok = config_builder_all_disabled(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_enable_default_uint8(&builder);
|
||||
ok = config_builder_enable_default_integers(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_build(builder, &config);
|
||||
assert(ok == 0);
|
||||
@@ -182,7 +182,7 @@ int main(void) {
|
||||
|
||||
ok = config_builder_all_disabled(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_enable_default_uint8_small(&builder);
|
||||
ok = config_builder_enable_default_integers_small(&builder);
|
||||
assert(ok == 0);
|
||||
ok = config_builder_build(builder, &config);
|
||||
assert(ok == 0);
|
||||
|
||||
@@ -108,8 +108,8 @@ void test_custom_keygen(void) {
|
||||
ShortintServerKey *sks = NULL;
|
||||
ShortintParameters *params = NULL;
|
||||
|
||||
int params_ok = shortint_create_parameters(10, 1, 1024, 10e-100, 10e-100, 2, 3, 2, 3, 2, 3,
|
||||
10e-100, 2, 3, 2, 2, SHORTINT_NATIVE_MODULUS, ¶ms);
|
||||
int params_ok = shortint_create_parameters(10, 1, 1024, 10e-100, 10e-100, 2, 3, 2, 3, 2, 2, 64,
|
||||
ShortintEncryptionKeyChoiceBig, ¶ms);
|
||||
assert(params_ok == 0);
|
||||
|
||||
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);
|
||||
|
||||
@@ -430,7 +430,7 @@ void test_server_key(void) {
|
||||
int get_params_ok = shortint_get_parameters(message_bits, carry_bits, ¶ms);
|
||||
assert(get_params_ok == 0);
|
||||
|
||||
int get_params_small_ok = shortint_get_parameters(message_bits, carry_bits, ¶ms_small);
|
||||
int get_params_small_ok = shortint_get_parameters_small(message_bits, carry_bits, ¶ms_small);
|
||||
assert(get_params_small_ok == 0);
|
||||
|
||||
int gen_cks_ok = shortint_gen_client_key(params, &cks);
|
||||
|
||||
@@ -9,7 +9,7 @@ Welcome to this tutorial about TFHE-rs `core_crypto` module.
|
||||
To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = [ "x86_64-unix" ] }
|
||||
tfhe = { version = "0.3.0", features = [ "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available, like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available. To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
|
||||
@@ -19,19 +19,19 @@ For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `a
|
||||
In short: For x86\_64-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = ["x86_64-unix"] }
|
||||
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
|
||||
```
|
||||
|
||||
For Apple Silicon or aarch64-based machines running Unix-like OSes:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = ["aarch64-unix"] }
|
||||
tfhe = { version = "0.3.0", features = ["aarch64-unix"] }
|
||||
```
|
||||
|
||||
For x86\_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = ["x86_64"] }
|
||||
tfhe = { version = "0.3.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.
|
||||
|
||||
@@ -58,11 +58,11 @@ All timings are related to parallelized Radix-based integer operations, where ea
|
||||
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 |
|
||||
| -------------------| ---------------| --------------------| ---------------------| -------------|
|
||||
| 8 bits | 129.0 ms | 227 ms | 111.9 ms | 186.8 ms |
|
||||
| 16 bits | 195 ms | 369 ms | 145.3 ms | 233.1 ms |
|
||||
| 32 bits | 238 ms | 519 ms | 192.0 ms | 282.9 ms |
|
||||
| 40 bits | 283 ms | 754 ms | 228.4 ms | 318.6 ms |
|
||||
| 64 bits | 297 ms | 1.18 s | 249.0 ms | 336.5 ms |
|
||||
| 128 bits | 424 ms | 3.13 s | 294.7 ms | 398.6 ms |
|
||||
| 256 bits | 500 ms | 11 s | 361.8 ms | 509.1 ms |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
|
||||
tfhe = { version = "0.3.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
|
||||
@@ -41,7 +41,7 @@ use tfhe::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
@@ -115,7 +115,7 @@ fn main() {
|
||||
let msg1 = 1;
|
||||
let msg2 = 0;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
@@ -143,7 +143,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
fn main() {
|
||||
// We create keys for radix represention to create 16 bits integers
|
||||
// using 8 blocks of 2 bits
|
||||
let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, 8);
|
||||
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, 8);
|
||||
|
||||
let clear_a = 2382u16;
|
||||
let clear_b = 29374u16;
|
||||
|
||||
@@ -237,7 +237,7 @@ 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 config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
@@ -285,7 +285,7 @@ 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 config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
@@ -331,7 +331,7 @@ 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 config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
@@ -380,7 +380,7 @@ 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 config = ConfigBuilder::all_disabled().enable_default_integers().build();
|
||||
let (keys, server_keys) = generate_keys(config);
|
||||
set_server_key(server_keys);
|
||||
|
||||
@@ -403,3 +403,56 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Casting.
|
||||
|
||||
Casting between integer types is possible via the `cast_from` associated function
|
||||
of `cast_into` method.
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheUint32, FheUint16};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
|
||||
// Casting requires server_key to set
|
||||
// (encryptions/decryptions do not need server_key to be set)
|
||||
set_server_key(server_key);
|
||||
|
||||
{
|
||||
let clear = 12_837u16;
|
||||
let a = FheUint16::encrypt(clear, &client_key);
|
||||
|
||||
// Downcasting
|
||||
let a: FheUint8 = a.cast_into();
|
||||
let da: u8 = a.decrypt(&client_key);
|
||||
assert_eq!(da, clear as u8);
|
||||
|
||||
// Upcasting
|
||||
let a: FheUint32 = a.cast_into();
|
||||
let da: u32 = a.decrypt(&client_key);
|
||||
assert_eq!(da, (clear as u8) as u32);
|
||||
}
|
||||
|
||||
{
|
||||
let clear = 12_837u16;
|
||||
let a = FheUint16::encrypt(clear, &client_key);
|
||||
|
||||
// Upcasting
|
||||
let a = FheUint32::cast_from(a);
|
||||
let da: u32 = a.decrypt(&client_key);
|
||||
assert_eq!(da, clear as u32);
|
||||
|
||||
// Downcasting
|
||||
let a = FheUint8::cast_from(a);
|
||||
let da: u8 = a.decrypt(&client_key);
|
||||
assert_eq!(da, (clear as u32) as u8);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
@@ -11,7 +11,7 @@ To serialize our data, a [data format](https://serde.rs/#data-formats) should be
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.2.0", features = ["integer"]}
|
||||
tfhe = { version = "0.3.0", features = ["integer","x86_64-unix"]}
|
||||
bincode = "1.3.3"
|
||||
```
|
||||
|
||||
@@ -27,7 +27,7 @@ use tfhe::prelude::*;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>>{
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let ( client_key, server_key) = generate_keys(config);
|
||||
|
||||
@@ -9,7 +9,7 @@ The basic steps for using the high-level API of TFHE-rs are:
|
||||
3. Client-side: Encrypting data;
|
||||
4. Server-side: Setting the server key;
|
||||
5. Server-side: Computing over encrypted data;
|
||||
6. Client-side: Encrypting data.
|
||||
6. Client-side: Decrypting data.
|
||||
|
||||
Here is the full example (mixing client and server parts):
|
||||
|
||||
@@ -19,7 +19,7 @@ use tfhe::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
// Client-side
|
||||
@@ -44,10 +44,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Default configuration for x86 Unix machines:
|
||||
```toml
|
||||
tfhe = { version = "0.2.0", features = ["integer"]}
|
||||
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
### Imports.
|
||||
|
||||
`tfhe` uses `traits` to have a consistent API for creating FHE types and enable users to write generic functions. To be able to use associated functions and methods of a trait, the trait has to be in scope.
|
||||
@@ -64,13 +67,8 @@ The first step is the creation of the configuration. The configuration is used t
|
||||
|
||||
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 %}
|
||||
In this example, 8-bit unsigned integers with default parameters are used. The `integers`
|
||||
feature must also be enabled, as per the table on the [Getting Started page](../getting_started/installation.md).
|
||||
|
||||
The config is done by first creating a builder with all types deactivated. Then, the `uint8` type with default parameters is activated.
|
||||
|
||||
@@ -79,7 +77,7 @@ use tfhe::{ConfigBuilder, generate_keys};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
@@ -101,7 +99,7 @@ use tfhe::{ConfigBuilder, generate_keys, set_server_key};
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
@@ -191,10 +189,13 @@ To use the `FheUint8` type, the `integer` feature must be activated:
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.2.0", features = ["integer"]}
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
|
||||
```rust
|
||||
use tfhe::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
|
||||
use tfhe::prelude::*;
|
||||
@@ -229,7 +230,7 @@ impl FheLatinString {
|
||||
.bytes()
|
||||
.map(|b| FheUint8::encrypt(b, client_key))
|
||||
.collect::<Vec<FheUint8>>();
|
||||
let cst = FheUint8::encrypt(32, client_key);
|
||||
let cst = FheUint8::encrypt(32u8, client_key);
|
||||
|
||||
Self {
|
||||
bytes: fhe_bytes,
|
||||
@@ -272,7 +273,7 @@ impl FheLatinString {
|
||||
|
||||
fn main() {
|
||||
let config = ConfigBuilder::all_disabled()
|
||||
.enable_default_uint8()
|
||||
.enable_default_integers()
|
||||
.build();
|
||||
|
||||
let (client_key, server_key) = generate_keys(config);
|
||||
@@ -317,11 +318,13 @@ To use Booleans, the `booleans` feature in our Cargo.toml must be enabled:
|
||||
```toml
|
||||
# Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
# ...
|
||||
tfhe = { version = "0.2.0", features = ["booleans"]}
|
||||
# Default configuration for x86 Unix machines:
|
||||
tfhe = { version = "0.3.0", features = ["boolean", "x86_64-unix"]}
|
||||
```
|
||||
|
||||
Other configurations can be found [here](../getting_started/installation.md).
|
||||
|
||||
|
||||
#### function definition
|
||||
|
||||
First, the verification function is defined.
|
||||
@@ -461,7 +464,7 @@ To make the `compute_parity_bit` function compatible with both `FheBool` and `bo
|
||||
|
||||
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`.
|
||||
Writing generic functions that use operator overloading for our FHE types can be trickier than normal, since `FHE` types are not copy. So using the reference `&` is mandatory, even though this is not the case when using native types, which are all `Copy`.
|
||||
|
||||
This will make the generic bounds trickier at first.
|
||||
|
||||
@@ -549,7 +552,7 @@ help: consider adding an explicit lifetime bound...
|
||||
|
|
||||
```
|
||||
|
||||
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):
|
||||
The way to fix this is to use `Higher-Rank Trait Bounds`:
|
||||
|
||||
```Rust
|
||||
where
|
||||
|
||||
@@ -26,7 +26,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -105,7 +105,7 @@ 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 (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12u64;
|
||||
let msg2 = 11u64;
|
||||
@@ -143,7 +143,7 @@ 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 (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12u64;
|
||||
let msg2 = 11u64;
|
||||
@@ -181,7 +181,7 @@ 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 (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12;
|
||||
let msg2 = 11;
|
||||
@@ -220,7 +220,7 @@ 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 (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 12;
|
||||
let msg2 = 11;
|
||||
|
||||
@@ -27,7 +27,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 201;
|
||||
let msg2 = 12;
|
||||
|
||||
@@ -34,7 +34,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -49,7 +49,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 128u64;
|
||||
let msg2 = 13u64;
|
||||
@@ -72,7 +72,7 @@ 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);
|
||||
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);
|
||||
@@ -98,7 +98,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
|
||||
fn main() {
|
||||
// We generate a set of client/server keys, using the default parameters:
|
||||
let num_block = 4;
|
||||
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
|
||||
|
||||
let msg1 = 128;
|
||||
let msg2 = 13;
|
||||
|
||||
@@ -14,7 +14,7 @@ Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory s
|
||||
|
||||
### Setting-up TFHE-rs JS on WASM API for use in nodejs programs.
|
||||
|
||||
To build the JS on WASM bindings for TFHE-rs, you need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.65) [`rust toolchain`](https://rustup.rs/).
|
||||
To build the JS on WASM bindings for TFHE-rs, you need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.67) [`rust toolchain`](https://rustup.rs/).
|
||||
|
||||
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):
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ fn main() {
|
||||
let msg2 = 3;
|
||||
let scalar = 4;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
@@ -88,7 +88,7 @@ fn main() {
|
||||
let msg2 = 3;
|
||||
let scalar = 4;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
@@ -131,7 +131,7 @@ fn main() {
|
||||
let msg2 = 3;
|
||||
let scalar = 4;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
@@ -165,7 +165,7 @@ fn main() {
|
||||
let msg2 = 3;
|
||||
let scalar = 4;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let mut ct_1 = client_key.encrypt(msg1);
|
||||
@@ -241,7 +241,7 @@ fn main() {
|
||||
let msg1 = 2;
|
||||
let msg2 = 1;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the private client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
@@ -272,7 +272,7 @@ fn main() {
|
||||
let msg1 = 2;
|
||||
let msg2 = 1;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the private client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
@@ -303,7 +303,7 @@ fn main() {
|
||||
let msg1 = 2;
|
||||
let msg2 = 1;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the private client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
@@ -331,7 +331,7 @@ fn main() {
|
||||
|
||||
let msg1 = 3;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the private client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
@@ -365,7 +365,7 @@ fn main() {
|
||||
let msg1 = 3;
|
||||
let msg2 = 2;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0 as u64;
|
||||
let modulus = client_key.parameters.message_modulus().0 as u64;
|
||||
|
||||
// We use the private client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
|
||||
@@ -44,7 +44,7 @@ In the case of multiplication, two algorithms are implemented: the first one rel
|
||||
|
||||
## User-defined parameter sets
|
||||
|
||||
It is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `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 `PBSParameters` structure fields.
|
||||
|
||||
For instance:
|
||||
|
||||
@@ -53,7 +53,7 @@ use tfhe::shortint::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let param = unsafe {
|
||||
Parameters::new(
|
||||
PBSParameters::new(
|
||||
LweDimension(656),
|
||||
GlweDimension(2),
|
||||
PolynomialSize(512),
|
||||
@@ -63,14 +63,10 @@ fn main() {
|
||||
DecompositionLevelCount(2),
|
||||
DecompositionBaseLog(3),
|
||||
DecompositionLevelCount(4),
|
||||
StandardDev(0.00000000037411618952047216),
|
||||
DecompositionBaseLog(15),
|
||||
DecompositionLevelCount(1),
|
||||
DecompositionLevelCount(0),
|
||||
DecompositionBaseLog(0),
|
||||
MessageModulus(4),
|
||||
CarryModulus(1),
|
||||
CiphertextModulus::new_native(),
|
||||
EncryptionKeyChoice::Big,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ fn main() {
|
||||
let msg1 = 1;
|
||||
let msg2 = 0;
|
||||
|
||||
let modulus = client_key.parameters.message_modulus.0;
|
||||
let modulus = client_key.parameters.message_modulus().0;
|
||||
|
||||
// We use the client key to encrypt two messages:
|
||||
let ct_1 = client_key.encrypt(msg1);
|
||||
|
||||
@@ -42,7 +42,15 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
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);
|
||||
write_to_json(
|
||||
&test_name,
|
||||
*params,
|
||||
*params_name,
|
||||
"KSK",
|
||||
&operator,
|
||||
0,
|
||||
vec![],
|
||||
);
|
||||
|
||||
println!(
|
||||
"Element in KSK: {}, size in bytes: {}",
|
||||
@@ -54,7 +62,15 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
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);
|
||||
write_to_json(
|
||||
&test_name,
|
||||
*params,
|
||||
*params_name,
|
||||
"BSK",
|
||||
&operator,
|
||||
0,
|
||||
vec![],
|
||||
);
|
||||
|
||||
println!(
|
||||
"Element in BSK: {}, size in bytes: {}",
|
||||
|
||||
@@ -4,8 +4,8 @@ use tfhe::shortint::parameters::parameters_wopbs_message_carry::{
|
||||
WOPBS_PARAM_MESSAGE_4_CARRY_4,
|
||||
};
|
||||
use tfhe::shortint::parameters::{
|
||||
Parameters, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2,
|
||||
PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_4_CARRY_4,
|
||||
PBSParameters, WopbsParameters, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1,
|
||||
PARAM_MESSAGE_2_CARRY_2, PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_4_CARRY_4,
|
||||
};
|
||||
|
||||
fn client_server_keys() {
|
||||
@@ -30,7 +30,7 @@ fn client_server_keys() {
|
||||
KEY_CACHE.clear_in_memory_cache()
|
||||
}
|
||||
|
||||
const WOPBS_PARAMS: [(Parameters, Parameters); 4] = [
|
||||
const WOPBS_PARAMS: [(PBSParameters, WopbsParameters); 4] = [
|
||||
(PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_1_CARRY_1),
|
||||
(PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_2_CARRY_2),
|
||||
(PARAM_MESSAGE_3_CARRY_3, WOPBS_PARAM_MESSAGE_3_CARRY_3),
|
||||
|
||||
@@ -50,7 +50,15 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
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);
|
||||
write_to_json(
|
||||
&test_name,
|
||||
*params,
|
||||
params.name(),
|
||||
"KSK",
|
||||
&operator,
|
||||
0,
|
||||
vec![],
|
||||
);
|
||||
|
||||
println!(
|
||||
"Element in KSK: {}, size in bytes: {}",
|
||||
@@ -62,7 +70,15 @@ fn client_server_key_sizes(results_file: &Path) {
|
||||
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);
|
||||
write_to_json(
|
||||
&test_name,
|
||||
*params,
|
||||
params.name(),
|
||||
"BSK",
|
||||
&operator,
|
||||
0,
|
||||
vec![],
|
||||
);
|
||||
|
||||
println!(
|
||||
"Element in BSK: {}, size in bytes: {}",
|
||||
|
||||
@@ -18,6 +18,9 @@ use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::seeders::new_seeder;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(crate) trait BinaryGatesEngine<L, R, K> {
|
||||
fn and(&mut self, ct_left: L, ct_right: R, server_key: &K) -> Ciphertext;
|
||||
fn nand(&mut self, ct_left: L, ct_right: R, server_key: &K) -> Ciphertext;
|
||||
@@ -257,6 +260,37 @@ impl Default for BooleanEngine {
|
||||
}
|
||||
|
||||
impl BooleanEngine {
|
||||
/// Replace the thread_local BooleanEngine
|
||||
///
|
||||
/// `new_engine` will replace the already_existing
|
||||
/// `thread_local` engine.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::boolean::engine::BooleanEngine;
|
||||
/// use tfhe::core_crypto::commons::generators::DeterministicSeeder;
|
||||
/// use tfhe::core_crypto::commons::math::random::Seed;
|
||||
/// use tfhe::core_crypto::prelude::ActivatedRandomGenerator;
|
||||
///
|
||||
/// // WARNING: Using a deterministic seed is not recommended
|
||||
/// // as it renders the random generation insecure
|
||||
///
|
||||
/// let deterministic_seed = Seed(0);
|
||||
///
|
||||
/// let mut seeder = DeterministicSeeder::<ActivatedRandomGenerator>::new(deterministic_seed);
|
||||
/// let boolean_engine = BooleanEngine::new_from_seeder(&mut seeder);
|
||||
/// BooleanEngine::replace_thread_local(boolean_engine);
|
||||
///
|
||||
/// // This uses the engine create earlier
|
||||
/// let (cks, sks) = tfhe::boolean::gen_keys();
|
||||
/// ```
|
||||
pub fn replace_thread_local(new_engine: Self) {
|
||||
Self::with_thread_local_mut(|local_engine| {
|
||||
let _ = std::mem::replace(local_engine, new_engine);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut root_seeder = new_seeder();
|
||||
|
||||
|
||||
54
tfhe/src/boolean/engine/tests.rs
Normal file
54
tfhe/src/boolean/engine/tests.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
#[test]
|
||||
fn test_replacing_thread_local_engine() {
|
||||
use crate::boolean::engine::BooleanEngine;
|
||||
use crate::core_crypto::commons::generators::DeterministicSeeder;
|
||||
use crate::core_crypto::commons::math::random::Seed;
|
||||
use crate::core_crypto::prelude::ActivatedRandomGenerator;
|
||||
|
||||
let deterministic_seed = Seed(0);
|
||||
|
||||
// We change the engine in the main thread
|
||||
// then generate a client key, and then encrypt
|
||||
// a boolean value and serialize it to compare
|
||||
// it with other ciphertext
|
||||
let mut seeder = DeterministicSeeder::<ActivatedRandomGenerator>::new(deterministic_seed);
|
||||
let boolean_engine = BooleanEngine::new_from_seeder(&mut seeder);
|
||||
BooleanEngine::replace_thread_local(boolean_engine);
|
||||
|
||||
let (cks, _) = crate::boolean::gen_keys();
|
||||
let ct = cks.encrypt(false);
|
||||
let main_thread_data = bincode::serialize(&ct).unwrap();
|
||||
|
||||
// In this thread, we don't change the engine
|
||||
// and so we expect the encrypted value to be
|
||||
// different compared with the one from the main thread
|
||||
//
|
||||
// This also "proves" that a thread is not affected
|
||||
// by engine changes from other thread as engines are
|
||||
// thread_local
|
||||
let second_thread_data = std::thread::spawn(|| {
|
||||
let (cks, _) = crate::boolean::gen_keys();
|
||||
let ct = cks.encrypt(false);
|
||||
bincode::serialize(&ct).unwrap()
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
assert_ne!(second_thread_data, main_thread_data);
|
||||
|
||||
// In this thread, we change the engine,
|
||||
// with a new engine that has the same seed
|
||||
// as the one in the main thread
|
||||
// So we expect the encrypted value to be the same
|
||||
// compared with the one from the main thread
|
||||
let third_thread_data = std::thread::spawn(move || {
|
||||
let mut seeder = DeterministicSeeder::<ActivatedRandomGenerator>::new(deterministic_seed);
|
||||
let boolean_engine = BooleanEngine::new_from_seeder(&mut seeder);
|
||||
BooleanEngine::replace_thread_local(boolean_engine);
|
||||
let (cks, _) = crate::boolean::gen_keys();
|
||||
let ct = cks.encrypt(false);
|
||||
bincode::serialize(&ct).unwrap()
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
assert_eq!(third_thread_data, main_thread_data);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ 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_trivial_on_type!(FheBool{crate::high_level_api::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);
|
||||
|
||||
|
||||
@@ -68,41 +68,9 @@ macro_rules! define_enable_default_fn(
|
||||
#[cfg(feature = "boolean")]
|
||||
define_enable_default_fn!(bool);
|
||||
#[cfg(feature = "integer")]
|
||||
define_enable_default_fn!(uint8);
|
||||
define_enable_default_fn!(integers);
|
||||
#[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);
|
||||
define_enable_default_fn!(integers @small);
|
||||
|
||||
/// Takes ownership of the builder
|
||||
#[no_mangle]
|
||||
|
||||
@@ -16,12 +16,12 @@ 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_binary_fn_on_type!($name => add, sub, mul, bitand, bitor, bitxor, eq, ge, gt, le, lt, min, max);
|
||||
impl_binary_assign_fn_on_type!($name => add_assign, sub_assign, mul_assign, bitand_assign, bitor_assign, bitxor_assign);
|
||||
impl_scalar_binary_fn_on_type!($name, $clear_scalar_type => add, sub, mul, shl, shr);
|
||||
impl_scalar_binary_assign_fn_on_type!($name, $clear_scalar_type => add_assign, sub_assign, mul_assign, shl_assign, shr_assign);
|
||||
|
||||
impl_unary_fn_on_type_mut!($name => neg);
|
||||
impl_unary_fn_on_type!($name => neg);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,40 +81,62 @@ 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_trivial_on_type!(FheUint8{crate::high_level_api::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_trivial_on_type!(FheUint10{crate::high_level_api::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_trivial_on_type!(FheUint12{crate::high_level_api::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_trivial_on_type!(FheUint14{crate::high_level_api::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_trivial_on_type!(FheUint16{crate::high_level_api::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_trivial_on_type!(FheUint32{crate::high_level_api::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_trivial_on_type!(FheUint64{crate::high_level_api::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_trivial_u128(
|
||||
low_word: u64,
|
||||
high_word: u64,
|
||||
result: *mut *mut FheUint128,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let value = ((high_word as u128) << 64u128) | low_word as u128;
|
||||
|
||||
let inner = <crate::high_level_api::FheUint128>::try_encrypt_trivial(value).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new(FheUint128(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_client_key_u128(
|
||||
low_word: u64,
|
||||
@@ -189,6 +211,18 @@ pub unsafe extern "C" fn fhe_uint128_decrypt(
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint256_try_encrypt_trivial_u256(
|
||||
value: *const U256,
|
||||
result: *mut *mut FheUint256,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
let inner = <crate::high_level_api::FheUint256>::try_encrypt_trivial((*value).0).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new(FheUint256(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_client_key_u256(
|
||||
value: *const U256,
|
||||
@@ -252,3 +286,34 @@ pub unsafe extern "C" fn fhe_uint256_decrypt(
|
||||
*result = Box::into_raw(Box::new(U256(inner)));
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! define_casting_operation(
|
||||
($from:ty => $($to:ty),*) => {
|
||||
$(
|
||||
::paste::paste!{
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn [<$from:snake _cast_into_ $to:snake>](
|
||||
sself: *const $from,
|
||||
result: *mut *mut $to,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
let from = $crate::c_api::utils::get_ref_checked(sself).unwrap();
|
||||
|
||||
let inner_to = from.0.clone().cast_into();
|
||||
*result = Box::into_raw(Box::new($to(inner_to)));
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
);
|
||||
|
||||
define_casting_operation!(FheUint8 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
define_casting_operation!(FheUint10 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
define_casting_operation!(FheUint12 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
define_casting_operation!(FheUint14 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
define_casting_operation!(FheUint16 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
define_casting_operation!(FheUint32 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
define_casting_operation!(FheUint64 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
define_casting_operation!(FheUint128 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
define_casting_operation!(FheUint256 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
|
||||
|
||||
@@ -59,6 +59,24 @@ macro_rules! impl_try_encrypt_with_public_key_on_type {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_try_encrypt_trivial_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_trivial_ $input_type:snake>](
|
||||
value: $input_type,
|
||||
result: *mut *mut $wrapper_type,
|
||||
) -> ::std::os::raw::c_int {
|
||||
$crate::c_api::utils::catch_panic(|| {
|
||||
|
||||
let inner = <$wrapped_type>::try_encrypt_trivial(value).unwrap();
|
||||
|
||||
*result = Box::into_raw(Box::new($wrapper_type(inner)));
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! impl_decrypt_on_type {
|
||||
($wrapper_type:ty, $output_type:ty) => {
|
||||
::paste::paste! {
|
||||
@@ -191,70 +209,19 @@ macro_rules! impl_unary_fn_on_type {
|
||||
};
|
||||
}
|
||||
|
||||
// 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 {
|
||||
macro_rules! impl_binary_assign_fn_on_type {
|
||||
($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,
|
||||
rhs: *const $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();
|
||||
let rhs = $crate::c_api::utils::get_ref_checked(rhs).unwrap();
|
||||
|
||||
lhs.0.$binary_assign_fn_name(&rhs.0);
|
||||
})
|
||||
@@ -264,20 +231,19 @@ macro_rules! impl_binary_assign_fn_on_type_mut {
|
||||
};
|
||||
}
|
||||
|
||||
// Meant for types on which makes use of interior mutability
|
||||
#[cfg(feature = "integer")]
|
||||
macro_rules! impl_scalar_binary_fn_on_type_mut {
|
||||
macro_rules! impl_scalar_binary_fn_on_type {
|
||||
($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,
|
||||
lhs: *const $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 lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
|
||||
|
||||
let inner = (&lhs.0).$binary_fn_name(rhs);
|
||||
|
||||
@@ -289,9 +255,8 @@ macro_rules! impl_scalar_binary_fn_on_type_mut {
|
||||
};
|
||||
}
|
||||
|
||||
// Meant for types on which makes use of interior mutability
|
||||
#[cfg(feature = "integer")]
|
||||
macro_rules! impl_scalar_binary_assign_fn_on_type_mut {
|
||||
macro_rules! impl_scalar_binary_assign_fn_on_type {
|
||||
($wrapper_type:ty, $scalar_type:ty => $($binary_assign_fn_name:ident),* $(,)?) => {
|
||||
$(
|
||||
::paste::paste! {
|
||||
|
||||
@@ -34,7 +34,7 @@ pub unsafe extern "C" fn shortint_gen_keys_with_parameters(
|
||||
|
||||
let shortint_parameters = get_ref_checked(shortint_parameters).unwrap();
|
||||
|
||||
let client_key = shortint::client_key::ClientKey::new(shortint_parameters.0.to_owned());
|
||||
let client_key = shortint::client_key::ClientKey::new(shortint_parameters.0);
|
||||
let server_key = shortint::server_key::ServerKey::new(&client_key);
|
||||
|
||||
let heap_allocated_client_key = Box::new(ShortintClientKey(client_key));
|
||||
|
||||
@@ -7,9 +7,26 @@ use std::os::raw::c_int;
|
||||
|
||||
use crate::shortint;
|
||||
|
||||
pub const SHORTINT_NATIVE_MODULUS: u64 = 0;
|
||||
#[repr(C)]
|
||||
pub enum ShortintEncryptionKeyChoice {
|
||||
ShortintEncryptionKeyChoiceBig,
|
||||
ShortintEncryptionKeyChoiceSmall,
|
||||
}
|
||||
|
||||
pub struct ShortintParameters(pub(in crate::c_api) shortint::parameters::Parameters);
|
||||
impl From<ShortintEncryptionKeyChoice> for crate::shortint::parameters::EncryptionKeyChoice {
|
||||
fn from(value: ShortintEncryptionKeyChoice) -> Self {
|
||||
match value {
|
||||
ShortintEncryptionKeyChoice::ShortintEncryptionKeyChoiceBig => {
|
||||
shortint::parameters::EncryptionKeyChoice::Big
|
||||
}
|
||||
ShortintEncryptionKeyChoice::ShortintEncryptionKeyChoiceSmall => {
|
||||
shortint::parameters::EncryptionKeyChoice::Small
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShortintParameters(pub(in crate::c_api) shortint::parameters::PBSParameters);
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn shortint_get_parameters(
|
||||
@@ -106,14 +123,10 @@ pub unsafe extern "C" fn shortint_create_parameters(
|
||||
pbs_level: usize,
|
||||
ks_base_log: usize,
|
||||
ks_level: usize,
|
||||
pfks_level: usize,
|
||||
pfks_base_log: usize,
|
||||
pfks_modular_std_dev: f64,
|
||||
cbs_level: usize,
|
||||
cbs_base_log: usize,
|
||||
message_modulus: usize,
|
||||
carry_modulus: usize,
|
||||
modulus_power_of_2_exponent: usize,
|
||||
encryption_key_choice: ShortintEncryptionKeyChoice,
|
||||
result: *mut *mut ShortintParameters,
|
||||
) -> c_int {
|
||||
catch_panic(|| {
|
||||
@@ -124,7 +137,7 @@ pub unsafe extern "C" fn shortint_create_parameters(
|
||||
*result = std::ptr::null_mut();
|
||||
|
||||
let heap_allocated_parameters =
|
||||
Box::new(ShortintParameters(shortint::parameters::Parameters {
|
||||
Box::new(ShortintParameters(shortint::parameters::PBSParameters {
|
||||
lwe_dimension: LweDimension(lwe_dimension),
|
||||
glwe_dimension: GlweDimension(glwe_dimension),
|
||||
polynomial_size: PolynomialSize(polynomial_size),
|
||||
@@ -134,11 +147,6 @@ pub unsafe extern "C" fn shortint_create_parameters(
|
||||
pbs_level: DecompositionLevelCount(pbs_level),
|
||||
ks_base_log: DecompositionBaseLog(ks_base_log),
|
||||
ks_level: DecompositionLevelCount(ks_level),
|
||||
pfks_level: DecompositionLevelCount(pfks_level),
|
||||
pfks_base_log: DecompositionBaseLog(pfks_base_log),
|
||||
pfks_modular_std_dev: StandardDev(pfks_modular_std_dev),
|
||||
cbs_level: DecompositionLevelCount(cbs_level),
|
||||
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:
|
||||
@@ -146,6 +154,7 @@ pub unsafe extern "C" fn shortint_create_parameters(
|
||||
modulus_power_of_2_exponent,
|
||||
)
|
||||
.unwrap(),
|
||||
encryption_key_choice: encryption_key_choice.into(),
|
||||
}));
|
||||
|
||||
*result = Box::into_raw(heap_allocated_parameters);
|
||||
|
||||
189
tfhe/src/core_crypto/algorithms/glwe_keyswitch.rs
Normal file
189
tfhe/src/core_crypto/algorithms/glwe_keyswitch.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
//! Module containing primitives pertaining to [`GLWE ciphertext
|
||||
//! keyswitch`](`GlweKeyswitchKey#glwe-keyswitch`).
|
||||
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Keyswitch an [`GLWE ciphertext`](`GlweCiphertext`) encrytped under an
|
||||
/// [`GLWE secret key`](`GlweSecretKey`) to another [`GLWE secret key`](`GlweSecretKey`).
|
||||
///
|
||||
/// # Formal Definition
|
||||
///
|
||||
/// See [`GLWE keyswitch key`](`GlweKeyswitchKey#glwe-keyswitch`).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweKeyswitchKey creation
|
||||
/// let input_glwe_dimension = GlweDimension(2);
|
||||
/// let poly_size = PolynomialSize(512);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let output_glwe_dimension = GlweDimension(1);
|
||||
/// 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();
|
||||
/// 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_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// input_glwe_dimension,
|
||||
/// poly_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// output_glwe_dimension,
|
||||
/// poly_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let ksk = allocate_and_generate_new_glwe_keyswitch_key(
|
||||
/// &input_glwe_secret_key,
|
||||
/// &output_glwe_secret_key,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// glwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the plaintext
|
||||
/// let msg = 3u64;
|
||||
/// let plaintext_list = PlaintextList::new(msg << 60, PlaintextCount(poly_size.0));
|
||||
///
|
||||
/// // Create a new GlweCiphertext
|
||||
/// let mut input_glwe = GlweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// input_glwe_dimension.to_glwe_size(),
|
||||
/// poly_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &input_glwe_secret_key,
|
||||
/// &mut input_glwe,
|
||||
/// &plaintext_list,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_glwe = GlweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
/// output_glwe_secret_key.polynomial_size(),
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// keyswitch_glwe_ciphertext(&ksk, &mut input_glwe, &mut output_glwe);
|
||||
///
|
||||
/// let mut output_plaintext_list = PlaintextList::new(0u64, plaintext_list.plaintext_count());
|
||||
///
|
||||
/// let decrypted_plaintext = decrypt_glwe_ciphertext(
|
||||
/// &output_glwe_secret_key,
|
||||
/// &output_glwe,
|
||||
/// &mut output_plaintext_list,
|
||||
/// );
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
|
||||
///
|
||||
/// output_plaintext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext_list = output_plaintext_list.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext_list.iter_mut().for_each(|elt| *elt = *elt >> 60);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext_list = cleartext_list;
|
||||
///
|
||||
/// // Check we recovered the original message for each plaintext we encrypted
|
||||
/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg));
|
||||
/// ```
|
||||
pub fn keyswitch_glwe_ciphertext<Scalar, KSKCont, InputCont, OutputCont>(
|
||||
glwe_keyswitch_key: &GlweKeyswitchKey<KSKCont>,
|
||||
input_glwe_ciphertext: &mut GlweCiphertext<InputCont>,
|
||||
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
KSKCont: Container<Element = Scalar>,
|
||||
InputCont: ContainerMut<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
glwe_keyswitch_key.input_key_glwe_dimension()
|
||||
== input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
|
||||
"Mismatched input GlweDimension. \
|
||||
GlweKeyswitchKey input GlweDimension: {:?}, input GlweCiphertext GlweDimension {:?}.",
|
||||
glwe_keyswitch_key.input_key_glwe_dimension(),
|
||||
input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
|
||||
);
|
||||
assert!(
|
||||
glwe_keyswitch_key.output_key_glwe_dimension()
|
||||
== output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
|
||||
"Mismatched output GlweDimension. \
|
||||
GlweKeyswitchKey output GlweDimension: {:?}, output GlweCiphertext GlweDimension {:?}.",
|
||||
glwe_keyswitch_key.output_key_glwe_dimension(),
|
||||
output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
|
||||
);
|
||||
assert!(
|
||||
glwe_keyswitch_key.polynomial_size() == input_glwe_ciphertext.polynomial_size(),
|
||||
"Mismatched input PolynomialSize. \
|
||||
GlweKeyswithcKey input PolynomialSize: {:?}, input GlweCiphertext PolynomialSize {:?}.",
|
||||
glwe_keyswitch_key.polynomial_size(),
|
||||
input_glwe_ciphertext.polynomial_size(),
|
||||
);
|
||||
assert!(
|
||||
glwe_keyswitch_key.polynomial_size() == output_glwe_ciphertext.polynomial_size(),
|
||||
"Mismatched output PolynomialSize. \
|
||||
GlweKeyswitchKey output PolynomialSize: {:?}, output GlweCiphertext PolynomialSize {:?}.",
|
||||
glwe_keyswitch_key.polynomial_size(),
|
||||
output_glwe_ciphertext.polynomial_size(),
|
||||
);
|
||||
|
||||
// Clear the output ciphertext, as it will get updated gradually
|
||||
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
// Copy the input body to the output ciphertext
|
||||
polynomial_wrapping_add_assign(
|
||||
&mut output_glwe_ciphertext.get_mut_body().as_mut_polynomial(),
|
||||
&input_glwe_ciphertext.get_body().as_polynomial(),
|
||||
);
|
||||
|
||||
// We instantiate a decomposer
|
||||
let decomposer = SignedDecomposer::new(
|
||||
glwe_keyswitch_key.decomposition_base_log(),
|
||||
glwe_keyswitch_key.decomposition_level_count(),
|
||||
);
|
||||
|
||||
for (keyswitch_key_block, input_mask_element) in glwe_keyswitch_key
|
||||
.iter()
|
||||
.zip(input_glwe_ciphertext.get_mask().as_polynomial_list().iter())
|
||||
{
|
||||
let mut decomposition_iter = decomposer.decompose_slice(input_mask_element.as_ref());
|
||||
// loop over the number of levels in reverse (from highest to lowest)
|
||||
for level_key_ciphertext in keyswitch_key_block.iter().rev() {
|
||||
let decomposed = decomposition_iter.next_term().unwrap();
|
||||
polynomial_list_wrapping_sub_scalar_mul_assign(
|
||||
&mut output_glwe_ciphertext.as_mut_polynomial_list(),
|
||||
&level_key_ciphertext.as_polynomial_list(),
|
||||
&Polynomial::from_container(decomposed.as_slice()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
207
tfhe/src/core_crypto/algorithms/glwe_keyswitch_key_generation.rs
Normal file
207
tfhe/src/core_crypto/algorithms/glwe_keyswitch_key_generation.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
//! Module containing primitives pertaining to [`GLWE keyswitch key generation`](`GlweKeyswitchKey`)
|
||||
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Fill a [`GLWE keyswitch key`](`GlweKeyswitchKey`) with an actual keyswitching key constructed
|
||||
/// from an input and an output key [`GLWE secret key`](`GlweSecretKey`).
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweKeyswitchKey creation
|
||||
/// let input_glwe_dimension = GlweDimension(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.000007069849454709433);
|
||||
/// let output_glwe_dimension = GlweDimension(1);
|
||||
/// 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();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the GlweSecretKey
|
||||
/// let input_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// input_glwe_dimension,
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// output_glwe_dimension,
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut ksk = GlweKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_glwe_dimension,
|
||||
/// output_glwe_dimension,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// generate_glwe_keyswitch_key(
|
||||
/// &input_glwe_secret_key,
|
||||
/// &output_glwe_secret_key,
|
||||
/// &mut ksk,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// assert!(ksk.as_ref().iter().all(|&x| x == 0) == false);
|
||||
/// ```
|
||||
pub fn generate_glwe_keyswitch_key<Scalar, InputKeyCont, OutputKeyCont, KSKeyCont, Gen>(
|
||||
input_glwe_sk: &GlweSecretKey<InputKeyCont>,
|
||||
output_glwe_sk: &GlweSecretKey<OutputKeyCont>,
|
||||
glwe_keyswitch_key: &mut GlweKeyswitchKey<KSKeyCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar>,
|
||||
KSKeyCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
glwe_keyswitch_key.input_key_glwe_dimension() == input_glwe_sk.glwe_dimension(),
|
||||
"The destination GlweKeyswitchKey input GlweDimension is not equal \
|
||||
to the input GlweSecretKey GlweDimension. Destination: {:?}, input: {:?}",
|
||||
glwe_keyswitch_key.input_key_glwe_dimension(),
|
||||
input_glwe_sk.glwe_dimension()
|
||||
);
|
||||
assert!(
|
||||
glwe_keyswitch_key.output_key_glwe_dimension() == output_glwe_sk.glwe_dimension(),
|
||||
"The destination GlweKeyswitchKey output GlweDimension is not equal \
|
||||
to the output GlweSecretKey GlweDimension. Destination: {:?}, output: {:?}",
|
||||
glwe_keyswitch_key.output_key_glwe_dimension(),
|
||||
input_glwe_sk.glwe_dimension()
|
||||
);
|
||||
assert!(
|
||||
glwe_keyswitch_key.polynomial_size() == input_glwe_sk.polynomial_size(),
|
||||
"The destination GlweKeyswitchKey input PolynomialSize is not equal \
|
||||
to the input GlweSecretKey PolynomialSize. Destination: {:?}, input: {:?}",
|
||||
glwe_keyswitch_key.polynomial_size(),
|
||||
input_glwe_sk.polynomial_size(),
|
||||
);
|
||||
assert!(
|
||||
glwe_keyswitch_key.polynomial_size() == output_glwe_sk.polynomial_size(),
|
||||
"The distination GlweKeyswitchKey output PolynomialSize is not equal \
|
||||
to the output GlweSecretKey PolynomialSize. Destination: {:?}, output: {:?}",
|
||||
glwe_keyswitch_key.polynomial_size(),
|
||||
output_glwe_sk.polynomial_size(),
|
||||
);
|
||||
|
||||
let decomp_base_log = glwe_keyswitch_key.decomposition_base_log();
|
||||
let decomp_level_count = glwe_keyswitch_key.decomposition_level_count();
|
||||
/*
|
||||
// forking the generator and using loop_generator works to generate glwe keyswitch keys but
|
||||
// break lwe_trace_packing_keyswotch_key_generation
|
||||
let gen_iter = generator
|
||||
.fork_glweks_to_glweks_chunks::<Scalar>(
|
||||
decomp_level_count,
|
||||
input_glwe_sk.glwe_dimension(),
|
||||
output_glwe_sk.glwe_dimension().to_glwe_size(),
|
||||
input_glwe_sk.polynomial_size(),
|
||||
)
|
||||
.unwrap();
|
||||
*/
|
||||
|
||||
// Iterate over the input key elements and the destination glwe_keyswitch_key memory
|
||||
//for ((input_key_polynomial, mut keyswitch_key_block), mut _loop_generator) in input_glwe_sk
|
||||
for (input_key_polynomial, mut keyswitch_key_block) in input_glwe_sk
|
||||
.as_polynomial_list()
|
||||
.iter()
|
||||
.zip(glwe_keyswitch_key.iter_mut())
|
||||
//.zip(gen_iter)
|
||||
{
|
||||
// The plaintexts used to encrypt a key element will be stored in this buffer
|
||||
let mut decomposition_polynomials_buffer = PolynomialList::new(
|
||||
Scalar::ZERO,
|
||||
input_glwe_sk.polynomial_size(),
|
||||
PolynomialCount(decomp_level_count.0),
|
||||
);
|
||||
|
||||
// We fill the buffer with the powers of the key elmements
|
||||
for (level, mut message_polynomial) in (1..=decomp_level_count.0)
|
||||
.map(DecompositionLevel)
|
||||
.zip(decomposition_polynomials_buffer.as_mut_view().iter_mut())
|
||||
{
|
||||
for (message, input_key_element) in message_polynomial
|
||||
.iter_mut()
|
||||
.zip(input_key_polynomial.iter())
|
||||
{
|
||||
*message = DecompositionTerm::new(level, decomp_base_log, *input_key_element)
|
||||
.to_recomposition_summand();
|
||||
}
|
||||
}
|
||||
|
||||
let decomposition_plaintexts_buffer =
|
||||
PlaintextList::from_container(decomposition_polynomials_buffer.into_container());
|
||||
|
||||
encrypt_glwe_ciphertext_list(
|
||||
output_glwe_sk,
|
||||
&mut keyswitch_key_block,
|
||||
&decomposition_plaintexts_buffer,
|
||||
noise_parameters,
|
||||
//&mut loop_generator,
|
||||
generator,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a new [`GLWE keyswitch key`](`GlweKeyswitchKey`) and fill it with an actual
|
||||
/// keyswitching key constructed from an input and an output key
|
||||
/// [`GLWE secret key`](`GlweSecretKey`).
|
||||
///
|
||||
/// See [`keyswitch_glwe_ciphertext`] for usage.
|
||||
pub fn allocate_and_generate_new_glwe_keyswitch_key<Scalar, InputKeyCont, OutputKeyCont, Gen>(
|
||||
input_glwe_sk: &GlweSecretKey<InputKeyCont>,
|
||||
output_glwe_sk: &GlweSecretKey<OutputKeyCont>,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> GlweKeyswitchKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut new_glwe_keyswitch_key = GlweKeyswitchKeyOwned::new(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_glwe_sk.glwe_dimension(),
|
||||
output_glwe_sk.glwe_dimension(),
|
||||
output_glwe_sk.polynomial_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_glwe_keyswitch_key(
|
||||
input_glwe_sk,
|
||||
output_glwe_sk,
|
||||
&mut new_glwe_keyswitch_key,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
|
||||
new_glwe_keyswitch_key
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
//! Module containing primitives pertaining to [`GLWE relinearisation key
|
||||
//! generation`](`GlweRelinearisationKey`).
|
||||
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount, PolynomialCount};
|
||||
|
||||
/// Fill a [`GLWE relinearisation key`](`GlweRelinearisationKey`)
|
||||
/// with an actual key.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(3);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(3);
|
||||
/// let decomp_level_count = DecompositionLevelCount(7);
|
||||
/// 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 GlweSecretKey
|
||||
/// let glwe_secret_key: GlweSecretKey<Vec<u64>> = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// allocate_and_generate_glwe_relinearisation_key(
|
||||
/// &glwe_secret_key,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// glwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn generate_glwe_relinearisation_key<Scalar, GlweKeyCont, RelinKeyCont, Gen>(
|
||||
glwe_secret_key: &GlweSecretKey<GlweKeyCont>,
|
||||
glwe_relinearisation_key: &mut GlweRelinearisationKey<RelinKeyCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
GlweKeyCont: Container<Element = Scalar>,
|
||||
RelinKeyCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
glwe_secret_key.glwe_dimension(),
|
||||
glwe_relinearisation_key.glwe_dimension()
|
||||
);
|
||||
assert_eq!(
|
||||
glwe_secret_key.polynomial_size(),
|
||||
glwe_relinearisation_key.polynomial_size()
|
||||
);
|
||||
|
||||
// We retrieve decomposition arguments
|
||||
let glwe_dimension = glwe_relinearisation_key.glwe_dimension();
|
||||
let decomp_level_count = glwe_relinearisation_key.decomposition_level_count();
|
||||
let decomp_base_log = glwe_relinearisation_key.decomposition_base_log();
|
||||
let polynomial_size = glwe_relinearisation_key.polynomial_size();
|
||||
let ciphertext_modulus = glwe_relinearisation_key.ciphertext_modulus();
|
||||
|
||||
// Construct the "glwe secret key" we want to keyswitch from, this is made up of the square
|
||||
// and cross terms appearing when squaring glwe_secret_key
|
||||
let mut input_sk_poly_list = PolynomialList::new(
|
||||
Scalar::ZERO,
|
||||
polynomial_size,
|
||||
PolynomialCount(glwe_dimension.0 * (glwe_dimension.0 + 1) / 2),
|
||||
);
|
||||
let mut input_sk_poly_list_iter = input_sk_poly_list.iter_mut();
|
||||
|
||||
for i in 0..glwe_dimension.0 {
|
||||
for j in 0..i + 1 {
|
||||
let mut input_key_pol = input_sk_poly_list_iter.next().unwrap();
|
||||
polynomial_wrapping_sub_mul_assign(
|
||||
&mut input_key_pol,
|
||||
&glwe_secret_key.as_polynomial_list().get(i),
|
||||
&glwe_secret_key.as_polynomial_list().get(j),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let input_glwe_sk = GlweSecretKey::from_container(input_sk_poly_list.as_ref(), polynomial_size);
|
||||
|
||||
let mut glwe_ks_key = GlweKeyswitchKey::from_container(
|
||||
glwe_relinearisation_key.as_mut(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
generate_glwe_keyswitch_key(
|
||||
&input_glwe_sk,
|
||||
glwe_secret_key,
|
||||
&mut glwe_ks_key,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn allocate_and_generate_glwe_relinearisation_key<Scalar, KeyCont, Gen>(
|
||||
glwe_secret_key: &GlweSecretKey<KeyCont>,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) -> GlweRelinearisationKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
let mut glwe_relinearisation_key = GlweRelinearisationKeyOwned::new(
|
||||
Scalar::ZERO,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
glwe_secret_key.polynomial_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
generate_glwe_relinearisation_key(
|
||||
glwe_secret_key,
|
||||
&mut glwe_relinearisation_key,
|
||||
noise_parameters,
|
||||
generator,
|
||||
);
|
||||
|
||||
glwe_relinearisation_key
|
||||
}
|
||||
|
||||
/*
|
||||
* Parallel variant of [`generate_glwe_relinearisation_key`]. You may want to use this
|
||||
* variant for better key generation times.
|
||||
*/
|
||||
457
tfhe/src/core_crypto/algorithms/glwe_tensor_product.rs
Normal file
457
tfhe/src/core_crypto/algorithms/glwe_tensor_product.rs
Normal file
@@ -0,0 +1,457 @@
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::prelude::*;
|
||||
|
||||
/// Compute the tensor product of the left-hand side [`GLWE ciphertext`](`GlweCiphertext`) with the
|
||||
/// right-hand side [`GLWE ciphertext`](`GlweCiphertext`)
|
||||
/// writing the result in the output [`GlweCiphertext<Vec<Scalar>>`](`GlweCiphertext<Vec<Scalar>>`).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// 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 glwe_size = GlweSize(3);
|
||||
/// let polynomial_size = PolynomialSize(512);
|
||||
/// let glwe_modular_std_dev = StandardDev(0.000000000000000000029403601535432533);
|
||||
/// let decomp_base_log = DecompositionBaseLog(3);
|
||||
/// let decomp_level_count = DecompositionLevelCount(7);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// let log_delta1 = 59;
|
||||
/// let log_delta2 = 60;
|
||||
/// let log_delta = std::cmp::min(log_delta1, log_delta2);
|
||||
/// let output_log_delta = log_delta1 + log_delta2 - log_delta;
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// // Create the GlweSecretKey
|
||||
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the first plaintext, we encrypt a single integer rather than a general polynomial
|
||||
/// let msg_1 = 3u64;
|
||||
/// let encoded_msg_1 = msg_1 << log_delta1;
|
||||
///
|
||||
/// let mut plaintext_list_1 = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
|
||||
/// plaintext_list_1.as_mut()[0] = encoded_msg_1;
|
||||
///
|
||||
/// // Create the first GlweCiphertext
|
||||
/// let mut glwe_1 = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut glwe_1,
|
||||
/// &plaintext_list_1,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Create the second plaintext
|
||||
/// let msg_2 = 2u64;
|
||||
/// let encoded_msg_2 = msg_2 << log_delta2;
|
||||
///
|
||||
/// let mut plaintext_list_2 = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
|
||||
/// plaintext_list_2.as_mut()[0] = encoded_msg_2;
|
||||
///
|
||||
/// // Create the second GlweCiphertext
|
||||
/// let mut glwe_2 = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// encrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut glwe_2,
|
||||
/// &plaintext_list_2,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// // Perform the tensor product
|
||||
/// let scale = 1u64 << log_delta;
|
||||
/// let tensor_output = glwe_tensor_product(&glwe_1, &glwe_2, scale);
|
||||
///
|
||||
/// // Compute the tensor product key
|
||||
/// let tensor_glwe_dim = GlweDimension((glwe_size.0 - 1) * (glwe_size.0 + 2) / 2);
|
||||
/// let mut tensor_key_poly_list =
|
||||
/// PolynomialList::new(0u64, polynomial_size, PolynomialCount(tensor_glwe_dim.0));
|
||||
/// let mut key_iter = tensor_key_poly_list.iter_mut();
|
||||
///
|
||||
/// for i in 0..glwe_size.0 - 1 {
|
||||
/// for j in 0..i + 1 {
|
||||
/// let mut key_pol = key_iter.next().unwrap();
|
||||
/// polynomial_wrapping_sub_mul_assign(
|
||||
/// &mut key_pol,
|
||||
/// &glwe_secret_key.as_polynomial_list().get(i),
|
||||
/// &glwe_secret_key.as_polynomial_list().get(j),
|
||||
/// );
|
||||
/// }
|
||||
/// let mut key_pol = key_iter.next().unwrap();
|
||||
/// polynomial_wrapping_add_assign(&mut key_pol, &glwe_secret_key.as_polynomial_list().get(i));
|
||||
/// }
|
||||
///
|
||||
/// let tensor_key = GlweSecretKey::from_container(tensor_key_poly_list.as_ref(), polynomial_size);
|
||||
///
|
||||
/// // Decrypt the tensor product ciphertext
|
||||
/// let mut output_plaintext = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(2), DecompositionLevelCount(4));
|
||||
///
|
||||
/// decrypt_glwe_ciphertext(&tensor_key, &tensor_output, &mut output_plaintext);
|
||||
/// output_plaintext
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext = output_plaintext.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt = *elt >> output_log_delta);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext = cleartext;
|
||||
///
|
||||
/// // Compute what the product should be
|
||||
/// let pt1 = Polynomial::from_container(
|
||||
/// plaintext_list_1
|
||||
/// .into_container()
|
||||
/// .iter()
|
||||
/// .map(|&x| <u64 as CastInto<u128>>::cast_into(x))
|
||||
/// .collect::<Vec<_>>(),
|
||||
/// );
|
||||
/// let pt2 = Polynomial::from_container(
|
||||
/// plaintext_list_2
|
||||
/// .into_container()
|
||||
/// .iter()
|
||||
/// .map(|&x| <u64 as CastInto<u128>>::cast_into(x))
|
||||
/// .collect::<Vec<_>>(),
|
||||
/// );
|
||||
///
|
||||
/// let mut product = Polynomial::new(0u128, polynomial_size);
|
||||
/// polynomial_wrapping_mul(&mut product, &pt1, &pt2);
|
||||
///
|
||||
/// let mut scaled_product = Polynomial::new(0u64, polynomial_size);
|
||||
/// scaled_product
|
||||
/// .as_mut()
|
||||
/// .iter_mut()
|
||||
/// .zip(product.as_ref().iter())
|
||||
/// .for_each(|(dest, &source)| {
|
||||
/// *dest = u64::cast_from(source / <u64 as CastInto<u128>>::cast_into(scale))
|
||||
/// >> output_log_delta
|
||||
/// });
|
||||
///
|
||||
/// // Check we recovered the correct message
|
||||
/// cleartext
|
||||
/// .iter()
|
||||
/// .zip(scaled_product.iter())
|
||||
/// .for_each(|(&elt, coeff)| assert_eq!(elt, *coeff));
|
||||
///
|
||||
/// let glwe_relin_key = allocate_and_generate_glwe_relinearisation_key(
|
||||
/// &glwe_secret_key,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// glwe_modular_std_dev,
|
||||
/// ciphertext_modulus,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_glwe_ciphertext =
|
||||
/// GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
///
|
||||
/// glwe_relinearisation(&tensor_output, &glwe_relin_key, &mut output_glwe_ciphertext);
|
||||
///
|
||||
/// // Decrypt the output glwe ciphertext
|
||||
/// let mut output_plaintext = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// decrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &output_glwe_ciphertext,
|
||||
/// &mut output_plaintext,
|
||||
/// );
|
||||
/// output_plaintext
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext = output_plaintext.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt = *elt >> output_log_delta);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext = cleartext;
|
||||
///
|
||||
/// // Check we recovered the correct message
|
||||
/// cleartext
|
||||
/// .iter()
|
||||
/// .zip(scaled_product.iter())
|
||||
/// .for_each(|(&elt, coeff)| assert_eq!(elt, *coeff));
|
||||
/// ```
|
||||
/// based on algorithm 1 of `<https://eprint.iacr.org/2021/729.pdf>` in the eprint paper the result
|
||||
/// of the division is rounded,
|
||||
/// here the division in u128 performs a floor hence the induced error might be twice as large
|
||||
pub fn glwe_tensor_product<InputCont, Scalar>(
|
||||
input_glwe_ciphertext_lhs: &GlweCiphertext<InputCont>,
|
||||
input_glwe_ciphertext_rhs: &GlweCiphertext<InputCont>,
|
||||
scale: Scalar,
|
||||
) -> GlweCiphertext<Vec<Scalar>>
|
||||
where
|
||||
Scalar: UnsignedTorus + CastInto<u128> + CastFrom<u128>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
input_glwe_ciphertext_lhs.polynomial_size().0
|
||||
== input_glwe_ciphertext_rhs.polynomial_size().0,
|
||||
"The input glwe ciphertexts do not have the same polynomial size. The polynomial size of \
|
||||
the lhs is {}, while for the rhs it is {}.",
|
||||
input_glwe_ciphertext_lhs.polynomial_size().0,
|
||||
input_glwe_ciphertext_rhs.polynomial_size().0
|
||||
);
|
||||
|
||||
assert!(
|
||||
input_glwe_ciphertext_lhs.glwe_size().0 == input_glwe_ciphertext_rhs.glwe_size().0,
|
||||
"The input glwe ciphertexts do not have the same glwe size. The glwe size of the lhs is \
|
||||
{}, while for the rhs it is {}.",
|
||||
input_glwe_ciphertext_lhs.glwe_size().0,
|
||||
input_glwe_ciphertext_rhs.glwe_size().0
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
input_glwe_ciphertext_lhs.ciphertext_modulus(),
|
||||
input_glwe_ciphertext_rhs.ciphertext_modulus()
|
||||
);
|
||||
|
||||
let k = input_glwe_ciphertext_lhs.glwe_size().to_glwe_dimension().0;
|
||||
|
||||
// This is k + k*(k-1)/2 + k: k square terms, k*(k-1)/2 cross terms, k linear terms
|
||||
let new_k = GlweDimension(k * (k + 3) / 2);
|
||||
|
||||
let mut output_glwe_ciphertext = GlweCiphertextOwned::new(
|
||||
Scalar::ZERO,
|
||||
new_k.to_glwe_size(),
|
||||
input_glwe_ciphertext_lhs.polynomial_size(),
|
||||
input_glwe_ciphertext_lhs.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
let mut output_mask = output_glwe_ciphertext.get_mut_mask();
|
||||
let mut output_mask_poly_list = output_mask.as_mut_polynomial_list();
|
||||
let mut iter_output_mask = output_mask_poly_list.iter_mut();
|
||||
let input_lhs = PolynomialList::from_container(
|
||||
input_glwe_ciphertext_lhs
|
||||
.get_mask()
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
|
||||
.collect::<Vec<_>>(),
|
||||
input_glwe_ciphertext_lhs.polynomial_size(),
|
||||
);
|
||||
let input_rhs = PolynomialList::from_container(
|
||||
input_glwe_ciphertext_rhs
|
||||
.get_mask()
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
|
||||
.collect::<Vec<_>>(),
|
||||
input_glwe_ciphertext_rhs.polynomial_size(),
|
||||
);
|
||||
|
||||
for (i, a_lhs_i) in input_lhs.iter().enumerate() {
|
||||
for (j, a_rhs_j) in input_rhs.iter().enumerate() {
|
||||
if i == j {
|
||||
//tensor elements corresponding to key -s_i^2
|
||||
let mut temp_poly_sq = Polynomial::new(0u128, a_lhs_i.polynomial_size());
|
||||
polynomial_wrapping_add_mul_assign(&mut temp_poly_sq, &a_lhs_i, &a_rhs_j);
|
||||
|
||||
let mut output_poly_sq = iter_output_mask.next().unwrap();
|
||||
output_poly_sq
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.zip(temp_poly_sq.as_ref().iter())
|
||||
.for_each(|(dest, &source)| {
|
||||
*dest =
|
||||
Scalar::cast_from(source / <Scalar as CastInto<u128>>::cast_into(scale))
|
||||
});
|
||||
|
||||
//tensor elements corresponding to key s_i
|
||||
let mut temp_poly_s1 = Polynomial::new(0u128, a_lhs_i.polynomial_size());
|
||||
polynomial_wrapping_add_mul_assign(
|
||||
&mut temp_poly_s1,
|
||||
&a_lhs_i,
|
||||
&Polynomial::from_container(
|
||||
input_glwe_ciphertext_rhs
|
||||
.get_body()
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
);
|
||||
|
||||
let mut temp_poly_s2 = Polynomial::new(0u128, a_lhs_i.polynomial_size());
|
||||
polynomial_wrapping_add_mul_assign(
|
||||
&mut temp_poly_s2,
|
||||
&Polynomial::from_container(
|
||||
input_glwe_ciphertext_lhs
|
||||
.get_body()
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
&a_rhs_j,
|
||||
);
|
||||
|
||||
polynomial_wrapping_add_assign(&mut temp_poly_s1, &temp_poly_s2);
|
||||
let mut output_poly_s = iter_output_mask.next().unwrap();
|
||||
output_poly_s
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.zip(temp_poly_s1.as_ref().iter())
|
||||
.for_each(|(dest, &source)| {
|
||||
*dest =
|
||||
Scalar::cast_from(source / <Scalar as CastInto<u128>>::cast_into(scale))
|
||||
});
|
||||
} else {
|
||||
//when i and j are different we only compute the terms where j < i
|
||||
if j < i {
|
||||
//tensor element corresponding to key -s_i*s_j
|
||||
let mut temp_poly = Polynomial::new(0u128, a_lhs_i.polynomial_size());
|
||||
polynomial_wrapping_add_mul_assign(&mut temp_poly, &a_lhs_i, &a_rhs_j);
|
||||
polynomial_wrapping_add_mul_assign(
|
||||
&mut temp_poly,
|
||||
&input_lhs.get(j),
|
||||
&input_rhs.get(i),
|
||||
);
|
||||
|
||||
let mut output_poly = iter_output_mask.next().unwrap();
|
||||
output_poly
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.zip(temp_poly.as_ref().iter())
|
||||
.for_each(|(dest, &source)| {
|
||||
*dest = Scalar::cast_from(
|
||||
source / <Scalar as CastInto<u128>>::cast_into(scale),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//tensor element corresponding to the body
|
||||
let mut temp_poly_body = Polynomial::new(0u128, input_glwe_ciphertext_lhs.polynomial_size());
|
||||
polynomial_wrapping_add_mul_assign(
|
||||
&mut temp_poly_body,
|
||||
&Polynomial::from_container(
|
||||
input_glwe_ciphertext_lhs
|
||||
.get_body()
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
&Polynomial::from_container(
|
||||
input_glwe_ciphertext_rhs
|
||||
.get_body()
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
);
|
||||
let mut output_body = output_glwe_ciphertext.get_mut_body();
|
||||
let mut output_poly_body = output_body.as_mut_polynomial();
|
||||
output_poly_body
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.zip(temp_poly_body.as_ref().iter())
|
||||
.for_each(|(dest, &source)| {
|
||||
*dest = Scalar::cast_from(source / <Scalar as CastInto<u128>>::cast_into(scale))
|
||||
});
|
||||
output_glwe_ciphertext
|
||||
}
|
||||
|
||||
/// Relinearise the [`GLWE ciphertext`](`GlweCiphertext`) that is output by the
|
||||
/// glwe_tensor_product operation using a [`GLWE relinearisation key`](`GlweRelinearisationKey`).
|
||||
pub fn glwe_relinearisation<InputCont, KeyCont, OutputCont, Scalar>(
|
||||
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
|
||||
relinearisation_key: &GlweRelinearisationKey<KeyCont>,
|
||||
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(
|
||||
relinearisation_key.glwe_dimension().0 * (relinearisation_key.glwe_dimension().0 + 3) / 2,
|
||||
input_glwe_ciphertext.glwe_size().to_glwe_dimension().0
|
||||
);
|
||||
assert_eq!(
|
||||
relinearisation_key.glwe_size(),
|
||||
output_glwe_ciphertext.glwe_size()
|
||||
);
|
||||
assert_eq!(
|
||||
relinearisation_key.polynomial_size(),
|
||||
input_glwe_ciphertext.polynomial_size()
|
||||
);
|
||||
assert_eq!(
|
||||
relinearisation_key.polynomial_size(),
|
||||
output_glwe_ciphertext.polynomial_size()
|
||||
);
|
||||
|
||||
// Clear the output ciphertext, as it will get updated gradually
|
||||
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
// Copy the input body to the output ciphertext
|
||||
polynomial_wrapping_add_assign(
|
||||
&mut output_glwe_ciphertext.get_mut_body().as_mut_polynomial(),
|
||||
&input_glwe_ciphertext.get_body().as_polynomial(),
|
||||
);
|
||||
|
||||
// We instantiate a decomposer
|
||||
let decomposer = SignedDecomposer::new(
|
||||
relinearisation_key.decomposition_base_log(),
|
||||
relinearisation_key.decomposition_level_count(),
|
||||
);
|
||||
|
||||
let mut relin_key_iter = relinearisation_key.iter();
|
||||
let input_glwe_mask = input_glwe_ciphertext.get_mask();
|
||||
let input_glwe_mask_poly_list = input_glwe_mask.as_polynomial_list();
|
||||
let mut input_poly_iter = input_glwe_mask_poly_list.iter();
|
||||
|
||||
for i in 0..relinearisation_key.glwe_size().0 - 1 {
|
||||
for _ in 0..i + 1 {
|
||||
let ksk = relin_key_iter.next().unwrap();
|
||||
let pol = input_poly_iter.next().unwrap();
|
||||
let mut decomposition_iter = decomposer.decompose_slice(pol.as_ref());
|
||||
// loop over the number of levels in reverse (from highest to lowest)
|
||||
for level_key_ciphertext in ksk.iter().rev() {
|
||||
let decomposed = decomposition_iter.next_term().unwrap();
|
||||
polynomial_list_wrapping_sub_scalar_mul_assign(
|
||||
&mut output_glwe_ciphertext.as_mut_polynomial_list(),
|
||||
&level_key_ciphertext.as_polynomial_list(),
|
||||
&Polynomial::from_container(decomposed.as_slice()),
|
||||
);
|
||||
}
|
||||
}
|
||||
let pol = input_poly_iter.next().unwrap();
|
||||
polynomial_wrapping_add_assign(
|
||||
&mut output_glwe_ciphertext.as_mut_polynomial_list().get_mut(i),
|
||||
&pol,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -779,16 +779,23 @@ pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
|
||||
|
||||
output.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
let mut tmp_zero_encryption =
|
||||
LweCiphertext::new(Scalar::ZERO, output.lwe_size(), output.ciphertext_modulus());
|
||||
|
||||
let mut ct_choice = vec![Scalar::ZERO; lwe_public_key.zero_encryption_count().0];
|
||||
|
||||
generator.fill_slice_with_random_uniform_binary(&mut ct_choice);
|
||||
|
||||
// Add the public encryption of zeros to get the zero encryption
|
||||
for (&chosen, public_encryption_of_zero) in ct_choice.iter().zip(lwe_public_key.iter()) {
|
||||
// TODO: can leak choice of ciphertexts
|
||||
if chosen == Scalar::ONE {
|
||||
lwe_ciphertext_add_assign(output, &public_encryption_of_zero);
|
||||
}
|
||||
// chosen is 1 if chosen, 0 otherwise, so use a multiplication to avoid having a branch
|
||||
// depending on a value that's supposed to remain secret
|
||||
lwe_ciphertext_cleartext_mul(
|
||||
&mut tmp_zero_encryption,
|
||||
&public_encryption_of_zero,
|
||||
Cleartext(chosen),
|
||||
);
|
||||
lwe_ciphertext_add_assign(output, &tmp_zero_encryption);
|
||||
}
|
||||
|
||||
let body = output.get_mut_body();
|
||||
@@ -905,7 +912,7 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
|
||||
|
||||
let ciphertext_modulus = output.ciphertext_modulus();
|
||||
|
||||
let mut tmp_ciphertext =
|
||||
let mut tmp_zero_encryption =
|
||||
LweCiphertext::new(Scalar::ZERO, lwe_public_key.lwe_size(), ciphertext_modulus);
|
||||
|
||||
let mut random_generator =
|
||||
@@ -913,7 +920,7 @@ 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();
|
||||
let (mut mask, body) = tmp_zero_encryption.get_mut_mask_and_body();
|
||||
random_generator
|
||||
.fill_slice_with_random_uniform_custom_mod(mask.as_mut(), ciphertext_modulus);
|
||||
if !ciphertext_modulus.is_native_modulus() {
|
||||
@@ -924,10 +931,10 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
|
||||
}
|
||||
*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);
|
||||
}
|
||||
// chosen is 1 if chosen, 0 otherwise, so use a multiplication to avoid having a branch
|
||||
// depending on a value that's supposed to remain secret
|
||||
lwe_ciphertext_cleartext_mul_assign(&mut tmp_zero_encryption, Cleartext(chosen));
|
||||
lwe_ciphertext_add_assign(output, &tmp_zero_encryption);
|
||||
}
|
||||
|
||||
// Add encoded plaintext
|
||||
|
||||
@@ -512,7 +512,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
|
||||
// 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),
|
||||
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
ct0.as_mut()
|
||||
|
||||
@@ -25,7 +25,8 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
|
||||
|
||||
/// Perform a blind rotation given an input [`LWE ciphertext`](`LweCiphertext`), modifying a look-up
|
||||
/// table passed as a [`GLWE ciphertext`](`GlweCiphertext`) and an [`LWE bootstrap
|
||||
/// key`](`LweBootstrapKey`) in the fourier domain.
|
||||
/// key`](`LweBootstrapKey`) in the fourier domain see [`fourier LWE bootstrap
|
||||
/// key`](`FourierLweBootstrapKey`).
|
||||
///
|
||||
/// If you want to manage the computation memory manually you can use
|
||||
/// [`blind_rotate_assign_mem_optimized`].
|
||||
@@ -348,8 +349,6 @@ pub fn add_external_product_assign<Scalar, OutputGlweCont, InputGlweCont, GgswCo
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
@@ -502,7 +501,7 @@ pub fn add_external_product_assign_mem_optimized<Scalar, OutputGlweCont, InputGl
|
||||
// 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),
|
||||
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
out.as_mut()
|
||||
@@ -791,7 +790,7 @@ pub fn cmux_assign_mem_optimized<Scalar, Cont0, Cont1, GgswCont>(
|
||||
// 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),
|
||||
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
ct0.as_mut()
|
||||
@@ -811,7 +810,8 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
|
||||
|
||||
/// 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
|
||||
/// key`](`LweBootstrapKey`) in the fourier domain see [`fourier LWE bootstrap
|
||||
/// key`](`FourierLweBootstrapKey`). The result is written in the provided output
|
||||
/// [`LWE ciphertext`](`LweCiphertext`).
|
||||
///
|
||||
/// If you want to manage the computation memory manually you can use
|
||||
@@ -1119,11 +1119,12 @@ pub fn programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement<Scalar>(
|
||||
|
||||
/// 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
|
||||
/// key`](`LweBootstrapKey`) in the fourier domain using f128 see [`fourier LWE bootstrap
|
||||
/// key`](`Fourier128LweBootstrapKey`). 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`].
|
||||
/// [`programmable_bootstrap_f128_lwe_ciphertext_mem_optimized`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
//! Module containing primitives pertaining to LWE ciphertext private functional keyswitch and
|
||||
//! packing keyswitch.
|
||||
//!
|
||||
//! Formal description can be found in: \
|
||||
//! Chillotti, I., Gama, N., Georgieva, M. et al. \
|
||||
//! TFHE: Fast Fully Homomorphic Encryption Over the Torus. \
|
||||
//! J. Cryptol 33, 34–91 (2020). \
|
||||
//! <https://doi.org/10.1007/s00145-019-09319-x>
|
||||
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
//use crate::core_crypto::algorithms::slice_algorithms::*;
|
||||
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
//use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Apply a public functional packing keyswitch on an input
|
||||
/// [`LWE ciphertext list`](`LweCiphertextList`) and write
|
||||
/// the result in an output [`GLWE ciphertext`](`GlweCiphertext`).
|
||||
/// # Example
|
||||
/// ```
|
||||
/// //define the inputs for the public functional key switching
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// let lwe_dimension = LweDimension(742);
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// let lwe_secret_key: LweSecretKeyOwned<u64> =
|
||||
/// LweSecretKey::generate_new_binary(lwe_dimension, &mut secret_generator);
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let glwe_secret_key: GlweSecretKeyOwned<u64> = GlweSecretKey::generate_new_binary(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
/// let mut lwe_pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// lwe_dimension,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// generate_lwe_public_functional_packing_keyswitch_key(
|
||||
/// &lwe_secret_key,
|
||||
/// &glwe_secret_key,
|
||||
/// &mut lwe_pubfpksk,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let lwe_ciphertext_count = LweCiphertextCount(20);
|
||||
/// let mut lwe_list = LweCiphertextList::new(
|
||||
/// 0u64,
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// lwe_ciphertext_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
/// let lwe_plaintext_list = PlaintextList::new(1u64 << 59, PlaintextCount(20));
|
||||
/// encrypt_lwe_ciphertext_list(
|
||||
/// &lwe_secret_key,
|
||||
/// &mut lwe_list,
|
||||
/// &lwe_plaintext_list,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_glwe_ciphertext =
|
||||
/// GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
|
||||
/// public_functional_keyswitch_lwe_ciphertexts_into_glwe_ciphertext(
|
||||
/// &mut lwe_pubfpksk,
|
||||
/// &mut output_glwe_ciphertext,
|
||||
/// &lwe_list,
|
||||
/// |mut x| {
|
||||
/// let mut sum = 0u64;
|
||||
/// x.iter().for_each(|y| sum = sum.wrapping_add(*y));
|
||||
/// let mut temp = vec![sum];
|
||||
/// temp.resize(polynomial_size.0, 0u64);
|
||||
/// Polynomial::from_container(temp)
|
||||
/// },
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(1), DecompositionLevelCount(4));
|
||||
///
|
||||
/// decrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &output_glwe_ciphertext,
|
||||
/// &mut output_plaintext_list,
|
||||
/// );
|
||||
/// output_plaintext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|x| *x.0 = decomposer.closest_representable(*x.0));
|
||||
///
|
||||
/// // Get the raw vecor
|
||||
/// let mut cleartext = output_plaintext_list.into_container();
|
||||
/// // Remove the encoding
|
||||
/// cleartext.iter_mut().for_each(|x| *x = *x >> 59);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext = cleartext;
|
||||
///
|
||||
/// // Check we get the correct result
|
||||
/// for (index, clear) in cleartext.iter().enumerate() {
|
||||
/// if index == 0 {
|
||||
/// assert_eq!(20, *clear);
|
||||
/// } else {
|
||||
/// assert_eq!(0, *clear);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn public_functional_keyswitch_lwe_ciphertexts_into_glwe_ciphertext<
|
||||
KeyCont,
|
||||
InputCont,
|
||||
OutputCont,
|
||||
Func,
|
||||
Scalar,
|
||||
>(
|
||||
lwe_pubfpksk: &LwePublicFunctionalPackingKeyswitchKey<KeyCont>,
|
||||
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
|
||||
input_lwe_ciphertext_list: &LweCiphertextList<InputCont>,
|
||||
f: Func,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
Func: Fn(Vec<Scalar>) -> Polynomial<Vec<Scalar>>,
|
||||
{
|
||||
assert_eq!(
|
||||
output_glwe_ciphertext.polynomial_size(),
|
||||
lwe_pubfpksk.output_polynomial_size()
|
||||
);
|
||||
assert_eq!(
|
||||
output_glwe_ciphertext.glwe_size(),
|
||||
lwe_pubfpksk.output_glwe_size()
|
||||
);
|
||||
assert_eq!(
|
||||
input_lwe_ciphertext_list.lwe_size().to_lwe_dimension(),
|
||||
lwe_pubfpksk.input_lwe_key_dimension()
|
||||
);
|
||||
//evaluate the function on this list of first elements
|
||||
let mut list_output_function =
|
||||
Vec::with_capacity(input_lwe_ciphertext_list.lwe_ciphertext_count().0);
|
||||
for i in 0..input_lwe_ciphertext_list.lwe_size().to_lwe_dimension().0 {
|
||||
//get list of ith elements of the input lwes
|
||||
let vec_of_ai: Vec<Scalar> = input_lwe_ciphertext_list
|
||||
.iter()
|
||||
.map(|lwe| lwe.get_mask().as_ref()[i])
|
||||
.collect();
|
||||
list_output_function.push(f(vec_of_ai));
|
||||
}
|
||||
// We reset the output
|
||||
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
|
||||
let vec_of_b: Vec<Scalar> = input_lwe_ciphertext_list
|
||||
.iter()
|
||||
.map(|lwe| *lwe.get_body().data)
|
||||
.collect();
|
||||
|
||||
assert!(f(vec_of_b.clone()).polynomial_size() == output_glwe_ciphertext.polynomial_size(),
|
||||
"the polynomial size of the output_glwe_ciphertext value needs to be equal to the polynomial size of the output of the function f");
|
||||
|
||||
//initiate the body of the output glwe ciphertext
|
||||
output_glwe_ciphertext
|
||||
.get_mut_body()
|
||||
.as_mut()
|
||||
.copy_from_slice(f(vec_of_b).as_ref());
|
||||
|
||||
//decompose the result of the function
|
||||
// We instantiate a decomposer
|
||||
let decomposer = SignedDecomposer::new(
|
||||
lwe_pubfpksk.decomposition_base_log(),
|
||||
lwe_pubfpksk.decomposition_level_count(),
|
||||
);
|
||||
for (keyswitch_key_block, output_function) in
|
||||
lwe_pubfpksk.iter().zip(list_output_function.iter_mut())
|
||||
{
|
||||
let mut decomposition_iter = decomposer.decompose_slice(output_function.as_ref());
|
||||
// loop over the number of levels in reverse (from highest to lowest)
|
||||
for level_key_ciphertext in keyswitch_key_block.iter().rev() {
|
||||
let decomposed = decomposition_iter.next_term().unwrap();
|
||||
polynomial_list_wrapping_sub_scalar_mul_assign(
|
||||
&mut output_glwe_ciphertext.as_mut_polynomial_list(),
|
||||
&level_key_ciphertext.as_polynomial_list(),
|
||||
&Polynomial::from_container(decomposed.as_slice()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
//! Module containing primitives pertaining to [`LWE public functional packing keyswitch key
|
||||
//! generation`](`LwePublicFunctionalPackingKeyswitchKey`).
|
||||
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// Fill an [`LWE public functional packing keyswitch
|
||||
/// key`](`LwePublicFunctionalPackingKeyswitchKey`) with an actual key.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
/// let lwe_dimension = LweDimension(8);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create the PRNG
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
///
|
||||
/// let input_lwe_secret_key =
|
||||
/// LweSecretKey::generate_new_binary(lwe_dimension, &mut secret_generator);
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let output_glwe_secret_key = GlweSecretKey::generate_new_binary(
|
||||
/// glwe_size.to_glwe_dimension(),
|
||||
/// polynomial_size,
|
||||
/// &mut secret_generator,
|
||||
/// );
|
||||
///
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let mut lwe_pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// lwe_dimension,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
/// generate_lwe_public_functional_packing_keyswitch_key(
|
||||
/// &input_lwe_secret_key,
|
||||
/// &output_glwe_secret_key,
|
||||
/// &mut lwe_pubfpksk,
|
||||
/// glwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn generate_lwe_public_functional_packing_keyswitch_key<
|
||||
Scalar,
|
||||
InputKeyCont,
|
||||
OutputKeyCont,
|
||||
KSKeyCont,
|
||||
Gen,
|
||||
>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
lwe_pubfpksk: &mut LwePublicFunctionalPackingKeyswitchKey<KSKeyCont>,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar>,
|
||||
KSKeyCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
input_lwe_secret_key.lwe_dimension() == lwe_pubfpksk.input_lwe_key_dimension(),
|
||||
"Mismatched LweDimension between input_lwe_secret_key {:?} and lwe_pfpksk input dimension \
|
||||
{:?}.",
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
lwe_pubfpksk.input_lwe_key_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_secret_key.glwe_dimension() == lwe_pubfpksk.output_glwe_key_dimension(),
|
||||
"Mismatched GlweDimension between output_glwe_secret_key {:?} and lwe_pfpksk output \
|
||||
dimension {:?}.",
|
||||
output_glwe_secret_key.glwe_dimension(),
|
||||
lwe_pubfpksk.output_glwe_key_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_secret_key.polynomial_size() == lwe_pubfpksk.output_polynomial_size(),
|
||||
"Mismatched PolynomialSize between output_glwe_secret_key {:?} and lwe_pfpksk output \
|
||||
polynomial size {:?}.",
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
lwe_pubfpksk.output_polynomial_size()
|
||||
);
|
||||
|
||||
// We instantiate a buffer
|
||||
let mut messages = PlaintextListOwned::new(
|
||||
Scalar::ZERO,
|
||||
PlaintextCount(
|
||||
lwe_pubfpksk.decomposition_level_count().0 * lwe_pubfpksk.output_polynomial_size().0,
|
||||
),
|
||||
);
|
||||
|
||||
// We retrieve decomposition arguments
|
||||
let decomp_level_count = lwe_pubfpksk.decomposition_level_count();
|
||||
let decomp_base_log = lwe_pubfpksk.decomposition_base_log();
|
||||
let polynomial_size = lwe_pubfpksk.output_polynomial_size();
|
||||
|
||||
let last_key_iter_bit = [Scalar::MAX];
|
||||
// add minus one for the function which will be applied to the decomposed body
|
||||
// ( Scalar::MAX = -Scalar::ONE )
|
||||
let input_key_bit_iter = input_lwe_secret_key
|
||||
.as_ref()
|
||||
.iter()
|
||||
.chain(last_key_iter_bit.iter());
|
||||
|
||||
//TODO should we rename fork_pfpksk_to_pfpksk_chunks?
|
||||
let gen_iter = generator
|
||||
.fork_pfpksk_to_pfpksk_chunks::<Scalar>(
|
||||
decomp_level_count,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
input_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// loop over the before key blocks
|
||||
for ((&input_key_bit, mut keyswitch_key_block), mut loop_generator) in input_key_bit_iter
|
||||
.zip(lwe_pubfpksk.iter_mut())
|
||||
.zip(gen_iter)
|
||||
{
|
||||
//we reset the buffer
|
||||
|
||||
// We fill the buffer with the powers of the decomposition scale times the key bits
|
||||
for (level, mut message) in (1..=decomp_level_count.0)
|
||||
.map(DecompositionLevel)
|
||||
.zip(messages.chunks_exact_mut(polynomial_size.0))
|
||||
{
|
||||
message.as_mut()[0] = DecompositionTerm::new(level, decomp_base_log, input_key_bit)
|
||||
.to_recomposition_summand();
|
||||
}
|
||||
|
||||
// We encrypt the buffer
|
||||
encrypt_glwe_ciphertext_list(
|
||||
output_glwe_secret_key,
|
||||
&mut keyswitch_key_block,
|
||||
&messages,
|
||||
noise_parameters,
|
||||
&mut loop_generator,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel variant of [`generate_lwe_public_functional_packing_keyswitch_key`]. You may want to
|
||||
/// use this variant for better key generation times.
|
||||
pub fn par_generate_lwe_public_functional_packing_keyswitch_key<
|
||||
Scalar,
|
||||
InputKeyCont,
|
||||
OutputKeyCont,
|
||||
KSKeyCont,
|
||||
Gen,
|
||||
>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
|
||||
lwe_pubfpksk: &mut LwePublicFunctionalPackingKeyswitchKey<KSKeyCont>,
|
||||
noise_parameters: impl DispersionParameter + Sync,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus + Sync + Send,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: Container<Element = Scalar> + Sync,
|
||||
KSKeyCont: ContainerMut<Element = Scalar> + Sync,
|
||||
Gen: ParallelByteRandomGenerator,
|
||||
{
|
||||
assert!(
|
||||
input_lwe_secret_key.lwe_dimension() == lwe_pubfpksk.input_lwe_key_dimension(),
|
||||
"Mismatched LweDimension between input_lwe_secret_key {:?} and lwe_pfpksk input dimension \
|
||||
{:?}.",
|
||||
input_lwe_secret_key.lwe_dimension(),
|
||||
lwe_pubfpksk.input_lwe_key_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_secret_key.glwe_dimension() == lwe_pubfpksk.output_glwe_key_dimension(),
|
||||
"Mismatched GlweDimension between output_glwe_secret_key {:?} and lwe_pfpksk output \
|
||||
dimension {:?}.",
|
||||
output_glwe_secret_key.glwe_dimension(),
|
||||
lwe_pubfpksk.output_glwe_key_dimension()
|
||||
);
|
||||
assert!(
|
||||
output_glwe_secret_key.polynomial_size() == lwe_pubfpksk.output_polynomial_size(),
|
||||
"Mismatched PolynomialSize between output_glwe_secret_key {:?} and lwe_pfpksk output \
|
||||
polynomial size {:?}.",
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
lwe_pubfpksk.output_polynomial_size()
|
||||
);
|
||||
|
||||
// We retrieve decomposition arguments
|
||||
let decomp_level_count = lwe_pubfpksk.decomposition_level_count();
|
||||
let decomp_base_log = lwe_pubfpksk.decomposition_base_log();
|
||||
let polynomial_size = lwe_pubfpksk.output_polynomial_size();
|
||||
|
||||
let last_key_iter_bit = [Scalar::MAX];
|
||||
// add minus one for the function which will be applied to the decomposed body
|
||||
// ( Scalar::MAX = -Scalar::ONE )
|
||||
let input_key_bit_iter = input_lwe_secret_key
|
||||
.as_ref()
|
||||
.par_iter()
|
||||
.chain(last_key_iter_bit.par_iter());
|
||||
|
||||
let gen_iter = generator
|
||||
.par_fork_pfpksk_to_pfpksk_chunks::<Scalar>(
|
||||
decomp_level_count,
|
||||
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
|
||||
output_glwe_secret_key.polynomial_size(),
|
||||
input_lwe_secret_key.lwe_dimension().to_lwe_size(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let plaintext_count = PlaintextCount(
|
||||
lwe_pubfpksk.decomposition_level_count().0 * lwe_pubfpksk.output_polynomial_size().0,
|
||||
);
|
||||
|
||||
// loop over the before key blocks
|
||||
input_key_bit_iter
|
||||
.zip(lwe_pubfpksk.par_iter_mut())
|
||||
.zip(gen_iter)
|
||||
.for_each(
|
||||
|((&input_key_bit, mut keyswitch_key_block), mut loop_generator)| {
|
||||
// We instantiate a buffer
|
||||
let mut messages = PlaintextListOwned::new(Scalar::ZERO, plaintext_count);
|
||||
|
||||
// We fill the buffer with the powers of the key bits
|
||||
for (level, mut message) in (1..=decomp_level_count.0)
|
||||
.map(DecompositionLevel)
|
||||
.zip(messages.chunks_exact_mut(polynomial_size.0))
|
||||
{
|
||||
message.as_mut()[0] =
|
||||
DecompositionTerm::new(level, decomp_base_log, input_key_bit)
|
||||
.to_recomposition_summand();
|
||||
}
|
||||
|
||||
// We encrypt the buffer
|
||||
encrypt_glwe_ciphertext_list(
|
||||
output_glwe_secret_key,
|
||||
&mut keyswitch_key_block,
|
||||
&messages,
|
||||
noise_parameters,
|
||||
&mut loop_generator,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::core_crypto::commons::generators::DeterministicSeeder;
|
||||
use crate::core_crypto::commons::math::random::Seed;
|
||||
use crate::core_crypto::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_pubfpksk_list_gen_equivalence() {
|
||||
const NB_TESTS: usize = 10;
|
||||
|
||||
for _ in 0..NB_TESTS {
|
||||
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield
|
||||
// correct computations
|
||||
let glwe_dimension =
|
||||
GlweDimension(crate::core_crypto::commons::test_tools::random_usize_between(5..10));
|
||||
let polynomial_size = PolynomialSize(
|
||||
crate::core_crypto::commons::test_tools::random_usize_between(5..10),
|
||||
);
|
||||
let pubfpksk_level_count = DecompositionLevelCount(
|
||||
crate::core_crypto::commons::test_tools::random_usize_between(2..5),
|
||||
);
|
||||
let pubfpksk_base_log = DecompositionBaseLog(
|
||||
crate::core_crypto::commons::test_tools::random_usize_between(2..5),
|
||||
);
|
||||
let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
|
||||
let common_encryption_seed =
|
||||
Seed(crate::core_crypto::commons::test_tools::random_uint_between(0..u128::MAX));
|
||||
|
||||
let var_small = Variance::from_variance(2f64.powf(-80.0));
|
||||
|
||||
// Create the PRNG
|
||||
let mut seeder = new_seeder();
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
|
||||
let glwe_sk: GlweSecretKeyOwned<u64> = allocate_and_generate_new_binary_glwe_secret_key(
|
||||
glwe_dimension,
|
||||
polynomial_size,
|
||||
&mut secret_generator,
|
||||
);
|
||||
let lwe_big_sk = glwe_sk.clone().into_lwe_secret_key();
|
||||
|
||||
let mut par_pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
|
||||
0u64,
|
||||
pubfpksk_base_log,
|
||||
pubfpksk_level_count,
|
||||
lwe_big_sk.lwe_dimension(),
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let mut ser_pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
|
||||
0u64,
|
||||
pubfpksk_base_log,
|
||||
pubfpksk_level_count,
|
||||
lwe_big_sk.lwe_dimension(),
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let mut seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(common_encryption_seed);
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
|
||||
seeder.seed(),
|
||||
&mut seeder,
|
||||
);
|
||||
|
||||
par_generate_lwe_public_functional_packing_keyswitch_key(
|
||||
&lwe_big_sk,
|
||||
&glwe_sk,
|
||||
&mut par_pubfpksk,
|
||||
var_small,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
let mut seeder =
|
||||
DeterministicSeeder::<ActivatedRandomGenerator>::new(common_encryption_seed);
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
|
||||
seeder.seed(),
|
||||
&mut seeder,
|
||||
);
|
||||
|
||||
generate_lwe_public_functional_packing_keyswitch_key(
|
||||
&lwe_big_sk,
|
||||
&glwe_sk,
|
||||
&mut ser_pubfpksk,
|
||||
var_small,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
assert_eq!(par_pubfpksk, ser_pubfpksk)
|
||||
}
|
||||
}
|
||||
}
|
||||
330
tfhe/src/core_crypto/algorithms/lwe_trace_packing_keyswitch.rs
Normal file
330
tfhe/src/core_crypto/algorithms/lwe_trace_packing_keyswitch.rs
Normal file
@@ -0,0 +1,330 @@
|
||||
//! Module containing primitives pertaining to LWE trace pacling keyswitch.
|
||||
|
||||
use crate::core_crypto::algorithms::glwe_keyswitch::*;
|
||||
use crate::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// Apply a trace packing keyswitch on an input [`LWE ciphertext list`](`LweCiphertextList`) and
|
||||
/// pack the result in an output [`GLWE ciphertext`](`GlweCiphertext`).
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweTracePackingKeyswitchKey creation
|
||||
/// let lwe_dimension = LweDimension(60);
|
||||
/// let lwe_count = LweCiphertextCount(25);
|
||||
/// let polynomial_size = PolynomialSize(32);
|
||||
/// let glwe_dimension = GlweDimension(2);
|
||||
/// let lwe_modular_std_dev = StandardDev(0.00000000000000000000001);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// let mut glwe_secret_key = GlweSecretKey::new_empty_key(0u64, glwe_dimension, polynomial_size);
|
||||
///
|
||||
/// generate_tpksk_output_glwe_secret_key(&lwe_secret_key, &mut glwe_secret_key);
|
||||
///
|
||||
/// let decomp_base_log = DecompositionBaseLog(2);
|
||||
/// let decomp_level_count = DecompositionLevelCount(8);
|
||||
/// let var_small = Variance::from_variance(2f64.powf(-90.0));
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
///
|
||||
/// let mut lwe_tpksk = LweTracePackingKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// glwe_dimension.to_glwe_size(),
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// generate_lwe_trace_packing_keyswitch_key(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut lwe_tpksk,
|
||||
/// var_small,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut lwe_ctxt_list = LweCiphertextList::new(
|
||||
/// 0u64,
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// lwe_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// let msg = 1u64;
|
||||
/// let plaintext_list = PlaintextList::new(msg << 56, PlaintextCount(lwe_count.0));
|
||||
///
|
||||
/// encrypt_lwe_ciphertext_list(
|
||||
/// &lwe_secret_key,
|
||||
/// &mut lwe_ctxt_list,
|
||||
/// &plaintext_list,
|
||||
/// lwe_modular_std_dev,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_glwe_ciphertext = GlweCiphertext::new(
|
||||
/// 0u64,
|
||||
/// glwe_dimension.to_glwe_size(),
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// let mut indices = vec![0_usize; lwe_count.0];
|
||||
/// for (index, item) in indices.iter_mut().enumerate() {
|
||||
/// *item = index;
|
||||
/// }
|
||||
///
|
||||
/// trace_packing_keyswitch_lwe_ciphertext_list_into_glwe_ciphertext(
|
||||
/// &lwe_tpksk,
|
||||
/// &mut output_glwe_ciphertext,
|
||||
/// &lwe_ctxt_list,
|
||||
/// indices,
|
||||
/// );
|
||||
///
|
||||
/// let mut output_plaintext_list = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
|
||||
///
|
||||
/// decrypt_glwe_ciphertext(
|
||||
/// &glwe_secret_key,
|
||||
/// &output_glwe_ciphertext,
|
||||
/// &mut output_plaintext_list,
|
||||
/// );
|
||||
///
|
||||
/// // Round and remove encoding
|
||||
/// // First create a decomposer working on the high 8 bits corresponding to our encoding.
|
||||
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(8), DecompositionLevelCount(1));
|
||||
///
|
||||
/// output_plaintext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
|
||||
///
|
||||
/// // Get the raw vector
|
||||
/// let mut cleartext_list = output_plaintext_list.into_container();
|
||||
/// // Remove the encoding
|
||||
/// // Due to not having an inverse of 2 the packing multiplies each value by polynomial_size
|
||||
/// // Hence the encoding delta is multiplied by polynomial_size
|
||||
/// cleartext_list
|
||||
/// .iter_mut()
|
||||
/// .for_each(|elt| *elt = *elt >> 56 + polynomial_size.log2().0);
|
||||
/// // Get the list immutably
|
||||
/// let cleartext_list = cleartext_list;
|
||||
///
|
||||
/// // Check we recovered the original message for each plaintext we encrypted
|
||||
/// for (index, elt) in cleartext_list.iter().enumerate() {
|
||||
/// if index < lwe_count.0 {
|
||||
/// assert_eq!(*elt, msg);
|
||||
/// } else {
|
||||
/// assert_eq!(*elt, 0);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn trace_packing_keyswitch_lwe_ciphertext_list_into_glwe_ciphertext<
|
||||
Scalar,
|
||||
KeyCont,
|
||||
InputCont,
|
||||
OutputCont,
|
||||
>(
|
||||
lwe_tpksk: &LweTracePackingKeyswitchKey<KeyCont>,
|
||||
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
|
||||
input_lwe_ciphertext_list: &LweCiphertextList<InputCont>,
|
||||
indices: Vec<usize>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
KeyCont: Container<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
assert!(
|
||||
input_lwe_ciphertext_list.lwe_ciphertext_count().0
|
||||
<= output_glwe_ciphertext.polynomial_size().0
|
||||
);
|
||||
assert_eq!(
|
||||
input_lwe_ciphertext_list.lwe_ciphertext_count().0,
|
||||
indices.len()
|
||||
);
|
||||
assert_eq!(
|
||||
input_lwe_ciphertext_list.lwe_size(),
|
||||
lwe_tpksk.input_lwe_size()
|
||||
);
|
||||
assert!(indices
|
||||
.iter()
|
||||
.all(|&x| x < output_glwe_ciphertext.polynomial_size().0));
|
||||
assert_eq!(
|
||||
output_glwe_ciphertext.polynomial_size(),
|
||||
lwe_tpksk.polynomial_size()
|
||||
);
|
||||
assert_eq!(
|
||||
output_glwe_ciphertext.glwe_size(),
|
||||
lwe_tpksk.output_glwe_size()
|
||||
);
|
||||
assert_eq!(
|
||||
input_lwe_ciphertext_list.ciphertext_modulus(),
|
||||
lwe_tpksk.ciphertext_modulus()
|
||||
);
|
||||
assert_eq!(
|
||||
output_glwe_ciphertext.ciphertext_modulus(),
|
||||
lwe_tpksk.ciphertext_modulus()
|
||||
);
|
||||
|
||||
// We reset the output
|
||||
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
|
||||
|
||||
let poly_size = output_glwe_ciphertext.polynomial_size();
|
||||
let glwe_count = GlweCiphertextCount(poly_size.0);
|
||||
let ciphertext_modulus = output_glwe_ciphertext.ciphertext_modulus();
|
||||
|
||||
let mut glwe_list = GlweCiphertextList::new(
|
||||
Scalar::ZERO,
|
||||
output_glwe_ciphertext.glwe_size(),
|
||||
poly_size,
|
||||
glwe_count,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
// Construct the initial Glwe Ciphertexts
|
||||
for (index1, mut glwe_ct) in glwe_list.iter_mut().enumerate() {
|
||||
for (index2, index) in indices.iter().enumerate() {
|
||||
if index1 == *index {
|
||||
let lwe_ct = input_lwe_ciphertext_list.get(index2);
|
||||
let lwe_body = lwe_ct.as_ref().last().unwrap();
|
||||
let lwe_mask = lwe_ct.get_mask();
|
||||
for (index3, mut ring_element) in glwe_ct
|
||||
.get_mut_mask()
|
||||
.as_mut_polynomial_list()
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
{
|
||||
for (index4, coef) in ring_element.iter_mut().enumerate() {
|
||||
if index3 * poly_size.0 + index4 < lwe_mask.lwe_dimension().0 {
|
||||
*coef =
|
||||
coef.wrapping_add(lwe_mask.as_ref()[index3 * poly_size.0 + index4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut poly_to_add = Polynomial::new(Scalar::ZERO, poly_size);
|
||||
poly_to_add[0] = poly_to_add[0].wrapping_add(*lwe_body);
|
||||
polynomial_wrapping_add_assign(
|
||||
&mut glwe_ct.get_mut_body().as_mut_polynomial(),
|
||||
&poly_to_add,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for l in 0..poly_size.log2().0 {
|
||||
for i in 0..(poly_size.0 / 2_usize.pow(l as u32 + 1)) {
|
||||
let ct_0 = glwe_list.get(i);
|
||||
let glwe_size = ct_0.glwe_size();
|
||||
let j = (poly_size.0 / 2_usize.pow(l as u32 + 1)) + i;
|
||||
let ct_1 = glwe_list.get(j);
|
||||
if ct_0.as_ref().iter().any(|&x| x != Scalar::ZERO)
|
||||
|| ct_1.as_ref().iter().any(|&x| x != Scalar::ZERO)
|
||||
{
|
||||
// Rotate ct_1 by N/2^(l+1)
|
||||
for mut pol in glwe_list.get_mut(j).as_mut_polynomial_list().iter_mut() {
|
||||
polynomial_wrapping_monic_monomial_mul_assign(
|
||||
&mut pol,
|
||||
MonomialDegree(poly_size.0 / 2_usize.pow(l as u32 + 1)),
|
||||
);
|
||||
}
|
||||
|
||||
let mut ct_plus =
|
||||
GlweCiphertext::new(Scalar::ZERO, glwe_size, poly_size, ciphertext_modulus);
|
||||
let mut ct_minus =
|
||||
GlweCiphertext::new(Scalar::ZERO, glwe_size, poly_size, ciphertext_modulus);
|
||||
|
||||
for ((mut pol_plus, pol_0), pol_1) in ct_plus
|
||||
.as_mut_polynomial_list()
|
||||
.iter_mut()
|
||||
.zip(glwe_list.get(i).as_polynomial_list().iter())
|
||||
.zip(glwe_list.get(j).as_polynomial_list().iter())
|
||||
{
|
||||
polynomial_wrapping_add_assign(&mut pol_plus, &pol_0);
|
||||
polynomial_wrapping_add_assign(&mut pol_plus, &pol_1);
|
||||
}
|
||||
|
||||
for ((mut pol_minus, pol_0), pol_1) in ct_minus
|
||||
.as_mut_polynomial_list()
|
||||
.iter_mut()
|
||||
.zip(glwe_list.get(i).as_polynomial_list().iter())
|
||||
.zip(glwe_list.get(j).as_polynomial_list().iter())
|
||||
{
|
||||
polynomial_wrapping_add_assign(&mut pol_minus, &pol_0);
|
||||
polynomial_wrapping_sub_assign(&mut pol_minus, &pol_1);
|
||||
}
|
||||
|
||||
// Now we should scale both ct_plus and ct_minus by 2^-1 mod q = (q+1) / 2 for odd q
|
||||
|
||||
let scalar = Scalar::ONE; // change to (q + 1)/2 for odd q
|
||||
if !ciphertext_modulus.is_power_of_two() {
|
||||
// Set scalar to (q + 1)/2
|
||||
}
|
||||
for mut pol in ct_plus.as_mut_polynomial_list().iter_mut() {
|
||||
polynomial_wrapping_scalar_mul_assign(&mut pol, scalar);
|
||||
}
|
||||
for mut pol in ct_minus.as_mut_polynomial_list().iter_mut() {
|
||||
polynomial_wrapping_scalar_mul_assign(&mut pol, scalar);
|
||||
}
|
||||
|
||||
// Apply the automorphism sending X to X^(2^(l+1) + 1) to ct_minus
|
||||
for mut pol in ct_minus.as_mut_polynomial_list().iter_mut() {
|
||||
apply_automorphism_assign(&mut pol, 2_usize.pow(l as u32 + 1) + 1)
|
||||
}
|
||||
|
||||
let mut ks_out = GlweCiphertext::new(
|
||||
Scalar::ZERO,
|
||||
ct_minus.glwe_size(),
|
||||
poly_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let glwe_ksk = GlweKeyswitchKey::from_container(
|
||||
lwe_tpksk.get(l).into_container(),
|
||||
lwe_tpksk.decomposition_base_log(),
|
||||
lwe_tpksk.decomposition_level_count(),
|
||||
lwe_tpksk.output_glwe_size(),
|
||||
lwe_tpksk.polynomial_size(),
|
||||
lwe_tpksk.ciphertext_modulus(),
|
||||
);
|
||||
|
||||
// Perform a Glwe keyswitch on ct_minus
|
||||
keyswitch_glwe_ciphertext(&glwe_ksk, &mut ct_minus, &mut ks_out);
|
||||
|
||||
// Set ct_0 to zero
|
||||
glwe_list.get_mut(i).as_mut().fill(Scalar::ZERO);
|
||||
|
||||
// Add the result to ct_plus and add this to ct_0
|
||||
for ((mut pol_plus, pol_ks), mut pol_0) in ct_plus
|
||||
.as_mut_polynomial_list()
|
||||
.iter_mut()
|
||||
.zip(ks_out.as_polynomial_list().iter())
|
||||
.zip(glwe_list.get_mut(i).as_mut_polynomial_list().iter_mut())
|
||||
{
|
||||
polynomial_wrapping_add_assign(&mut pol_plus, &pol_ks);
|
||||
polynomial_wrapping_add_assign(&mut pol_0, &pol_plus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let res = glwe_list.get(0);
|
||||
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
|
||||
for (mut pol_out, pol_res) in output_glwe_ciphertext
|
||||
.as_mut_polynomial_list()
|
||||
.iter_mut()
|
||||
.zip(res.as_polynomial_list().iter())
|
||||
{
|
||||
polynomial_wrapping_add_assign(&mut pol_out, &pol_res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
//! Module containing primitives pertaining to [`LWE trace packing keyswitch key
|
||||
//! generation`](`LweTracePackingKeyswitchKey`).
|
||||
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::DispersionParameter;
|
||||
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
use crate::core_crypto::prelude::polynomial_algorithms::apply_automorphism_wrapping_add_assign;
|
||||
|
||||
/// Fill a [`GLWE secret key`](`GlweSecretKey`) with an actual key derived from an
|
||||
/// [`LWE secret key`](`LweSecretKey`) for use in the [`LWE trace packing keyswitch key`]
|
||||
/// (`LweTracePackingKeyswitchKey`)
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweCiphertext creation
|
||||
/// let glwe_size = GlweSize(3);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let lwe_dimension = LweDimension(900);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let mut secret_generator =
|
||||
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
/// let lwe_secret_key =
|
||||
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
|
||||
///
|
||||
/// let mut glwe_secret_key =
|
||||
/// GlweSecretKey::new_empty_key(0u64, glwe_size.to_glwe_dimension(), polynomial_size);
|
||||
///
|
||||
/// generate_tpksk_output_glwe_secret_key(&lwe_secret_key, &mut glwe_secret_key);
|
||||
///
|
||||
/// let decomp_base_log = DecompositionBaseLog(2);
|
||||
/// let decomp_level_count = DecompositionLevelCount(8);
|
||||
/// let var_small = Variance::from_variance(2f64.powf(-80.0));
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
/// let mut encryption_generator =
|
||||
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
///
|
||||
/// let mut lwe_tpksk = LweTracePackingKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// lwe_dimension.to_lwe_size(),
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// generate_lwe_trace_packing_keyswitch_key(
|
||||
/// &glwe_secret_key,
|
||||
/// &mut lwe_tpksk,
|
||||
/// var_small,
|
||||
/// &mut encryption_generator,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn generate_tpksk_output_glwe_secret_key<Scalar, InputKeyCont, OutputKeyCont>(
|
||||
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
|
||||
output_glwe_secret_key: &mut GlweSecretKey<OutputKeyCont>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
OutputKeyCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
let lwe_dimension = input_lwe_secret_key.lwe_dimension();
|
||||
let glwe_dimension = output_glwe_secret_key.glwe_dimension();
|
||||
let glwe_poly_size = output_glwe_secret_key.polynomial_size();
|
||||
|
||||
assert!(
|
||||
lwe_dimension.0 <= glwe_dimension.0 * glwe_poly_size.0,
|
||||
"Mismatched between input_lwe_secret_key dimension {:?} and number of coefficients of \
|
||||
output_glwe_secret_key {:?}.",
|
||||
lwe_dimension.0,
|
||||
glwe_dimension.0 * glwe_poly_size.0
|
||||
);
|
||||
|
||||
let glwe_key_container = output_glwe_secret_key.as_mut();
|
||||
|
||||
for (index, lwe_key_bit) in input_lwe_secret_key.as_ref().iter().enumerate() {
|
||||
if index % glwe_poly_size.0 == 0 {
|
||||
glwe_key_container[index] = *lwe_key_bit;
|
||||
} else {
|
||||
let rem = index % glwe_poly_size.0;
|
||||
let quo = index / glwe_poly_size.0;
|
||||
let new_index = (quo + 1) * glwe_poly_size.0 - rem;
|
||||
glwe_key_container[new_index] = Scalar::ZERO.wrapping_sub(*lwe_key_bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fill an [`LWE trace packing keyswitch key`](`LweTracePackingKeyswitchKey`)
|
||||
/// with an actual key.
|
||||
pub fn generate_lwe_trace_packing_keyswitch_key<Scalar, InputKeyCont, KSKeyCont, Gen>(
|
||||
input_glwe_secret_key: &GlweSecretKey<InputKeyCont>,
|
||||
lwe_tpksk: &mut LweTracePackingKeyswitchKey<KSKeyCont>,
|
||||
noise_parameters: impl DispersionParameter,
|
||||
generator: &mut EncryptionRandomGenerator<Gen>,
|
||||
) where
|
||||
Scalar: UnsignedTorus,
|
||||
InputKeyCont: Container<Element = Scalar>,
|
||||
KSKeyCont: ContainerMut<Element = Scalar>,
|
||||
Gen: ByteRandomGenerator,
|
||||
{
|
||||
assert_eq!(
|
||||
input_glwe_secret_key.glwe_dimension(),
|
||||
lwe_tpksk.output_glwe_key_dimension()
|
||||
);
|
||||
assert_eq!(
|
||||
input_glwe_secret_key.polynomial_size(),
|
||||
lwe_tpksk.polynomial_size()
|
||||
);
|
||||
|
||||
// We retrieve decomposition arguments
|
||||
let glwe_dimension = lwe_tpksk.output_glwe_key_dimension();
|
||||
let decomp_level_count = lwe_tpksk.decomposition_level_count();
|
||||
let decomp_base_log = lwe_tpksk.decomposition_base_log();
|
||||
let polynomial_size = lwe_tpksk.polynomial_size();
|
||||
let ciphertext_modulus = lwe_tpksk.ciphertext_modulus();
|
||||
|
||||
let automorphism_index_iter = 1..=polynomial_size.log2().0;
|
||||
|
||||
let gen_iter = generator
|
||||
.fork_tpksk_to_tpksk_chunks::<Scalar>(
|
||||
decomp_level_count,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// loop over the before key blocks
|
||||
|
||||
for ((auto_index, glwe_keyswitch_block), mut loop_generator) in automorphism_index_iter
|
||||
.zip(lwe_tpksk.iter_mut())
|
||||
.zip(gen_iter)
|
||||
{
|
||||
let mut auto_glwe_sk = GlweSecretKey::new_empty_key(
|
||||
Scalar::ZERO,
|
||||
input_glwe_secret_key.glwe_dimension(),
|
||||
input_glwe_secret_key.polynomial_size(),
|
||||
);
|
||||
let input_key_block_iter = input_glwe_secret_key
|
||||
.as_ref()
|
||||
.chunks_exact(polynomial_size.0);
|
||||
let auto_key_block_iter = auto_glwe_sk.as_mut().chunks_exact_mut(polynomial_size.0);
|
||||
for (auto_key_block, input_key_block) in auto_key_block_iter.zip(input_key_block_iter) {
|
||||
let mut output_poly = Polynomial::from_container(auto_key_block);
|
||||
let input_poly = Polynomial::from_container(input_key_block);
|
||||
apply_automorphism_wrapping_add_assign(
|
||||
&mut output_poly,
|
||||
&input_poly,
|
||||
2_usize.pow(auto_index as u32) + 1,
|
||||
);
|
||||
}
|
||||
let mut glwe_ksk = GlweKeyswitchKey::from_container(
|
||||
glwe_keyswitch_block.into_container(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
generate_glwe_keyswitch_key(
|
||||
&auto_glwe_sk,
|
||||
input_glwe_secret_key,
|
||||
&mut glwe_ksk,
|
||||
noise_parameters,
|
||||
&mut loop_generator,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,12 @@
|
||||
pub mod ggsw_conversion;
|
||||
pub mod ggsw_encryption;
|
||||
pub mod glwe_encryption;
|
||||
pub mod glwe_keyswitch;
|
||||
pub mod glwe_keyswitch_key_generation;
|
||||
pub mod glwe_relinearisation_key_generation;
|
||||
pub mod glwe_sample_extraction;
|
||||
pub mod glwe_secret_key_generation;
|
||||
pub mod glwe_tensor_product;
|
||||
pub mod lwe_bootstrap_key_conversion;
|
||||
pub mod lwe_bootstrap_key_generation;
|
||||
pub mod lwe_encryption;
|
||||
@@ -19,8 +23,12 @@ pub mod lwe_multi_bit_programmable_bootstrapping;
|
||||
pub mod lwe_private_functional_packing_keyswitch;
|
||||
pub mod lwe_private_functional_packing_keyswitch_key_generation;
|
||||
pub mod lwe_programmable_bootstrapping;
|
||||
pub mod lwe_public_functional_packing_keyswitch;
|
||||
pub mod lwe_public_functional_packing_keyswitch_key_generation;
|
||||
pub mod lwe_public_key_generation;
|
||||
pub mod lwe_secret_key_generation;
|
||||
pub mod lwe_trace_packing_keyswitch;
|
||||
pub mod lwe_trace_packing_keyswitch_key_generation;
|
||||
pub mod lwe_wopbs;
|
||||
pub mod polynomial_algorithms;
|
||||
pub mod seeded_ggsw_ciphertext_decompression;
|
||||
@@ -42,8 +50,12 @@ mod test;
|
||||
pub use ggsw_conversion::*;
|
||||
pub use ggsw_encryption::*;
|
||||
pub use glwe_encryption::*;
|
||||
pub use glwe_keyswitch::*;
|
||||
pub use glwe_keyswitch_key_generation::*;
|
||||
pub use glwe_relinearisation_key_generation::*;
|
||||
pub use glwe_sample_extraction::*;
|
||||
pub use glwe_secret_key_generation::*;
|
||||
pub use glwe_tensor_product::*;
|
||||
pub use lwe_bootstrap_key_conversion::*;
|
||||
pub use lwe_bootstrap_key_generation::*;
|
||||
pub use lwe_encryption::*;
|
||||
@@ -56,8 +68,12 @@ pub use lwe_multi_bit_programmable_bootstrapping::*;
|
||||
pub use lwe_private_functional_packing_keyswitch::*;
|
||||
pub use lwe_private_functional_packing_keyswitch_key_generation::*;
|
||||
pub use lwe_programmable_bootstrapping::*;
|
||||
pub use lwe_public_functional_packing_keyswitch::*;
|
||||
pub use lwe_public_functional_packing_keyswitch_key_generation::*;
|
||||
pub use lwe_public_key_generation::*;
|
||||
pub use lwe_secret_key_generation::*;
|
||||
pub use lwe_trace_packing_keyswitch::*;
|
||||
pub use lwe_trace_packing_keyswitch_key_generation::*;
|
||||
pub use lwe_wopbs::*;
|
||||
pub use seeded_ggsw_ciphertext_decompression::*;
|
||||
pub use seeded_ggsw_ciphertext_list_decompression::*;
|
||||
|
||||
@@ -406,6 +406,33 @@ pub fn polynomial_wrapping_mul<Scalar, OutputCont, LhsCont, RhsCont>(
|
||||
polynomial_wrapping_add_mul_assign(output, lhs, rhs);
|
||||
}
|
||||
|
||||
/// Multiply a polynomial by a scalar.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
|
||||
/// unsigned integer capacity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
|
||||
/// use tfhe::core_crypto::entities::*;
|
||||
/// let mut pol = Polynomial::from_container(vec![1u8, 2, 3, 4, 5, 6]);
|
||||
/// let scalar = 127u8;
|
||||
/// polynomial_wrapping_scalar_mul_assign(&mut pol, scalar);
|
||||
/// assert_eq!(pol.as_ref(), &[127u8, 254, 125, 252, 123, 250]);
|
||||
/// ```
|
||||
pub fn polynomial_wrapping_scalar_mul_assign<Scalar, PolyCont>(
|
||||
output: &mut Polynomial<PolyCont>,
|
||||
scalar: Scalar,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
PolyCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
slice_wrapping_scalar_mul_assign(output, scalar)
|
||||
}
|
||||
|
||||
/// Fill the output polynomial, with the result of the product of two polynomials, reduced modulo
|
||||
/// $(X^{N} + 1)$ with the Karatsuba algorithm Complexity: $O(N^{1.58})$
|
||||
///
|
||||
@@ -530,6 +557,70 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn apply_automorphism_wrapping_add_assign<Scalar, OutputCont, PolyCont>(
|
||||
output: &mut Polynomial<OutputCont>,
|
||||
input: &Polynomial<PolyCont>,
|
||||
automorphism_exponent: usize,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
PolyCont: Container<Element = Scalar>,
|
||||
{
|
||||
// check input and output polynomials have the same size
|
||||
assert_eq!(input.polynomial_size(), output.polynomial_size());
|
||||
// check the automorphism exponent is odd so the function X -> X^automorphism_exponent is an
|
||||
// automorphism (assumes polysize is a power of 2)
|
||||
assert_eq!(automorphism_exponent % 2, 1);
|
||||
|
||||
let poly_size = input.polynomial_size().0;
|
||||
|
||||
for (index, coef) in input.iter().enumerate() {
|
||||
let new_index = (index * automorphism_exponent) % poly_size;
|
||||
if (index * automorphism_exponent) % (2 * poly_size) == new_index {
|
||||
output[new_index] = output[new_index].wrapping_add(*coef);
|
||||
} else {
|
||||
output[new_index] = output[new_index].wrapping_sub(*coef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_automorphism_assign<Scalar, PolyCont>(
|
||||
input: &mut Polynomial<PolyCont>,
|
||||
automorphism_exponent: usize,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
PolyCont: ContainerMut<Element = Scalar>,
|
||||
{
|
||||
let mut temp = Polynomial::new(Scalar::ZERO, input.polynomial_size());
|
||||
apply_automorphism_wrapping_add_assign(&mut temp, input, automorphism_exponent);
|
||||
input.fill(Scalar::ZERO);
|
||||
polynomial_wrapping_add_assign(input, &temp);
|
||||
}
|
||||
|
||||
pub fn polynomial_list_wrapping_sub_scalar_mul_assign<Scalar, InputCont, OutputCont, PolyCont>(
|
||||
output_poly_list: &mut PolynomialList<OutputCont>,
|
||||
input_poly_list: &PolynomialList<InputCont>,
|
||||
scalar_poly: &Polynomial<PolyCont>,
|
||||
) where
|
||||
Scalar: UnsignedInteger,
|
||||
OutputCont: ContainerMut<Element = Scalar>,
|
||||
InputCont: Container<Element = Scalar>,
|
||||
PolyCont: Container<Element = Scalar>,
|
||||
{
|
||||
assert_eq!(
|
||||
output_poly_list.polynomial_size(),
|
||||
input_poly_list.polynomial_size()
|
||||
);
|
||||
assert_eq!(
|
||||
output_poly_list.polynomial_count(),
|
||||
input_poly_list.polynomial_count()
|
||||
);
|
||||
for (mut output_poly, input_poly) in output_poly_list.iter_mut().zip(input_poly_list.iter()) {
|
||||
polynomial_wrapping_sub_mul_assign(&mut output_poly, &input_poly, scalar_poly)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::Rng;
|
||||
|
||||
@@ -164,7 +164,7 @@ pub const TEST_PARAMS_3_BITS_127_U128: TestParams<u128> = TestParams {
|
||||
cbs_level: DecompositionLevelCount(0),
|
||||
cbs_base_log: DecompositionBaseLog(0),
|
||||
message_modulus_log: CiphertextModulusLog(3),
|
||||
ciphertext_modulus: CiphertextModulus::new_unchecked(1 << 127),
|
||||
ciphertext_modulus: unsafe { CiphertextModulus::new_unchecked(1 << 127) },
|
||||
};
|
||||
|
||||
fn lwe_encrypt_pbs_f128_decrypt_custom_mod<
|
||||
|
||||
@@ -87,7 +87,7 @@ pub const TEST_PARAMS_3_BITS_63_U64: TestParams<u64> = TestParams {
|
||||
cbs_level: DecompositionLevelCount(0),
|
||||
cbs_base_log: DecompositionBaseLog(0),
|
||||
message_modulus_log: CiphertextModulusLog(3),
|
||||
ciphertext_modulus: CiphertextModulus::new_unchecked(1 << 63),
|
||||
ciphertext_modulus: unsafe { CiphertextModulus::new_unchecked(1 << 63) },
|
||||
};
|
||||
|
||||
pub const DUMMY_NATIVE_U32: TestParams<u32> = TestParams {
|
||||
@@ -125,7 +125,7 @@ pub const DUMMY_31_U32: TestParams<u32> = TestParams {
|
||||
cbs_level: DecompositionLevelCount(0),
|
||||
cbs_base_log: DecompositionBaseLog(0),
|
||||
message_modulus_log: CiphertextModulusLog(3),
|
||||
ciphertext_modulus: CiphertextModulus::new_unchecked(1 << 31),
|
||||
ciphertext_modulus: unsafe { CiphertextModulus::new_unchecked(1 << 31) },
|
||||
};
|
||||
|
||||
// Our representation of non native power of 2 moduli puts the information in the MSBs and leaves
|
||||
@@ -166,7 +166,7 @@ pub fn get_encoding_with_padding<Scalar: UnsignedInteger>(
|
||||
if ciphertext_modulus.is_native_modulus() {
|
||||
Scalar::ONE << (Scalar::BITS - 1)
|
||||
} else {
|
||||
Scalar::cast_from(ciphertext_modulus.get() / 2)
|
||||
Scalar::cast_from(ciphertext_modulus.get_custom_modulus() / 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
//! Module containing the definition of the [`CiphertextModulus`].
|
||||
|
||||
use crate::core_crypto::commons::traits::UnsignedInteger;
|
||||
use core::num::NonZeroU128;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
/// A value of 0 is always interpreted as a native modulus, this is useful to work with u128 using
|
||||
/// the native modulus as $2^{128}$ cannot be stored in a u128 value.
|
||||
/// Private enum to avoid end user mis-instantiating a CiphertextModulus
|
||||
///
|
||||
/// This also allows to not rely on an enum which would require a discriminant field adding 8 bytes
|
||||
/// to store 1 bit of information (native variant vs custom variant with u128 payload).
|
||||
pub struct CiphertextModulus<Scalar: UnsignedInteger>(u128, PhantomData<Scalar>);
|
||||
/// NonZeroU128 allows to always have a correct modulus and to have an enum that is no bigger than a
|
||||
/// u128 with the 0 optimization as the tag then corresponds to the Native variant.
|
||||
enum CiphertextModulusInner {
|
||||
Native,
|
||||
Custom(NonZeroU128),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
/// Structure representing a [`CiphertextModulus`] often noted $q$.
|
||||
pub struct CiphertextModulus<Scalar: UnsignedInteger> {
|
||||
inner: CiphertextModulusInner,
|
||||
_scalar: PhantomData<Scalar>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct SerialiazableLweCiphertextModulus {
|
||||
@@ -23,8 +33,13 @@ impl<Scalar: UnsignedInteger> serde::Serialize for CiphertextModulus<Scalar> {
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let modulus = match self.inner {
|
||||
CiphertextModulusInner::Native => 0,
|
||||
CiphertextModulusInner::Custom(modulus) => modulus.get(),
|
||||
};
|
||||
|
||||
SerialiazableLweCiphertextModulus {
|
||||
modulus: self.get(),
|
||||
modulus,
|
||||
scalar_bits: Scalar::BITS,
|
||||
}
|
||||
.serialize(serializer)
|
||||
@@ -49,52 +64,99 @@ impl<'de, Scalar: UnsignedInteger> serde::Deserialize<'de> for CiphertextModulus
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(CiphertextModulus(thing.modulus, PhantomData))
|
||||
let res = if thing.modulus == 0 {
|
||||
CiphertextModulus {
|
||||
inner: CiphertextModulusInner::Native,
|
||||
_scalar: PhantomData,
|
||||
}
|
||||
} else {
|
||||
CiphertextModulus {
|
||||
inner: CiphertextModulusInner::Custom(NonZeroU128::new(thing.modulus).ok_or(
|
||||
serde::de::Error::custom(
|
||||
"Got zero modulus for CiphertextModulusInner::Custom variant",
|
||||
),
|
||||
)?),
|
||||
_scalar: PhantomData,
|
||||
}
|
||||
};
|
||||
Ok(res.canonicalize())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger> CiphertextModulus<Scalar> {
|
||||
pub const fn new_native() -> Self {
|
||||
Self(0, PhantomData)
|
||||
Self {
|
||||
inner: CiphertextModulusInner::Native,
|
||||
_scalar: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn try_new_power_of_2(exponent: usize) -> Result<Self, &'static str> {
|
||||
if exponent > Scalar::BITS {
|
||||
Err("Modulus is bigger than the maximum value of the associated Scalar type")
|
||||
} else {
|
||||
let modulus = match 1u128.checked_shl(exponent as u32) {
|
||||
Some(modulus) => modulus,
|
||||
None => 0,
|
||||
let res = match 1u128.checked_shl(exponent as u32) {
|
||||
Some(modulus) => {
|
||||
let non_zero_modulus = match NonZeroU128::new(modulus) {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
panic!("Got zero modulus for CiphertextModulusInner::Custom variant",)
|
||||
}
|
||||
};
|
||||
Self {
|
||||
inner: CiphertextModulusInner::Custom(non_zero_modulus),
|
||||
_scalar: PhantomData,
|
||||
}
|
||||
}
|
||||
None => {
|
||||
assert!(exponent == 128);
|
||||
assert!(Scalar::BITS == 128);
|
||||
Self {
|
||||
inner: CiphertextModulusInner::Native,
|
||||
_scalar: PhantomData,
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(Self(modulus, PhantomData).canonicalize())
|
||||
Ok(res.canonicalize())
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn canonicalize(self) -> Self {
|
||||
match self.inner {
|
||||
CiphertextModulusInner::Native => self,
|
||||
CiphertextModulusInner::Custom(modulus) => {
|
||||
if Scalar::BITS < 128 && modulus.get() == (1 << Scalar::BITS) {
|
||||
CiphertextModulus::new_native()
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub const fn new_unchecked(modulus: u128) -> Self {
|
||||
Self(modulus, PhantomData)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Return the u128 value storing the modulus. This returns 0 if the modulus is the native
|
||||
/// modulus of the associated scalar type.
|
||||
pub const fn get(&self) -> u128 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub const fn canonicalize(self) -> Self {
|
||||
if self.is_native_modulus() {
|
||||
Self(0, PhantomData)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
/// # Safety
|
||||
/// modulus needs to be able to fit in the associated Scalar type
|
||||
pub const unsafe fn new_unchecked(modulus: u128) -> Self {
|
||||
let res = match modulus {
|
||||
0 => Self {
|
||||
inner: CiphertextModulusInner::Native,
|
||||
_scalar: PhantomData,
|
||||
},
|
||||
_ => Self {
|
||||
inner: CiphertextModulusInner::Custom(NonZeroU128::new_unchecked(modulus)),
|
||||
_scalar: PhantomData,
|
||||
},
|
||||
};
|
||||
res.canonicalize()
|
||||
}
|
||||
|
||||
pub fn get_scaling_to_native_torus(&self) -> Scalar {
|
||||
if !self.is_native_modulus() {
|
||||
Scalar::ONE.wrapping_shl(Scalar::BITS as u32 - self.0.ilog2())
|
||||
} else {
|
||||
Scalar::ONE
|
||||
match self.inner {
|
||||
CiphertextModulusInner::Native => Scalar::ONE,
|
||||
CiphertextModulusInner::Custom(modulus) => {
|
||||
Scalar::ONE.wrapping_shl(Scalar::BITS as u32 - modulus.ilog2())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,15 +165,17 @@ impl<Scalar: UnsignedInteger> CiphertextModulus<Scalar> {
|
||||
/// implementations than can rely on wrapping arithmetic operations behavior to compute the
|
||||
/// modulus.
|
||||
pub const fn is_native_modulus(&self) -> bool {
|
||||
if self.0 == 0 {
|
||||
return true;
|
||||
}
|
||||
matches!(self.inner, CiphertextModulusInner::Native)
|
||||
}
|
||||
|
||||
if Scalar::BITS < 128 {
|
||||
return self.0 == (1 << Scalar::BITS);
|
||||
/// Panics if the modulus is not a custom modulus
|
||||
pub const fn get_custom_modulus(&self) -> u128 {
|
||||
match self.inner {
|
||||
CiphertextModulusInner::Native => {
|
||||
panic!("Tried getting custom modulus from native modulus")
|
||||
}
|
||||
CiphertextModulusInner::Custom(modulus) => modulus.get(),
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub const fn is_compatible_with_native_modulus(&self) -> bool {
|
||||
@@ -119,16 +183,20 @@ impl<Scalar: UnsignedInteger> CiphertextModulus<Scalar> {
|
||||
}
|
||||
|
||||
pub const fn is_power_of_two(&self) -> bool {
|
||||
self.0.is_power_of_two()
|
||||
match self.inner {
|
||||
CiphertextModulusInner::Native => true,
|
||||
CiphertextModulusInner::Custom(modulus) => modulus.is_power_of_two(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger> std::fmt::Display for CiphertextModulus<Scalar> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.is_native_modulus() {
|
||||
write!(f, "CiphertextModulus(2^{})", Scalar::BITS)
|
||||
} else {
|
||||
write!(f, "CiphertextModulus({})", self.0)
|
||||
match self.inner {
|
||||
CiphertextModulusInner::Native => write!(f, "CiphertextModulus(2^{})", Scalar::BITS),
|
||||
CiphertextModulusInner::Custom(modulus) => {
|
||||
write!(f, "CiphertextModulus({})", modulus.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +213,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_modulus_struct() {
|
||||
assert!(std::mem::size_of::<CiphertextModulus<u32>>() == std::mem::size_of::<u128>());
|
||||
assert!(std::mem::size_of::<CiphertextModulus<u64>>() == std::mem::size_of::<u128>());
|
||||
assert!(std::mem::size_of::<CiphertextModulus<u128>>() == std::mem::size_of::<u128>());
|
||||
assert!(std::mem::align_of::<CiphertextModulus<u32>>() == std::mem::align_of::<u128>());
|
||||
assert!(std::mem::align_of::<CiphertextModulus<u64>>() == std::mem::align_of::<u128>());
|
||||
assert!(std::mem::align_of::<CiphertextModulus<u128>>() == std::mem::align_of::<u128>());
|
||||
|
||||
{
|
||||
let mod_32_res = CiphertextModulus::<u32>::try_new_power_of_2(32);
|
||||
assert!(mod_32_res.is_ok());
|
||||
@@ -174,6 +249,28 @@ mod tests {
|
||||
{
|
||||
let native_mod_128 = CiphertextModulus::<u128>::new_native();
|
||||
assert!(native_mod_128.is_native_modulus());
|
||||
|
||||
let ser = bincode::serialize(&native_mod_128).unwrap();
|
||||
let deser: CiphertextModulus<u128> = bincode::deserialize(&ser).unwrap();
|
||||
|
||||
assert_eq!(native_mod_128, deser);
|
||||
|
||||
let deser_error: Result<CiphertextModulus<u32>, _> = bincode::deserialize(&ser);
|
||||
assert!(deser_error.is_err());
|
||||
match deser_error {
|
||||
Ok(_) => unreachable!(),
|
||||
Err(e) => match *e {
|
||||
bincode::ErrorKind::Custom(err) => {
|
||||
assert_eq!(
|
||||
err.as_str(),
|
||||
"Expected an unsigned integer with 32 bits, \
|
||||
found 128 bits during deserialization of CiphertextModulus, \
|
||||
have you mixed types during deserialization?",
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@@ -181,7 +278,29 @@ mod tests {
|
||||
assert!(mod_128_res.is_ok());
|
||||
|
||||
let mod_128 = mod_128_res.unwrap();
|
||||
assert_eq!(mod_128.get(), 1 << 64);
|
||||
assert_eq!(mod_128.get_custom_modulus(), 1 << 64);
|
||||
|
||||
let ser = bincode::serialize(&mod_128).unwrap();
|
||||
let deser: CiphertextModulus<u128> = bincode::deserialize(&ser).unwrap();
|
||||
|
||||
assert_eq!(mod_128, deser);
|
||||
|
||||
let deser_error: Result<CiphertextModulus<u32>, _> = bincode::deserialize(&ser);
|
||||
assert!(deser_error.is_err());
|
||||
match deser_error {
|
||||
Ok(_) => unreachable!(),
|
||||
Err(e) => match *e {
|
||||
bincode::ErrorKind::Custom(err) => {
|
||||
assert_eq!(
|
||||
err.as_str(),
|
||||
"Expected an unsigned integer with 32 bits, \
|
||||
found 128 bits during deserialization of CiphertextModulus, \
|
||||
have you mixed types during deserialization?",
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,31 @@ impl<G: ByteRandomGenerator> EncryptionRandomGenerator<G> {
|
||||
self.try_fork(lwe_size.0, mask_bytes, noise_bytes)
|
||||
}
|
||||
|
||||
// Forks the generator, when splitting a tpksk into chunks
|
||||
pub(crate) fn fork_tpksk_to_tpksk_chunks<T: UnsignedInteger>(
|
||||
&mut self,
|
||||
level: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
) -> Result<impl Iterator<Item = EncryptionRandomGenerator<G>>, ForkError> {
|
||||
let mask_bytes = mask_bytes_per_tpksk_chunk::<T>(level, glwe_size, poly_size);
|
||||
let noise_bytes = noise_bytes_per_tpksk_chunk(level, poly_size);
|
||||
self.try_fork(poly_size.log2().0, mask_bytes, noise_bytes)
|
||||
}
|
||||
|
||||
// Forks the generator, when splitting a glwe keyswitch into chunks
|
||||
pub(crate) fn fork_glweks_to_glweks_chunks<T: UnsignedInteger>(
|
||||
&mut self,
|
||||
level: DecompositionLevelCount,
|
||||
input_glwe_dimension: GlweDimension,
|
||||
output_glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
) -> Result<impl Iterator<Item = EncryptionRandomGenerator<G>>, ForkError> {
|
||||
let mask_bytes = mask_bytes_per_glweks_chunk::<T>(level, output_glwe_size, poly_size);
|
||||
let noise_bytes = noise_bytes_per_glweks_chunk(level, poly_size);
|
||||
self.try_fork(input_glwe_dimension.0, mask_bytes, noise_bytes)
|
||||
}
|
||||
|
||||
// Forks both generators into an iterator
|
||||
fn try_fork(
|
||||
&mut self,
|
||||
@@ -229,7 +254,11 @@ impl<G: ByteRandomGenerator> EncryptionRandomGenerator<G> {
|
||||
where
|
||||
Scalar: UnsignedInteger + RandomGenerable<Gaussian<f64>, CustomModulus = f64>,
|
||||
{
|
||||
let custom_modulus_f64: f64 = custom_modulus.get().cast_into();
|
||||
if custom_modulus.is_native_modulus() {
|
||||
return self.random_noise(std);
|
||||
}
|
||||
|
||||
let custom_modulus_f64: f64 = custom_modulus.get_custom_modulus().cast_into();
|
||||
Scalar::generate_one_custom_modulus(
|
||||
&mut self.noise,
|
||||
Gaussian {
|
||||
@@ -427,6 +456,22 @@ impl<G: ParallelByteRandomGenerator> EncryptionRandomGenerator<G> {
|
||||
self.par_try_fork(lwe_size.0, mask_bytes, noise_bytes)
|
||||
}
|
||||
|
||||
// Forks the generator, when splitting a tpksk into chunks
|
||||
pub(crate) fn par_fork_tpksk_to_tpksk_chunks<T: UnsignedInteger>(
|
||||
&mut self,
|
||||
level: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
) -> Result<impl IndexedParallelIterator<Item = EncryptionRandomGenerator<G>>, ForkError> {
|
||||
let mask_bytes = mask_bytes_per_tpksk_chunk::<T>(level, glwe_size, poly_size);
|
||||
let noise_bytes = noise_bytes_per_tpksk_chunk(level, poly_size);
|
||||
self.par_try_fork(
|
||||
poly_size.log2().0 * glwe_size.to_glwe_dimension().0,
|
||||
mask_bytes,
|
||||
noise_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
// Forks both generators into a parallel iterator.
|
||||
fn par_try_fork(
|
||||
&mut self,
|
||||
@@ -500,6 +545,32 @@ fn mask_bytes_per_pfpksk<T: UnsignedInteger>(
|
||||
lwe_size.0 * mask_bytes_per_pfpksk_chunk::<T>(level, glwe_size, poly_size)
|
||||
}
|
||||
|
||||
fn mask_bytes_per_tpksk_chunk<T: UnsignedInteger>(
|
||||
level: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
) -> usize {
|
||||
glwe_size.to_glwe_dimension().0 * mask_bytes_per_glweks_chunk::<T>(level, glwe_size, poly_size)
|
||||
}
|
||||
|
||||
fn mask_bytes_per_tpksk<T: UnsignedInteger>(
|
||||
level: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
) -> usize {
|
||||
poly_size.log2().0 * mask_bytes_per_tpksk_chunk::<T>(level, glwe_size, poly_size)
|
||||
}
|
||||
|
||||
fn mask_bytes_per_glweks_chunk<T: UnsignedInteger>(
|
||||
level: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
) -> usize {
|
||||
glwe_size.to_glwe_dimension().0
|
||||
* level.0
|
||||
* mask_bytes_per_glwe::<T>(glwe_size.to_glwe_dimension(), poly_size)
|
||||
}
|
||||
|
||||
fn noise_bytes_per_coef() -> usize {
|
||||
// We use f64 to sample the noise for every precision, and we need 4/pi inputs to generate
|
||||
// such an output (here we take 32 to keep a safety margin).
|
||||
@@ -549,17 +620,39 @@ fn noise_bytes_per_pfpksk(
|
||||
lwe_size.0 * noise_bytes_per_pfpksk_chunk(level, poly_size)
|
||||
}
|
||||
|
||||
fn noise_bytes_per_tpksk_chunk(level: DecompositionLevelCount, poly_size: PolynomialSize) -> usize {
|
||||
level.0 * noise_bytes_per_glwe(poly_size)
|
||||
}
|
||||
|
||||
fn noise_bytes_per_tpksk(
|
||||
level: DecompositionLevelCount,
|
||||
poly_size: PolynomialSize,
|
||||
glwe_size: GlweSize,
|
||||
) -> usize {
|
||||
glwe_size.to_glwe_dimension().0
|
||||
* poly_size.log2().0
|
||||
* noise_bytes_per_tpksk_chunk(level, poly_size)
|
||||
}
|
||||
|
||||
fn noise_bytes_per_glweks_chunk(
|
||||
level: DecompositionLevelCount,
|
||||
poly_size: PolynomialSize,
|
||||
) -> usize {
|
||||
level.0 * noise_bytes_per_glwe(poly_size)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::dispersion::Variance;
|
||||
use crate::core_crypto::commons::dispersion::{StandardDev, Variance};
|
||||
use crate::core_crypto::commons::parameters::{
|
||||
CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount, GlweSize, LweDimension,
|
||||
PolynomialSize,
|
||||
};
|
||||
use crate::core_crypto::commons::test_tools::{
|
||||
new_encryption_random_generator, new_secret_random_generator,
|
||||
new_encryption_random_generator, new_secret_random_generator, normality_test_f64,
|
||||
};
|
||||
use crate::core_crypto::commons::traits::UnsignedTorus;
|
||||
|
||||
#[test]
|
||||
fn test_gaussian_sampling_margin_factor_does_not_panic() {
|
||||
@@ -598,4 +691,541 @@ mod test {
|
||||
&mut enc_generator,
|
||||
);
|
||||
}
|
||||
|
||||
fn noise_gen_native<Scalar: UnsignedTorus>() {
|
||||
let mut gen = new_encryption_random_generator();
|
||||
|
||||
let bits = (Scalar::BITS / 2) as i32;
|
||||
|
||||
for _ in 0..1000 {
|
||||
let mut retries = 100;
|
||||
|
||||
let mut val = Scalar::ZERO;
|
||||
while retries >= 0 {
|
||||
val = gen.random_noise(StandardDev(2.0f64.powi(-bits)));
|
||||
if val != Scalar::ZERO {
|
||||
break;
|
||||
}
|
||||
retries -= 1;
|
||||
}
|
||||
|
||||
assert!(retries != 0);
|
||||
assert!(val != Scalar::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_native_u32() {
|
||||
noise_gen_native::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_native_u64() {
|
||||
noise_gen_native::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_native_u128() {
|
||||
noise_gen_native::<u128>();
|
||||
}
|
||||
|
||||
fn noise_gen_custom_mod<Scalar: UnsignedTorus>(ciphertext_modulus: CiphertextModulus<Scalar>) {
|
||||
let mut gen = new_encryption_random_generator();
|
||||
|
||||
let bits = (Scalar::BITS / 2) as i32;
|
||||
|
||||
for _ in 0..1000 {
|
||||
let mut retries = 100;
|
||||
|
||||
let mut val = Scalar::ZERO;
|
||||
while retries >= 0 {
|
||||
val = gen
|
||||
.random_noise_custom_mod(StandardDev(2.0f64.powi(-bits)), ciphertext_modulus);
|
||||
if val != Scalar::ZERO {
|
||||
break;
|
||||
}
|
||||
retries -= 1;
|
||||
}
|
||||
|
||||
assert!(retries != 0);
|
||||
assert!(val != Scalar::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_custom_mod_u32() {
|
||||
noise_gen_custom_mod::<u32>(CiphertextModulus::try_new_power_of_2(31).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_custom_mod_u64() {
|
||||
noise_gen_custom_mod::<u64>(CiphertextModulus::try_new_power_of_2(63).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_custom_mod_u128() {
|
||||
noise_gen_custom_mod::<u128>(CiphertextModulus::try_new_power_of_2(127).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_native_custom_mod_u32() {
|
||||
noise_gen_custom_mod::<u32>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_native_custom_mod_u64() {
|
||||
noise_gen_custom_mod::<u64>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_native_custom_mod_u128() {
|
||||
noise_gen_custom_mod::<u128>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
fn noise_gen_slice_native<Scalar: UnsignedTorus>() {
|
||||
let mut gen = new_encryption_random_generator();
|
||||
|
||||
let bits = (Scalar::BITS / 2) as i32;
|
||||
|
||||
let mut vec = vec![Scalar::ZERO; 1000];
|
||||
let mut retries = 100;
|
||||
while retries >= 0 {
|
||||
gen.fill_slice_with_random_noise(&mut vec, StandardDev(2.0f64.powi(-bits)));
|
||||
if vec.iter().all(|&x| x != Scalar::ZERO) {
|
||||
break;
|
||||
}
|
||||
|
||||
retries -= 1;
|
||||
}
|
||||
assert!(retries != 0);
|
||||
assert!(vec.iter().all(|&x| x != Scalar::ZERO));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_native_u32() {
|
||||
noise_gen_slice_native::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_native_u64() {
|
||||
noise_gen_slice_native::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_native_u128() {
|
||||
noise_gen_slice_native::<u128>();
|
||||
}
|
||||
|
||||
fn test_normal_random_encryption_native<Scalar: UnsignedTorus>() {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_encryption_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
|
||||
|
||||
rng.fill_slice_with_random_noise(&mut samples, StandardDev(f64::powi(2., -20)));
|
||||
|
||||
let samples: Vec<f64> = samples
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| {
|
||||
let torus = x.into_torus();
|
||||
// The upper half of the torus corresponds to the negative domain when
|
||||
// mapping unsigned integer back to float (MSB or
|
||||
// sign bit is set)
|
||||
if torus > 0.5 {
|
||||
torus - 1.0
|
||||
} else {
|
||||
torus
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_native_u32() {
|
||||
test_normal_random_encryption_native::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_native_u64() {
|
||||
test_normal_random_encryption_native::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_native_u128() {
|
||||
test_normal_random_encryption_native::<u128>();
|
||||
}
|
||||
|
||||
fn test_normal_random_encryption_add_assign_native<Scalar: UnsignedTorus>() {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_encryption_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
|
||||
|
||||
rng.unsigned_torus_slice_wrapping_add_random_noise_assign(
|
||||
&mut samples,
|
||||
StandardDev(f64::powi(2., -20)),
|
||||
);
|
||||
|
||||
let samples: Vec<f64> = samples
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| {
|
||||
let torus = x.into_torus();
|
||||
// The upper half of the torus corresponds to the negative domain when
|
||||
// mapping unsigned integer back to float (MSB or
|
||||
// sign bit is set)
|
||||
if torus > 0.5 {
|
||||
torus - 1.0
|
||||
} else {
|
||||
torus
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_native_u32() {
|
||||
test_normal_random_encryption_add_assign_native::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_native_u64() {
|
||||
test_normal_random_encryption_add_assign_native::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_native_u128() {
|
||||
test_normal_random_encryption_add_assign_native::<u128>();
|
||||
}
|
||||
|
||||
fn noise_gen_slice_custom_mod<Scalar: UnsignedTorus>(
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) {
|
||||
let mut gen = new_encryption_random_generator();
|
||||
|
||||
let bits = (Scalar::BITS / 2) as i32;
|
||||
|
||||
let mut vec = vec![Scalar::ZERO; 1000];
|
||||
let mut retries = 100;
|
||||
while retries >= 0 {
|
||||
gen.fill_slice_with_random_noise_custom_mod(
|
||||
&mut vec,
|
||||
StandardDev(2.0f64.powi(-bits)),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
if vec.iter().all(|&x| x != Scalar::ZERO) {
|
||||
break;
|
||||
}
|
||||
|
||||
retries -= 1;
|
||||
}
|
||||
assert!(retries != 0);
|
||||
assert!(vec.iter().all(|&x| x != Scalar::ZERO));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_custom_mod_u32() {
|
||||
noise_gen_slice_custom_mod::<u32>(CiphertextModulus::try_new_power_of_2(31).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_custom_mod_u64() {
|
||||
noise_gen_slice_custom_mod::<u64>(CiphertextModulus::try_new_power_of_2(63).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_custom_mod_u128() {
|
||||
noise_gen_slice_custom_mod::<u128>(CiphertextModulus::try_new_power_of_2(127).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_native_custom_mod_u32() {
|
||||
noise_gen_slice_custom_mod::<u32>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_native_custom_mod_u64() {
|
||||
noise_gen_slice_custom_mod::<u64>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_gen_slice_native_custom_mod_u128() {
|
||||
noise_gen_slice_custom_mod::<u128>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
fn test_normal_random_encryption_custom_mod<Scalar: UnsignedTorus>(
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_encryption_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
|
||||
|
||||
rng.fill_slice_with_random_noise_custom_mod(
|
||||
&mut samples,
|
||||
StandardDev(f64::powi(2., -20)),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let samples: Vec<f64> = samples
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| {
|
||||
let torus = x.into_torus();
|
||||
// The upper half of the torus corresponds to the negative domain when
|
||||
// mapping unsigned integer back to float (MSB or
|
||||
// sign bit is set)
|
||||
if torus > 0.5 {
|
||||
torus - 1.0
|
||||
} else {
|
||||
torus
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_custom_mod_u32() {
|
||||
test_normal_random_encryption_custom_mod::<u32>(
|
||||
CiphertextModulus::try_new_power_of_2(31).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_custom_mod_u64() {
|
||||
test_normal_random_encryption_custom_mod::<u64>(
|
||||
CiphertextModulus::try_new_power_of_2(63).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_custom_mod_u128() {
|
||||
test_normal_random_encryption_custom_mod::<u128>(
|
||||
CiphertextModulus::try_new_power_of_2(127).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_native_custom_mod_u32() {
|
||||
test_normal_random_encryption_custom_mod::<u32>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_native_custom_mod_u64() {
|
||||
test_normal_random_encryption_custom_mod::<u64>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_native_custom_mod_u128() {
|
||||
test_normal_random_encryption_custom_mod::<u128>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
fn test_normal_random_encryption_add_assign_custom_mod<Scalar: UnsignedTorus>(
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_encryption_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
|
||||
|
||||
rng.unsigned_torus_slice_wrapping_add_random_noise_custom_mod_assign(
|
||||
&mut samples,
|
||||
StandardDev(f64::powi(2., -20)),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let samples: Vec<f64> = samples
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| {
|
||||
let torus = x.into_torus();
|
||||
// The upper half of the torus corresponds to the negative domain when
|
||||
// mapping unsigned integer back to float (MSB or
|
||||
// sign bit is set)
|
||||
if torus > 0.5 {
|
||||
torus - 1.0
|
||||
} else {
|
||||
torus
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_custom_mod_u32() {
|
||||
test_normal_random_encryption_add_assign_custom_mod::<u32>(
|
||||
CiphertextModulus::try_new_power_of_2(31).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_custom_mod_u64() {
|
||||
test_normal_random_encryption_add_assign_custom_mod::<u64>(
|
||||
CiphertextModulus::try_new_power_of_2(63).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_custom_mod_u128() {
|
||||
test_normal_random_encryption_add_assign_custom_mod::<u128>(
|
||||
CiphertextModulus::try_new_power_of_2(127).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_native_custom_mod_u32() {
|
||||
test_normal_random_encryption_add_assign_custom_mod::<u32>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_native_custom_mod_u64() {
|
||||
test_normal_random_encryption_add_assign_custom_mod::<u64>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_encryption_add_assign_native_custom_mod_u128() {
|
||||
test_normal_random_encryption_add_assign_custom_mod::<u128>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
fn mask_gen_slice_native<Scalar: UnsignedTorus>() {
|
||||
let mut gen = new_encryption_random_generator();
|
||||
|
||||
let mut vec = vec![Scalar::ZERO; 1000];
|
||||
let mut retries = 100;
|
||||
while retries >= 0 {
|
||||
gen.fill_slice_with_random_mask(&mut vec);
|
||||
if vec.iter().all(|&x| x != Scalar::ZERO) {
|
||||
break;
|
||||
}
|
||||
|
||||
retries -= 1;
|
||||
}
|
||||
assert!(retries != 0);
|
||||
assert!(vec.iter().all(|&x| x != Scalar::ZERO));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_native_u32() {
|
||||
mask_gen_slice_native::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_native_u64() {
|
||||
mask_gen_slice_native::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_native_u128() {
|
||||
mask_gen_slice_native::<u128>();
|
||||
}
|
||||
|
||||
fn mask_gen_slice_custom_mod<Scalar: UnsignedTorus>(
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) {
|
||||
let mut gen = new_encryption_random_generator();
|
||||
|
||||
let mut vec = vec![Scalar::ZERO; 1000];
|
||||
let mut retries = 100;
|
||||
while retries >= 0 {
|
||||
gen.fill_slice_with_random_mask_custom_mod(&mut vec, ciphertext_modulus);
|
||||
if vec.iter().all(|&x| x != Scalar::ZERO) {
|
||||
break;
|
||||
}
|
||||
|
||||
retries -= 1;
|
||||
}
|
||||
assert!(retries != 0);
|
||||
assert!(vec.iter().all(|&x| x != Scalar::ZERO));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_slice_custom_mod_u32() {
|
||||
mask_gen_slice_custom_mod::<u32>(CiphertextModulus::try_new_power_of_2(31).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_slice_custom_mod_u64() {
|
||||
mask_gen_slice_custom_mod::<u64>(CiphertextModulus::try_new_power_of_2(63).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_slice_custom_mod_u128() {
|
||||
mask_gen_slice_custom_mod::<u128>(CiphertextModulus::try_new_power_of_2(127).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_slice_native_custom_mod_u32() {
|
||||
mask_gen_slice_custom_mod::<u32>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_slice_native_custom_mod_u64() {
|
||||
mask_gen_slice_custom_mod::<u64>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_gen_slice_native_custom_mod_u128() {
|
||||
mask_gen_slice_custom_mod::<u128>(CiphertextModulus::new_native());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::core_crypto::commons::math::decomposition::SignedDecompositionIter;
|
||||
use crate::core_crypto::commons::math::decomposition::{
|
||||
SignedDecompositionIter, SliceSignedDecompositionIter,
|
||||
};
|
||||
use crate::core_crypto::commons::numeric::{Numeric, UnsignedInteger};
|
||||
use crate::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
use std::marker::PhantomData;
|
||||
@@ -113,6 +115,29 @@ where
|
||||
res << non_rep_bit_count
|
||||
}
|
||||
|
||||
/// Fills a mutable tensor-like objects with the closest representable values from another
|
||||
/// tensor-like object.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
///
|
||||
/// let input = vec![1_340_987_234_u32; 2];
|
||||
/// let mut closest = vec![0u32; 2];
|
||||
/// decomposer.fill_slice_with_closest_representable(&mut closest, &input);
|
||||
/// assert!(closest.iter().all(|&x| x == 1_341_128_704_u32));
|
||||
/// ```
|
||||
pub fn fill_slice_with_closest_representable(&self, output: &mut [Scalar], input: &[Scalar]) {
|
||||
output
|
||||
.iter_mut()
|
||||
.zip(input.iter())
|
||||
.for_each(|(dst, &src)| *dst = self.closest_representable(src));
|
||||
}
|
||||
|
||||
/// Generate an iterator over the terms of the decomposition of the input.
|
||||
///
|
||||
/// # Warning
|
||||
@@ -173,4 +198,87 @@ where
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates an iterator-like object over tensors of terms of the decomposition of the input
|
||||
/// tensor.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The returned iterator yields the terms $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ in
|
||||
/// order of decreasing $i$.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::commons::numeric::UnsignedInteger;
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
/// let decomposable = vec![1_340_987_234_u32, 1_340_987_234_u32];
|
||||
/// let mut decomp = decomposer.decompose_slice(&decomposable);
|
||||
///
|
||||
/// let mut count = 0;
|
||||
/// while let Some(term) = decomp.next_term() {
|
||||
/// assert!(1 <= term.level().0);
|
||||
/// assert!(term.level().0 <= 3);
|
||||
/// for elmt in term.as_slice().iter() {
|
||||
/// let signed_term = elmt.into_signed();
|
||||
/// let half_basis = 2i32.pow(4) / 2i32;
|
||||
/// assert!(-half_basis <= signed_term);
|
||||
/// assert!(signed_term < half_basis);
|
||||
/// }
|
||||
/// count += 1;
|
||||
/// }
|
||||
/// assert_eq!(count, 3);
|
||||
/// ```
|
||||
pub fn decompose_slice(&self, input: &[Scalar]) -> SliceSignedDecompositionIter<Scalar> {
|
||||
// Note that there would be no sense of making the decomposition on an input which was
|
||||
// not rounded to the closest representable first. We then perform it before decomposing.
|
||||
let mut rounded = vec![Scalar::ZERO; input.len()];
|
||||
self.fill_slice_with_closest_representable(&mut rounded, input);
|
||||
SliceSignedDecompositionIter::new(
|
||||
&rounded,
|
||||
DecompositionBaseLog(self.base_log),
|
||||
DecompositionLevelCount(self.level_count),
|
||||
)
|
||||
}
|
||||
|
||||
/// Fills the output tensor with the recomposition of an other tensor.
|
||||
///
|
||||
/// Returns `Some(())` if the decomposition was fresh, and the output was filled with a
|
||||
/// recomposition, and `None`, if not.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
/// let decomposable = vec![1_340_987_234_u32; 2];
|
||||
/// let mut rounded = vec![0u32; 2];
|
||||
/// decomposer.fill_slice_with_closest_representable(&mut rounded, &decomposable);
|
||||
/// let mut decomp = decomposer.decompose_slice(&rounded);
|
||||
/// let mut recomposition = vec![0u32; 2];
|
||||
/// decomposer
|
||||
/// .fill_slice_with_recompose(decomp, &mut recomposition)
|
||||
/// .unwrap();
|
||||
/// assert_eq!(recomposition, rounded);
|
||||
/// ```
|
||||
pub fn fill_slice_with_recompose(
|
||||
&self,
|
||||
decomp: SliceSignedDecompositionIter<Scalar>,
|
||||
output: &mut [Scalar],
|
||||
) -> Option<()> {
|
||||
let mut decomp = decomp;
|
||||
if decomp.is_fresh() {
|
||||
while let Some(term) = decomp.next_term() {
|
||||
term.update_slice_with_recomposition_summand_wrapping_addition(output);
|
||||
}
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
|
||||
use crate::core_crypto::commons::math::decomposition::{
|
||||
DecompositionLevel, DecompositionTerm, DecompositionTermSlice,
|
||||
};
|
||||
use crate::core_crypto::commons::numeric::UnsignedInteger;
|
||||
use crate::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
|
||||
@@ -114,6 +116,155 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator-like object that yields the terms of the signed decomposition of a tensor of values.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// On each call to [`SliceSignedDecompositionIter::next_term`], this structure yields a new
|
||||
/// [`DecompositionTermSlice`], backed by a `Vec` owned by the structure. This vec is mutated at
|
||||
/// each call of the `next_term` method, and as such the term must be dropped before `next_term` is
|
||||
/// called again.
|
||||
///
|
||||
/// Such a pattern can not be implemented with iterators yet (without GATs), which is why this
|
||||
/// iterator must be explicitly called.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This iterator yields the decomposition in reverse order. That means that the highest level
|
||||
/// will be yielded first.
|
||||
pub struct SliceSignedDecompositionIter<Scalar>
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
// The base log of the decomposition
|
||||
base_log: usize,
|
||||
// The number of levels of the decomposition
|
||||
level_count: usize,
|
||||
// The current level
|
||||
current_level: usize,
|
||||
// A mask which allows to compute the mod B of a value. For B=2^4, this guy is of the form:
|
||||
// ...0001111
|
||||
mod_b_mask: Scalar,
|
||||
// The values being decomposed
|
||||
inputs: Vec<Scalar>,
|
||||
// The internal states of each decomposition
|
||||
states: Vec<Scalar>,
|
||||
// In order to avoid allocating a new Vec every time we yield a decomposition term, we store
|
||||
// a Vec inside the structure and yield slices pointing to it.
|
||||
outputs: Vec<Scalar>,
|
||||
// A flag which stores whether the iterator is a fresh one (for the recompose method).
|
||||
fresh: bool,
|
||||
}
|
||||
|
||||
impl<Scalar> SliceSignedDecompositionIter<Scalar>
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
// Creates a new tensor decomposition iterator.
|
||||
pub(crate) fn new(
|
||||
input: &[Scalar],
|
||||
base_log: DecompositionBaseLog,
|
||||
level: DecompositionLevelCount,
|
||||
) -> SliceSignedDecompositionIter<Scalar> {
|
||||
let len = input.len();
|
||||
SliceSignedDecompositionIter {
|
||||
base_log: base_log.0,
|
||||
level_count: level.0,
|
||||
current_level: level.0,
|
||||
mod_b_mask: (Scalar::ONE << base_log.0) - Scalar::ONE,
|
||||
inputs: input.to_vec(),
|
||||
outputs: vec![Scalar::ZERO; len],
|
||||
states: input
|
||||
.iter()
|
||||
.map(|i| *i >> (Scalar::BITS - base_log.0 * level.0))
|
||||
.collect(),
|
||||
fresh: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_fresh(&self) -> bool {
|
||||
self.fresh
|
||||
}
|
||||
|
||||
/// Returns the logarithm in base two of the base of this decomposition.
|
||||
///
|
||||
/// If the decomposition uses a base $B=2^b$, this returns $b$.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
/// let decomposable = vec![1_340_987_234_u32; 2];
|
||||
/// let decomp = decomposer.decompose_slice(&decomposable);
|
||||
/// assert_eq!(decomp.base_log(), DecompositionBaseLog(4));
|
||||
/// ```
|
||||
pub fn base_log(&self) -> DecompositionBaseLog {
|
||||
DecompositionBaseLog(self.base_log)
|
||||
}
|
||||
|
||||
/// Returns the number of levels of this decomposition.
|
||||
///
|
||||
/// If the decomposition uses $l$ levels, this returns $l$.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
/// let decomposable = vec![1_340_987_234_u32; 2];
|
||||
/// let decomp = decomposer.decompose_slice(&decomposable);
|
||||
/// assert_eq!(decomp.level_count(), DecompositionLevelCount(3));
|
||||
/// ```
|
||||
pub fn level_count(&self) -> DecompositionLevelCount {
|
||||
DecompositionLevelCount(self.level_count)
|
||||
}
|
||||
|
||||
/// Yield the next term of the decomposition, if any.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Because this function returns a borrowed tensor, owned by the iterator, the term must be
|
||||
/// dropped before `next_term` is called again.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
/// let decomposable = vec![1_340_987_234_u32; 2];
|
||||
/// let mut decomp = decomposer.decompose_slice(&decomposable);
|
||||
/// let term = decomp.next_term().unwrap();
|
||||
/// assert_eq!(term.level(), DecompositionLevel(3));
|
||||
/// assert_eq!(term.as_slice()[0], 4294967295);
|
||||
/// ```
|
||||
pub fn next_term(&mut self) -> Option<DecompositionTermSlice<'_, Scalar>> {
|
||||
// The iterator is not fresh anymore.
|
||||
self.fresh = false;
|
||||
// We check if the decomposition is over
|
||||
if self.current_level == 0 {
|
||||
return None;
|
||||
}
|
||||
// We iterate over the elements of the outputs and decompose
|
||||
for (output_i, state_i) in self.outputs.iter_mut().zip(self.states.iter_mut()) {
|
||||
*output_i = decompose_one_level(self.base_log, state_i, self.mod_b_mask);
|
||||
}
|
||||
self.current_level -= 1;
|
||||
// We return the term tensor.
|
||||
Some(DecompositionTermSlice::new(
|
||||
DecompositionLevel(self.current_level + 1),
|
||||
DecompositionBaseLog(self.base_log),
|
||||
&self.outputs,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn decompose_one_level<S: UnsignedInteger>(base_log: usize, state: &mut S, mod_b_mask: S) -> S {
|
||||
let res = *state & mod_b_mask;
|
||||
*state >>= base_log;
|
||||
|
||||
@@ -91,3 +91,115 @@ where
|
||||
DecompositionLevel(self.level)
|
||||
}
|
||||
}
|
||||
|
||||
/// A tensor whose elements are the terms of the decomposition of another tensor.
|
||||
///
|
||||
/// If we decompose each elements of a set of values $(\theta^{(a)})\_{a\in\mathbb{N}}$ as a set of
|
||||
/// sums $(\sum\_{i=1}^l\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$, this represents a
|
||||
/// set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct DecompositionTermSlice<'a, Scalar>
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
level: usize,
|
||||
base_log: usize,
|
||||
slice: &'a [Scalar],
|
||||
}
|
||||
|
||||
impl<'a, Scalar> DecompositionTermSlice<'a, Scalar>
|
||||
where
|
||||
Scalar: UnsignedInteger,
|
||||
{
|
||||
// Creates a new tensor decomposition term.
|
||||
pub(crate) fn new(
|
||||
level: DecompositionLevel,
|
||||
base_log: DecompositionBaseLog,
|
||||
slice: &'a [Scalar],
|
||||
) -> DecompositionTermSlice<Scalar> {
|
||||
DecompositionTermSlice {
|
||||
level: level.0,
|
||||
base_log: base_log.0,
|
||||
slice,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills the output tensor with the terms turned to summands.
|
||||
///
|
||||
/// If our term tensor represents a set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ of the
|
||||
/// decomposition, this method fills the output tensor with a set of
|
||||
/// $(\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
/// let input = vec![2u32.pow(19); 2];
|
||||
/// let mut decomp = decomposer.decompose_slice(&input);
|
||||
/// let term = decomp.next_term().unwrap();
|
||||
/// let mut output = vec![0, 2];
|
||||
/// term.fill_slice_with_recomposition_summand(&mut output);
|
||||
/// assert!(output.iter().all(|&x| x == 1048576));
|
||||
/// ```
|
||||
pub fn fill_slice_with_recomposition_summand(&self, output: &mut [Scalar]) {
|
||||
output
|
||||
.iter_mut()
|
||||
.zip(self.slice.iter())
|
||||
.for_each(|(dst, &value)| {
|
||||
let shift: usize = <Scalar as Numeric>::BITS - self.base_log * self.level;
|
||||
*dst = value << shift
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn update_slice_with_recomposition_summand_wrapping_addition(
|
||||
&self,
|
||||
output: &mut [Scalar],
|
||||
) {
|
||||
output
|
||||
.iter_mut()
|
||||
.zip(self.slice.iter())
|
||||
.for_each(|(out, &value)| {
|
||||
let shift: usize = <Scalar as Numeric>::BITS - self.base_log * self.level;
|
||||
*out = (*out).wrapping_add(value << shift);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns a tensor with the values of term.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
/// let input = vec![2u32.pow(19); 2];
|
||||
/// let mut decomp = decomposer.decompose_slice(&input);
|
||||
/// let term = decomp.next_term().unwrap();
|
||||
/// assert_eq!(term.as_slice()[0], 1);
|
||||
/// ```
|
||||
pub fn as_slice(&self) -> &'a [Scalar] {
|
||||
self.slice
|
||||
}
|
||||
|
||||
/// Returns the level of this decomposition term tensor.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use tfhe::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
|
||||
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
|
||||
/// let decomposer =
|
||||
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
|
||||
/// let input = vec![2u32.pow(19); 2];
|
||||
/// let mut decomp = decomposer.decompose_slice(&input);
|
||||
/// let term = decomp.next_term().unwrap();
|
||||
/// assert_eq!(term.level(), DecompositionLevel(3));
|
||||
/// ```
|
||||
pub fn level(&self) -> DecompositionLevel {
|
||||
DecompositionLevel(self.level)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,8 +175,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
/// use concrete_csprng::seeders::Seed;
|
||||
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
|
||||
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
|
||||
/// let mut vec = vec![1u32; 100];
|
||||
/// let mut vec = vec![0u32; 1000];
|
||||
/// generator.fill_slice_with_random_uniform(&mut vec);
|
||||
/// assert!(vec.iter().any(|&x| x != 0));
|
||||
/// ```
|
||||
pub fn fill_slice_with_random_uniform<Scalar>(&mut self, output: &mut [Scalar])
|
||||
where
|
||||
@@ -196,11 +197,12 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::parameters::CiphertextModulus;
|
||||
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
|
||||
/// let mut vec = vec![1u32; 100];
|
||||
/// let mut vec = vec![0u32; 1000];
|
||||
/// generator.fill_slice_with_random_uniform_custom_mod(
|
||||
/// &mut vec,
|
||||
/// CiphertextModulus::try_new_power_of_2(31).unwrap(),
|
||||
/// );
|
||||
/// assert!(vec.iter().any(|&x| x != 0));
|
||||
/// ```
|
||||
pub fn fill_slice_with_random_uniform_custom_mod<Scalar>(
|
||||
&mut self,
|
||||
@@ -212,10 +214,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
self.fill_slice_with_random_uniform(output);
|
||||
|
||||
if !custom_modulus.is_native_modulus() {
|
||||
output
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.for_each(|x| *x = (*x).wrapping_rem(custom_modulus.get().cast_into()));
|
||||
output.as_mut().iter_mut().for_each(|x| {
|
||||
*x = (*x).wrapping_rem(custom_modulus.get_custom_modulus().cast_into())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,8 +244,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
/// use concrete_csprng::seeders::Seed;
|
||||
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
|
||||
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
|
||||
/// let mut vec = vec![2u32; 100];
|
||||
/// let mut vec = vec![0u32; 1000];
|
||||
/// generator.fill_slice_with_random_uniform_binary(&mut vec);
|
||||
/// assert!(vec.iter().any(|&x| x != 0));
|
||||
/// ```
|
||||
pub fn fill_slice_with_random_uniform_binary<Scalar>(&mut self, output: &mut [Scalar])
|
||||
where
|
||||
@@ -371,8 +373,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
/// use concrete_csprng::seeders::Seed;
|
||||
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
|
||||
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
|
||||
/// let mut vec = vec![1000f32; 100];
|
||||
/// let mut vec = vec![0f32; 1000];
|
||||
/// generator.fill_slice_with_random_gaussian(&mut vec, 0., 1.);
|
||||
/// assert!(vec.iter().any(|&x| x != 0.));
|
||||
/// ```
|
||||
pub fn fill_slice_with_random_gaussian<Float, Scalar>(
|
||||
&mut self,
|
||||
@@ -404,13 +407,14 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::parameters::CiphertextModulus;
|
||||
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
|
||||
/// let mut vec = vec![1000u64; 100];
|
||||
/// let mut vec = vec![0u64; 1000];
|
||||
/// generator.fill_slice_with_random_gaussian_custom_mod(
|
||||
/// &mut vec,
|
||||
/// 0.,
|
||||
/// 1.,
|
||||
/// CiphertextModulus::try_new_power_of_2(63).unwrap(),
|
||||
/// );
|
||||
/// assert!(vec.iter().any(|&x| x != 0));
|
||||
/// ```
|
||||
pub fn fill_slice_with_random_gaussian_custom_mod<Float, Scalar>(
|
||||
&mut self,
|
||||
@@ -423,7 +427,12 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
Scalar: UnsignedInteger,
|
||||
(Scalar, Scalar): RandomGenerable<Gaussian<Float>, CustomModulus = Float>,
|
||||
{
|
||||
let custom_modulus_float: Float = custom_modulus.get().cast_into();
|
||||
if custom_modulus.is_native_modulus() {
|
||||
self.fill_slice_with_random_gaussian(output, mean, std);
|
||||
return;
|
||||
}
|
||||
|
||||
let custom_modulus_float: Float = custom_modulus.get_custom_modulus().cast_into();
|
||||
output.chunks_mut(2).for_each(|s| {
|
||||
let (g1, g2) = <(Scalar, Scalar)>::generate_one_custom_modulus(
|
||||
self,
|
||||
@@ -448,8 +457,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
/// use concrete_csprng::seeders::Seed;
|
||||
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
|
||||
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
|
||||
/// let mut vec = vec![1000u32; 100];
|
||||
/// let mut vec = vec![0u32; 1000];
|
||||
/// generator.unsigned_torus_slice_wrapping_add_random_gaussian_assign(&mut vec, 0., 1.);
|
||||
/// assert!(vec.iter().any(|&x| x != 0));
|
||||
/// ```
|
||||
pub fn unsigned_torus_slice_wrapping_add_random_gaussian_assign<Float, Scalar>(
|
||||
&mut self,
|
||||
@@ -479,9 +489,16 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
/// use concrete_csprng::generators::SoftwareRandomGenerator;
|
||||
/// use concrete_csprng::seeders::Seed;
|
||||
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
|
||||
/// use tfhe::core_crypto::commons::parameters::CiphertextModulus;
|
||||
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
|
||||
/// let mut vec = vec![1000u32; 100];
|
||||
/// generator.unsigned_torus_slice_wrapping_add_random_gaussian_assign(&mut vec, 0., 1.);
|
||||
/// let mut vec = vec![0u32; 1000];
|
||||
/// generator.unsigned_torus_slice_wrapping_add_random_gaussian_custom_mod_assign(
|
||||
/// &mut vec,
|
||||
/// 0.,
|
||||
/// 1.,
|
||||
/// CiphertextModulus::try_new_power_of_2(31).unwrap(),
|
||||
/// );
|
||||
/// assert!(vec.iter().any(|&x| x != 0));
|
||||
/// ```
|
||||
pub fn unsigned_torus_slice_wrapping_add_random_gaussian_custom_mod_assign<Float, Scalar>(
|
||||
&mut self,
|
||||
@@ -494,7 +511,12 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
|
||||
Float: FloatingPoint + CastFrom<u128>,
|
||||
(Scalar, Scalar): RandomGenerable<Gaussian<Float>, CustomModulus = Float>,
|
||||
{
|
||||
let custom_modulus_float: Float = custom_modulus.get().cast_into();
|
||||
if custom_modulus.is_native_modulus() {
|
||||
self.unsigned_torus_slice_wrapping_add_random_gaussian_assign(output, mean, std);
|
||||
return;
|
||||
}
|
||||
|
||||
let custom_modulus_float: Float = custom_modulus.get_custom_modulus().cast_into();
|
||||
output.chunks_mut(2).for_each(|s| {
|
||||
let (g1, g2) = <(Scalar, Scalar)>::generate_one_custom_modulus(
|
||||
self,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::core_crypto::commons::dispersion::LogStandardDev;
|
||||
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulus;
|
||||
use crate::core_crypto::commons::math::torus::UnsignedTorus;
|
||||
use crate::core_crypto::commons::test_tools::*;
|
||||
|
||||
fn test_normal_random<T: UnsignedTorus>() {
|
||||
fn test_normal_random_three_sigma<T: UnsignedTorus>() {
|
||||
//! test if the normal random generation with std_dev is below 3*std_dev (99.7%)
|
||||
|
||||
// settings
|
||||
@@ -22,8 +22,10 @@ fn test_normal_random<T: UnsignedTorus>() {
|
||||
.zip(samples_int.iter())
|
||||
.for_each(|(out, &elt)| *out = elt.into_torus());
|
||||
for x in samples_float.iter_mut() {
|
||||
// The upper half of the torus corresponds to the negative domain when mapping unsigned
|
||||
// integer back to float (MSB or sign bit is set)
|
||||
if *x > 0.5 {
|
||||
*x = 1. - *x;
|
||||
*x -= 1.;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,39 +50,306 @@ fn test_normal_random<T: UnsignedTorus>() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_u32() {
|
||||
test_normal_random::<u32>();
|
||||
fn test_normal_random_three_sigma_u32() {
|
||||
test_normal_random_three_sigma::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_u64() {
|
||||
test_normal_random::<u64>();
|
||||
fn test_normal_random_three_sigma_u64() {
|
||||
test_normal_random_three_sigma::<u64>();
|
||||
}
|
||||
|
||||
fn test_distribution<T: UnsignedTorus>() {
|
||||
//! tests gaussianity against the rand crate generation
|
||||
// settings
|
||||
let std_dev: f64 = f64::powi(2., -5);
|
||||
let mean: f64 = 0.;
|
||||
let k = 10_000_000;
|
||||
let mut generator = new_random_generator();
|
||||
#[test]
|
||||
fn test_normal_random_f64() {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![0.0f64; SAMPLES_PER_RUN];
|
||||
|
||||
// generate normal random
|
||||
let first = vec![T::ZERO; k];
|
||||
let mut second = vec![T::ZERO; k];
|
||||
generator.fill_slice_with_random_gaussian(&mut second, mean, std_dev);
|
||||
rng.fill_slice_with_random_gaussian(&mut samples, 0.0, 1.0);
|
||||
|
||||
assert_noise_distribution(&first, &second, LogStandardDev(-5.));
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
// // These tests are notoriously flaky
|
||||
fn test_normal_random_native<Scalar: UnsignedTorus>() {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
|
||||
|
||||
// #[test]
|
||||
// fn test_distribution_u32() {
|
||||
// test_distribution::<u32>();
|
||||
// }
|
||||
rng.fill_slice_with_random_gaussian(&mut samples, 0.0, f64::powi(2., -20));
|
||||
|
||||
// #[test]
|
||||
// fn test_distribution_u64() {
|
||||
// test_distribution::<u64>();
|
||||
// }
|
||||
let samples: Vec<f64> = samples
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| {
|
||||
let torus = x.into_torus();
|
||||
// The upper half of the torus corresponds to the negative domain when mapping
|
||||
// unsigned integer back to float (MSB or sign bit is set)
|
||||
if torus > 0.5 {
|
||||
torus - 1.0
|
||||
} else {
|
||||
torus
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_native_u32() {
|
||||
test_normal_random_native::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_native_u64() {
|
||||
test_normal_random_native::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_native_u128() {
|
||||
test_normal_random_native::<u128>();
|
||||
}
|
||||
|
||||
fn test_normal_random_custom_mod<Scalar: UnsignedTorus>(
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
|
||||
|
||||
rng.fill_slice_with_random_gaussian_custom_mod(
|
||||
&mut samples,
|
||||
0.0,
|
||||
f64::powi(2., -20),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let samples: Vec<f64> = samples
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| {
|
||||
let torus = x.into_torus();
|
||||
// The upper half of the torus corresponds to the negative domain when mapping
|
||||
// unsigned integer back to float (MSB or sign bit is set)
|
||||
if torus > 0.5 {
|
||||
torus - 1.0
|
||||
} else {
|
||||
torus
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_custom_mod_u32() {
|
||||
test_normal_random_custom_mod::<u32>(CiphertextModulus::try_new_power_of_2(31).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_custom_mod_u64() {
|
||||
test_normal_random_custom_mod::<u64>(CiphertextModulus::try_new_power_of_2(63).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_custom_mod_u128() {
|
||||
test_normal_random_custom_mod::<u128>(CiphertextModulus::try_new_power_of_2(127).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_native_custom_mod_u32() {
|
||||
test_normal_random_custom_mod::<u32>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_native_custom_mod_u64() {
|
||||
test_normal_random_custom_mod::<u64>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_native_custom_mod_u128() {
|
||||
test_normal_random_custom_mod::<u128>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
fn test_normal_random_add_assign_native<Scalar: UnsignedTorus>() {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
|
||||
|
||||
rng.unsigned_torus_slice_wrapping_add_random_gaussian_assign(
|
||||
&mut samples,
|
||||
0.0,
|
||||
f64::powi(2., -20),
|
||||
);
|
||||
|
||||
let samples: Vec<f64> = samples
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| {
|
||||
let torus = x.into_torus();
|
||||
// The upper half of the torus corresponds to the negative domain when mapping
|
||||
// unsigned integer back to float (MSB or sign bit is set)
|
||||
if torus > 0.5 {
|
||||
torus - 1.0
|
||||
} else {
|
||||
torus
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_native_u32() {
|
||||
test_normal_random_add_assign_native::<u32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_native_u64() {
|
||||
test_normal_random_add_assign_native::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_native_u128() {
|
||||
test_normal_random_add_assign_native::<u128>();
|
||||
}
|
||||
|
||||
fn test_normal_random_add_assign_custom_mod<Scalar: UnsignedTorus>(
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = new_random_generator();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
|
||||
|
||||
rng.unsigned_torus_slice_wrapping_add_random_gaussian_custom_mod_assign(
|
||||
&mut samples,
|
||||
0.0,
|
||||
f64::powi(2., -20),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
let samples: Vec<f64> = samples
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| {
|
||||
let torus = x.into_torus();
|
||||
// The upper half of the torus corresponds to the negative domain when mapping
|
||||
// unsigned integer back to float (MSB or sign bit is set)
|
||||
if torus > 0.5 {
|
||||
torus - 1.0
|
||||
} else {
|
||||
torus
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_custom_mod_u32() {
|
||||
test_normal_random_add_assign_custom_mod::<u32>(
|
||||
CiphertextModulus::try_new_power_of_2(31).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_custom_mod_u64() {
|
||||
test_normal_random_add_assign_custom_mod::<u64>(
|
||||
CiphertextModulus::try_new_power_of_2(63).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_custom_mod_u128() {
|
||||
test_normal_random_add_assign_custom_mod::<u128>(
|
||||
CiphertextModulus::try_new_power_of_2(127).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_native_custom_mod_u32() {
|
||||
test_normal_random_add_assign_custom_mod::<u32>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_native_custom_mod_u64() {
|
||||
test_normal_random_add_assign_custom_mod::<u64>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_random_add_assign_native_custom_mod_u128() {
|
||||
test_normal_random_add_assign_custom_mod::<u128>(CiphertextModulus::new_native());
|
||||
}
|
||||
|
||||
@@ -67,13 +67,14 @@ pub mod test_tools {
|
||||
use crate::core_crypto::commons::generators::{
|
||||
EncryptionRandomGenerator, SecretRandomGenerator,
|
||||
};
|
||||
use crate::core_crypto::commons::math::random::{RandomGenerable, RandomGenerator, Uniform};
|
||||
use crate::core_crypto::commons::math::random::{
|
||||
ActivatedRandomGenerator, RandomGenerable, RandomGenerator, Uniform,
|
||||
};
|
||||
use crate::core_crypto::commons::parameters::{
|
||||
CiphertextCount, DecompositionBaseLog, DecompositionLevelCount, GlweDimension,
|
||||
LweDimension, PlaintextCount, PolynomialSize,
|
||||
};
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use concrete_csprng::generators::SoftwareRandomGenerator;
|
||||
use concrete_csprng::seeders::{Seed, Seeder};
|
||||
|
||||
fn modular_distance<T: UnsignedInteger>(first: T, other: T) -> T {
|
||||
@@ -94,15 +95,16 @@ pub mod test_tools {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_random_generator() -> RandomGenerator<SoftwareRandomGenerator> {
|
||||
pub fn new_random_generator() -> RandomGenerator<ActivatedRandomGenerator> {
|
||||
RandomGenerator::new(random_seed())
|
||||
}
|
||||
|
||||
pub fn new_secret_random_generator() -> SecretRandomGenerator<SoftwareRandomGenerator> {
|
||||
pub fn new_secret_random_generator() -> SecretRandomGenerator<ActivatedRandomGenerator> {
|
||||
SecretRandomGenerator::new(random_seed())
|
||||
}
|
||||
|
||||
pub fn new_encryption_random_generator() -> EncryptionRandomGenerator<SoftwareRandomGenerator> {
|
||||
pub fn new_encryption_random_generator() -> EncryptionRandomGenerator<ActivatedRandomGenerator>
|
||||
{
|
||||
EncryptionRandomGenerator::new(random_seed(), &mut UnsafeRandSeeder)
|
||||
}
|
||||
|
||||
@@ -143,49 +145,85 @@ pub mod test_tools {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_noise_distribution<First, Second, Element>(
|
||||
first: &First,
|
||||
second: &Second,
|
||||
dist: impl DispersionParameter,
|
||||
) where
|
||||
First: Container<Element = Element>,
|
||||
Second: Container<Element = Element>,
|
||||
Element: UnsignedTorus,
|
||||
{
|
||||
use rand_distr::Distribution;
|
||||
pub struct NormalityTestResult {
|
||||
pub w_prime: f64,
|
||||
pub p_value: f64,
|
||||
pub null_hypothesis_is_valid: bool,
|
||||
}
|
||||
|
||||
let std_dev = dist.get_standard_dev();
|
||||
let confidence = 0.95;
|
||||
let n_slots = first.container_len();
|
||||
/// Based on Shapiro-Francia normality test
|
||||
pub fn normality_test_f64(samples: &[f64], alpha: f64) -> NormalityTestResult {
|
||||
assert!(
|
||||
samples.len() <= 5000,
|
||||
"normality_test_f64 produces a relevant pvalue for less than 5000 samples"
|
||||
);
|
||||
|
||||
// allocate 2 slices: one for the error samples obtained, the second for fresh samples
|
||||
// according to the std_dev computed
|
||||
let mut sdk_samples = vec![0.0_f64; n_slots];
|
||||
// From "A handy approximation for the error function and its inverse" by Sergei Winitzki
|
||||
fn erf_inv(x: f64) -> f64 {
|
||||
let sign = if x < 0.0 { -1.0 } else { 1.0 };
|
||||
// 1 - x**2
|
||||
let one_minus_x_2 = (1.0 - x) * (1.0 + x);
|
||||
// ln(1 - x**2)
|
||||
let log_term = f64::ln(one_minus_x_2);
|
||||
let a = 0.147;
|
||||
let term_1 = 2.0 / (std::f64::consts::PI * a) + 0.5 * log_term;
|
||||
let term_2 = 1.0 / a * log_term;
|
||||
|
||||
// recover the errors from each ciphertexts
|
||||
sdk_samples
|
||||
.iter_mut()
|
||||
.zip(first.as_ref().iter().zip(second.as_ref().iter()))
|
||||
.for_each(|(out, (&lhs, &rhs))| *out = torus_modular_distance(lhs, rhs));
|
||||
|
||||
// fill the theoretical sample vector according to std_dev using the rand crate
|
||||
let mut theoretical_samples: Vec<f64> = Vec::with_capacity(n_slots);
|
||||
let normal = rand_distr::Normal::new(0.0, std_dev).unwrap();
|
||||
for _i in 0..n_slots {
|
||||
theoretical_samples.push(normal.sample(&mut rand::thread_rng()));
|
||||
sign * f64::sqrt(-term_1 + f64::sqrt(term_1 * term_1 - term_2))
|
||||
}
|
||||
|
||||
// compute the kolmogorov smirnov test
|
||||
let result = kolmogorov_smirnov::test_f64(
|
||||
sdk_samples.as_slice(),
|
||||
theoretical_samples.as_slice(),
|
||||
confidence,
|
||||
);
|
||||
assert!(
|
||||
!result.is_rejected,
|
||||
"Not the same distribution with a probability of {}",
|
||||
result.reject_probability
|
||||
);
|
||||
// Normal law CDF
|
||||
fn phi(x: f64) -> f64 {
|
||||
0.5 * (1.0 + libm::erf(x / f64::sqrt(2.0)))
|
||||
}
|
||||
|
||||
fn phi_inv(x: f64) -> f64 {
|
||||
f64::sqrt(2.0) * erf_inv(2.0 * x - 1.0)
|
||||
}
|
||||
|
||||
let n = samples.len();
|
||||
let n_f64 = n as f64;
|
||||
// Sort the input
|
||||
let mut samples: Vec<_> = samples.to_vec();
|
||||
samples.sort_by(|x, y| x.partial_cmp(y).unwrap());
|
||||
let samples = samples;
|
||||
// Compute the mean
|
||||
let mean = samples.iter().copied().sum::<f64>() / n_f64;
|
||||
let frac_three_eight = 3. / 8.;
|
||||
let frac_one_four = 1. / 4.;
|
||||
// Compute Blom scores
|
||||
let m_tilde: Vec<_> = (1..=n)
|
||||
.map(|i| phi_inv((i as f64 - frac_three_eight) / (n_f64 + frac_one_four)))
|
||||
.collect();
|
||||
// Blom scores norm2
|
||||
let m_norm = f64::sqrt(m_tilde.iter().fold(0.0, |acc, x| acc + x * x));
|
||||
// Coefficients
|
||||
let mut coeffs = m_tilde;
|
||||
coeffs.iter_mut().for_each(|x| *x /= m_norm);
|
||||
// Test statistic
|
||||
let denominator = samples.iter().fold(0.0, |acc, x| acc + (x - mean).powi(2));
|
||||
let numerator = samples
|
||||
.iter()
|
||||
.zip(coeffs.iter())
|
||||
.fold(0.0, |acc, (&sample, &coeff)| acc + sample * coeff)
|
||||
.powi(2);
|
||||
let w_prime = numerator / denominator;
|
||||
|
||||
let g_w_prime = f64::ln(1.0 - w_prime);
|
||||
let log_n = n_f64.ln();
|
||||
let log_log_n = log_n.ln();
|
||||
let u = log_log_n - log_n;
|
||||
let mu = 1.0521 * u - 1.2725;
|
||||
let v = log_log_n + 2.0 / log_n;
|
||||
let sigma = -0.26758 * v + 1.0308;
|
||||
let z = (g_w_prime - mu) / sigma;
|
||||
let p_value = 1.0 - phi(z);
|
||||
|
||||
NormalityTestResult {
|
||||
w_prime,
|
||||
p_value,
|
||||
null_hypothesis_is_valid: p_value > alpha,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a random plaintext count in [1;max].
|
||||
@@ -260,4 +298,52 @@ pub mod test_tools {
|
||||
let mut generator = new_random_generator();
|
||||
generator.random_uniform()
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_normality_tool() {
|
||||
use rand_distr::{Distribution, Normal};
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = rand::thread_rng();
|
||||
let normal = Normal::new(0.0, 1.0).unwrap();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![0.0f64; SAMPLES_PER_RUN];
|
||||
samples
|
||||
.iter_mut()
|
||||
.for_each(|x| *x = normal.sample(&mut rng));
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
println!("failure_rate: {failure_rate}");
|
||||
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
|
||||
assert!(failure_rate <= 0.065);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_normality_tool_fail_uniform() {
|
||||
const RUNS: usize = 10000;
|
||||
const SAMPLES_PER_RUN: usize = 1000;
|
||||
let mut rng = rand::thread_rng();
|
||||
let failures: f64 = (0..RUNS)
|
||||
.map(|_| {
|
||||
let mut samples = vec![0.0f64; SAMPLES_PER_RUN];
|
||||
samples.iter_mut().for_each(|x| *x = rng.gen());
|
||||
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
|
||||
// If we are normal return 0, it's not a failure
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
})
|
||||
.sum::<f64>();
|
||||
let failure_rate = failures / (RUNS as f64);
|
||||
assert!(failure_rate == 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
428
tfhe/src/core_crypto/entities/glwe_keyswitch_key.rs
Normal file
428
tfhe/src/core_crypto/entities/glwe_keyswitch_key.rs
Normal file
@@ -0,0 +1,428 @@
|
||||
//! Module containing the definition of the GlweKeyswitchKey.
|
||||
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// A [`GLWE keyswitch key`](`GlweKeyswitchKey`).
|
||||
///
|
||||
/// # Formal Definition
|
||||
///
|
||||
/// ## Key Switching Key
|
||||
///
|
||||
/// A key switching key is a vector of GLev ciphertexts (described on the bottom of
|
||||
/// [`this page`](`crate::core_crypto::entities::GgswCiphertext#Glev-ciphertext`)).
|
||||
/// It encrypts the coefficient of
|
||||
/// the [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
|
||||
/// $\vec{S}\_{\mathsf{in}}$ under the
|
||||
/// [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
|
||||
/// $\vec{S}\_{\mathsf{out}}$.
|
||||
///
|
||||
/// $$\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow \vec{S}\_{\mathsf{out}}} = \left(
|
||||
/// \overline{\mathsf{CT}\_0}, \cdots , \overline{\mathsf{CT}\_{k\_{\mathsf{in}}-1}}\right)
|
||||
/// \subseteq R\_q^{(k\_{\mathsf{out}}+1)\cdot k\_{\mathsf{in}}\cdot \ell}$$
|
||||
///
|
||||
/// where $\vec{S}\_{\mathsf{in}} = \left( S\_0 , \cdots , S\_{\mathsf{in}-1} \right)$ and for all
|
||||
/// $0\le i <k\_{\mathsf{in}}$ we have $\overline{\mathsf{CT}\_i} \in
|
||||
/// \mathsf{GLev}\_{\vec{S}\_{\mathsf{out}}}^{\beta, \ell}\left(S\_i\right)$.
|
||||
///
|
||||
/// ## GLWE Keyswitch
|
||||
///
|
||||
/// This homomorphic procedure transforms an input
|
||||
/// [`GLWE ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
|
||||
/// $\mathsf{CT}\_{\mathsf{in}} =
|
||||
/// \left( \vec{A}\_{\mathsf{in}} , B\_{\mathsf{in}}\right) \in \mathsf{GLWE}^{k\_{\mathsf{in}}}\_
|
||||
/// {\vec{S}\_{\mathsf{in}}}( \mathsf{PT} ) \subseteq R\_q^{(k\_{\mathsf{in}}+1)}$ into an
|
||||
/// output [`GLWE ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
|
||||
/// $\mathsf{CT}\_{\mathsf{out}} =
|
||||
/// \left( \vec{A}\_{\mathsf{out}} , B\_{\mathsf{out}}\right) \in
|
||||
/// \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}}( \mathsf{PT} )\subseteq
|
||||
/// R\_q^{(k\_{\mathsf{out}}+1)}$ where $k\_{\mathsf{in}} = |\vec{S}\_{\mathsf{in}}|$ and
|
||||
/// $k\_{\mathsf{out}} = |\vec{S}\_{\mathsf{out}}|$. It requires a
|
||||
/// [`key switching key`](`crate::core_crypto::entities::GlweKeyswitchKey`).
|
||||
/// The input ciphertext is encrypted under the
|
||||
/// [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
|
||||
/// $\vec{S}\_{\mathsf{in}}$ and the output ciphertext is
|
||||
/// encrypted under the [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
|
||||
/// $\vec{S}\_{\mathsf{out}}$.
|
||||
///
|
||||
/// $$\mathsf{CT}\_{\mathsf{in}} \in \mathsf{GLWE}^{k\_{\mathsf{in}}}\_{\vec{S}\_{\mathsf{in}}}(
|
||||
/// \mathsf{PT} ) ~~~~~~~~~~\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow
|
||||
/// \vec{S}\_{\mathsf{out}}}$$ $$ \mathsf{keyswitch}\left(\mathsf{CT}\_{\mathsf{in}} , \mathsf{KSK}
|
||||
/// \right) \rightarrow \mathsf{CT}\_{\mathsf{out}} \in
|
||||
/// \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}} \left( \mathsf{PT} \right)$$
|
||||
///
|
||||
/// ## Algorithm
|
||||
/// ###### inputs:
|
||||
/// - $\mathsf{CT}\_{\mathsf{in}} = \left( \vec{A}\_{\mathsf{in}} , B\_{\mathsf{in}}\right) \in
|
||||
/// \mathsf{GLWE}^{k\_{\mathsf{in}}}\_{\vec{S}\_{\mathsf{in}}}( \mathsf{PT} )$: a [`GLWE
|
||||
/// ciphertext`](`GlweCiphertext`) with $\vec{A}\_{\mathsf{in}}=\left(A\_0, \cdots
|
||||
/// A\_{k\_{\mathsf{in}}-1}\right)$
|
||||
/// - $\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow \vec{S}\_{\mathsf{out}}}$: a
|
||||
/// [`key switching key`](`crate::core_crypto::entities::GlweKeyswitchKey`)
|
||||
///
|
||||
/// ###### outputs:
|
||||
/// - $\mathsf{CT}\_{\mathsf{out}} \in \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}}
|
||||
/// \left( \mathsf{PT} \right)$: a
|
||||
/// [`GLWE ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
|
||||
///
|
||||
/// ###### algorithm:
|
||||
/// 1. set $\mathsf{cCT}=\left( 0 , \cdots , 0 , B\_{\mathsf{in}} \right) \in
|
||||
/// R\_q^{(k\_{\mathsf{out}}+1)}$
|
||||
/// 2. compute $\mathsf{CT}\_{\mathsf{out}} = \mathsf{CT} -
|
||||
/// \sum\_{i=0}^{k\_{\mathsf{in}}-1} \mathsf{decompProduct}\left( A\_i , \overline{\mathsf{CT}\_i}
|
||||
/// \right)$
|
||||
/// 3. output $\mathsf{CT}\_{\mathsf{out}}$
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GlweKeyswitchKey<C: Container>
|
||||
where
|
||||
C::Element: UnsignedInteger,
|
||||
{
|
||||
data: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for GlweKeyswitchKey<C> {
|
||||
fn as_ref(&self) -> &[T] {
|
||||
self.data.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for GlweKeyswitchKey<C> {
|
||||
fn as_mut(&mut self) -> &mut [T] {
|
||||
self.data.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element for a
|
||||
/// [GlweKeyswitchKey`] given a [`DecompositionLevelCount`] and output [`GlweSize`].
|
||||
pub fn glwe_keyswitch_key_input_key_element_encrypted_size(
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
) -> usize {
|
||||
// One ciphertext per level encrypted under the output key
|
||||
decomp_level_count.0 * output_glwe_size.0 * poly_size.0
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> GlweKeyswitchKey<C> {
|
||||
/// Create an [`GlweKeyswitchKey`] from an existing container.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function only wraps a container in the appropriate type. If you want to generate an
|
||||
/// [`GlweKeyswitchKey`] you need to call
|
||||
/// [`crate::core_crypto::algorithms::generate_glwe_keyswitch_key`] using this key as output.
|
||||
///
|
||||
/// This docstring exhibits [`GlweKeyswitchKey`] primitives usage.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweKeyswitchKey creation
|
||||
/// let input_glwe_dimension = GlweDimension(1);
|
||||
/// let output_glwe_dimension = GlweDimension(2);
|
||||
/// let poly_size = PolynomialSize(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(4);
|
||||
/// let decomp_level_count = DecompositionLevelCount(5);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create a new LweKeyswitchKey
|
||||
/// let glwe_ksk = GlweKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_glwe_dimension,
|
||||
/// output_glwe_dimension,
|
||||
/// poly_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(glwe_ksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(glwe_ksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(glwe_ksk.input_key_glwe_dimension(), input_glwe_dimension);
|
||||
/// assert_eq!(glwe_ksk.output_key_glwe_dimension(), output_glwe_dimension);
|
||||
/// assert_eq!(glwe_ksk.polynomial_size(), poly_size);
|
||||
/// assert_eq!(
|
||||
/// glwe_ksk.output_glwe_size(),
|
||||
/// output_glwe_dimension.to_glwe_size()
|
||||
/// );
|
||||
/// assert_eq!(glwe_ksk.ciphertext_modulus(), ciphertext_modulus);
|
||||
///
|
||||
/// // Demonstrate how to recover the allocated container
|
||||
/// let underlying_container: Vec<u64> = glwe_ksk.into_container();
|
||||
///
|
||||
/// // Recreate a keyswithc key using from_container
|
||||
/// let glwe_ksk = GlweKeyswitchKey::from_container(
|
||||
/// underlying_container,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// output_glwe_dimension.to_glwe_size(),
|
||||
/// poly_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(glwe_ksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(glwe_ksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(glwe_ksk.input_key_glwe_dimension(), input_glwe_dimension);
|
||||
/// assert_eq!(glwe_ksk.output_key_glwe_dimension(), output_glwe_dimension);
|
||||
/// assert_eq!(
|
||||
/// glwe_ksk.output_glwe_size(),
|
||||
/// output_glwe_dimension.to_glwe_size()
|
||||
/// );
|
||||
/// assert_eq!(glwe_ksk.ciphertext_modulus(), ciphertext_modulus);
|
||||
/// ```
|
||||
pub fn from_container(
|
||||
container: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
container.container_len() > 0,
|
||||
"Got an empty container to create an LweKeyswitchKey"
|
||||
);
|
||||
assert!(
|
||||
container.container_len() % (decomp_level_count.0 * output_glwe_size.0 * poly_size.0)
|
||||
== 0,
|
||||
"The provided container length is not valid. \
|
||||
It needs to be dividable by decomp_level_count * output_glwe_size * output_poly_size: {}. \
|
||||
Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
|
||||
output_glwe_size: {output_glwe_size:?}, poly_size: {poly_size:?}.",
|
||||
decomp_level_count.0 * output_glwe_size.0 * poly_size.0,
|
||||
container.container_len()
|
||||
);
|
||||
|
||||
GlweKeyswitchKey {
|
||||
data: container,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
poly_size,
|
||||
ciphertext_modulus,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionBaseLog`] of the [`LweKeyswitchKey`].
|
||||
///
|
||||
/// See [`LweKeyswitchKey::from_container`] for usage.
|
||||
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.decomp_base_log
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionLevelCount`] of the [`LweKeyswitchKey`].
|
||||
///
|
||||
/// See [`LweKeyswitchKey::from_container`] for usage.
|
||||
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.decomp_level_count
|
||||
}
|
||||
|
||||
/// Return the input [`GlweDimension`] of the [`GlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`GlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn input_key_glwe_dimension(&self) -> GlweDimension {
|
||||
GlweDimension(self.data.container_len() / self.input_key_element_encrypted_size())
|
||||
}
|
||||
|
||||
/// Return the input [`PolynomialSize`] of the [`GlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`GlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn polynomial_size(&self) -> PolynomialSize {
|
||||
self.poly_size
|
||||
}
|
||||
|
||||
/// Return the output [`GlweDimension`] of the [`GlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`GlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_key_glwe_dimension(&self) -> GlweDimension {
|
||||
self.output_glwe_size.to_glwe_dimension()
|
||||
}
|
||||
|
||||
/// Return the output [`GlweSize`] of the [`GlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`GlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_glwe_size(&self) -> GlweSize {
|
||||
self.output_glwe_size
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element of the
|
||||
/// current [`GlweKeyswitchKey`].
|
||||
pub fn input_key_element_encrypted_size(&self) -> usize {
|
||||
glwe_keyswitch_key_input_key_element_encrypted_size(
|
||||
self.decomp_level_count,
|
||||
self.output_glwe_size,
|
||||
self.poly_size,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return a view of the [`GlweKeyswitchKey`]. This is useful if an algorithm takes a view by
|
||||
/// value.
|
||||
pub fn as_view(&self) -> GlweKeyswitchKey<&'_ [Scalar]> {
|
||||
GlweKeyswitchKey::from_container(
|
||||
self.as_ref(),
|
||||
self.decomp_base_log,
|
||||
self.decomp_level_count,
|
||||
self.output_glwe_size,
|
||||
self.poly_size,
|
||||
self.ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
|
||||
/// Consume the entity and return its underlying container.
|
||||
///
|
||||
/// See [`GlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn into_container(self) -> C {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Return the [`CiphertextModulus`] of the [`GlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`GlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
|
||||
self.ciphertext_modulus
|
||||
}
|
||||
|
||||
pub fn as_glwe_ciphertext_list(&self) -> GlweCiphertextListView<'_, Scalar> {
|
||||
GlweCiphertextListView::from_container(
|
||||
self.as_ref(),
|
||||
self.output_glwe_size(),
|
||||
self.polynomial_size(),
|
||||
self.ciphertext_modulus(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> GlweKeyswitchKey<C> {
|
||||
/// Mutable variant of [`GlweKeyswitchKey::as_view`].
|
||||
pub fn as_mut_view(&mut self) -> GlweKeyswitchKey<&'_ mut [Scalar]> {
|
||||
let decomp_base_log = self.decomp_base_log;
|
||||
let decomp_level_count = self.decomp_level_count;
|
||||
let output_glwe_size = self.output_glwe_size;
|
||||
let poly_size = self.poly_size;
|
||||
let ciphertext_modulus = self.ciphertext_modulus;
|
||||
GlweKeyswitchKey::from_container(
|
||||
self.as_mut(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
poly_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn as_mut_glwe_ciphertext_list(&mut self) -> GlweCiphertextListMutView<'_, Scalar> {
|
||||
let output_glwe_size = self.output_glwe_size();
|
||||
let poly_size = self.polynomial_size();
|
||||
let ciphertext_modulus = self.ciphertext_modulus();
|
||||
GlweCiphertextListMutView::from_container(
|
||||
self.as_mut(),
|
||||
output_glwe_size,
|
||||
poly_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`GlweKeyswitchKey`] owning the memory for its own storage.
|
||||
pub type GlweKeyswitchKeyOwned<Scalar> = GlweKeyswitchKey<Vec<Scalar>>;
|
||||
|
||||
impl<Scalar: UnsignedInteger> GlweKeyswitchKeyOwned<Scalar> {
|
||||
/// Allocate memory and create a new owned [`GlweKeyswitchKey`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
|
||||
/// type. If you want to generate an [`GlweKeyswitchKey`] you need to call
|
||||
/// [`crate::core_crypto::algorithms::generate_glwe_keyswitch_key`] using this key as output.
|
||||
///
|
||||
/// See [`GlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn new(
|
||||
fill_with: Scalar,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_key_glwe_dimension: GlweDimension,
|
||||
output_key_glwe_dimension: GlweDimension,
|
||||
poly_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) -> GlweKeyswitchKeyOwned<Scalar> {
|
||||
GlweKeyswitchKeyOwned::from_container(
|
||||
vec![
|
||||
fill_with;
|
||||
input_key_glwe_dimension.0
|
||||
* glwe_keyswitch_key_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
output_key_glwe_dimension.to_glwe_size(),
|
||||
poly_size,
|
||||
)
|
||||
],
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_key_glwe_dimension.to_glwe_size(),
|
||||
poly_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
|
||||
for GlweKeyswitchKey<C>
|
||||
{
|
||||
type Element = C::Element;
|
||||
|
||||
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Scalar>;
|
||||
|
||||
type EntityView<'this> = GlweCiphertextListView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
type SelfViewMetadata = ();
|
||||
|
||||
// At the moment it does not make sense to return "sub" keyswitch keys. So we use a dummy
|
||||
// placeholder type here.
|
||||
type SelfView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
fn get_entity_view_creation_metadata(&self) -> GlweCiphertextListCreationMetadata<Scalar> {
|
||||
GlweCiphertextListCreationMetadata(
|
||||
self.output_glwe_size(),
|
||||
self.polynomial_size(),
|
||||
self.ciphertext_modulus(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_entity_view_pod_size(&self) -> usize {
|
||||
self.input_key_element_encrypted_size()
|
||||
}
|
||||
|
||||
/// Unimplemented for [`GlweKeyswitchKey`]. At the moment it does not make sense to
|
||||
/// return "sub" keyswitch keys.
|
||||
fn get_self_view_creation_metadata(&self) {
|
||||
unimplemented!(
|
||||
"This function is not supported for GlweKeyswitchKey. \
|
||||
At the moment it does not make sense to return 'sub' keyswitch keys."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
|
||||
for GlweKeyswitchKey<C>
|
||||
{
|
||||
type EntityMutView<'this> = GlweCiphertextListMutView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
// At the moment it does not make sense to return "sub" keyswitch keys. So we use a dummy
|
||||
// placeholder type here.
|
||||
type SelfMutView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
}
|
||||
382
tfhe/src/core_crypto/entities/glwe_relinearisation_key.rs
Normal file
382
tfhe/src/core_crypto/entities/glwe_relinearisation_key.rs
Normal file
@@ -0,0 +1,382 @@
|
||||
//! Module containing the definition of the GlweRelinearisationKey.
|
||||
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// A [`GLWE relinearisation key`](`GlweRelinearisationKey`).
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GlweRelinearisationKey<C: Container>
|
||||
where
|
||||
C::Element: UnsignedInteger,
|
||||
{
|
||||
data: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for GlweRelinearisationKey<C> {
|
||||
fn as_ref(&self) -> &[T] {
|
||||
self.data.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for GlweRelinearisationKey<C> {
|
||||
fn as_mut(&mut self) -> &mut [T] {
|
||||
self.data.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element for a
|
||||
/// [`GlweRelinearisationKey`] given a [`DecompositionLevelCount`], [`GlweSize`] and
|
||||
/// [`PolynomialSize`].
|
||||
pub fn glwe_relinearisation_key_input_key_element_encrypted_size(
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
) -> usize {
|
||||
// One ciphertext per level encrypted under the output key
|
||||
decomp_level_count.0 * glwe_size.0 * polynomial_size.0
|
||||
}
|
||||
|
||||
/// Return the number of elements in a [`GlweRelinearisationKey`] given a
|
||||
/// [`DecompositionLevelCount`], [`GlweSize`], and [`PolynomialSize`].
|
||||
pub fn glwe_relinearisation_key_size(
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
) -> usize {
|
||||
(glwe_size.to_glwe_dimension().0 * (glwe_size.to_glwe_dimension().0 + 1)) / 2
|
||||
* glwe_relinearisation_key_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
)
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> GlweRelinearisationKey<C> {
|
||||
/// Create a [`GlweRelinearisationKey`] from an existing container.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function only wraps a container in the appropriate type. If you want to generate an
|
||||
/// [`GlweRelinearisationKey`] you need to use
|
||||
/// [`crate::core_crypto::algorithms::generate_glwe_relinearisation_key`]
|
||||
/// using this key as output.
|
||||
///
|
||||
/// This docstring exhibits [`GlweRelinearisationKey`] primitives usage.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for GlweRelinearisationKey creation
|
||||
/// let glwe_size = GlweSize(3);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create a new GlweRelinearisationKey
|
||||
/// let relin_key = GlweRelinearisationKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(relin_key.glwe_dimension(), glwe_size.to_glwe_dimension());
|
||||
/// assert_eq!(relin_key.glwe_size(), glwe_size);
|
||||
/// assert_eq!(relin_key.polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(relin_key.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(relin_key.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(relin_key.ciphertext_modulus(), ciphertext_modulus);
|
||||
///
|
||||
/// // Demonstrate how to recover the allocated container
|
||||
/// let underlying_container: Vec<u64> = relin_key.into_container();
|
||||
///
|
||||
/// // Recreate a key using from_container
|
||||
/// let relin_key = GlweRelinearisationKey::from_container(
|
||||
/// underlying_container,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(relin_key.glwe_dimension(), glwe_size.to_glwe_dimension());
|
||||
/// assert_eq!(relin_key.glwe_size(), glwe_size);
|
||||
/// assert_eq!(relin_key.polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(relin_key.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(relin_key.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(relin_key.ciphertext_modulus(), ciphertext_modulus);
|
||||
/// ```
|
||||
pub fn from_container(
|
||||
container: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
) -> GlweRelinearisationKey<C> {
|
||||
assert!(
|
||||
container.container_len() > 0,
|
||||
"Got an empty container to create an LweKeyswitchKey"
|
||||
);
|
||||
assert!(
|
||||
container.container_len()
|
||||
% glwe_relinearisation_key_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
glwe_size,
|
||||
polynomial_size
|
||||
)
|
||||
== 0,
|
||||
"The provided container length is not valid. \
|
||||
It needs to be divisable by decomp_level_count * glwe_size * polynomial_size:\
|
||||
{}. Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
|
||||
glwe_size: {glwe_size:?}, polynomial_size: {polynomial_size:?}.",
|
||||
glwe_relinearisation_key_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
glwe_size,
|
||||
polynomial_size
|
||||
),
|
||||
container.container_len()
|
||||
);
|
||||
|
||||
GlweRelinearisationKey {
|
||||
data: container,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`GlweDimension`] of the [`GlweRelinearisationKey`].
|
||||
///
|
||||
/// See [`GlweRelinearisationKey::from_container`] for usage.
|
||||
pub fn glwe_dimension(&self) -> GlweDimension {
|
||||
self.glwe_size.to_glwe_dimension()
|
||||
}
|
||||
|
||||
/// Return the [`GlweSize`] of the [`GlweRelinearisationKey`].
|
||||
///
|
||||
/// See [`GlweRelinearisationKey::from_container`] for usage.
|
||||
pub fn glwe_size(&self) -> GlweSize {
|
||||
self.glwe_size
|
||||
}
|
||||
|
||||
/// Return the output [`PolynomialSize`] of the [`GlweRelinearisationKey`].
|
||||
///
|
||||
/// See [`GlweRelinearisationKey::from_container`] for usage.
|
||||
pub fn polynomial_size(&self) -> PolynomialSize {
|
||||
self.polynomial_size
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionLevelCount`] of the [`GlweRelinearisationKey`].
|
||||
///
|
||||
/// See [`GlweRelinearisationKey::from_container`] for usage.
|
||||
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.decomp_level_count
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionBaseLog`] of the [`GlweRelinearisationKey`].
|
||||
///
|
||||
/// See [`GlweRelinearisationKey::from_container`] for usage.
|
||||
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.decomp_base_log
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element of the
|
||||
/// current [`GlweRelinearisationKey`].
|
||||
pub fn input_key_element_encrypted_size(&self) -> usize {
|
||||
glwe_relinearisation_key_input_key_element_encrypted_size(
|
||||
self.decomp_level_count,
|
||||
self.glwe_size,
|
||||
self.polynomial_size,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return a view of the [`GlweRelinearisationKey`]. This is useful if an
|
||||
/// algorithm takes a view by value.
|
||||
pub fn as_view(&self) -> GlweRelinearisationKey<&'_ [Scalar]> {
|
||||
GlweRelinearisationKey::from_container(
|
||||
self.as_ref(),
|
||||
self.decomp_base_log,
|
||||
self.decomp_level_count,
|
||||
self.glwe_size,
|
||||
self.polynomial_size,
|
||||
self.ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
|
||||
/// Consume the entity and return its underlying container.
|
||||
///
|
||||
/// See [`GlweRelinearisationKey::from_container`] for usage.
|
||||
pub fn into_container(self) -> C {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Return the [`CiphertextModulus`] of the [`GlweRelinearisationKey`]
|
||||
///
|
||||
/// See [`GlweRelinearisationKey::from_container`] for usage.
|
||||
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
|
||||
self.ciphertext_modulus
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> GlweRelinearisationKey<C> {
|
||||
/// Mutable variant of [`LweTracePackingKeyswitchKey::as_view`].
|
||||
pub fn as_mut_view(&mut self) -> GlweRelinearisationKey<&'_ mut [Scalar]> {
|
||||
let decomp_base_log = self.decomp_base_log;
|
||||
let decomp_level_count = self.decomp_level_count;
|
||||
let glwe_size = self.glwe_size;
|
||||
let polynomial_size = self.polynomial_size;
|
||||
let ciphertext_modulus = self.ciphertext_modulus;
|
||||
|
||||
GlweRelinearisationKey::from_container(
|
||||
self.as_mut(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`GlweRelinearisationKey`] owning the memory for its own storage.
|
||||
pub type GlweRelinearisationKeyOwned<Scalar> = GlweRelinearisationKey<Vec<Scalar>>;
|
||||
|
||||
impl<Scalar: UnsignedInteger> GlweRelinearisationKeyOwned<Scalar> {
|
||||
/// Create a new [`GlweRelinearisationKey`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
|
||||
/// type. If you want to generate an [`GlweRelinearisationKey`] you need to use
|
||||
/// [`crate::core_crypto::algorithms::generate_glwe_relinearisation_key`]
|
||||
/// using this key as output.
|
||||
///
|
||||
/// See [`GlweRelinearisationKey::from_container`] for usage.
|
||||
pub fn new(
|
||||
fill_with: Scalar,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) -> GlweRelinearisationKeyOwned<Scalar> {
|
||||
GlweRelinearisationKeyOwned::from_container(
|
||||
vec![
|
||||
fill_with;
|
||||
glwe_relinearisation_key_size(decomp_level_count, glwe_size, polynomial_size)
|
||||
],
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
|
||||
for GlweRelinearisationKey<C>
|
||||
{
|
||||
type Element = C::Element;
|
||||
|
||||
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Scalar>;
|
||||
|
||||
type EntityView<'this> = GlweCiphertextListView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
type SelfViewMetadata = ();
|
||||
|
||||
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
|
||||
// dummy placeholder type here.
|
||||
type SelfView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
|
||||
// Fix to use the correct ciphertext modulus
|
||||
GlweCiphertextListCreationMetadata(
|
||||
self.glwe_size,
|
||||
self.polynomial_size,
|
||||
self.ciphertext_modulus(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_entity_view_pod_size(&self) -> usize {
|
||||
self.input_key_element_encrypted_size()
|
||||
}
|
||||
|
||||
/// Unimplemented for [`GlweRelinearisationKey`]. At the moment it does not
|
||||
/// make sense to return "sub" packing keyswitch keys.
|
||||
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
|
||||
unimplemented!(
|
||||
"This function is not supported for GlweRelinearisationKey. \
|
||||
At the moment it does not make sense to return 'sub' relinearisation keys."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
|
||||
for GlweRelinearisationKey<C>
|
||||
{
|
||||
type EntityMutView<'this> = GlweCiphertextListMutView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
// At the moment it does not make sense to return "sub" relinearisation keys. So we use a
|
||||
// dummy placeholder type here.
|
||||
type SelfMutView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
}
|
||||
|
||||
/// Metadata used in the [`CreateFrom`] implementation to create
|
||||
/// [`GlweRelinearisationKey`] entities.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GlweRelinearisationKeyCreationMetadata<Scalar: UnsignedInteger>(
|
||||
pub DecompositionBaseLog,
|
||||
pub DecompositionLevelCount,
|
||||
pub GlweSize,
|
||||
pub PolynomialSize,
|
||||
pub CiphertextModulus<Scalar>,
|
||||
);
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
|
||||
for GlweRelinearisationKey<C>
|
||||
{
|
||||
type Metadata = GlweRelinearisationKeyCreationMetadata<Scalar>;
|
||||
|
||||
#[inline]
|
||||
fn create_from(from: C, meta: Self::Metadata) -> GlweRelinearisationKey<C> {
|
||||
let GlweRelinearisationKeyCreationMetadata(
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
) = meta;
|
||||
GlweRelinearisationKey::from_container(
|
||||
from,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
//! Module containing the definition of the LwePublicFunctionalPackingKeyswitchKey.
|
||||
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// A [`LWE public functional packing keyswitch key`](`LwePublicFunctionalPackingKeyswitchKey`).
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LwePublicFunctionalPackingKeyswitchKey<C: Container>
|
||||
where
|
||||
C::Element: UnsignedInteger,
|
||||
{
|
||||
data: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
output_polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]>
|
||||
for LwePublicFunctionalPackingKeyswitchKey<C>
|
||||
{
|
||||
fn as_ref(&self) -> &[T] {
|
||||
self.data.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]>
|
||||
for LwePublicFunctionalPackingKeyswitchKey<C>
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut [T] {
|
||||
self.data.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of a list of input [`LweSecretKey`] elements for
|
||||
/// a [`LwePublicFunctionalPackingKeyswitchKey`] given a [`DecompositionLevelCount`] and output
|
||||
/// [`GlweSize`] and [`PolynomialSize`].
|
||||
pub fn lwe_pubfpksk_input_key_element_encrypted_size(
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
output_polynomial_size: PolynomialSize,
|
||||
) -> usize {
|
||||
// One ciphertext per level encrypted under the output key
|
||||
decomp_level_count.0 * output_glwe_size.0 * output_polynomial_size.0
|
||||
}
|
||||
|
||||
/// Return the number of elements in an [`LwePublicFunctionalPackingKeyswitchKey`] given an input
|
||||
/// ['size of the list of LWe] [`LweSize`], [`DecompositionLevelCount`], output [`GlweSize`], and
|
||||
/// output [`PolynomialSize`].
|
||||
pub fn lwe_pubfpksk_size(
|
||||
input_lwe_size: LweSize,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
output_polynomial_size: PolynomialSize,
|
||||
) -> usize {
|
||||
input_lwe_size.0
|
||||
* lwe_pubfpksk_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
)
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>>
|
||||
LwePublicFunctionalPackingKeyswitchKey<C>
|
||||
{
|
||||
/// Create an [`LwePublicFunctionalPackingKeyswitchKey`] from an existing container.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function only wraps a container in the appropriate type. If you want to generate an
|
||||
/// [`LwePublicFunctionalPackingKeyswitchKey`] you need to use
|
||||
/// [`crate::core_crypto::algorithms::generate_lwe_public_functional_packing_keyswitch_key`] or
|
||||
/// the parallel variant
|
||||
/// [`crate::core_crypto::algorithms::par_generate_lwe_public_functional_packing_keyswitch_key`]
|
||||
/// using this key as output.
|
||||
///
|
||||
/// This docstring exhibits [`LwePublicFunctionalPackingKeyswitchKey`] primitives usage.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LwePublicFunctionalPackingKeyswitchKey creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let input_lwe_dimension = LweDimension(600);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create a new LwePublicFunctionalPackingKeyswitchKey
|
||||
/// let pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// pubfpksk.output_glwe_key_dimension(),
|
||||
/// glwe_size.to_glwe_dimension()
|
||||
/// );
|
||||
/// assert_eq!(pubfpksk.output_glwe_size(), glwe_size);
|
||||
/// assert_eq!(pubfpksk.output_polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(pubfpksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(pubfpksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(pubfpksk.input_lwe_key_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(pubfpksk.ciphertext_modulus(), ciphertext_modulus);
|
||||
///
|
||||
/// // Demonstrate how to recover the allocated container
|
||||
/// let underlying_container: Vec<u64> = pubfpksk.into_container();
|
||||
///
|
||||
/// // Recreate a key using from_container
|
||||
/// let pubfpksk = LwePublicFunctionalPackingKeyswitchKeyList::from_container(
|
||||
/// underlying_container,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension.to_lwe_size(),
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// pubfpksk.output_glwe_key_dimension(),
|
||||
/// glwe_size.to_glwe_dimension()
|
||||
/// );
|
||||
/// assert_eq!(pubfpksk.output_glwe_size(), glwe_size);
|
||||
/// assert_eq!(pubfpksk.output_polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(pubfpksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(pubfpksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(pubfpksk.input_lwe_key_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(pubfpksk.ciphertext_modulus(), ciphertext_modulus);
|
||||
/// ```
|
||||
pub fn from_container(
|
||||
container: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
output_polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
) -> LwePublicFunctionalPackingKeyswitchKey<C> {
|
||||
assert!(
|
||||
container.container_len() > 0,
|
||||
"Got an empty container to create an LweKeyswitchKey"
|
||||
);
|
||||
assert!(
|
||||
container.container_len()
|
||||
% lwe_pubfpksk_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size
|
||||
)
|
||||
== 0,
|
||||
"The provided container length is not valid. \
|
||||
It needs to be dividable by decomp_level_count * output_glwe_size * output_polynomial_size:\
|
||||
{}. Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
|
||||
output_glwe_size: {output_glwe_size:?}, output_polynomial_size: \
|
||||
{output_polynomial_size:?}.",
|
||||
lwe_pubfpksk_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size
|
||||
),
|
||||
container.container_len()
|
||||
);
|
||||
|
||||
LwePublicFunctionalPackingKeyswitchKey {
|
||||
data: container,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the output key [`GlweDimension`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_glwe_key_dimension(&self) -> GlweDimension {
|
||||
self.output_glwe_size.to_glwe_dimension()
|
||||
}
|
||||
|
||||
/// Return the output [`GlweSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_glwe_size(&self) -> GlweSize {
|
||||
self.output_glwe_size
|
||||
}
|
||||
|
||||
/// Return the output [`PolynomialSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_polynomial_size(&self) -> PolynomialSize {
|
||||
self.output_polynomial_size
|
||||
}
|
||||
|
||||
/// Return the input key [`LweDimension`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn input_lwe_key_dimension(&self) -> LweDimension {
|
||||
LweDimension(self.data.container_len() / self.input_key_element_encrypted_size() - 1)
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionLevelCount`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.decomp_level_count
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionBaseLog`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.decomp_base_log
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`LweSecretKey`] element of the
|
||||
/// current [`LwePublicFunctionalPackingKeyswitchKey`].
|
||||
pub fn input_key_element_encrypted_size(&self) -> usize {
|
||||
lwe_pubfpksk_input_key_element_encrypted_size(
|
||||
self.decomp_level_count,
|
||||
self.output_glwe_size,
|
||||
self.output_polynomial_size,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return a view of the [`LwePublicFunctionalPackingKeyswitchKey`]. This is useful if an
|
||||
/// algorithm takes a view by value.
|
||||
pub fn as_view(&self) -> LwePublicFunctionalPackingKeyswitchKey<&'_ [Scalar]> {
|
||||
LwePublicFunctionalPackingKeyswitchKey::from_container(
|
||||
self.as_ref(),
|
||||
self.decomp_base_log,
|
||||
self.decomp_level_count,
|
||||
self.output_glwe_size,
|
||||
self.output_polynomial_size,
|
||||
self.ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
|
||||
/// Consume the entity and return its underlying container.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn into_container(self) -> C {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Return the [`CiphertextModulus`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
|
||||
self.ciphertext_modulus
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>>
|
||||
LwePublicFunctionalPackingKeyswitchKey<C>
|
||||
{
|
||||
/// Mutable variant of [`LwePublicFunctionalPackingKeyswitchKey::as_view`].
|
||||
pub fn as_mut_view(&mut self) -> LwePublicFunctionalPackingKeyswitchKey<&'_ mut [Scalar]> {
|
||||
let decomp_base_log = self.decomp_base_log;
|
||||
let decomp_level_count = self.decomp_level_count;
|
||||
let output_glwe_size = self.output_glwe_size;
|
||||
let output_polynomial_size = self.output_polynomial_size;
|
||||
let ciphertext_modulus = self.ciphertext_modulus;
|
||||
|
||||
LwePublicFunctionalPackingKeyswitchKey::from_container(
|
||||
self.as_mut(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`LwePublicFunctionalPackingKeyswitchKey`] owning the memory for its own storage.
|
||||
pub type LwePublicFunctionalPackingKeyswitchKeyOwned<Scalar> =
|
||||
LwePublicFunctionalPackingKeyswitchKey<Vec<Scalar>>;
|
||||
|
||||
impl<Scalar: UnsignedInteger> LwePublicFunctionalPackingKeyswitchKeyOwned<Scalar> {
|
||||
/// Create an [`LwePublicFunctionalPackingKeyswitchKey`] from an existing container.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
|
||||
/// type. If you want to generate an [`LwePublicFunctionalPackingKeyswitchKey`] you need to use
|
||||
/// [`crate::core_crypto::algorithms::generate_lwe_public_functional_packing_keyswitch_key`] or
|
||||
/// the parallel variant
|
||||
/// [`crate::core_crypto::algorithms::par_generate_lwe_public_functional_packing_keyswitch_key`]
|
||||
/// using this key as output.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn new(
|
||||
fill_with: Scalar,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_key_lwe_dimension: LweDimension,
|
||||
output_glwe_size: GlweSize,
|
||||
output_polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) -> LwePublicFunctionalPackingKeyswitchKeyOwned<Scalar> {
|
||||
LwePublicFunctionalPackingKeyswitchKeyOwned::from_container(
|
||||
vec![
|
||||
fill_with;
|
||||
lwe_pubfpksk_size(
|
||||
input_key_lwe_dimension.to_lwe_size(),
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size
|
||||
)
|
||||
],
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
|
||||
for LwePublicFunctionalPackingKeyswitchKey<C>
|
||||
{
|
||||
type Element = C::Element;
|
||||
|
||||
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Scalar>;
|
||||
|
||||
type EntityView<'this> = GlweCiphertextListView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
type SelfViewMetadata = ();
|
||||
|
||||
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
|
||||
// dummy placeholder type here.
|
||||
type SelfView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
|
||||
GlweCiphertextListCreationMetadata(
|
||||
self.output_glwe_size,
|
||||
self.output_polynomial_size,
|
||||
self.ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_entity_view_pod_size(&self) -> usize {
|
||||
self.input_key_element_encrypted_size()
|
||||
}
|
||||
|
||||
/// Unimplemented for [`LwePublicFunctionalPackingKeyswitchKey`]. At the moment it does not
|
||||
/// make sense to return "sub" packing keyswitch keys.
|
||||
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
|
||||
unimplemented!(
|
||||
"This function is not supported for LwePublicFunctionalPackingKeyswitchKey. \
|
||||
At the moment it does not make sense to return 'sub' packing keyswitch keys."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
|
||||
for LwePublicFunctionalPackingKeyswitchKey<C>
|
||||
{
|
||||
type EntityMutView<'this> = GlweCiphertextListMutView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
|
||||
// dummy placeholder type here.
|
||||
type SelfMutView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
}
|
||||
|
||||
/// Metadata used in the [`CreateFrom`] implementation to create
|
||||
/// [`LwePublicFunctionalPackingKeyswitchKey`] entities.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LwePublicFunctionalPackingKeyswitchKeyCreationMetadata<Scalar: UnsignedInteger>(
|
||||
pub DecompositionBaseLog,
|
||||
pub DecompositionLevelCount,
|
||||
pub GlweSize,
|
||||
pub PolynomialSize,
|
||||
pub CiphertextModulus<Scalar>,
|
||||
);
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
|
||||
for LwePublicFunctionalPackingKeyswitchKey<C>
|
||||
{
|
||||
type Metadata = LwePublicFunctionalPackingKeyswitchKeyCreationMetadata<Scalar>;
|
||||
|
||||
#[inline]
|
||||
fn create_from(from: C, meta: Self::Metadata) -> LwePublicFunctionalPackingKeyswitchKey<C> {
|
||||
let LwePublicFunctionalPackingKeyswitchKeyCreationMetadata(
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
) = meta;
|
||||
LwePublicFunctionalPackingKeyswitchKey::from_container(
|
||||
from,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
//! Module containing the definition of the LwePublicFunctionalPackingKeyswitchKeyList.
|
||||
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// A contiguous list containing [`LWE private functional packing keyswitch
|
||||
/// keys`](`crate::core_crypto::entities::LwePrivateFunctionalPackingKeyswitchKey`).
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LwePublicFunctionalPackingKeyswitchKeyList<C: Container>
|
||||
where
|
||||
C::Element: UnsignedInteger,
|
||||
{
|
||||
data: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_lwe_size: LweSize,
|
||||
output_glwe_size: GlweSize,
|
||||
output_polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]>
|
||||
for LwePublicFunctionalPackingKeyswitchKeyList<C>
|
||||
{
|
||||
fn as_ref(&self) -> &[T] {
|
||||
self.data.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]>
|
||||
for LwePublicFunctionalPackingKeyswitchKeyList<C>
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut [T] {
|
||||
self.data.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>>
|
||||
LwePublicFunctionalPackingKeyswitchKeyList<C>
|
||||
{
|
||||
/// Create an [`LwePublicFunctionalPackingKeyswitchKeyList`] from an existing container.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function only wraps a container in the appropriate type. If you want to generate keys
|
||||
/// in the list you need to use
|
||||
/// [`crate::core_crypto::algorithms::generate_lwe_public_functional_packing_keyswitch_key`] or
|
||||
/// the parallel variant
|
||||
/// [`crate::core_crypto::algorithms::par_generate_lwe_public_functional_packing_keyswitch_key`]
|
||||
/// on the individual keys in the list.
|
||||
///
|
||||
/// This docstring exhibits [`LwePublicFunctionalPackingKeyswitchKeyList`] primitives usage.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LwePublicFunctionalPackingKeyswitchKeyList creation
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let input_lwe_dimension = LweDimension(600);
|
||||
/// let lwe_pubfpksk_count = FunctionalPackingKeyswitchKeyCount(2);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create a new LwePublicFunctionalPackingKeyswitchKeyList
|
||||
/// let pubfpksk_list = LwePublicFunctionalPackingKeyswitchKeyList::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// lwe_pubfpksk_count,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// pubfpksk_list.output_glwe_key_dimension(),
|
||||
/// glwe_size.to_glwe_dimension()
|
||||
/// );
|
||||
/// assert_eq!(pubfpksk_list.output_glwe_size(), glwe_size);
|
||||
/// assert_eq!(pubfpksk_list.output_polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(pubfpksk_list.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(
|
||||
/// pubfpksk_list.decomposition_level_count(),
|
||||
/// decomp_level_count
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// pubfpksk_list.input_lwe_size(),
|
||||
/// input_lwe_dimension.to_lwe_size()
|
||||
/// );
|
||||
/// assert_eq!(pubfpksk_list.input_lwe_key_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(pubfpksk_list.lwe_pubfpksk_count(), lwe_pubfpksk_count);
|
||||
/// assert_eq!(pubfpksk_list.ciphertext_modulus(), ciphertext_modulus);
|
||||
///
|
||||
/// // Demonstrate how to recover the allocated container
|
||||
/// let underlying_container: Vec<u64> = pubfpksk_list.into_container();
|
||||
///
|
||||
/// // Recreate a list using from_container
|
||||
/// let pubfpksk_list = LwePublicFunctionalPackingKeyswitchKeyList::from_container(
|
||||
/// underlying_container,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension.to_lwe_size(),
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// pubfpksk_list.output_glwe_key_dimension(),
|
||||
/// glwe_size.to_glwe_dimension()
|
||||
/// );
|
||||
/// assert_eq!(pubfpksk_list.output_glwe_size(), glwe_size);
|
||||
/// assert_eq!(pubfpksk_list.output_polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(pubfpksk_list.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(
|
||||
/// pubfpksk_list.decomposition_level_count(),
|
||||
/// decomp_level_count
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// pubfpksk_list.input_lwe_size(),
|
||||
/// input_lwe_dimension.to_lwe_size()
|
||||
/// );
|
||||
/// assert_eq!(pubfpksk_list.input_lwe_key_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(pubfpksk_list.lwe_pubfpksk_count(), lwe_pubfpksk_count);
|
||||
/// assert_eq!(pubfpksk_list.ciphertext_modulus(), ciphertext_modulus);
|
||||
/// ```
|
||||
pub fn from_container(
|
||||
container: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_lwe_size: LweSize,
|
||||
output_glwe_size: GlweSize,
|
||||
output_polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
) -> LwePublicFunctionalPackingKeyswitchKeyList<C> {
|
||||
assert!(
|
||||
container.container_len()
|
||||
% lwe_pubfpksk_size(
|
||||
input_lwe_size,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size
|
||||
)
|
||||
== 0,
|
||||
"The provided container length is not valid. \
|
||||
It needs to be dividable by input_lwe_size * decomp_level_count * output_glwe_size * \
|
||||
output_polynomial_size: {}. Got container length: {} and input_lwe_size: {input_lwe_size:?}\
|
||||
decomp_level_count: {decomp_level_count:?}, output_glwe_size: {output_glwe_size:?}, \
|
||||
output_polynomial_size: {output_polynomial_size:?}.",
|
||||
lwe_pubfpksk_size(
|
||||
input_lwe_size,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size
|
||||
),
|
||||
container.container_len()
|
||||
);
|
||||
|
||||
LwePublicFunctionalPackingKeyswitchKeyList {
|
||||
data: container,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the output key [`GlweDimension`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
|
||||
/// stored in the list.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn output_glwe_key_dimension(&self) -> GlweDimension {
|
||||
self.output_glwe_size.to_glwe_dimension()
|
||||
}
|
||||
|
||||
/// Return the output [`GlweSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`] stored in
|
||||
/// the list.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn output_glwe_size(&self) -> GlweSize {
|
||||
self.output_glwe_size
|
||||
}
|
||||
|
||||
/// Return the output [`PolynomialSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
|
||||
/// stored in the list.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn output_polynomial_size(&self) -> PolynomialSize {
|
||||
self.output_polynomial_size
|
||||
}
|
||||
|
||||
/// Return the input key [`LweDimension`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
|
||||
/// stored in the list.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn input_lwe_key_dimension(&self) -> LweDimension {
|
||||
self.input_lwe_size.to_lwe_dimension()
|
||||
}
|
||||
|
||||
/// Return the input [`LweSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`]stored in the
|
||||
/// list.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn input_lwe_size(&self) -> LweSize {
|
||||
self.input_lwe_size
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionLevelCount`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
|
||||
/// stored in the list.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.decomp_level_count
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionBaseLog`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
|
||||
/// stored in the list.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.decomp_base_log
|
||||
}
|
||||
|
||||
/// Return the number of elements in a [`LwePublicFunctionalPackingKeyswitchKey`] stored in
|
||||
/// the list.
|
||||
pub fn lwe_pubfpksk_size(&self) -> usize {
|
||||
lwe_pubfpksk_size(
|
||||
self.input_lwe_size,
|
||||
self.decomp_level_count,
|
||||
self.output_glwe_size,
|
||||
self.output_polynomial_size,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the [`FunctionalPackingKeyswitchKeyCount`] of the
|
||||
/// [`LwePublicFunctionalPackingKeyswitchKeyList`].
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn lwe_pubfpksk_count(&self) -> FunctionalPackingKeyswitchKeyCount {
|
||||
FunctionalPackingKeyswitchKeyCount(self.as_ref().container_len() / self.lwe_pubfpksk_size())
|
||||
}
|
||||
|
||||
/// Return a view of the [`LwePublicFunctionalPackingKeyswitchKeyList`]. This is useful if an
|
||||
/// algorithm takes a view by value.
|
||||
pub fn as_view(&self) -> LwePublicFunctionalPackingKeyswitchKeyList<&'_ [Scalar]> {
|
||||
LwePublicFunctionalPackingKeyswitchKeyList::from_container(
|
||||
self.as_ref(),
|
||||
self.decomp_base_log,
|
||||
self.decomp_level_count,
|
||||
self.input_lwe_size,
|
||||
self.output_glwe_size,
|
||||
self.output_polynomial_size,
|
||||
self.ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
|
||||
/// Consume the entity and return its underlying container.
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn into_container(self) -> C {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Return the [`CiphertextModulus`] of the [`LwePublicFunctionalPackingKeyswitchKeyList`]
|
||||
///
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
|
||||
self.ciphertext_modulus
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>>
|
||||
LwePublicFunctionalPackingKeyswitchKeyList<C>
|
||||
{
|
||||
/// Mutable variant of [`LwePublicFunctionalPackingKeyswitchKeyList::as_view`].
|
||||
pub fn as_mut_view(&mut self) -> LwePublicFunctionalPackingKeyswitchKeyList<&'_ mut [Scalar]> {
|
||||
let decomp_base_log = self.decomp_base_log;
|
||||
let decomp_level_count = self.decomp_level_count;
|
||||
let input_lwe_size = self.input_lwe_size;
|
||||
let output_glwe_size = self.output_glwe_size;
|
||||
let output_polynomial_size = self.output_polynomial_size;
|
||||
let ciphertext_modulus = self.ciphertext_modulus;
|
||||
|
||||
LwePublicFunctionalPackingKeyswitchKeyList::from_container(
|
||||
self.as_mut(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`LwePublicFunctionalPackingKeyswitchKeyList`] owning the memory for its own storage.
|
||||
pub type LwePublicFunctionalPackingKeyswitchKeyListOwned<Scalar> =
|
||||
LwePublicFunctionalPackingKeyswitchKeyList<Vec<Scalar>>;
|
||||
|
||||
impl<Scalar: UnsignedInteger> LwePublicFunctionalPackingKeyswitchKeyListOwned<Scalar> {
|
||||
/// Allocate memory and create a new owned [`LwePublicFunctionalPackingKeyswitchKeyList`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
|
||||
/// type. If you want to generate keys in the list you need to use
|
||||
/// [`crate::core_crypto::algorithms::generate_lwe_public_functional_packing_keyswitch_key`] or
|
||||
/// the parallel variant
|
||||
/// [`crate::core_crypto::algorithms::par_generate_lwe_public_functional_packing_keyswitch_key`]
|
||||
/// on the individual keys in the list.
|
||||
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
fill_with: Scalar,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_key_lwe_dimension: LweDimension,
|
||||
output_glwe_size: GlweSize,
|
||||
output_polynomial_size: PolynomialSize,
|
||||
pubfpksk_count: FunctionalPackingKeyswitchKeyCount,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) -> LwePublicFunctionalPackingKeyswitchKeyListOwned<Scalar> {
|
||||
LwePublicFunctionalPackingKeyswitchKeyListOwned::from_container(
|
||||
vec![
|
||||
fill_with;
|
||||
pubfpksk_count.0
|
||||
* lwe_pubfpksk_size(
|
||||
input_key_lwe_dimension.to_lwe_size(),
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
output_polynomial_size
|
||||
)
|
||||
],
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_key_lwe_dimension.to_lwe_size(),
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata used in the [`CreateFrom`] implementation to create
|
||||
/// [`LwePublicFunctionalPackingKeyswitchKeyList`] entities.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata<Scalar: UnsignedInteger>(
|
||||
pub DecompositionBaseLog,
|
||||
pub DecompositionLevelCount,
|
||||
pub LweSize,
|
||||
pub GlweSize,
|
||||
pub PolynomialSize,
|
||||
pub CiphertextModulus<Scalar>,
|
||||
);
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
|
||||
for LwePublicFunctionalPackingKeyswitchKeyList<C>
|
||||
{
|
||||
type Metadata = LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata<Scalar>;
|
||||
|
||||
#[inline]
|
||||
fn create_from(from: C, meta: Self::Metadata) -> Self {
|
||||
let LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata(
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
) = meta;
|
||||
LwePublicFunctionalPackingKeyswitchKeyList::from_container(
|
||||
from,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
output_polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
|
||||
for LwePublicFunctionalPackingKeyswitchKeyList<C>
|
||||
{
|
||||
type Element = C::Element;
|
||||
|
||||
type EntityViewMetadata = LwePublicFunctionalPackingKeyswitchKeyCreationMetadata<Scalar>;
|
||||
|
||||
type EntityView<'this> = LwePublicFunctionalPackingKeyswitchKey<&'this [Self::Element]>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
type SelfViewMetadata = LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata<Scalar>;
|
||||
|
||||
type SelfView<'this> = LwePublicFunctionalPackingKeyswitchKeyList<&'this [Self::Element]>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
|
||||
LwePublicFunctionalPackingKeyswitchKeyCreationMetadata(
|
||||
self.decomposition_base_log(),
|
||||
self.decomposition_level_count(),
|
||||
self.output_glwe_size(),
|
||||
self.output_polynomial_size(),
|
||||
self.ciphertext_modulus(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_entity_view_pod_size(&self) -> usize {
|
||||
self.lwe_pubfpksk_size()
|
||||
}
|
||||
|
||||
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
|
||||
LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata(
|
||||
self.decomposition_base_log(),
|
||||
self.decomposition_level_count(),
|
||||
self.input_lwe_size(),
|
||||
self.output_glwe_size(),
|
||||
self.output_polynomial_size(),
|
||||
self.ciphertext_modulus(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
|
||||
for LwePublicFunctionalPackingKeyswitchKeyList<C>
|
||||
{
|
||||
type EntityMutView<'this> = LwePublicFunctionalPackingKeyswitchKey<&'this mut [Self::Element]>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
type SelfMutView<'this> = LwePublicFunctionalPackingKeyswitchKeyList<&'this mut [Self::Element]>
|
||||
where
|
||||
Self: 'this;
|
||||
}
|
||||
410
tfhe/src/core_crypto/entities/lwe_trace_packing_keyswitch_key.rs
Normal file
410
tfhe/src/core_crypto/entities/lwe_trace_packing_keyswitch_key.rs
Normal file
@@ -0,0 +1,410 @@
|
||||
//! Module containing the definition of the LweTracePackingKeyswitchKey.
|
||||
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// An [`LWE trace packing keyswitch key`](`LweTracePackingKeyswitchKey`).
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LweTracePackingKeyswitchKey<C: Container>
|
||||
where
|
||||
C::Element: UnsignedInteger,
|
||||
{
|
||||
data: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_lwe_size: LweSize,
|
||||
output_glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for LweTracePackingKeyswitchKey<C> {
|
||||
fn as_ref(&self) -> &[T] {
|
||||
self.data.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]>
|
||||
for LweTracePackingKeyswitchKey<C>
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut [T] {
|
||||
self.data.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`LweSecretKey`] element for a
|
||||
/// [`LweTracePackingKeyswitchKey`] given a [`DecompositionLevelCount`] and output
|
||||
/// [`GlweSize`] and [`PolynomialSize`].
|
||||
pub fn lwe_tpksk_input_key_element_encrypted_size(
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
) -> usize {
|
||||
// One ciphertext per level encrypted under the output key
|
||||
decomp_level_count.0 * output_glwe_size.0 * polynomial_size.0
|
||||
}
|
||||
|
||||
/// Return the number of elements in an [`LweTracePackingKeyswitchKey`] given a
|
||||
/// [`DecompositionLevelCount`], output [`GlweSize`], and output [`PolynomialSize`].
|
||||
pub fn lwe_tpksk_size(
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
) -> usize {
|
||||
output_glwe_size.to_glwe_dimension().0
|
||||
* polynomial_size.log2().0
|
||||
* lwe_tpksk_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
polynomial_size,
|
||||
)
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> LweTracePackingKeyswitchKey<C> {
|
||||
/// Create an [`LweTracePackingKeyswitchKey`] from an existing container.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function only wraps a container in the appropriate type. If you want to generate an
|
||||
/// [`LweTracePackingKeyswitchKey`] you need to use
|
||||
/// [`crate::core_crypto::algorithms::generate_lwe_trace_packing_keyswitch_key`]
|
||||
/// using this key as output.
|
||||
///
|
||||
/// This docstring exhibits [`LweTracePackingKeyswitchKey`] primitives usage.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for LweTracePackingKeyswitchKey creation
|
||||
/// let lwe_size = LweSize(1001);
|
||||
/// let glwe_size = GlweSize(2);
|
||||
/// let polynomial_size = PolynomialSize(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(8);
|
||||
/// let decomp_level_count = DecompositionLevelCount(3);
|
||||
/// let ciphertext_modulus = CiphertextModulus::new_native();
|
||||
///
|
||||
/// // Create a new LweTracePackingKeyswitchKey
|
||||
/// let tpksk = LweTracePackingKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// lwe_size,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// tpksk.output_glwe_key_dimension(),
|
||||
/// glwe_size.to_glwe_dimension()
|
||||
/// );
|
||||
/// assert_eq!(tpksk.output_glwe_size(), glwe_size);
|
||||
/// assert_eq!(tpksk.polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(tpksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(tpksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(tpksk.ciphertext_modulus(), ciphertext_modulus);
|
||||
///
|
||||
/// // Demonstrate how to recover the allocated container
|
||||
/// let underlying_container: Vec<u64> = tpksk.into_container();
|
||||
///
|
||||
/// // Recreate a key using from_container
|
||||
/// let tpksk = LweTracePackingKeyswitchKey::from_container(
|
||||
/// underlying_container,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// lwe_size,
|
||||
/// glwe_size,
|
||||
/// polynomial_size,
|
||||
/// ciphertext_modulus,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// tpksk.output_glwe_key_dimension(),
|
||||
/// glwe_size.to_glwe_dimension()
|
||||
/// );
|
||||
/// assert_eq!(tpksk.input_lwe_size(), lwe_size);
|
||||
/// assert_eq!(tpksk.output_glwe_size(), glwe_size);
|
||||
/// assert_eq!(tpksk.polynomial_size(), polynomial_size);
|
||||
/// assert_eq!(tpksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(tpksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(tpksk.ciphertext_modulus(), ciphertext_modulus);
|
||||
/// ```
|
||||
pub fn from_container(
|
||||
container: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_lwe_size: LweSize,
|
||||
output_glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<C::Element>,
|
||||
) -> LweTracePackingKeyswitchKey<C> {
|
||||
assert!(
|
||||
container.container_len() > 0,
|
||||
"Got an empty container to create an LweKeyswitchKey"
|
||||
);
|
||||
assert!(
|
||||
container.container_len()
|
||||
% lwe_tpksk_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
polynomial_size
|
||||
)
|
||||
== 0,
|
||||
"The provided container length is not valid. \
|
||||
It needs to be divisable by decomp_level_count * output_glwe_size * polynomial_size:\
|
||||
{}. Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
|
||||
output_glwe_size: {output_glwe_size:?}, polynomial_size: \
|
||||
{polynomial_size:?}.",
|
||||
lwe_tpksk_input_key_element_encrypted_size(
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
polynomial_size
|
||||
),
|
||||
container.container_len()
|
||||
);
|
||||
|
||||
LweTracePackingKeyswitchKey {
|
||||
data: container,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the output key [`GlweDimension`] of the [`LweTracePackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_glwe_key_dimension(&self) -> GlweDimension {
|
||||
self.output_glwe_size.to_glwe_dimension()
|
||||
}
|
||||
|
||||
/// Return the output [`GlweSize`] of the [`LweTracePackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_glwe_size(&self) -> GlweSize {
|
||||
self.output_glwe_size
|
||||
}
|
||||
|
||||
/// Return the output [`PolynomialSize`] of the [`LweTracePackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn polynomial_size(&self) -> PolynomialSize {
|
||||
self.polynomial_size
|
||||
}
|
||||
|
||||
/// Return the input [`LweSize`] of the [`LweTracePackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn input_lwe_size(&self) -> LweSize {
|
||||
self.input_lwe_size
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionLevelCount`] of the [`LweTracePackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.decomp_level_count
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionBaseLog`] of the [`LweTracePackingKeyswitchKey`].
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.decomp_base_log
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`LweSecretKey`] element of the
|
||||
/// current [`LweTracePackingKeyswitchKey`].
|
||||
pub fn input_key_element_encrypted_size(&self) -> usize {
|
||||
lwe_tpksk_input_key_element_encrypted_size(
|
||||
self.decomp_level_count,
|
||||
self.output_glwe_size,
|
||||
self.polynomial_size,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return a view of the [`LweTracePackingKeyswitchKey`]. This is useful if an
|
||||
/// algorithm takes a view by value.
|
||||
pub fn as_view(&self) -> LweTracePackingKeyswitchKey<&'_ [Scalar]> {
|
||||
LweTracePackingKeyswitchKey::from_container(
|
||||
self.as_ref(),
|
||||
self.decomp_base_log,
|
||||
self.decomp_level_count,
|
||||
self.input_lwe_size,
|
||||
self.output_glwe_size,
|
||||
self.polynomial_size,
|
||||
self.ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
|
||||
/// Consume the entity and return its underlying container.
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn into_container(self) -> C {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Return the [`CiphertextModulus`] of the [`LweTracePackingKeyswitchKey`]
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
|
||||
self.ciphertext_modulus
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> LweTracePackingKeyswitchKey<C> {
|
||||
/// Mutable variant of [`LweTracePackingKeyswitchKey::as_view`].
|
||||
pub fn as_mut_view(&mut self) -> LweTracePackingKeyswitchKey<&'_ mut [Scalar]> {
|
||||
let decomp_base_log = self.decomp_base_log;
|
||||
let decomp_level_count = self.decomp_level_count;
|
||||
let input_lwe_size = self.input_lwe_size;
|
||||
let output_glwe_size = self.output_glwe_size;
|
||||
let polynomial_size = self.polynomial_size;
|
||||
let ciphertext_modulus = self.ciphertext_modulus;
|
||||
|
||||
LweTracePackingKeyswitchKey::from_container(
|
||||
self.as_mut(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`LweTracePackingKeyswitchKey`] owning the memory for its own storage.
|
||||
pub type LweTracePackingKeyswitchKeyOwned<Scalar> = LweTracePackingKeyswitchKey<Vec<Scalar>>;
|
||||
|
||||
impl<Scalar: UnsignedInteger> LweTracePackingKeyswitchKeyOwned<Scalar> {
|
||||
/// Create an [`LweTracePackingKeyswitchKey`] from an existing container.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
|
||||
/// type. If you want to generate an [`LweTracePackingKeyswitchKey`] you need to use
|
||||
/// [`crate::core_crypto::algorithms::generate_lwe_trace_packing_keyswitch_key`]
|
||||
/// using this key as output.
|
||||
///
|
||||
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
|
||||
pub fn new(
|
||||
fill_with: Scalar,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_lwe_size: LweSize,
|
||||
output_glwe_size: GlweSize,
|
||||
polynomial_size: PolynomialSize,
|
||||
ciphertext_modulus: CiphertextModulus<Scalar>,
|
||||
) -> LweTracePackingKeyswitchKeyOwned<Scalar> {
|
||||
LweTracePackingKeyswitchKeyOwned::from_container(
|
||||
vec![fill_with; lwe_tpksk_size(decomp_level_count, output_glwe_size, polynomial_size)],
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
|
||||
for LweTracePackingKeyswitchKey<C>
|
||||
{
|
||||
type Element = C::Element;
|
||||
|
||||
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Scalar>;
|
||||
|
||||
type EntityView<'this> = GlweCiphertextListView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
type SelfViewMetadata = ();
|
||||
|
||||
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
|
||||
// dummy placeholder type here.
|
||||
type SelfView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
|
||||
GlweCiphertextListCreationMetadata(
|
||||
self.output_glwe_size,
|
||||
self.polynomial_size,
|
||||
self.ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_entity_view_pod_size(&self) -> usize {
|
||||
self.input_key_element_encrypted_size() * self.output_glwe_size.to_glwe_dimension().0
|
||||
}
|
||||
|
||||
/// Unimplemented for [`LweTracePackingKeyswitchKey`]. At the moment it does not
|
||||
/// make sense to return "sub" packing keyswitch keys.
|
||||
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
|
||||
unimplemented!(
|
||||
"This function is not supported for LweTracePackingKeyswitchKey. \
|
||||
At the moment it does not make sense to return 'sub' packing keyswitch keys."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
|
||||
for LweTracePackingKeyswitchKey<C>
|
||||
{
|
||||
type EntityMutView<'this> = GlweCiphertextListMutView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
|
||||
// dummy placeholder type here.
|
||||
type SelfMutView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
}
|
||||
|
||||
/// Metadata used in the [`CreateFrom`] implementation to create
|
||||
/// [`LweTracePackingKeyswitchKey`] entities.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LweTracePackingKeyswitchKeyCreationMetadata<Scalar: UnsignedInteger>(
|
||||
pub DecompositionBaseLog,
|
||||
pub DecompositionLevelCount,
|
||||
pub LweSize,
|
||||
pub GlweSize,
|
||||
pub PolynomialSize,
|
||||
pub CiphertextModulus<Scalar>,
|
||||
);
|
||||
|
||||
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
|
||||
for LweTracePackingKeyswitchKey<C>
|
||||
{
|
||||
type Metadata = LweTracePackingKeyswitchKeyCreationMetadata<Scalar>;
|
||||
|
||||
#[inline]
|
||||
fn create_from(from: C, meta: Self::Metadata) -> LweTracePackingKeyswitchKey<C> {
|
||||
let LweTracePackingKeyswitchKeyCreationMetadata(
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
) = meta;
|
||||
LweTracePackingKeyswitchKey::from_container(
|
||||
from,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
input_lwe_size,
|
||||
output_glwe_size,
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ pub mod ggsw_ciphertext;
|
||||
pub mod ggsw_ciphertext_list;
|
||||
pub mod glwe_ciphertext;
|
||||
pub mod glwe_ciphertext_list;
|
||||
pub mod glwe_keyswitch_key;
|
||||
pub mod glwe_relinearisation_key;
|
||||
pub mod glwe_secret_key;
|
||||
pub mod gsw_ciphertext;
|
||||
pub mod lwe_bootstrap_key;
|
||||
@@ -17,8 +19,11 @@ pub mod lwe_keyswitch_key;
|
||||
pub mod lwe_multi_bit_bootstrap_key;
|
||||
pub mod lwe_private_functional_packing_keyswitch_key;
|
||||
pub mod lwe_private_functional_packing_keyswitch_key_list;
|
||||
pub mod lwe_public_functional_packing_keyswitch_key;
|
||||
pub mod lwe_public_functional_packing_keyswitch_key_list;
|
||||
pub mod lwe_public_key;
|
||||
pub mod lwe_secret_key;
|
||||
pub mod lwe_trace_packing_keyswitch_key;
|
||||
pub mod plaintext;
|
||||
pub mod plaintext_list;
|
||||
pub mod polynomial;
|
||||
@@ -27,6 +32,7 @@ pub mod seeded_ggsw_ciphertext;
|
||||
pub mod seeded_ggsw_ciphertext_list;
|
||||
pub mod seeded_glwe_ciphertext;
|
||||
pub mod seeded_glwe_ciphertext_list;
|
||||
pub mod seeded_glwe_keyswitch_key;
|
||||
pub mod seeded_lwe_bootstrap_key;
|
||||
pub mod seeded_lwe_ciphertext;
|
||||
pub mod seeded_lwe_ciphertext_list;
|
||||
@@ -51,6 +57,8 @@ pub use ggsw_ciphertext::*;
|
||||
pub use ggsw_ciphertext_list::*;
|
||||
pub use glwe_ciphertext::*;
|
||||
pub use glwe_ciphertext_list::*;
|
||||
pub use glwe_keyswitch_key::*;
|
||||
pub use glwe_relinearisation_key::*;
|
||||
pub use glwe_secret_key::*;
|
||||
pub use gsw_ciphertext::*;
|
||||
pub use lwe_bootstrap_key::*;
|
||||
@@ -60,8 +68,11 @@ pub use lwe_keyswitch_key::*;
|
||||
pub use lwe_multi_bit_bootstrap_key::*;
|
||||
pub use lwe_private_functional_packing_keyswitch_key::*;
|
||||
pub use lwe_private_functional_packing_keyswitch_key_list::*;
|
||||
pub use lwe_public_functional_packing_keyswitch_key::*;
|
||||
pub use lwe_public_functional_packing_keyswitch_key_list::*;
|
||||
pub use lwe_public_key::*;
|
||||
pub use lwe_secret_key::*;
|
||||
pub use lwe_trace_packing_keyswitch_key::*;
|
||||
pub use plaintext::*;
|
||||
pub use plaintext_list::*;
|
||||
pub use polynomial::*;
|
||||
@@ -70,6 +81,7 @@ pub use seeded_ggsw_ciphertext::*;
|
||||
pub use seeded_ggsw_ciphertext_list::*;
|
||||
pub use seeded_glwe_ciphertext::*;
|
||||
pub use seeded_glwe_ciphertext_list::*;
|
||||
pub use seeded_glwe_keyswitch_key::*;
|
||||
pub use seeded_lwe_bootstrap_key::*;
|
||||
pub use seeded_lwe_ciphertext::*;
|
||||
pub use seeded_lwe_ciphertext_list::*;
|
||||
|
||||
379
tfhe/src/core_crypto/entities/seeded_glwe_keyswitch_key.rs
Normal file
379
tfhe/src/core_crypto/entities/seeded_glwe_keyswitch_key.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
//! Module containing the definition of the SeededGlweKeyswitchKey.
|
||||
|
||||
use crate::core_crypto::algorithms::*;
|
||||
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, CompressionSeed};
|
||||
use crate::core_crypto::commons::parameters::*;
|
||||
use crate::core_crypto::commons::traits::*;
|
||||
use crate::core_crypto::entities::*;
|
||||
|
||||
/// A [`seeded GLWE keyswitch key`](`SeededGlweKeyswitchKey`).
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SeededGlweKeyswitchKey<C: Container> {
|
||||
data: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
compression_seed: CompressionSeed,
|
||||
}
|
||||
|
||||
impl<T, C: Container<Element = T>> AsRef<[T]> for SeededGlweKeyswitchKey<C> {
|
||||
fn as_ref(&self) -> &[T] {
|
||||
self.data.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C: ContainerMut<Element = T>> AsMut<[T]> for SeededGlweKeyswitchKey<C> {
|
||||
fn as_mut(&mut self) -> &mut [T] {
|
||||
self.data.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element for a
|
||||
/// [`SeededGlweKeyswitchKey`] given a [`DecompositionLevelCount`] and output [`GlweSize`].
|
||||
pub fn seeded_glwe_keyswitch_key_input_key_element_encrypted_size(
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
) -> usize {
|
||||
// One seeded ciphertext per level
|
||||
decomp_level_count.0
|
||||
}
|
||||
|
||||
impl<Scalar, C: Container<Element = Scalar>> SeededGlweKeyswitchKey<C> {
|
||||
/// Create an [`SeededGlweKeyswitchKey`] from an existing container.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function only wraps a container in the appropriate type. If you want to generate an
|
||||
/// [`SeededGlweKeyswitchKey`] you need to call
|
||||
/// [`crate::core_crypto::algorithms::generate_seeded_glwe_keyswitch_key`] using this key as
|
||||
/// output.
|
||||
///
|
||||
/// This docstring exhibits [`SeededGlweKeyswitchKey`] primitives usage.
|
||||
///
|
||||
/// ```
|
||||
/// use tfhe::core_crypto::prelude::*;
|
||||
///
|
||||
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
|
||||
/// // computations
|
||||
/// // Define parameters for SeededLweKeyswitchKey creation
|
||||
/// let input_lwe_dimension = LweDimension(600);
|
||||
/// let output_lwe_dimension = LweDimension(1024);
|
||||
/// let decomp_base_log = DecompositionBaseLog(4);
|
||||
/// let decomp_level_count = DecompositionLevelCount(5);
|
||||
///
|
||||
/// // Get a seeder
|
||||
/// let mut seeder = new_seeder();
|
||||
/// let seeder = seeder.as_mut();
|
||||
///
|
||||
/// // Create a new SeededLweKeyswitchKey
|
||||
/// let lwe_ksk = SeededLweKeyswitchKey::new(
|
||||
/// 0u64,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// input_lwe_dimension,
|
||||
/// output_lwe_dimension,
|
||||
/// seeder.seed().into(),
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(lwe_ksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(lwe_ksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(lwe_ksk.input_key_lwe_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(lwe_ksk.output_key_lwe_dimension(), output_lwe_dimension);
|
||||
/// assert_eq!(
|
||||
/// lwe_ksk.output_lwe_size(),
|
||||
/// output_lwe_dimension.to_lwe_size()
|
||||
/// );
|
||||
///
|
||||
/// let compression_seed = lwe_ksk.compression_seed();
|
||||
///
|
||||
/// // Demonstrate how to recover the allocated container
|
||||
/// let underlying_container: Vec<u64> = lwe_ksk.into_container();
|
||||
///
|
||||
/// // Recreate a secret key using from_container
|
||||
/// let lwe_ksk = SeededLweKeyswitchKey::from_container(
|
||||
/// underlying_container,
|
||||
/// decomp_base_log,
|
||||
/// decomp_level_count,
|
||||
/// output_lwe_dimension.to_lwe_size(),
|
||||
/// compression_seed,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(lwe_ksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(lwe_ksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(lwe_ksk.input_key_lwe_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(lwe_ksk.output_key_lwe_dimension(), output_lwe_dimension);
|
||||
/// assert_eq!(
|
||||
/// lwe_ksk.output_lwe_size(),
|
||||
/// output_lwe_dimension.to_lwe_size()
|
||||
/// );
|
||||
///
|
||||
/// let lwe_ksk = lwe_ksk.decompress_into_lwe_keyswitch_key();
|
||||
///
|
||||
/// assert_eq!(lwe_ksk.decomposition_base_log(), decomp_base_log);
|
||||
/// assert_eq!(lwe_ksk.decomposition_level_count(), decomp_level_count);
|
||||
/// assert_eq!(lwe_ksk.input_key_lwe_dimension(), input_lwe_dimension);
|
||||
/// assert_eq!(lwe_ksk.output_key_lwe_dimension(), output_lwe_dimension);
|
||||
/// assert_eq!(
|
||||
/// lwe_ksk.output_lwe_size(),
|
||||
/// output_lwe_dimension.to_lwe_size()
|
||||
/// );
|
||||
/// ```
|
||||
pub fn from_container(
|
||||
container: C,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
output_glwe_size: GlweSize,
|
||||
poly_size: PolynomialSize,
|
||||
compression_seed: CompressionSeed,
|
||||
) -> Self {
|
||||
assert!(
|
||||
container.container_len() > 0,
|
||||
"Got an empty container to create an SeededLweKeyswitchKey"
|
||||
);
|
||||
assert!(
|
||||
container.container_len() % (decomp_level_count.0) == 0,
|
||||
"The provided container length is not valid. \
|
||||
It needs to be dividable by decomp_level_count: {}. \
|
||||
Got container length: {} and decomp_level_count: {decomp_level_count:?}.",
|
||||
decomp_level_count.0,
|
||||
container.container_len()
|
||||
);
|
||||
|
||||
SeededGlweKeyswitchKey {
|
||||
data: container,
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
poly_size,
|
||||
compression_seed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionBaseLog`] of the [`SeededGlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
|
||||
self.decomp_base_log
|
||||
}
|
||||
|
||||
/// Return the [`DecompositionLevelCount`] of the [`SeededGlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
|
||||
self.decomp_level_count
|
||||
}
|
||||
|
||||
/// Return the input [`GlweDimension`] of the [`SeededGlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn input_key_glwe_dimension(&self) -> GlweDimension {
|
||||
GlweDimension(self.data.container_len() / (self.seeded_input_key_element_encrypted_size()
|
||||
* self.input_poly_size))
|
||||
}
|
||||
|
||||
/// Return the input [`PolynomialSize`] of the [`SeededGlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn polynomial_size(&self) -> PolynomialSize {
|
||||
self.poly_size
|
||||
}
|
||||
|
||||
/// Return the output [`GlweDimension`] of the [`SeededGlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_key_glwe_dimension(&self) -> GlweDimension {
|
||||
self.output_glwe_size.to_glwe_dimension()
|
||||
}
|
||||
|
||||
/// Return the output [`GlweSize`] of the [`SeededGlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn output_glwe_size(&self) -> GlweSize {
|
||||
self.output_glwe_size
|
||||
}
|
||||
|
||||
/// Return the output [`CompressionSeed`] of the [`SeededGlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn compression_seed(&self) -> CompressionSeed {
|
||||
self.compression_seed
|
||||
}
|
||||
|
||||
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element of the
|
||||
/// current [`SeededGlweKeyswitchKey`].
|
||||
pub fn seeded_input_key_element_encrypted_size(&self) -> usize {
|
||||
seeded_glwe_keyswitch_key_input_key_element_encrypted_size(self.decomp_level_count)
|
||||
}
|
||||
|
||||
/// Return a view of the [`SeededGlweKeyswitchKey`]. This is useful if an algorithm takes a view
|
||||
/// by value.
|
||||
pub fn as_view(&self) -> SeededGlweKeyswitchKey<&'_ [Scalar]> {
|
||||
SeededGlweKeyswitchKey::from_container(
|
||||
self.as_ref(),
|
||||
self.decomp_base_log,
|
||||
self.decomp_level_count,
|
||||
self.output_glwe_size,
|
||||
self.poly_size,
|
||||
self.compression_seed,
|
||||
)
|
||||
}
|
||||
|
||||
/// Consume the entity and return its underlying container.
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn into_container(self) -> C {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Consume the [`SeededGlweKeyswitchKey`] and decompress it into a standard
|
||||
/// [`GlweKeyswitchKey`].
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn decompress_into_glwe_keyswitch_key(self) -> GlweKeyswitchKeyOwned<Scalar>
|
||||
where
|
||||
Scalar: UnsignedTorus,
|
||||
{
|
||||
let mut decompressed_ksk = GlweKeyswitchKeyOwned::new(
|
||||
Scalar::ZERO,
|
||||
self.decomposition_base_log(),
|
||||
self.decomposition_level_count(),
|
||||
self.input_key_glwe_dimension(),
|
||||
self.output_key_glwe_dimension(),
|
||||
self.polynomial_size(),
|
||||
);
|
||||
decompress_seeded_glwe_keyswitch_key::<_, _, _, ActivatedRandomGenerator>(
|
||||
&mut decompressed_ksk,
|
||||
&self,
|
||||
);
|
||||
decompressed_ksk
|
||||
}
|
||||
|
||||
pub fn as_seeded_glwe_ciphertext_list(&self) -> SeededGlweCiphertextListView<'_, Scalar> {
|
||||
SeededGlweCiphertextListView::from_container(
|
||||
self.as_ref(),
|
||||
self.output_glwe_size(),
|
||||
self.polynomial_size(),
|
||||
self.compression_seed(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Scalar, C: ContainerMut<Element = Scalar>> SeededGlweKeyswitchKey<C> {
|
||||
/// Mutable variant of [`SeededGlweKeyswitchKey::as_view`].
|
||||
pub fn as_mut_view(&mut self) -> SeededGlweKeyswitchKey<&'_ mut [Scalar]> {
|
||||
let decomp_base_log = self.decomp_base_log;
|
||||
let decomp_level_count = self.decomp_level_count;
|
||||
let output_glwe_size = self.output_glwe_size;
|
||||
let poly_size = self.poly_size;
|
||||
let compression_seed = self.compression_seed;
|
||||
SeededGlweKeyswitchKey::from_container(
|
||||
self.as_mut(),
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_glwe_size,
|
||||
poly_size,
|
||||
compression_seed,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn as_mut_seeded_glwe_ciphertext_list(
|
||||
&mut self,
|
||||
) -> SeededGlweCiphertextListMutView<'_, Scalar> {
|
||||
let output_glwe_size = self.output_glwe_size();
|
||||
let poly_size = self.polynomial_size();
|
||||
let compression_seed = self.compression_seed();
|
||||
SeededGlweCiphertextListMutView::from_container(
|
||||
self.as_mut(),
|
||||
output_glwe_size,
|
||||
poly_size,
|
||||
compression_seed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`SeededGlweKeyswitchKey`] owning the memory for its own storage.
|
||||
pub type SeededGlweKeyswitchKeyOwned<Scalar> = SeededGlweKeyswitchKey<Vec<Scalar>>;
|
||||
|
||||
impl<Scalar: Copy> SeededGlweKeyswitchKeyOwned<Scalar> {
|
||||
/// Allocate memory and create a new owned [`SeededGlweKeyswitchKey`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
|
||||
/// type. If you want to generate an [`SeededGlweKeyswitchKey`] you need to call
|
||||
/// [`crate::core_crypto::algorithms::generate_seeded_glwe_keyswitch_key`] using this key as
|
||||
/// output.
|
||||
///
|
||||
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
|
||||
pub fn new(
|
||||
fill_with: Scalar,
|
||||
decomp_base_log: DecompositionBaseLog,
|
||||
decomp_level_count: DecompositionLevelCount,
|
||||
input_key_glwe_dimension: GlweDimension,
|
||||
output_key_glwe_dimension: GlweDimension,
|
||||
polynomial_size: PolynomialSize,
|
||||
compression_seed: CompressionSeed,
|
||||
) -> SeededGlweKeyswitchKeyOwned<Scalar> {
|
||||
SeededGlweKeyswitchKeyOwned::from_container(
|
||||
vec![
|
||||
fill_with;
|
||||
input_key_glwe_dimension.0 * polynomial_size.0
|
||||
* seeded_lwe_keyswitch_key_input_key_element_encrypted_size(decomp_level_count,)
|
||||
],
|
||||
decomp_base_log,
|
||||
decomp_level_count,
|
||||
output_key_glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
compression_seed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Container> ContiguousEntityContainer for SeededGlweKeyswitchKey<C> {
|
||||
type Element = C::Element;
|
||||
|
||||
type EntityViewMetadata = SeededGlweCiphertextListCreationMetadata;
|
||||
|
||||
type EntityView<'this> = SeededGlweCiphertextListView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
type SelfViewMetadata = ();
|
||||
|
||||
// At the moment it does not make sense to return "sub" keyswitch keys. So we use a dummy
|
||||
// placeholder type here.
|
||||
type SelfView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
fn get_entity_view_creation_metadata(&self) -> SeededGlweCiphertextListCreationMetadata {
|
||||
SeededGlweCiphertextListCreationMetadata(self.output_glwe_size(), self.compression_seed())
|
||||
}
|
||||
|
||||
fn get_entity_view_pod_size(&self) -> usize {
|
||||
self.seeded_input_key_element_encrypted_size()
|
||||
}
|
||||
|
||||
/// Unimplemented for [`SeededGlweKeyswitchKey`]. At the moment it does not make sense to
|
||||
/// return "sub" keyswitch keys.
|
||||
fn get_self_view_creation_metadata(&self) {
|
||||
unimplemented!(
|
||||
"This function is not supported for SeededLweKeyswitchKey. \
|
||||
At the moment it does not make sense to return 'sub' keyswitch keys."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ContainerMut> ContiguousEntityContainerMut for SeededGlweKeyswitchKey<C> {
|
||||
type EntityMutView<'this> = SeededGlweCiphertextListMutView<'this, Self::Element>
|
||||
where
|
||||
Self: 'this;
|
||||
|
||||
// At the moment it does not make sense to return "sub" keyswitch keys. So we use a dummy
|
||||
// placeholder type here.
|
||||
type SelfMutView<'this> = DummyCreateFrom
|
||||
where
|
||||
Self: 'this;
|
||||
}
|
||||
*/
|
||||
@@ -324,7 +324,7 @@ where
|
||||
// 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),
|
||||
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
ct0.as_mut()
|
||||
|
||||
@@ -124,7 +124,7 @@ fn f128_floor(x: f128) -> f128 {
|
||||
let f128(x0, x1) = x;
|
||||
let x0_floor = x0.floor();
|
||||
if x0_floor == x0 {
|
||||
f128(x0_floor, x1.floor())
|
||||
f128::add_f64_f64(x0_floor, x1.floor())
|
||||
} else {
|
||||
f128(x0_floor, 0.0)
|
||||
}
|
||||
@@ -225,7 +225,7 @@ fn u128_to_signed_to_f128(x: u128) -> f128 {
|
||||
|
||||
#[inline(always)]
|
||||
fn u128_from_torus_f128(x: f128) -> u128 {
|
||||
let mut x = x - f128_floor(x);
|
||||
let mut x = f128::sub_estimate_f128_f128(x, f128_floor(x));
|
||||
|
||||
let normalization = 2.0f64.powi(128);
|
||||
x.0 *= normalization;
|
||||
|
||||
@@ -166,7 +166,7 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap_u128<ContLweOut, ContLweIn, ContAcc>(
|
||||
pub fn bootstrap_u128<ContLweOut, ContLweIn, ContAcc>(
|
||||
&self,
|
||||
lwe_out: &mut LweCiphertext<ContLweOut>,
|
||||
lwe_in: &LweCiphertext<ContLweIn>,
|
||||
@@ -237,7 +237,7 @@ where
|
||||
// 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),
|
||||
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
local_accumulator
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
pub mod bootstrap;
|
||||
pub mod ggsw;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
279
tfhe/src/core_crypto/fft_impl/fft128_u128/crypto/tests.rs
Normal file
279
tfhe/src/core_crypto/fft_impl/fft128_u128/crypto/tests.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use dyn_stack::{GlobalPodBuffer, PodStack, ReborrowMut};
|
||||
|
||||
use super::super::super::{fft128, fft128_u128};
|
||||
use super::super::math::fft::{Fft128, Fft128View};
|
||||
use crate::core_crypto::prelude::*;
|
||||
use aligned_vec::CACHELINE_ALIGN;
|
||||
|
||||
fn sqr(x: f64) -> f64 {
|
||||
x * x
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_external_product() {
|
||||
let small_lwe_dimension = LweDimension(742);
|
||||
let glwe_dimension = GlweDimension(1);
|
||||
let polynomial_size = PolynomialSize(2048);
|
||||
let pbs_base_log = DecompositionBaseLog(23);
|
||||
let pbs_level = DecompositionLevelCount(1);
|
||||
let glwe_modular_std_dev = StandardDev(sqr(0.00000000000000029403601535432533));
|
||||
let ciphertext_modulus = CiphertextModulus::<u128>::new_native();
|
||||
let ciphertext_modulus_split = CiphertextModulus::<u64>::new_native();
|
||||
|
||||
let mut glwe = GlweCiphertext::new(
|
||||
0u128,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
for x in glwe.as_mut() {
|
||||
*x = rand::random();
|
||||
}
|
||||
|
||||
let mut boxed_seeder = new_seeder();
|
||||
let seeder = boxed_seeder.as_mut();
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
|
||||
let small_lwe_sk =
|
||||
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
|
||||
let glwe_sk =
|
||||
GlweSecretKey::generate_new_binary(glwe_dimension, polynomial_size, &mut secret_generator);
|
||||
|
||||
let std_bootstrapping_key = par_allocate_and_generate_new_lwe_bootstrap_key(
|
||||
&small_lwe_sk,
|
||||
&glwe_sk,
|
||||
pbs_base_log,
|
||||
pbs_level,
|
||||
glwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
let fft = Fft128::new(polynomial_size);
|
||||
let fft = fft.as_view();
|
||||
fourier_bsk.fill_with_forward_fourier(&std_bootstrapping_key, fft);
|
||||
let ggsw = fourier_bsk.as_view().into_ggsw_iter().next().unwrap();
|
||||
|
||||
let mut glwe_lo = GlweCiphertext::new(
|
||||
0u64,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus_split,
|
||||
);
|
||||
let mut glwe_hi = GlweCiphertext::new(
|
||||
0u64,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus_split,
|
||||
);
|
||||
|
||||
for ((lo, hi), val) in glwe_lo
|
||||
.as_mut()
|
||||
.iter_mut()
|
||||
.zip(glwe_hi.as_mut())
|
||||
.zip(glwe.as_ref())
|
||||
{
|
||||
*lo = *val as u64;
|
||||
*hi = (*val >> 64) as u64;
|
||||
}
|
||||
|
||||
let mut out = GlweCiphertext::new(
|
||||
0u128,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
fft128::crypto::ggsw::add_external_product_assign(
|
||||
&mut out,
|
||||
&ggsw,
|
||||
&glwe,
|
||||
fft,
|
||||
PodStack::new(&mut GlobalPodBuffer::new(
|
||||
fft128::crypto::ggsw::add_external_product_assign_scratch::<u128>(
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
fft,
|
||||
)
|
||||
.unwrap(),
|
||||
)),
|
||||
);
|
||||
|
||||
let mut out_lo = GlweCiphertext::new(
|
||||
0u64,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus_split,
|
||||
);
|
||||
let mut out_hi = GlweCiphertext::new(
|
||||
0u64,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus_split,
|
||||
);
|
||||
|
||||
fft128_u128::crypto::ggsw::add_external_product_assign_split(
|
||||
&mut out_lo,
|
||||
&mut out_hi,
|
||||
&ggsw,
|
||||
&glwe_lo,
|
||||
&glwe_hi,
|
||||
fft,
|
||||
PodStack::new(&mut GlobalPodBuffer::new(
|
||||
fft128::crypto::ggsw::add_external_product_assign_scratch::<u128>(
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
fft,
|
||||
)
|
||||
.unwrap(),
|
||||
)),
|
||||
);
|
||||
|
||||
for ((lo, hi), val) in out_lo
|
||||
.as_ref()
|
||||
.iter()
|
||||
.zip(out_hi.as_ref())
|
||||
.zip(out.as_ref())
|
||||
{
|
||||
assert_eq!(*val as u64, *lo);
|
||||
assert_eq!((*val >> 64) as u64, *hi);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_pbs() {
|
||||
let small_lwe_dimension = LweDimension(742);
|
||||
let glwe_dimension = GlweDimension(1);
|
||||
let polynomial_size = PolynomialSize(2048);
|
||||
let pbs_base_log = DecompositionBaseLog(23);
|
||||
let pbs_level = DecompositionLevelCount(1);
|
||||
let glwe_modular_std_dev = StandardDev(sqr(0.00000000000000029403601535432533));
|
||||
let ciphertext_modulus = CiphertextModulus::<u128>::new_native();
|
||||
|
||||
let mut boxed_seeder = new_seeder();
|
||||
let seeder = boxed_seeder.as_mut();
|
||||
let mut secret_generator =
|
||||
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
|
||||
let mut encryption_generator =
|
||||
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
|
||||
|
||||
let small_lwe_sk =
|
||||
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
|
||||
let glwe_sk =
|
||||
GlweSecretKey::generate_new_binary(glwe_dimension, polynomial_size, &mut secret_generator);
|
||||
|
||||
let std_bootstrapping_key = par_allocate_and_generate_new_lwe_bootstrap_key(
|
||||
&small_lwe_sk,
|
||||
&glwe_sk,
|
||||
pbs_base_log,
|
||||
pbs_level,
|
||||
glwe_modular_std_dev,
|
||||
ciphertext_modulus,
|
||||
&mut encryption_generator,
|
||||
);
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
let fft = Fft128::new(polynomial_size);
|
||||
let fft = fft.as_view();
|
||||
fourier_bsk.fill_with_forward_fourier(&std_bootstrapping_key, fft);
|
||||
|
||||
let mut lwe_in =
|
||||
LweCiphertext::new(0u128, small_lwe_dimension.to_lwe_size(), ciphertext_modulus);
|
||||
let mut accumulator = GlweCiphertext::new(
|
||||
0u128,
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
for x in lwe_in.as_mut() {
|
||||
*x = rand::random();
|
||||
}
|
||||
for x in accumulator.as_mut() {
|
||||
*x = rand::random();
|
||||
}
|
||||
|
||||
let mut mem = GlobalPodBuffer::new(
|
||||
fft128::crypto::bootstrap::bootstrap_scratch::<u128>(
|
||||
glwe_dimension.to_glwe_size(),
|
||||
polynomial_size,
|
||||
fft,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let mut stack = PodStack::new(&mut mem);
|
||||
|
||||
let mut lwe_out_non_split = LweCiphertext::new(
|
||||
0u128,
|
||||
LweDimension(polynomial_size.0 * glwe_dimension.0).to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
|
||||
// Needed as the basic bootstrap function dispatches to the more efficient split version for
|
||||
// u128
|
||||
fn bootstrap_non_split<Scalar: UnsignedTorus + CastInto<usize>>(
|
||||
this: Fourier128LweBootstrapKey<&[f64]>,
|
||||
mut lwe_out: LweCiphertext<&mut [Scalar]>,
|
||||
lwe_in: LweCiphertext<&[Scalar]>,
|
||||
accumulator: GlweCiphertext<&[Scalar]>,
|
||||
fft: Fft128View<'_>,
|
||||
stack: PodStack<'_>,
|
||||
) {
|
||||
let (mut local_accumulator_data, stack) =
|
||||
stack.collect_aligned(CACHELINE_ALIGN, accumulator.as_ref().iter().copied());
|
||||
let mut local_accumulator = GlweCiphertextMutView::from_container(
|
||||
&mut *local_accumulator_data,
|
||||
accumulator.polynomial_size(),
|
||||
accumulator.ciphertext_modulus(),
|
||||
);
|
||||
this.blind_rotate_assign(&mut local_accumulator.as_mut_view(), &lwe_in, fft, stack);
|
||||
extract_lwe_sample_from_glwe_ciphertext(
|
||||
&local_accumulator,
|
||||
&mut lwe_out,
|
||||
MonomialDegree(0),
|
||||
);
|
||||
}
|
||||
|
||||
bootstrap_non_split(
|
||||
fourier_bsk.as_view(),
|
||||
lwe_out_non_split.as_mut_view(),
|
||||
lwe_in.as_view(),
|
||||
accumulator.as_view(),
|
||||
fft,
|
||||
stack.rb_mut(),
|
||||
);
|
||||
|
||||
let mut lwe_out_split = LweCiphertext::new(
|
||||
0u128,
|
||||
LweDimension(polynomial_size.0 * glwe_dimension.0).to_lwe_size(),
|
||||
ciphertext_modulus,
|
||||
);
|
||||
fourier_bsk.bootstrap_u128(
|
||||
&mut lwe_out_split,
|
||||
&lwe_in,
|
||||
&accumulator,
|
||||
fft,
|
||||
stack.rb_mut(),
|
||||
);
|
||||
|
||||
assert_eq!(lwe_out_split, lwe_out_non_split);
|
||||
}
|
||||
@@ -206,10 +206,11 @@ pub fn wrapping_add_avx2(
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[inline(always)]
|
||||
pub fn wrapping_neg_avx2(simd: V3, (lo, hi): (u64x4, u64x4)) -> (u64x4, u64x4) {
|
||||
let diff_lo = pulp::cast(simd.wrapping_sub_u64x4(simd.splat_u64x4(0), lo));
|
||||
let overflow = pulp::cast(simd.cmp_lt_u64x4(simd.splat_u64x4(0), lo));
|
||||
let diff_hi = simd.wrapping_sub_u64x4(overflow, hi);
|
||||
(diff_lo, diff_hi)
|
||||
wrapping_add_avx2(
|
||||
simd,
|
||||
(simd.splat_u64x4(1), simd.splat_u64x4(0)),
|
||||
(simd.not_u64x4(lo), simd.not_u64x4(hi)),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
@@ -246,10 +247,11 @@ pub fn wrapping_add_avx512(
|
||||
#[cfg(feature = "nightly-avx512")]
|
||||
#[inline(always)]
|
||||
pub fn wrapping_neg_avx512(simd: V4, (lo, hi): (u64x8, u64x8)) -> (u64x8, u64x8) {
|
||||
let diff_lo = simd.wrapping_sub_u64x8(simd.splat_u64x8(0), lo);
|
||||
let overflow = simd.convert_mask_b8_to_u64x8(simd.cmp_lt_u64x8(simd.splat_u64x8(0), lo));
|
||||
let diff_hi = simd.wrapping_sub_u64x8(overflow, hi);
|
||||
(diff_lo, diff_hi)
|
||||
wrapping_add_avx512(
|
||||
simd,
|
||||
(simd.splat_u64x8(1), simd.splat_u64x8(0)),
|
||||
(simd.not_u64x8(lo), simd.not_u64x8(hi)),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -385,9 +387,10 @@ fn f64_to_i128_avx2(simd: V3, f: f64x4) -> (u64x4, u64x4) {
|
||||
simd.shr_dyn_u64x4(hi, shift),
|
||||
);
|
||||
let neg = wrapping_neg_avx2(simd, abs);
|
||||
let mask = simd.cmp_eq_u64x4(simd.and_u64x4(sign_bit, f), sign_bit);
|
||||
(
|
||||
simd.select_u64x4(simd.cmp_eq_u64x4(f, sign_bit), neg.0, abs.0),
|
||||
simd.select_u64x4(simd.cmp_eq_u64x4(f, sign_bit), neg.1, abs.1),
|
||||
simd.select_u64x4(mask, neg.0, abs.0),
|
||||
simd.select_u64x4(mask, neg.1, abs.1),
|
||||
)
|
||||
};
|
||||
(
|
||||
@@ -554,7 +557,7 @@ fn f128_floor(x: f128) -> f128 {
|
||||
let f128(x0, x1) = x;
|
||||
let x0_floor = x0.floor();
|
||||
if x0_floor == x0 {
|
||||
f128(x0_floor, x1.floor())
|
||||
f128::add_f64_f64(x0_floor, x1.floor())
|
||||
} else {
|
||||
f128(x0_floor, 0.0)
|
||||
}
|
||||
@@ -566,7 +569,8 @@ fn f128_floor_avx2(simd: V3, (x0, x1): (f64x4, f64x4)) -> (f64x4, f64x4) {
|
||||
let x0_floor = simd.floor_f64x4(x0);
|
||||
let x1_floor = simd.floor_f64x4(x1);
|
||||
|
||||
(
|
||||
two_sum_f64x4(
|
||||
simd,
|
||||
x0_floor,
|
||||
simd.select_f64x4(
|
||||
simd.cmp_eq_f64x4(x0_floor, x0),
|
||||
@@ -583,7 +587,8 @@ fn f128_floor_avx512(simd: V4, (x0, x1): (f64x8, f64x8)) -> (f64x8, f64x8) {
|
||||
let x0_floor = simd.floor_f64x8(x0);
|
||||
let x1_floor = simd.floor_f64x8(x1);
|
||||
|
||||
(
|
||||
two_sum_f64x8(
|
||||
simd,
|
||||
x0_floor,
|
||||
simd.select_f64x8(
|
||||
simd.cmp_eq_f64x8(x0_floor, x0),
|
||||
@@ -1338,3 +1343,43 @@ impl<'a> Fft128View<'a> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// copied from the standard library
|
||||
fn next_up(this: f64) -> f64 {
|
||||
// We must use strictly integer arithmetic to prevent denormals from
|
||||
// flushing to zero after an arithmetic operation on some platforms.
|
||||
const TINY_BITS: u64 = 0x1; // Smallest positive f64.
|
||||
const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff;
|
||||
|
||||
let bits = this.to_bits();
|
||||
if this.is_nan() || bits == f64::INFINITY.to_bits() {
|
||||
return this;
|
||||
}
|
||||
|
||||
let abs = bits & CLEAR_SIGN_MASK;
|
||||
let next_bits = if abs == 0 {
|
||||
TINY_BITS
|
||||
} else if bits == abs {
|
||||
bits + 1
|
||||
} else {
|
||||
bits - 1
|
||||
};
|
||||
f64::from_bits(next_bits)
|
||||
}
|
||||
|
||||
fn ulp(x: f64) -> f64 {
|
||||
next_up(x.abs()) - x.abs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f128_floor() {
|
||||
let a = f128(-11984547.0, -1.0316078675142442e-10);
|
||||
let b = f128_floor(a);
|
||||
|
||||
assert!(b.1.abs() <= 0.5 * ulp(b.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ impl<'a> FourierLweBootstrapKeyView<'a> {
|
||||
// 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),
|
||||
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
|
||||
DecompositionLevelCount(1),
|
||||
);
|
||||
ct0.as_mut()
|
||||
|
||||
@@ -190,3 +190,14 @@ fn test_compressed_bool() {
|
||||
assert_eq!(a.decrypt(&keys), true);
|
||||
assert_eq!(b.decrypt(&keys), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trivial_bool() {
|
||||
let keys = setup_static_default();
|
||||
|
||||
let a = FheBool::encrypt_trivial(true);
|
||||
let b = FheBool::encrypt_trivial(false);
|
||||
|
||||
assert_eq!(a.decrypt(&keys), true);
|
||||
assert_eq!(b.decrypt(&keys), false);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::high_level_api::keys::{
|
||||
ClientKey, PublicKey, RefKeyFromKeyChain, RefKeyFromPublicKeyChain,
|
||||
};
|
||||
use crate::high_level_api::traits::{
|
||||
FheDecrypt, FheEncrypt, FheEq, FheTrivialEncrypt, FheTryEncrypt, FheTryTrivialEncrypt,
|
||||
FheDecrypt, FheEq, FheTrivialEncrypt, FheTryEncrypt, FheTryTrivialEncrypt,
|
||||
};
|
||||
|
||||
/// The FHE boolean data type.
|
||||
@@ -151,39 +151,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FheEncrypt<bool, ClientKey> for CompressedBool<P>
|
||||
where
|
||||
P: BooleanParameterSet,
|
||||
P::Id: RefKeyFromKeyChain<Key = GenericBoolClientKey<P>> + Default,
|
||||
{
|
||||
#[track_caller]
|
||||
fn encrypt(value: bool, key: &ClientKey) -> Self {
|
||||
Self::try_encrypt(value, key).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FheEncrypt<bool, ClientKey> for GenericBool<P>
|
||||
where
|
||||
P: BooleanParameterSet,
|
||||
P::Id: RefKeyFromKeyChain<Key = GenericBoolClientKey<P>> + Default,
|
||||
{
|
||||
#[track_caller]
|
||||
fn encrypt(value: bool, key: &ClientKey) -> Self {
|
||||
<Self as FheTryEncrypt<bool, ClientKey>>::try_encrypt(value, key).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FheEncrypt<bool, PublicKey> for GenericBool<P>
|
||||
where
|
||||
P: BooleanParameterSet,
|
||||
P::Id: RefKeyFromPublicKeyChain<Key = GenericBoolPublicKey<P>> + Default,
|
||||
{
|
||||
#[track_caller]
|
||||
fn encrypt(value: bool, key: &PublicKey) -> Self {
|
||||
<Self as FheTryEncrypt<bool, PublicKey>>::try_encrypt(value, key).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FheTryEncrypt<bool, ClientKey> for GenericBool<P>
|
||||
where
|
||||
P: BooleanParameterSet,
|
||||
|
||||
@@ -6,14 +6,14 @@ use crate::high_level_api::integers::IntegerConfig;
|
||||
use crate::high_level_api::shortints::ShortIntConfig;
|
||||
|
||||
/// The config type
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
#[cfg(feature = "boolean")]
|
||||
pub(crate) boolean_config: BooleanConfig,
|
||||
#[cfg(feature = "integer")]
|
||||
pub(crate) integer_config: IntegerConfig,
|
||||
#[cfg(feature = "shortint")]
|
||||
pub(crate) shortint_config: ShortIntConfig,
|
||||
#[cfg(feature = "integer")]
|
||||
pub(crate) integer_config: IntegerConfig,
|
||||
}
|
||||
|
||||
/// The builder to create your config
|
||||
@@ -49,10 +49,10 @@ impl ConfigBuilder {
|
||||
config: Config {
|
||||
#[cfg(feature = "boolean")]
|
||||
boolean_config: BooleanConfig::all_default(),
|
||||
#[cfg(feature = "integer")]
|
||||
integer_config: IntegerConfig::all_default(),
|
||||
#[cfg(feature = "shortint")]
|
||||
shortint_config: ShortIntConfig::all_default(),
|
||||
#[cfg(feature = "integer")]
|
||||
integer_config: IntegerConfig::all_default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -63,10 +63,10 @@ impl ConfigBuilder {
|
||||
config: Config {
|
||||
#[cfg(feature = "boolean")]
|
||||
boolean_config: BooleanConfig::all_none(),
|
||||
#[cfg(feature = "integer")]
|
||||
integer_config: IntegerConfig::all_none(),
|
||||
#[cfg(feature = "shortint")]
|
||||
shortint_config: ShortIntConfig::all_none(),
|
||||
#[cfg(feature = "integer")]
|
||||
integer_config: IntegerConfig::all_none(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -108,155 +108,38 @@ impl ConfigBuilder {
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint8(mut self) -> Self {
|
||||
self.config.integer_config.uint8_params = Some(Default::default());
|
||||
pub fn enable_default_integers(mut self) -> Self {
|
||||
self.config.integer_config = IntegerConfig::default_big();
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint8_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint8Parameters::small();
|
||||
self.config.integer_config.uint8_params = Some(params);
|
||||
pub fn enable_default_integers_small(mut self) -> Self {
|
||||
self.config.integer_config = IntegerConfig::default_small();
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_function_evaluation_integers(mut self) -> Self {
|
||||
self.config.integer_config.enable_wopbs();
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn disable_uint8(mut self) -> Self {
|
||||
self.config.integer_config.uint8_params = None;
|
||||
pub fn enable_default_custom_integers(
|
||||
mut self,
|
||||
block_parameters: crate::shortint::PBSParameters,
|
||||
wopbs_block_parameters: Option<crate::shortint::WopbsParameters>,
|
||||
) -> Self {
|
||||
self.config.integer_config =
|
||||
IntegerConfig::new(Some(block_parameters), wopbs_block_parameters);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint10(mut self) -> Self {
|
||||
self.config.integer_config.uint10_params = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint10_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint10Parameters::small();
|
||||
self.config.integer_config.uint10_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn disable_uint10(mut self) -> Self {
|
||||
self.config.integer_config.uint10_params = None;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint12(mut self) -> Self {
|
||||
self.config.integer_config.uint12_params = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint12_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint12Parameters::small();
|
||||
self.config.integer_config.uint12_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn disable_uint12(mut self) -> Self {
|
||||
self.config.integer_config.uint12_params = None;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint14(mut self) -> Self {
|
||||
self.config.integer_config.uint14_params = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint14_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint14Parameters::small();
|
||||
self.config.integer_config.uint14_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn disable_uint14(mut self) -> Self {
|
||||
self.config.integer_config.uint14_params = None;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint16(mut self) -> Self {
|
||||
self.config.integer_config.uint16_params = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint16_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint16Parameters::small();
|
||||
self.config.integer_config.uint16_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn disable_uint16(mut self) -> Self {
|
||||
self.config.integer_config.uint16_params = None;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint32(mut self) -> Self {
|
||||
self.config.integer_config.uint32_params = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint32_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint32Parameters::small();
|
||||
self.config.integer_config.uint32_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint64(mut self) -> Self {
|
||||
self.config.integer_config.uint64_params = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint64_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint64Parameters::small();
|
||||
self.config.integer_config.uint64_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint128(mut self) -> Self {
|
||||
self.config.integer_config.uint128_params = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint128_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint128Parameters::small();
|
||||
self.config.integer_config.uint128_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint256(mut self) -> Self {
|
||||
self.config.integer_config.uint256_params = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn enable_default_uint256_small(mut self) -> Self {
|
||||
let params = crate::high_level_api::integers::FheUint256Parameters::small();
|
||||
self.config.integer_config.uint256_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "integer")]
|
||||
pub fn disable_uint256(mut self) -> Self {
|
||||
self.config.integer_config.uint256_params = None;
|
||||
pub fn disable_integers(mut self) -> Self {
|
||||
self.config.integer_config = IntegerConfig::all_none();
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ In practice it is a bit more complex as we have to introduce traits to internall
|
||||
"parameter struct", eg a trait to convert the `FheUint2Parameters` and `FheUint4Parameters` back into `shortint::Parameters`,
|
||||
|
||||
```rust
|
||||
pub trait ShortIntegerParameter: Copy + Into<crate::shortint::Parameters> {
|
||||
pub trait ShortIntegerParameter: Copy + Into< crate::shortint::PBSParameters> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
@@ -23,7 +23,7 @@ macro_rules! define_key_structs {
|
||||
///////////////////////
|
||||
// Config
|
||||
///////////////////////
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub(crate) struct [<$base_struct_name Config>] {
|
||||
$(
|
||||
pub(crate) [<$name _params>]: Option<[<$base_ty_name Parameters>]>,
|
||||
@@ -55,6 +55,7 @@ macro_rules! define_key_structs {
|
||||
pub(crate) struct [<$base_struct_name ClientKey>] {
|
||||
$(
|
||||
pub(super) [<$name _key>]: Option<[<$base_ty_name ClientKey>]>,
|
||||
pub(super) [<$name _params>]: Option<[<$base_ty_name Parameters>]>,
|
||||
)*
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ macro_rules! define_key_structs {
|
||||
fn from(config: [<$base_struct_name Config>]) -> Self {
|
||||
Self {
|
||||
$(
|
||||
[<$name _params>]: None,
|
||||
[<$name _key>]: config.[<$name _params>].map(<[<$base_ty_name ClientKey>]>::from),
|
||||
)*
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ impl Display for UninitializedClientKey {
|
||||
|
||||
impl std::error::Error for UninitializedClientKey {}
|
||||
|
||||
/// The client key of a given type was not initialized
|
||||
/// The public key of a given type was not initialized
|
||||
#[derive(Debug)]
|
||||
pub struct UninitializedPublicKey(pub(crate) Type);
|
||||
|
||||
@@ -110,6 +110,24 @@ impl Display for UninitializedPublicKey {
|
||||
|
||||
impl std::error::Error for UninitializedPublicKey {}
|
||||
|
||||
/// The compresesd public key of a given type was not initialized
|
||||
#[derive(Debug)]
|
||||
pub struct UninitializedCompressedPublicKey(pub(crate) Type);
|
||||
|
||||
impl Display for UninitializedCompressedPublicKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"The compressed public key for the type '{:?}' was not properly initialized\n\
|
||||
Dis you forget do enable the type in the config ?
|
||||
",
|
||||
self.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UninitializedCompressedPublicKey {}
|
||||
|
||||
/// Error when trying to create a short integer from a value that was too big to be represented
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct OutOfRangeError;
|
||||
|
||||
@@ -1,144 +1,36 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::high_level_api::integers::parameters::{IntegerParameter, RadixParameters};
|
||||
use crate::high_level_api::internal_traits::{DecryptionKey, EncryptionKey, FromParameters};
|
||||
use crate::integer::{CrtCiphertext, CrtClientKey, U256};
|
||||
|
||||
use super::server_key::RadixCiphertextDyn;
|
||||
use crate::high_level_api::internal_traits::DecryptionKey;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub struct RadixClientKey {
|
||||
pub(in crate::high_level_api::integers) inner: crate::integer::RadixClientKey,
|
||||
// To know if we have to encrypt into a big or small ciphertext
|
||||
pub(in crate::high_level_api::integers) pbs_order: crate::shortint::PBSOrder,
|
||||
}
|
||||
|
||||
// This is needed by the impl EvaluationKey
|
||||
impl AsRef<crate::integer::ClientKey> for RadixClientKey {
|
||||
fn as_ref(&self) -> &crate::integer::ClientKey {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FromParameters<P> for RadixClientKey
|
||||
where
|
||||
P: Into<RadixParameters>,
|
||||
{
|
||||
fn from_parameters(parameters: P) -> Self {
|
||||
let params = parameters.into();
|
||||
#[cfg(feature = "internal-keycache")]
|
||||
{
|
||||
use crate::integer::keycache::KEY_CACHE;
|
||||
let key = KEY_CACHE.get_from_params(params.block_parameters).0;
|
||||
let inner = crate::integer::RadixClientKey::from((key, params.num_block));
|
||||
Self {
|
||||
inner,
|
||||
pbs_order: params.pbs_order,
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "internal-keycache"))]
|
||||
{
|
||||
let inner =
|
||||
crate::integer::RadixClientKey::new(params.block_parameters, params.num_block);
|
||||
Self {
|
||||
inner,
|
||||
pbs_order: params.pbs_order,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EncryptionKey<u64, RadixCiphertextDyn> for RadixClientKey {
|
||||
fn encrypt(&self, value: u64) -> RadixCiphertextDyn {
|
||||
match self.pbs_order {
|
||||
crate::shortint::PBSOrder::KeyswitchBootstrap => {
|
||||
RadixCiphertextDyn::Big(self.inner.encrypt(value))
|
||||
}
|
||||
crate::shortint::PBSOrder::BootstrapKeyswitch => RadixCiphertextDyn::Small(
|
||||
self.inner
|
||||
.as_ref()
|
||||
.encrypt_radix_small(value, self.inner.num_blocks()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EncryptionKey<U256, RadixCiphertextDyn> for RadixClientKey {
|
||||
fn encrypt(&self, value: U256) -> RadixCiphertextDyn {
|
||||
match self.pbs_order {
|
||||
crate::shortint::PBSOrder::KeyswitchBootstrap => RadixCiphertextDyn::Big(
|
||||
self.inner
|
||||
.as_ref()
|
||||
.encrypt_radix(value, self.inner.num_blocks()),
|
||||
),
|
||||
crate::shortint::PBSOrder::BootstrapKeyswitch => RadixCiphertextDyn::Small(
|
||||
self.inner
|
||||
.as_ref()
|
||||
.encrypt_radix_small(value, self.inner.num_blocks()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DecryptionKey<RadixCiphertextDyn, u16> for RadixClientKey {
|
||||
impl DecryptionKey<RadixCiphertextDyn, u16> for crate::integer::ClientKey {
|
||||
fn decrypt(&self, ciphertext: &RadixCiphertextDyn) -> u16 {
|
||||
let clear: u64 = match ciphertext {
|
||||
RadixCiphertextDyn::Big(ct) => self.inner.decrypt(ct),
|
||||
RadixCiphertextDyn::Small(ct) => self.inner.decrypt(ct),
|
||||
RadixCiphertextDyn::Big(ct) => self.decrypt_radix(ct),
|
||||
RadixCiphertextDyn::Small(ct) => self.decrypt_radix(ct),
|
||||
};
|
||||
|
||||
clear as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl DecryptionKey<RadixCiphertextDyn, u32> for RadixClientKey {
|
||||
impl DecryptionKey<RadixCiphertextDyn, u32> for crate::integer::ClientKey {
|
||||
fn decrypt(&self, ciphertext: &RadixCiphertextDyn) -> u32 {
|
||||
let clear: u64 = match ciphertext {
|
||||
RadixCiphertextDyn::Big(ct) => self.inner.decrypt(ct),
|
||||
RadixCiphertextDyn::Small(ct) => self.inner.decrypt(ct),
|
||||
RadixCiphertextDyn::Big(ct) => self.decrypt_radix(ct),
|
||||
RadixCiphertextDyn::Small(ct) => self.decrypt_radix(ct),
|
||||
};
|
||||
|
||||
clear as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl<ClearType> DecryptionKey<RadixCiphertextDyn, ClearType> for RadixClientKey
|
||||
impl<ClearType> DecryptionKey<RadixCiphertextDyn, ClearType> for crate::integer::ClientKey
|
||||
where
|
||||
ClearType: crate::integer::encryption::AsLittleEndianWords + Default,
|
||||
{
|
||||
fn decrypt(&self, ciphertext: &RadixCiphertextDyn) -> ClearType {
|
||||
match ciphertext {
|
||||
RadixCiphertextDyn::Big(ct) => self.inner.decrypt(ct),
|
||||
RadixCiphertextDyn::Small(ct) => self.inner.decrypt(ct),
|
||||
RadixCiphertextDyn::Big(ct) => self.decrypt_radix(ct),
|
||||
RadixCiphertextDyn::Small(ct) => self.decrypt_radix(ct),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EncryptionKey<u64, CrtCiphertext> for CrtClientKey {
|
||||
fn encrypt(&self, value: u64) -> CrtCiphertext {
|
||||
self.encrypt(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl DecryptionKey<CrtCiphertext, u64> for CrtClientKey {
|
||||
fn decrypt(&self, ciphertext: &CrtCiphertext) -> u64 {
|
||||
self.decrypt(ciphertext)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct GenericIntegerClientKey<P: IntegerParameter> {
|
||||
pub(in crate::high_level_api::integers) inner: P::InnerClientKey,
|
||||
pub(in crate::high_level_api::integers) params: P,
|
||||
}
|
||||
|
||||
impl<P> From<P> for GenericIntegerClientKey<P>
|
||||
where
|
||||
P: IntegerParameter,
|
||||
P::InnerClientKey: FromParameters<P>,
|
||||
{
|
||||
fn from(params: P) -> Self {
|
||||
let key = P::InnerClientKey::from_parameters(params.clone());
|
||||
Self { inner: key, params }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,133 @@
|
||||
define_key_structs! {
|
||||
Integer {
|
||||
uint8: FheUint8,
|
||||
uint10: FheUint10,
|
||||
uint12: FheUint12,
|
||||
uint14: FheUint14,
|
||||
uint16: FheUint16,
|
||||
uint32: FheUint32,
|
||||
uint64: FheUint64,
|
||||
uint128: FheUint128,
|
||||
uint256: FheUint256,
|
||||
use crate::shortint::EncryptionKeyChoice;
|
||||
|
||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub(crate) struct IntegerConfig {
|
||||
pub(crate) block_parameters: Option<crate::shortint::PBSParameters>,
|
||||
pub(crate) wopbs_block_parameters: Option<crate::shortint::WopbsParameters>,
|
||||
}
|
||||
|
||||
impl IntegerConfig {
|
||||
pub(crate) fn new(
|
||||
block_parameters: Option<crate::shortint::PBSParameters>,
|
||||
wopbs_block_parameters: Option<crate::shortint::WopbsParameters>,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_parameters,
|
||||
wopbs_block_parameters,
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::high_level_api) fn all_default() -> Self {
|
||||
Self::default_big()
|
||||
}
|
||||
|
||||
pub(in crate::high_level_api) fn all_none() -> Self {
|
||||
Self::new(None, None)
|
||||
}
|
||||
|
||||
pub(in crate::high_level_api) fn default_big() -> Self {
|
||||
Self {
|
||||
block_parameters: Some(crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2),
|
||||
wopbs_block_parameters: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::high_level_api) fn default_small() -> Self {
|
||||
Self {
|
||||
block_parameters: Some(crate::shortint::parameters::PARAM_SMALL_MESSAGE_2_CARRY_2),
|
||||
wopbs_block_parameters: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_wopbs(&mut self) {
|
||||
let block_parameter = self
|
||||
.block_parameters
|
||||
.get_or_insert(crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2);
|
||||
|
||||
let wopbs_block_parameters = match block_parameter.encryption_key_choice {
|
||||
EncryptionKeyChoice::Big => crate::shortint::parameters::parameters_wopbs_message_carry::WOPBS_PARAM_MESSAGE_2_CARRY_2,
|
||||
EncryptionKeyChoice::Small => crate::shortint::parameters::parameters_wopbs_message_carry::WOPBS_PARAM_MESSAGE_2_CARRY_2,
|
||||
};
|
||||
|
||||
self.wopbs_block_parameters = Some(wopbs_block_parameters);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub(crate) struct IntegerClientKey {
|
||||
pub(crate) key: Option<crate::integer::ClientKey>,
|
||||
pub(crate) wopbs_block_parameters: Option<crate::shortint::WopbsParameters>,
|
||||
pub(crate) encryption_type: EncryptionKeyChoice,
|
||||
}
|
||||
|
||||
impl IntegerClientKey {
|
||||
pub(crate) fn encryption_type(&self) -> EncryptionKeyChoice {
|
||||
self.encryption_type
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IntegerConfig> for IntegerClientKey {
|
||||
fn from(config: IntegerConfig) -> Self {
|
||||
let (key, encryption_type) = match config.block_parameters {
|
||||
Some(params) => {
|
||||
let encryption_type = params.encryption_key_choice;
|
||||
let cks = crate::integer::ClientKey::new(params);
|
||||
(Some(cks), encryption_type)
|
||||
}
|
||||
// setting a default value to encryption_type
|
||||
// when block parameters is none (ie integers not activated)
|
||||
// is fine, as since the key will be none, no risk of
|
||||
// mismatch
|
||||
None => (None, EncryptionKeyChoice::Big),
|
||||
};
|
||||
Self {
|
||||
key,
|
||||
wopbs_block_parameters: config.wopbs_block_parameters,
|
||||
encryption_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct IntegerServerKey {
|
||||
pub(crate) key: Option<crate::integer::ServerKey>,
|
||||
pub(crate) wopbs_key: Option<crate::integer::wopbs::WopbsKey>,
|
||||
// Needed to encrypt trivial ciphertexts
|
||||
pub(crate) encryption_type: crate::shortint::EncryptionKeyChoice,
|
||||
}
|
||||
|
||||
impl Default for IntegerServerKey {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
encryption_type: EncryptionKeyChoice::Big,
|
||||
key: None,
|
||||
wopbs_key: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntegerServerKey {
|
||||
pub(in crate::high_level_api) fn new(client_key: &IntegerClientKey) -> Self {
|
||||
let Some(cks) = &client_key.key else {
|
||||
return Self::default();
|
||||
};
|
||||
let base_integer_key = crate::integer::ServerKey::new(cks);
|
||||
let wopbs_key = client_key
|
||||
.wopbs_block_parameters
|
||||
.as_ref()
|
||||
.map(|wopbs_params| {
|
||||
crate::integer::wopbs::WopbsKey::new_wopbs_key(cks, &base_integer_key, wopbs_params)
|
||||
});
|
||||
Self {
|
||||
key: Some(base_integer_key),
|
||||
wopbs_key,
|
||||
encryption_type: client_key.encryption_type(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::high_level_api::integers) fn pbs_key(&self) -> &crate::integer::ServerKey {
|
||||
self.key
|
||||
.as_ref()
|
||||
.expect("Integer ServerKey is not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
pub(crate) use keys::{
|
||||
IntegerClientKey, IntegerCompressedPublicKey, IntegerConfig, IntegerPublicKey, IntegerServerKey,
|
||||
};
|
||||
pub use parameters::{CrtParameters, RadixParameters};
|
||||
pub(in crate::high_level_api) use types::static_::{
|
||||
FheUint10Parameters, FheUint128Parameters, FheUint12Parameters, FheUint14Parameters,
|
||||
FheUint16Parameters, FheUint256Parameters, FheUint32Parameters, FheUint64Parameters,
|
||||
FheUint8Parameters,
|
||||
};
|
||||
pub use types::{
|
||||
CompressedFheUint10, CompressedFheUint12, CompressedFheUint128, CompressedFheUint14,
|
||||
CompressedFheUint16, CompressedFheUint256, CompressedFheUint32, CompressedFheUint64,
|
||||
@@ -14,6 +5,10 @@ pub use types::{
|
||||
FheUint32, FheUint64, FheUint8, GenericInteger,
|
||||
};
|
||||
|
||||
pub(in crate::high_level_api) use keys::{IntegerClientKey, IntegerConfig, IntegerServerKey};
|
||||
pub(in crate::high_level_api) use public_key::compressed::CompressedPublicKeyDyn;
|
||||
pub(in crate::high_level_api) use public_key::PublicKeyDyn;
|
||||
|
||||
mod client_key;
|
||||
mod keys;
|
||||
mod parameters;
|
||||
|
||||
@@ -1,31 +1,4 @@
|
||||
use crate::high_level_api::internal_traits::{FromParameters, ParameterType};
|
||||
use crate::integer::CrtClientKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Parameters for 'radix' decomposition
|
||||
///
|
||||
/// Radix decomposition works by using multiple shortint blocks
|
||||
/// with the same parameters to represent an integer.
|
||||
///
|
||||
/// For example, by taking 4 blocks with parameters
|
||||
/// for 2bits shortints, with have a 4 * 2 = 8 bit integer.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RadixParameters {
|
||||
pub block_parameters: crate::shortint::Parameters,
|
||||
pub num_block: usize,
|
||||
pub pbs_order: crate::shortint::PBSOrder,
|
||||
pub wopbs_block_parameters: crate::shortint::Parameters,
|
||||
}
|
||||
|
||||
/// Parameters for 'CRT' decomposition
|
||||
///
|
||||
/// (Chinese Remainder Theorem)
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CrtParameters {
|
||||
pub block_parameters: crate::shortint::Parameters,
|
||||
pub moduli: Vec<u64>,
|
||||
pub wopbs_block_parameters: crate::shortint::Parameters,
|
||||
}
|
||||
use crate::high_level_api::internal_traits::ParameterType;
|
||||
|
||||
/// Meant to be implemented on the inner server key
|
||||
/// eg the crate::integer::ServerKey
|
||||
@@ -35,66 +8,11 @@ pub trait EvaluationIntegerKey<ClientKey> {
|
||||
fn new_wopbs_key(
|
||||
client_key: &ClientKey,
|
||||
server_key: &Self,
|
||||
wopbs_block_parameters: crate::shortint::Parameters,
|
||||
wopbs_block_parameters: crate::shortint::WopbsParameters,
|
||||
) -> crate::integer::wopbs::WopbsKey;
|
||||
}
|
||||
|
||||
impl<P> FromParameters<P> for crate::integer::CrtClientKey
|
||||
where
|
||||
P: Into<CrtParameters>,
|
||||
{
|
||||
fn from_parameters(parameters: P) -> Self {
|
||||
let params = parameters.into();
|
||||
#[cfg(feature = "internal-keycache")]
|
||||
{
|
||||
use crate::integer::keycache::KEY_CACHE;
|
||||
let key = KEY_CACHE.get_from_params(params.block_parameters).0;
|
||||
crate::integer::CrtClientKey::from((key, params.moduli))
|
||||
}
|
||||
#[cfg(not(feature = "internal-keycache"))]
|
||||
{
|
||||
crate::integer::CrtClientKey::new(params.block_parameters, params.moduli)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to mark parameters type for integers
|
||||
pub trait IntegerParameter: ParameterType {
|
||||
fn wopbs_block_parameters(&self) -> crate::shortint::Parameters;
|
||||
|
||||
fn block_parameters(&self) -> crate::shortint::Parameters;
|
||||
}
|
||||
|
||||
/// Marker struct for the RadixRepresentation
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RadixRepresentation;
|
||||
/// Marker struct for the CrtRepresentation
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CrtRepresentation;
|
||||
|
||||
/// Trait to mark parameters type for static integers
|
||||
///
|
||||
/// Static means the integer types with parameters provided by
|
||||
/// the crate, so parameters for which we know the number of
|
||||
/// bits the represent.
|
||||
pub trait StaticIntegerParameter: IntegerParameter {
|
||||
type Representation: Default + Eq;
|
||||
|
||||
const MESSAGE_BITS: usize;
|
||||
}
|
||||
|
||||
pub trait StaticRadixParameter:
|
||||
StaticIntegerParameter<Representation = RadixRepresentation>
|
||||
where
|
||||
Self: IntegerParameter<
|
||||
InnerClientKey = crate::high_level_api::integers::client_key::RadixClientKey,
|
||||
InnerServerKey = crate::integer::ServerKey,
|
||||
>,
|
||||
{
|
||||
}
|
||||
pub trait StaticCrtParameter: StaticIntegerParameter<Representation = CrtRepresentation>
|
||||
where
|
||||
Self:
|
||||
IntegerParameter<InnerClientKey = CrtClientKey, InnerServerKey = crate::integer::ServerKey>,
|
||||
{
|
||||
fn num_blocks() -> usize;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user