Compare commits

...

31 Commits

Author SHA1 Message Date
Arthur Meyre
2b80f449a5 chore(tfhe): bump version to 0.2.5 2023-07-25 21:00:36 +02:00
Arthur Meyre
a37f6a2e05 fix(integer): set proper MaxDegree for CompressedServerKey
- add shortint API to generate a CompressedServerKey with MaxDegree
- add non regression test based on the user issue
- factorize MaxDegree computation for integer server keys
2023-07-25 21:00:36 +02:00
Arthur Meyre
cb1a95e20d chore(tfhe): bump version to 0.2.4 2023-05-09 16:07:55 +02:00
dependabot[bot]
1d19fcfdb9 chore(deps): bump JS-DevTools/npm-publish from 2.0.0 to 2.1.0
Bumps [JS-DevTools/npm-publish](https://github.com/JS-DevTools/npm-publish) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/JS-DevTools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](0be441d808...541aa6b21b)

---
updated-dependencies:
- dependency-name: JS-DevTools/npm-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-09 12:01:49 +02:00
sarah el kazdadi
c93bb51714 fix(pbs): fix bug in rounding code in f128 pbs 2023-05-09 11:21:30 +02:00
Arthur Meyre
b1788cc9df chore(core): re-enable split pbs for u128 2023-04-26 09:23:17 +02:00
Arthur Meyre
6000ef39ab chore(doc): fix docstring ref 2023-04-26 09:23:17 +02:00
Arthur Meyre
c087858f65 refactor(integer): remove usage of Mutex for determinism 2023-04-25 17:16:30 +02:00
sarah el kazdadi
62d6852d07 fix(split): fix split pbs backward conversion 2023-04-25 11:17:44 +02:00
Arthur Meyre
85e8988f29 chore(core): change rng tests to better avoid false failures
- we still check we generate non zero values but add retry conditions or
have less stringent checks, to allow some values to be zero for example as
it's a valid value that can be generated
- each test suite (test and doctest) for these tests ran 1000 times without
failure
2023-04-24 14:49:14 +02:00
dependabot[bot]
ac04ed0893 chore(deps): bump JS-DevTools/npm-publish from 1.4.3 to 2.0.0
Bumps [JS-DevTools/npm-publish](https://github.com/JS-DevTools/npm-publish) from 1.4.3 to 2.0.0.
- [Release notes](https://github.com/JS-DevTools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](0f451a9417...0be441d808)

---
updated-dependencies:
- dependency-name: JS-DevTools/npm-publish
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-24 14:49:01 +02:00
Arthur Meyre
60385f1489 chore(tfhe): bump version to 0.2.3 2023-04-24 10:48:30 +02:00
Arthur Meyre
7c8926b645 chore(doc): fix typo 2023-04-24 09:30:08 +02:00
David Testé
e5cf51230a chore(ci): publish tfhe release on-demand
This will perform on-demand release publication.
It will publish on the following channels:
 * crates.io
 * web and node package on npmjs

(cherry picked from commit 25a2586eae)
2023-04-21 15:56:09 +02:00
Arthur Meyre
cdc25d6c60 chore(core): add more sanity checks on RNG 2023-04-21 13:16:18 +02:00
Arthur Meyre
ab59514b0d fix(core): fix rng 2023-04-21 13:16:18 +02:00
tmontaigu
7c2fa7529c feat(boolean): add BooleanEngine::replace_thread_local
This new associated function allows to replace
the engine used in the thread.
2023-04-20 17:22:37 +02:00
Arthur Meyre
945ce4617f chore(tfhe): bump version to 0.2.2 2023-04-20 09:21:53 +02:00
Arthur Meyre
ff84e70ca9 chore(core): disable split pbs128 2023-04-20 09:21:53 +02:00
tmontaigu
956c4080f5 feat(hlapi): add trivial encryptions 2023-04-19 11:12:45 +02:00
tmontaigu
fea1b4db92 feat(integer): add trivial encryption 2023-04-19 11:12:45 +02:00
Arthur Meyre
13c1fcb6e7 chore(tfhe): bump version to 0.2.1 2023-04-19 10:18:51 +02:00
tmontaigu
69b9bd3860 fix(hlapi): use correct number of blocks for FheUint32
The FheUint32 was wrongly defined as being 32 blocks of 2 bits
when it should have been 16 blocks.
2023-04-19 10:18:30 +02:00
Arthur Meyre
747693e889 chore(doc): updated benchmarks for min to reflect the fix done to min/max 2023-04-19 10:16:44 +02:00
Arthur Meyre
e452d5d6d2 chore(bench): only run avx512 benches 2023-04-18 16:37:38 +02:00
Arthur Meyre
5425ba5199 fix(integer): fix mul correctness
- update benches accordingly
2023-04-18 16:37:38 +02:00
dependabot[bot]
aeda381f12 chore(deps): bump actions/checkout from 3.5.0 to 3.5.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 3.5.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8f4b7f8486...8e5e7e5ab8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-18 10:46:04 +02:00
Arthur Meyre
9b2cccfee6 chore(integer): restore empty carry check for default comparator tests
- only extract assign message instead of doing a full propagate as carries
are not supposed to be non zero (though the degree will have grown)
2023-04-18 10:35:20 +02:00
J-B Orfila
b534f6a406 chore(doc): fix typo 2023-04-14 15:28:29 +02:00
J-B Orfila
7a72dd2619 chore(doc): fix TOML 2023-04-14 14:50:50 +02:00
J-B Orfila
343f31e070 chore(doc): fix dead links 2023-04-13 17:56:01 +02:00
55 changed files with 1476 additions and 317 deletions

View File

@@ -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: |

View File

@@ -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: |

View File

@@ -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
@@ -118,7 +102,7 @@ jobs:
- name: Send data to Slab
shell: bash
env:
COMPRESSED_RESULTS : ${{ env.RESULTS_FILENAME }}.gz
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 }}')"

View File

@@ -21,7 +21,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Run pcc checks
run: |

View File

@@ -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

View File

@@ -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
View 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 }}

View File

@@ -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

View File

@@ -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

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
repository: zama-ai/slab
path: slab

View File

@@ -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

View File

@@ -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
@@ -174,14 +180,14 @@ 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
.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 \

View File

@@ -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:

View File

@@ -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}"

View File

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "0.2.0"
version = "0.2.5"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]

View File

@@ -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;
@@ -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);

View File

@@ -4,7 +4,6 @@
#include <inttypes.h>
#include <stdio.h>
int uint256_client_key(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
@@ -49,6 +48,50 @@ int uint256_client_key(const ClientKey *client_key) {
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);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
return ok;
}
int uint256_public_key(const ClientKey *client_key, const PublicKey *public_key) {
int ok;
FheUint256 *lhs = NULL;
@@ -112,6 +155,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);

View File

@@ -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);

View File

@@ -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.2.5", 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.2.5", 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.2.5", 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.2.5", features = ["x86_64"] }
```
### Commented code to double a 2-bits message in a leveled fashion and using a PBS with the `core_crypto` module.

View File

@@ -59,10 +59,10 @@ To ensure predictable timings, the operation flavor is the `default` one: a carr
| 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.2 ms | 111.9 ms | 186.8 ms |
| 16 bits | 256.3 ms | 756.0 ms | 145.3 ms | 233.1 ms |
| 32 bits | 469.4 ms | 2.10 s | 192.0 ms | 282.9 ms |
| 40 bits | 608.0 ms | 3.37 s | 228.4 ms | 318.6 ms |
| 64 bits | 959.9 ms | 5.53 s | 249.0 ms | 336.5 ms |
| 128 bits | 1.88 s | 14.1 s | 294.7 ms | 398.6 ms |
| 256 bits | 3.66 s | 29.2 s | 361.8 ms | 509.1 ms |

View File

@@ -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.2.5", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
```
{% hint style="info" %}

View File

@@ -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.2.5", features = ["integer","x86_64-unix"]}
bincode = "1.3.3"
```

View File

@@ -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):
@@ -44,10 +44,13 @@ fn main() {
}
```
Default configuration for x86 Unix machines:
```toml
tfhe = { version = "0.2.0", features = ["integer"]}
tfhe = { version = "0.2.5", 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.
@@ -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.2.5", 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::*;
@@ -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.2.5", 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

View File

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

View 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);
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -59,6 +59,23 @@ 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! {

View File

@@ -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`].
@@ -811,7 +812,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 +1121,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
///

View File

@@ -229,6 +229,10 @@ impl<G: ByteRandomGenerator> EncryptionRandomGenerator<G> {
where
Scalar: UnsignedInteger + RandomGenerable<Gaussian<f64>, CustomModulus = f64>,
{
if custom_modulus.is_native_modulus() {
return self.random_noise(std);
}
let custom_modulus_f64: f64 = custom_modulus.get().cast_into();
Scalar::generate_one_custom_modulus(
&mut self.noise,
@@ -552,7 +556,7 @@ fn noise_bytes_per_pfpksk(
#[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,
@@ -560,6 +564,7 @@ mod test {
use crate::core_crypto::commons::test_tools::{
new_encryption_random_generator, new_secret_random_generator,
};
use crate::core_crypto::commons::traits::UnsignedTorus;
#[test]
fn test_gaussian_sampling_margin_factor_does_not_panic() {
@@ -598,4 +603,264 @@ 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 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 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());
}
}

View File

@@ -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,
@@ -243,8 +245,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 +374,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 +408,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,6 +428,11 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
Scalar: UnsignedInteger,
(Scalar, Scalar): RandomGenerable<Gaussian<Float>, CustomModulus = Float>,
{
if custom_modulus.is_native_modulus() {
self.fill_slice_with_random_gaussian(output, mean, std);
return;
}
let custom_modulus_float: Float = custom_modulus.get().cast_into();
output.chunks_mut(2).for_each(|s| {
let (g1, g2) = <(Scalar, Scalar)>::generate_one_custom_modulus(
@@ -448,8 +458,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 +490,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,6 +512,11 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
Float: FloatingPoint + CastFrom<u128>,
(Scalar, Scalar): RandomGenerable<Gaussian<Float>, CustomModulus = Float>,
{
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().cast_into();
output.chunks_mut(2).for_each(|s| {
let (g1, g2) = <(Scalar, Scalar)>::generate_one_custom_modulus(

View File

@@ -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;

View File

@@ -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>,

View File

@@ -1,2 +1,5 @@
pub mod bootstrap;
pub mod ggsw;
#[cfg(test)]
mod tests;

View 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);
}

View File

@@ -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))
}
}

View File

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

View File

@@ -2,7 +2,7 @@ use std::marker::PhantomData;
use crate::high_level_api::integers::parameters::EvaluationIntegerKey;
use super::client_key::GenericIntegerClientKey;
use super::client_key::{GenericIntegerClientKey, RadixClientKey};
use super::parameters::IntegerParameter;
use crate::integer::wopbs::WopbsKey;
@@ -11,12 +11,16 @@ use crate::integer::wopbs::WopbsKey;
pub struct GenericIntegerServerKey<P: IntegerParameter> {
pub(in crate::high_level_api::integers) inner: P::InnerServerKey,
pub(in crate::high_level_api::integers) wopbs_key: WopbsKey,
// To know if we have to encrypt into a big or small when trivial encrypting
pub(in crate::high_level_api::integers) pbs_order: crate::shortint::PBSOrder,
// To know the num block when trivial encrypting
pub(in crate::high_level_api::integers) num_block: usize,
_marker: PhantomData<P>,
}
impl<P> GenericIntegerServerKey<P>
where
P: IntegerParameter,
P: IntegerParameter<InnerClientKey = RadixClientKey>,
P::InnerServerKey: EvaluationIntegerKey<P::InnerClientKey>,
{
pub(super) fn new(client_key: &GenericIntegerClientKey<P>) -> Self {
@@ -29,6 +33,8 @@ where
Self {
inner,
wopbs_key,
pbs_order: client_key.inner.pbs_order,
num_block: client_key.inner.inner.num_blocks(),
_marker: Default::default(),
}
}

View File

@@ -201,3 +201,39 @@ fn test_integer_compressed_public_key() {
let clear: u8 = a.decrypt(&client_key);
assert_eq!(clear, 213u8);
}
#[test]
fn test_trivial_fhe_uint8() {
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
let (client_key, sks) = generate_keys(config);
set_server_key(sks);
let a = FheUint8::try_encrypt_trivial(234u8).unwrap();
assert!(matches!(
&*a.ciphertext.borrow(),
crate::high_level_api::integers::server_key::RadixCiphertextDyn::Big(_)
));
let clear: u8 = a.decrypt(&client_key);
assert_eq!(clear, 234);
}
#[test]
fn test_trivial_fhe_uint256_small() {
let config = ConfigBuilder::all_disabled()
.enable_default_uint256_small()
.build();
let (client_key, sks) = generate_keys(config);
set_server_key(sks);
let clear_a = U256::from(u128::MAX);
let a = FheUint256::try_encrypt_trivial(clear_a).unwrap();
assert!(matches!(
&*a.ciphertext.borrow(),
crate::high_level_api::integers::server_key::RadixCiphertextDyn::Small(_)
));
let clear: U256 = a.decrypt(&client_key);
assert_eq!(clear, clear_a);
}

View File

@@ -21,7 +21,9 @@ use crate::high_level_api::keys::{
CompressedPublicKey, RefKeyFromCompressedPublicKeyChain, RefKeyFromKeyChain,
RefKeyFromPublicKeyChain,
};
use crate::high_level_api::traits::{FheBootstrap, FheDecrypt, FheEq, FheOrd, FheTryEncrypt};
use crate::high_level_api::traits::{
FheBootstrap, FheDecrypt, FheEq, FheOrd, FheTrivialEncrypt, FheTryEncrypt, FheTryTrivialEncrypt,
};
use crate::high_level_api::{ClientKey, PublicKey};
use crate::integer::U256;
@@ -166,6 +168,47 @@ where
}
}
impl<P, T> FheTryTrivialEncrypt<T> for GenericInteger<P>
where
T: Into<U256>,
P: IntegerParameter<
InnerCiphertext = RadixCiphertextDyn,
InnerServerKey = crate::integer::ServerKey,
>,
P::Id: WithGlobalKey<Key = GenericIntegerServerKey<P>> + Default,
{
type Error = crate::high_level_api::errors::Error;
fn try_encrypt_trivial(value: T) -> Result<Self, Self::Error> {
let value = value.into();
let id = P::Id::default();
let ciphertext = id.with_global(|key| match key.pbs_order {
crate::shortint::PBSOrder::KeyswitchBootstrap => {
RadixCiphertextDyn::Big(key.inner.create_trivial_radix(value, key.num_block))
}
crate::shortint::PBSOrder::BootstrapKeyswitch => {
RadixCiphertextDyn::Small(key.inner.create_trivial_radix(value, key.num_block))
}
})?;
Ok(Self::new(ciphertext, id))
}
}
impl<P, T> FheTrivialEncrypt<T> for GenericInteger<P>
where
T: Into<U256>,
P: IntegerParameter<
InnerCiphertext = RadixCiphertextDyn,
InnerServerKey = crate::integer::ServerKey,
>,
P::Id: WithGlobalKey<Key = GenericIntegerServerKey<P>> + Default,
{
#[track_caller]
fn encrypt_trivial(value: T) -> Self {
Self::try_encrypt_trivial(value).unwrap()
}
}
impl<P> GenericInteger<P>
where
P: IntegerParameter,

View File

@@ -413,7 +413,7 @@ static_int_type! {
parameters: Radix {
big_block_parameters: crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2,
small_block_parameters: crate::shortint::parameters::PARAM_SMALL_MESSAGE_2_CARRY_2,
num_block: 32,
num_block: 16,
wopbs_block_parameters: crate::shortint::parameters::parameters_wopbs_message_carry::WOPBS_PARAM_MESSAGE_2_CARRY_2,
},
}

View File

@@ -1,5 +1,7 @@
use crate::high_level_api::prelude::*;
use crate::high_level_api::{generate_keys, CompressedFheUint2, ConfigBuilder, FheUint2};
use crate::high_level_api::{
generate_keys, set_server_key, CompressedFheUint2, ConfigBuilder, FheUint2,
};
use crate::CompressedPublicKey;
#[test]
@@ -24,3 +26,15 @@ fn test_shortint_compressed_public_key() {
let clear = a.decrypt(&client_key);
assert_eq!(clear, 2);
}
#[test]
fn test_trivial_shortint() {
let config = ConfigBuilder::all_disabled().enable_default_uint2().build();
let (client_key, sks) = generate_keys(config);
set_server_key(sks);
let a = FheUint2::try_encrypt_trivial(2).unwrap();
let clear = a.decrypt(&client_key);
assert_eq!(clear, 2);
}

View File

@@ -17,7 +17,8 @@ use crate::high_level_api::keys::{
};
use crate::high_level_api::shortints::public_key::compressed::GenericShortIntCompressedPublicKey;
use crate::high_level_api::traits::{
FheBootstrap, FheDecrypt, FheEq, FheNumberConstant, FheOrd, FheTryEncrypt, FheTryTrivialEncrypt,
FheBootstrap, FheDecrypt, FheEq, FheNumberConstant, FheOrd, FheTrivialEncrypt, FheTryEncrypt,
FheTryTrivialEncrypt,
};
use crate::high_level_api::PublicKey;
@@ -311,6 +312,18 @@ where
}
}
impl<Clear, P> FheTrivialEncrypt<Clear> for GenericShortInt<P>
where
Clear: TryInto<u8>,
P: StaticShortIntegerParameter,
P::Id: Default + WithGlobalKey<Key = GenericShortIntServerKey<P>>,
{
#[track_caller]
fn encrypt_trivial(value: Clear) -> Self {
Self::try_encrypt_trivial(value).unwrap()
}
}
impl<P> GenericShortInt<P>
where
P: ShortIntegerParameter,

View File

@@ -7,7 +7,7 @@ use crate::shortint::{
use serde::{Deserialize, Serialize};
/// Structure containing a ciphertext in radix decomposition.
#[derive(Serialize, Clone, Deserialize)]
#[derive(Serialize, Clone, Deserialize, PartialEq, Eq, Debug)]
pub struct BaseRadixCiphertext<Block> {
/// The blocks are stored from LSB to MSB
pub(crate) blocks: Vec<Block>,

View File

@@ -109,29 +109,35 @@ impl AsLittleEndianWords for U256 {
}
}
pub(crate) trait BlockEncryptionKey {
fn parameters(&self) -> &crate::shortint::Parameters;
pub(crate) trait KnowsMessageModulus {
fn message_modulus(&self) -> MessageModulus;
}
impl BlockEncryptionKey for crate::shortint::ClientKey {
fn parameters(&self) -> &crate::shortint::Parameters {
&self.parameters
impl KnowsMessageModulus for crate::shortint::ClientKey {
fn message_modulus(&self) -> MessageModulus {
self.parameters.message_modulus
}
}
impl<OpOrder: crate::shortint::PBSOrderMarker> BlockEncryptionKey
impl<OpOrder: crate::shortint::PBSOrderMarker> KnowsMessageModulus
for crate::shortint::PublicKeyBase<OpOrder>
{
fn parameters(&self) -> &crate::shortint::Parameters {
&self.parameters
fn message_modulus(&self) -> MessageModulus {
self.parameters.message_modulus
}
}
impl<OpOrder: crate::shortint::PBSOrderMarker> BlockEncryptionKey
impl<OpOrder: crate::shortint::PBSOrderMarker> KnowsMessageModulus
for crate::shortint::CompressedPublicKeyBase<OpOrder>
{
fn parameters(&self) -> &crate::shortint::Parameters {
&self.parameters
fn message_modulus(&self) -> MessageModulus {
self.parameters.message_modulus
}
}
impl KnowsMessageModulus for crate::shortint::ServerKey {
fn message_modulus(&self) -> MessageModulus {
self.message_modulus
}
}
@@ -151,7 +157,7 @@ pub(crate) fn encrypt_words_radix_impl<BlockKey, Block, RadixCiphertextType, T,
) -> RadixCiphertextType
where
T: AsLittleEndianWords,
BlockKey: BlockEncryptionKey,
BlockKey: KnowsMessageModulus,
F: Fn(&BlockKey, u64) -> Block,
RadixCiphertextType: From<Vec<Block>>,
{
@@ -165,8 +171,8 @@ where
// | bit values are not valid and should not be encrypted)
// |-> current_power (start of next block of bits to encrypt (inclusive))
let mask = (encrypting_key.parameters().message_modulus.0 - 1) as u128;
let block_modulus = encrypting_key.parameters().message_modulus.0 as u128;
let mask = (encrypting_key.message_modulus().0 - 1) as u128;
let block_modulus = encrypting_key.message_modulus().0 as u128;
let mut blocks = Vec::with_capacity(num_blocks);

View File

@@ -1107,7 +1107,9 @@ impl<'a> Comparator<'a> {
};
let mut res = self.unchecked_max_parallelized(lhs, rhs);
self.server_key.full_propagate_parallelized(&mut res);
res.blocks
.par_iter_mut()
.for_each(|block| self.server_key.key.message_extract_assign(block));
res
}
@@ -1143,7 +1145,9 @@ impl<'a> Comparator<'a> {
};
let mut res = self.unchecked_min_parallelized(lhs, rhs);
self.server_key.full_propagate_parallelized(&mut res);
res.blocks
.par_iter_mut()
.for_each(|block| self.server_key.key.message_extract_assign(block));
res
}
}
@@ -1333,7 +1337,7 @@ mod tests {
assert!(super::has_non_zero_carries(&ct_0));
assert!(super::has_non_zero_carries(&ct_1));
let encrypted_result = default_comparator_method(&comparator, &ct_0, &ct_1);
// assert!(!super::has_non_zero_carries(&encrypted_result));
assert!(!super::has_non_zero_carries(&encrypted_result));
// Sanity decryption checks
{

View File

@@ -30,6 +30,15 @@ impl From<ServerKey> for crate::shortint::ServerKey {
}
}
/// Compute the [`MaxDegree`] for an integer server key (compressed or uncompressed). This formula
/// provisions a free carry bit. This allows carry propagation between shortint blocks in a
/// [`RadixCiphertext`](`crate::integer::ciphertext::RadixCiphertext`), as that process requires
/// adding a bit of carry from one shortint block to the next, which would overflow and lead to
/// wrong results if we did not provision that carry bit.
fn integer_server_key_max_degree(parameters: crate::shortint::Parameters) -> MaxDegree {
MaxDegree((parameters.message_modulus.0 - 1) * parameters.carry_modulus.0 - 1)
}
impl ServerKey {
/// Generates a server key.
///
@@ -51,13 +60,11 @@ impl ServerKey {
{
// It should remain just enough space to add a carry
let client_key = cks.as_ref();
let max = (client_key.key.parameters.message_modulus.0 - 1)
* client_key.key.parameters.carry_modulus.0
- 1;
let max_degree = integer_server_key_max_degree(client_key.key.parameters);
let sks = crate::shortint::server_key::ServerKey::new_with_max_degree(
&client_key.key,
MaxDegree(max),
max_degree,
);
ServerKey { key: sks }
@@ -84,10 +91,9 @@ impl ServerKey {
mut key: crate::shortint::server_key::ServerKey,
) -> ServerKey {
// It should remain just enough space add a carry
let max =
(cks.key.parameters.message_modulus.0 - 1) * cks.key.parameters.carry_modulus.0 - 1;
let max_degree = integer_server_key_max_degree(cks.key.parameters);
key.max_degree = MaxDegree(max);
key.max_degree = max_degree;
ServerKey { key }
}
}
@@ -98,7 +104,10 @@ pub struct CompressedServerKey {
impl CompressedServerKey {
pub fn new(client_key: &ClientKey) -> CompressedServerKey {
let key = crate::shortint::CompressedServerKey::new(&client_key.key);
let max_degree = integer_server_key_max_degree(client_key.key.parameters);
let key =
crate::shortint::CompressedServerKey::new_with_max_degree(&client_key.key, max_degree);
Self { key }
}
}
@@ -109,3 +118,46 @@ impl From<CompressedServerKey> for ServerKey {
Self { key }
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::integer::RadixClientKey;
use crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
/// https://github.com/zama-ai/tfhe-rs/issues/460
/// Problem with CompressedServerKey degree being set to shortint MaxDegree not accounting for
/// the necessary carry bits for e.g. Radix carry propagation.
#[test]
fn test_compressed_server_key_max_degree() {
let cks = ClientKey::new(crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2);
// msg_mod = 4, carry_mod = 4, (msg_mod - 1) * carry_mod = 12; minus 1 => 11
let expected_max_degree = MaxDegree(11);
let sks = ServerKey::new(&cks);
assert_eq!(sks.key.max_degree, expected_max_degree);
let csks = CompressedServerKey::new(&cks);
assert_eq!(csks.key.max_degree, expected_max_degree);
let decompressed_sks: ServerKey = csks.into();
assert_eq!(decompressed_sks.key.max_degree, expected_max_degree);
// Repro case from the user
{
let client_key = RadixClientKey::new(PARAM_MESSAGE_2_CARRY_2, 14);
let compressed_eval_key = CompressedServerKey::new(client_key.as_ref());
let evaluation_key = ServerKey::from(compressed_eval_key);
let modulus = (client_key.parameters().message_modulus.0 as u128)
.pow(client_key.num_blocks() as u32);
let mut ct = client_key.encrypt(modulus - 1);
let mut res_ct = ct.clone();
for _ in 0..5 {
res_ct = evaluation_key.smart_add_parallelized(&mut res_ct, &mut ct);
}
let res = client_key.decrypt::<u128, _>(&res_ct);
assert_eq!(modulus - 6, res);
}
}
}

View File

@@ -12,6 +12,7 @@ mod sub;
use super::ServerKey;
use crate::integer::ciphertext::RadixCiphertext;
use crate::integer::encryption::{encrypt_words_radix_impl, AsLittleEndianWords};
use crate::shortint::PBSOrderMarker;
#[cfg(test)]
@@ -49,6 +50,44 @@ impl ServerKey {
RadixCiphertext::from(vec_res)
}
/// Create a trivial radix ciphertext
///
/// Trivial means that the value is not encrypted
///
/// # Example
///
/// ```rust
/// use tfhe::integer::{gen_keys_radix, RadixCiphertextBig};
/// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
///
/// let num_blocks = 4;
///
/// // Generate the client key and the server key:
/// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_blocks);
///
/// let ctxt: RadixCiphertextBig = sks.create_trivial_radix(212u64, num_blocks);
///
/// // Decrypt:
/// let dec: u64 = cks.decrypt(&ctxt);
/// assert_eq!(212, dec);
/// ```
pub fn create_trivial_radix<T, PBSOrder>(
&self,
value: T,
num_blocks: usize,
) -> RadixCiphertext<PBSOrder>
where
PBSOrder: PBSOrderMarker,
T: AsLittleEndianWords,
{
encrypt_words_radix_impl(
&self.key,
value,
num_blocks,
crate::shortint::ServerKey::create_trivial,
)
}
/// Propagate the carry of the 'index' block to the next one.
///
/// # Example

View File

@@ -1,5 +1,3 @@
use std::sync::Mutex;
use crate::integer::ciphertext::RadixCiphertext;
use crate::integer::ServerKey;
use crate::shortint::PBSOrderMarker;
@@ -196,24 +194,25 @@ impl ServerKey {
} else {
// we repeatedly divide the number of terms by two by iteratively reducing
// consecutive terms in the array
let num_blocks = ct_seq[0].as_mut().blocks.len();
while ct_seq.len() > 1 {
let results = Mutex::new(Vec::<RadixCiphertext<PBSOrder>>::with_capacity(
ct_seq.len() / 2,
));
let mut results =
vec![sks.create_trivial_radix(0u64, num_blocks); ct_seq.len() / 2];
// if the number of elements is odd, we skip the first element
let untouched_prefix = ct_seq.len() % 2;
let ct_seq_slice = &mut ct_seq[untouched_prefix..];
ct_seq_slice.par_chunks_mut(2).for_each(|chunk| {
let (first, second) = chunk.split_at_mut(1);
let first = &mut first[0];
let second = &mut second[0];
let result = op(sks, first.as_mut(), second.as_mut());
results.lock().unwrap().push(result);
});
results
.par_iter_mut()
.zip(ct_seq_slice.par_chunks_exact_mut(2))
.for_each(|(ct_res, chunk)| {
let (first, second) = chunk.split_at_mut(1);
let first = first[0].as_mut();
let second = second[0].as_mut();
*ct_res = op(sks, first, second);
});
let results = results.into_inner().unwrap();
ct_seq.truncate(untouched_prefix);
ct_seq.extend(results.into_iter().map(CiphertextCow::Owned));
}
@@ -281,24 +280,23 @@ impl ServerKey {
} else {
// we repeatedly divide the number of terms by two by iteratively reducing
// consecutive terms in the array
let num_blocks = ct_seq[0].as_ref().blocks.len();
while ct_seq.len() > 1 {
let results = Mutex::new(Vec::<RadixCiphertext<PBSOrder>>::with_capacity(
ct_seq.len() / 2,
));
let mut results =
vec![sks.create_trivial_radix(0u64, num_blocks); ct_seq.len() / 2];
// if the number of elements is odd, we skip the first element
let untouched_prefix = ct_seq.len() % 2;
let ct_seq_slice = &mut ct_seq[untouched_prefix..];
ct_seq_slice.par_chunks(2).for_each(|chunk| {
let (first, second) = chunk.split_at(1);
let first = &first[0];
let second = &second[0];
let result = op(sks, first.as_ref(), second.as_ref());
results.lock().unwrap().push(result);
});
results
.par_iter_mut()
.zip(ct_seq_slice.par_chunks_exact(2))
.for_each(|(ct_res, chunk)| {
let first = chunk[0].as_ref();
let second = chunk[1].as_ref();
*ct_res = op(sks, first, second);
});
let results = results.into_inner().unwrap();
ct_seq.truncate(untouched_prefix);
ct_seq.extend(results.into_iter().map(CiphertextCow::Owned));
}

View File

@@ -1,5 +1,3 @@
use std::sync::Mutex;
use crate::integer::ciphertext::RadixCiphertext;
use crate::integer::ServerKey;
use crate::shortint::PBSOrderMarker;
@@ -335,17 +333,18 @@ impl ServerKey {
) -> RadixCiphertext<PBSOrder> {
let mut result = self.create_trivial_zero_radix(ct1.blocks.len());
let terms = Mutex::new(Vec::new());
let num_blocks = ct1.blocks.len();
let mut terms = vec![self.create_trivial_zero_radix(num_blocks); num_blocks];
ct2.blocks.par_iter().enumerate().for_each(|(i, ct2_i)| {
let term = self.unchecked_block_mul_parallelized(ct1, ct2_i, i);
terms.lock().unwrap().push(term);
});
terms
.par_iter_mut()
.zip(ct2.blocks.par_iter().enumerate())
.for_each(|(term, (i, ct2_i))| {
*term = self.unchecked_block_mul_parallelized(ct1, ct2_i, i);
});
let terms = terms.into_inner().unwrap();
for term in terms {
self.unchecked_add_assign(&mut result, &term);
for term in terms.iter_mut() {
self.smart_add_assign(&mut result, term);
}
result
@@ -407,12 +406,15 @@ impl ServerKey {
|| self.full_propagate_parallelized(ct2),
);
let terms = Mutex::new(Vec::new());
ct2.blocks.par_iter().enumerate().for_each(|(i, ct2_i)| {
let term = self.unchecked_block_mul_parallelized(ct1, ct2_i, i);
terms.lock().unwrap().push(term);
});
let mut terms = terms.into_inner().unwrap();
let num_blocks = ct1.blocks.len();
let mut terms = vec![self.create_trivial_zero_radix(num_blocks); num_blocks];
terms
.par_iter_mut()
.zip(ct2.blocks.par_iter().enumerate())
.for_each(|(term, (i, ct2_i))| {
*term = self.unchecked_block_mul_parallelized(ct1, ct2_i, i);
});
self.smart_binary_op_seq_parallelized(&mut terms, ServerKey::smart_add_parallelized)
.unwrap_or_else(|| self.create_trivial_zero_radix(ct1.blocks.len()))
@@ -495,7 +497,20 @@ impl ServerKey {
(ct1, &tmp_rhs)
}
};
self.unchecked_mul_assign_parallelized(lhs, rhs);
let num_blocks = lhs.blocks.len();
let mut terms = vec![self.create_trivial_zero_radix(num_blocks); num_blocks];
terms
.par_iter_mut()
.zip(rhs.blocks.par_iter().enumerate())
.for_each(|(term, (i, rhs_i))| {
*term = self.unchecked_block_mul_parallelized(lhs, rhs_i, i);
});
*lhs = self
.smart_binary_op_seq_parallelized(&mut terms, ServerKey::smart_add_parallelized)
.unwrap_or_else(|| self.create_trivial_zero_radix(num_blocks));
self.full_propagate_parallelized(lhs);
}
}

View File

@@ -4,8 +4,6 @@ use crate::integer::server_key::CheckError::CarryFull;
use crate::integer::ServerKey;
use crate::shortint::PBSOrderMarker;
use rayon::prelude::*;
use std::collections::HashMap;
use std::sync::Mutex;
impl ServerKey {
/// Computes homomorphically a multiplication between a scalar and a ciphertext.
@@ -363,53 +361,68 @@ impl ServerKey {
return zero;
}
let num_tasks = self.key.message_modulus.0;
let b = self.key.message_modulus.0 as u64;
let n = ct.blocks.len();
let num_blocks = ct.blocks.len();
//Propagate the carries before doing the multiplications
self.full_propagate_parallelized(ct);
let ct = &*ct;
// key is the small scalar we multiply by
// value is the vector of blockshifts
let mut task_map = HashMap::<u64, Vec<usize>>::new();
// index is the small scalar we multiply by, value is the vector of blockshifts
let mut task_vec: Vec<Vec<usize>> =
vec![Vec::with_capacity((u64::BITS / b.ilog2()) as usize); num_tasks];
// Divide scalar progressively towards zero
let mut scalar_i = scalar;
for i in 0..n {
for i in 0..num_blocks {
let u_i = scalar_i % b;
task_map.entry(u_i).or_insert_with(Vec::new).push(i);
task_vec[u_i as usize].push(i);
scalar_i /= b;
if scalar_i == 0 {
break;
}
}
let terms = Mutex::new(Vec::<RadixCiphertext<PBSOrder>>::new());
task_map.par_iter().for_each(|(&u_i, blockshifts)| {
if u_i == 0 {
return;
}
let task_vec: Vec<_> = task_vec
.into_iter()
.enumerate()
.skip(1) // skip u_i == 0, multiplying by 0 yielding 0
.filter(|(_u_i, blockshifts)| !blockshifts.is_empty())
.collect();
let blockshifts = &**blockshifts;
let min_blockshift = *blockshifts.iter().min().unwrap();
let mut terms: Vec<_> = task_vec
.iter()
.map(|(_, blockshifts)| {
vec![self.create_trivial_zero_radix(num_blocks); blockshifts.len()]
})
.collect();
terms
.par_iter_mut()
.zip(task_vec.par_iter())
.for_each(|(term_vec, (u_i, blockshifts))| {
let min_blockshift = blockshifts.iter().min().unwrap();
let mut tmp = ct.clone();
if u_i != 1 {
tmp.blocks[0..n - min_blockshift]
let u_i = *u_i;
let mut tmp = ct.clone();
if u_i != 1 {
tmp.blocks[0..num_blocks - *min_blockshift]
.par_iter_mut()
.for_each(|ct_i| self.key.unchecked_scalar_mul_assign(ct_i, u_i as u8));
}
term_vec
.par_iter_mut()
.for_each(|ct_i| self.key.unchecked_scalar_mul_assign(ct_i, u_i as u8));
}
let tmp = &tmp;
blockshifts.par_iter().for_each(|&shift| {
let term = self.blockshift(tmp, shift);
terms.lock().unwrap().push(term);
.zip(blockshifts.par_iter())
.for_each(|(term, &shift)| {
*term = self.blockshift(&tmp, shift);
});
});
});
let mut terms = terms.into_inner().unwrap();
self.smart_binary_op_seq_parallelized(&mut terms, ServerKey::smart_add_parallelized)
.unwrap_or(zero)
self.smart_binary_op_seq_parallelized(
terms.iter_mut().flatten(),
ServerKey::smart_add_parallelized,
)
.unwrap_or(zero)
}
pub fn smart_scalar_mul_assign_parallelized<PBSOrder: PBSOrderMarker>(
@@ -475,51 +488,67 @@ impl ServerKey {
return;
}
let num_tasks = self.key.message_modulus.0;
let b = self.key.message_modulus.0 as u64;
let n = ct.blocks.len();
let num_blocks = ct.blocks.len();
//Propagate the carries before doing the multiplications
self.full_propagate_parallelized(ct);
// key is the small scalar we multiply by
// value is the vector of blockshifts
let mut task_map = HashMap::<u64, Vec<usize>>::new();
// index is the small scalar we multiply by, value is the vector of blockshifts
let mut task_vec: Vec<Vec<usize>> =
vec![Vec::with_capacity((u64::BITS / b.ilog2()) as usize); num_tasks];
// Divide scalar progressively towards zero
let mut scalar_i = scalar;
for i in 0..n {
for i in 0..num_blocks {
let u_i = scalar_i % b;
task_map.entry(u_i).or_insert_with(Vec::new).push(i);
task_vec[u_i as usize].push(i);
scalar_i /= b;
if scalar_i == 0 {
break;
}
}
let terms = Mutex::new(Vec::<RadixCiphertext<PBSOrder>>::new());
task_map.par_iter().for_each(|(&u_i, blockshifts)| {
if u_i == 0 {
return;
}
let task_vec: Vec<_> = task_vec
.into_iter()
.enumerate()
.skip(1) // skip u_i == 0, multiplying by 0 yielding 0
.filter(|(_u_i, blockshifts)| !blockshifts.is_empty())
.collect();
let blockshifts = &**blockshifts;
let min_blockshift = *blockshifts.iter().min().unwrap();
let mut terms: Vec<_> = task_vec
.iter()
.map(|(_, blockshifts)| {
vec![self.create_trivial_zero_radix(num_blocks); blockshifts.len()]
})
.collect();
terms
.par_iter_mut()
.zip(task_vec.par_iter())
.for_each(|(term_vec, (u_i, blockshifts))| {
let min_blockshift = blockshifts.iter().min().unwrap();
let mut tmp = ct.clone();
if u_i != 1 {
tmp.blocks[0..n - min_blockshift]
let u_i = *u_i;
let mut tmp = ct.clone();
if u_i != 1 {
tmp.blocks[0..num_blocks - *min_blockshift]
.par_iter_mut()
.for_each(|ct_i| self.key.unchecked_scalar_mul_assign(ct_i, u_i as u8));
}
term_vec
.par_iter_mut()
.for_each(|ct_i| self.key.unchecked_scalar_mul_assign(ct_i, u_i as u8));
}
let tmp = &tmp;
blockshifts.par_iter().for_each(|&shift| {
let term = self.blockshift(tmp, shift);
terms.lock().unwrap().push(term);
.zip(blockshifts.par_iter())
.for_each(|(term, &shift)| {
*term = self.blockshift(&tmp, shift);
});
});
});
let terms = terms.into_inner().unwrap();
*ct = self
.default_binary_op_seq_parallelized(&terms, ServerKey::add_parallelized)
.smart_binary_op_seq_parallelized(
terms.iter_mut().flatten(),
ServerKey::smart_add_parallelized,
)
.unwrap_or(zero);
self.full_propagate_parallelized(ct);
}

View File

@@ -97,7 +97,6 @@ fn integer_smart_add(param: Parameters) {
// encryption of an integer
let mut ctxt_1 = cks.encrypt(clear_1);
// add the two ciphertexts
let mut ct_res = sks.smart_add_parallelized(&mut ctxt_0, &mut ctxt_1);
clear = (clear_0 + clear_1) % modulus;
@@ -141,7 +140,6 @@ fn integer_smart_add_sequence_multi_thread(param: Parameters) {
.map(|clear| cks.encrypt(clear))
.collect::<Vec<_>>();
// add the ciphertexts
let ct_res = sks
.smart_binary_op_seq_parallelized(&mut ctxts, ServerKey::smart_add_parallelized)
.unwrap();
@@ -176,7 +174,6 @@ fn integer_smart_add_sequence_single_thread(param: Parameters) {
.map(|clear| cks.encrypt(clear))
.collect::<Vec<_>>();
// add the ciphertexts
let threadpool = rayon::ThreadPoolBuilder::new()
.num_threads(1)
.build()
@@ -217,9 +214,10 @@ fn integer_default_add(param: Parameters) {
// encryption of an integer
let ctxt_1 = cks.encrypt(clear_1);
// add the two ciphertexts
let mut ct_res = sks.add_parallelized(&ctxt_0, &ctxt_1);
let tmp_ct = sks.add_parallelized(&ctxt_0, &ctxt_1);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp_ct);
clear = (clear_0 + clear_1) % modulus;
@@ -263,11 +261,14 @@ fn integer_default_add_sequence_multi_thread(param: Parameters) {
.map(|clear| cks.encrypt(clear))
.collect::<Vec<_>>();
// add the ciphertexts
let ct_res = sks
.default_binary_op_seq_parallelized(&ctxts, ServerKey::add_parallelized)
.unwrap();
let tmp_ct = sks
.default_binary_op_seq_parallelized(&ctxts, ServerKey::add_parallelized)
.unwrap();
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp_ct);
let ct_res: u64 = cks.decrypt(&ct_res);
let clear = clears.iter().sum::<u64>() % modulus;
@@ -299,7 +300,6 @@ fn integer_default_add_sequence_single_thread(param: Parameters) {
.map(|clear| cks.encrypt(clear))
.collect::<Vec<_>>();
// add the ciphertexts
let threadpool = rayon::ThreadPoolBuilder::new()
.num_threads(1)
.build()
@@ -341,7 +341,6 @@ fn integer_smart_bitand(param: Parameters) {
// encryption of an integer
let mut ctxt_1 = cks.encrypt(clear_1);
// add the two ciphertexts
let mut ct_res = sks.smart_bitand_parallelized(&mut ctxt_0, &mut ctxt_1);
clear = clear_0 & clear_1;
@@ -387,7 +386,6 @@ fn integer_smart_bitor(param: Parameters) {
// encryption of an integer
let mut ctxt_1 = cks.encrypt(clear_1);
// add the two ciphertexts
let mut ct_res = sks.smart_bitor_parallelized(&mut ctxt_0, &mut ctxt_1);
clear = (clear_0 | clear_1) % modulus;
@@ -433,7 +431,6 @@ fn integer_smart_bitxor(param: Parameters) {
// encryption of an integer
let mut ctxt_1 = cks.encrypt(clear_1);
// add the two ciphertexts
let mut ct_res = sks.smart_bitxor_parallelized(&mut ctxt_0, &mut ctxt_1);
clear = (clear_0 ^ clear_1) % modulus;
@@ -479,7 +476,6 @@ fn integer_default_bitand(param: Parameters) {
// encryption of an integer
let ctxt_1 = cks.encrypt(clear_1);
// add the two ciphertexts
let mut ct_res = sks.bitand_parallelized(&ctxt_0, &ctxt_1);
assert!(ct_res.block_carries_are_empty());
@@ -491,8 +487,10 @@ fn integer_default_bitand(param: Parameters) {
// encryption of an integer
let ctxt_2 = cks.encrypt(clear_2);
let tmp = sks.bitand_parallelized(&ct_res, &ctxt_2);
ct_res = sks.bitand_parallelized(&ct_res, &ctxt_2);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
clear &= clear_2;
// decryption of ct_res
@@ -527,7 +525,6 @@ fn integer_default_bitor(param: Parameters) {
// encryption of an integer
let ctxt_1 = cks.encrypt(clear_1);
// add the two ciphertexts
let mut ct_res = sks.bitor_parallelized(&ctxt_0, &ctxt_1);
assert!(ct_res.block_carries_are_empty());
@@ -539,8 +536,10 @@ fn integer_default_bitor(param: Parameters) {
// encryption of an integer
let ctxt_2 = cks.encrypt(clear_2);
let tmp = sks.bitor_parallelized(&ct_res, &ctxt_2);
ct_res = sks.bitor_parallelized(&ct_res, &ctxt_2);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
clear = (clear | clear_2) % modulus;
// decryption of ct_res
@@ -575,7 +574,6 @@ fn integer_default_bitxor(param: Parameters) {
// encryption of an integer
let ctxt_1 = cks.encrypt(clear_1);
// add the two ciphertexts
let mut ct_res = sks.bitxor_parallelized(&ctxt_0, &ctxt_1);
assert!(ct_res.block_carries_are_empty());
@@ -587,8 +585,10 @@ fn integer_default_bitxor(param: Parameters) {
// encryption of an integer
let ctxt_2 = cks.encrypt(clear_2);
let tmp = sks.bitxor_parallelized(&ct_res, &ctxt_2);
ct_res = sks.bitxor_parallelized(&ct_res, &ctxt_2);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
clear = (clear ^ clear_2) % modulus;
// decryption of ct_res
@@ -620,7 +620,6 @@ fn integer_unchecked_small_scalar_mul(param: Parameters) {
// encryption of an integer
let ct = cks.encrypt(clear);
// add the two ciphertexts
let ct_res = sks.unchecked_small_scalar_mul_parallelized(&ct, scalar);
// decryption of ct_res
@@ -696,8 +695,10 @@ fn integer_default_small_scalar_mul(param: Parameters) {
clear_res = clear * scalar;
for _ in 0..NB_TEST_SMALLER {
// scalar multiplication
let tmp = sks.small_scalar_mul_parallelized(&ct_res, scalar);
ct_res = sks.small_scalar_mul_parallelized(&ct_res, scalar);
assert!(ct_res.block_carries_are_empty());
assert_eq!(tmp, ct_res);
clear_res *= scalar;
}
@@ -758,7 +759,9 @@ fn integer_default_scalar_mul(param: Parameters) {
// scalar mul
let ct_res = sks.scalar_mul_parallelized(&ct, scalar);
let tmp = sks.scalar_mul_parallelized(&ct, scalar);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
// decryption of ct_res
let dec_res: u64 = cks.decrypt(&ct_res);
@@ -790,7 +793,6 @@ fn integer_unchecked_scalar_left_shift(param: Parameters) {
// encryption of an integer
let ct = cks.encrypt(clear);
// add the two ciphertexts
let ct_res = sks.unchecked_scalar_left_shift_parallelized(&ct, scalar);
// decryption of ct_res
@@ -823,9 +825,10 @@ fn integer_default_scalar_left_shift(param: Parameters) {
// encryption of an integer
let ct = cks.encrypt(clear);
// add the two ciphertexts
let ct_res = sks.scalar_left_shift_parallelized(&ct, scalar);
let tmp = sks.scalar_left_shift_parallelized(&ct, scalar);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
// decryption of ct_res
let dec_res: u64 = cks.decrypt(&ct_res);
@@ -857,7 +860,6 @@ fn integer_unchecked_scalar_right_shift(param: Parameters) {
// encryption of an integer
let ct = cks.encrypt(clear);
// add the two ciphertexts
let ct_res = sks.unchecked_scalar_right_shift_parallelized(&ct, scalar);
// decryption of ct_res
@@ -890,9 +892,10 @@ fn integer_default_scalar_right_shift(param: Parameters) {
// encryption of an integer
let ct = cks.encrypt(clear);
// add the two ciphertexts
let ct_res = sks.scalar_right_shift_parallelized(&ct, scalar);
let tmp = sks.scalar_right_shift_parallelized(&ct, scalar);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
// decryption of ct_res
let dec_res: u64 = cks.decrypt(&ct_res);
@@ -950,11 +953,13 @@ fn integer_default_neg(param: Parameters) {
let ctxt = cks.encrypt(clear);
// Negates the ctxt
let ct_tmp = sks.neg_parallelized(&ctxt);
assert!(ct_tmp.block_carries_are_empty());
let ct_res = sks.neg_parallelized(&ctxt);
let tmp = sks.neg_parallelized(&ctxt);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
// Decrypt the result
let dec: u64 = cks.decrypt(&ct_tmp);
let dec: u64 = cks.decrypt(&ct_res);
// Check the correctness
let clear_result = clear.wrapping_neg() % modulus;
@@ -1022,8 +1027,10 @@ fn integer_default_sub(param: Parameters) {
//subtract multiple times to raise the degree
for _ in 0..NB_TEST_SMALLER {
let tmp = sks.sub_parallelized(&res, &ctxt_2);
res = sks.sub_parallelized(&res, &ctxt_2);
assert!(res.block_carries_are_empty());
assert_eq!(res, tmp);
clear = (clear.wrapping_sub(clear2)) % modulus;
// println!("clear = {}, clear2 = {}", clear, cks.decrypt(&res));
}
@@ -1057,7 +1064,6 @@ fn integer_unchecked_block_mul(param: Parameters) {
// encryption of an integer
let ct_one = cks.encrypt_one_block(clear_1);
// add the two ciphertexts
let ct_res = sks.unchecked_block_mul_parallelized(&ct_zero, &ct_one, 0);
// decryption of ct_res
@@ -1133,8 +1139,10 @@ fn integer_default_block_mul(param: Parameters) {
res = sks.block_mul_parallelized(&res, &ctxt_2, 0);
assert!(res.block_carries_are_empty());
for _ in 0..5 {
let tmp = sks.block_mul_parallelized(&res, &ctxt_2, 0);
res = sks.block_mul_parallelized(&res, &ctxt_2, 0);
assert!(res.block_carries_are_empty());
assert_eq!(res, tmp);
clear = (clear * clear2) % modulus;
}
let dec: u64 = cks.decrypt(&res);
@@ -1211,8 +1219,10 @@ fn integer_default_mul(param: Parameters) {
res = sks.mul_parallelized(&res, &ctxt_2);
assert!(res.block_carries_are_empty());
for _ in 0..5 {
let tmp = sks.mul_parallelized(&res, &ctxt_2);
res = sks.mul_parallelized(&res, &ctxt_2);
assert!(res.block_carries_are_empty());
assert_eq!(res, tmp);
clear = (clear * clear2) % modulus;
}
let dec: u64 = cks.decrypt(&res);
@@ -1245,7 +1255,6 @@ fn integer_smart_scalar_add(param: Parameters) {
// encryption of an integer
let mut ctxt_0 = cks.encrypt(clear_0);
// add the two ciphertexts
let mut ct_res = sks.smart_scalar_add_parallelized(&mut ctxt_0, clear_1);
clear = (clear_0 + clear_1) % modulus;
@@ -1287,7 +1296,6 @@ fn integer_default_scalar_add(param: Parameters) {
// encryption of an integer
let ctxt_0 = cks.encrypt(clear_0);
// add the two ciphertexts
let mut ct_res = sks.scalar_add_parallelized(&ctxt_0, clear_1);
assert!(ct_res.block_carries_are_empty());
@@ -1296,8 +1304,10 @@ fn integer_default_scalar_add(param: Parameters) {
// println!("clear_0 = {}, clear_1 = {}", clear_0, clear_1);
//add multiple times to raise the degree
for _ in 0..NB_TEST_SMALLER {
let tmp = sks.scalar_add_parallelized(&ct_res, clear_1);
ct_res = sks.scalar_add_parallelized(&ct_res, clear_1);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
clear = (clear + clear_1) % modulus;
// decryption of ct_res
@@ -1331,7 +1341,6 @@ fn integer_smart_scalar_sub(param: Parameters) {
// encryption of an integer
let mut ctxt_0 = cks.encrypt(clear_0);
// add the two ciphertexts
let mut ct_res = sks.smart_scalar_sub_parallelized(&mut ctxt_0, clear_1);
clear = (clear_0 - clear_1) % modulus;
@@ -1373,7 +1382,6 @@ fn integer_default_scalar_sub(param: Parameters) {
// encryption of an integer
let ctxt_0 = cks.encrypt(clear_0);
// add the two ciphertexts
let mut ct_res = sks.scalar_sub_parallelized(&ctxt_0, clear_1);
assert!(ct_res.block_carries_are_empty());
@@ -1382,8 +1390,10 @@ fn integer_default_scalar_sub(param: Parameters) {
// println!("clear_0 = {}, clear_1 = {}", clear_0, clear_1);
//add multiple times to raise the degree
for _ in 0..NB_TEST_SMALLER {
let tmp = sks.scalar_sub_parallelized(&ct_res, clear_1);
ct_res = sks.scalar_sub_parallelized(&ct_res, clear_1);
assert!(ct_res.block_carries_are_empty());
assert_eq!(ct_res, tmp);
clear = (clear.wrapping_sub(clear_1)) % modulus;
// decryption of ct_res

View File

@@ -1,4 +1,4 @@
//! Welcome to the TFHR-rs API documentation!
//! Welcome to the TFHE-rs API documentation!
//!
//! TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE.

View File

@@ -35,7 +35,7 @@ pub trait PBSOrderMarker: seal::Sealed + Debug + Clone + Copy + Send + Sync {
fn pbs_order() -> PBSOrder;
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct KeyswitchBootstrap;
impl PBSOrderMarker for KeyswitchBootstrap {
@@ -118,7 +118,7 @@ impl Degree {
}
}
#[derive(Clone)]
#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use]
pub struct CiphertextBase<OpOrder: PBSOrderMarker> {
pub ct: LweCiphertextOwned<u64>,

View File

@@ -44,4 +44,13 @@ impl CompressedServerKey {
engine.new_compressed_server_key(client_key).unwrap()
})
}
/// Generate a compressed server key with a chosen maximum degree
pub fn new_with_max_degree(cks: &ClientKey, max_degree: MaxDegree) -> CompressedServerKey {
ShortintEngine::with_thread_local_mut(|engine| {
engine
.new_compressed_server_key_with_max_degree(cks, max_degree)
.unwrap()
})
}
}